change some HVAC properties to be zoned
am: 4fff3d5
* commit '4fff3d5cc4112a341c331688bda9fcb65ed9016a':
change some HVAC properties to be zoned
Change-Id: I3f88e3845668e225bd451a9f6a8fd96b1a27084c
diff --git a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoInstrumentClusterRenderer.java b/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoInstrumentClusterRenderer.java
index 71cccdb..3b09be7 100644
--- a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoInstrumentClusterRenderer.java
+++ b/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoInstrumentClusterRenderer.java
@@ -15,32 +15,61 @@
*/
package android.car.cluster.demorenderer;
-import android.car.cluster.renderer.DisplayConfiguration;
+import android.app.Presentation;
import android.car.cluster.renderer.InstrumentClusterRenderer;
import android.car.cluster.renderer.NavigationRenderer;
-import android.car.navigation.CarNavigationInstrumentCluster;
import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.Display;
+import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
/**
* Demo implementation of {@code InstrumentClusterRenderer}.
*/
public class DemoInstrumentClusterRenderer extends InstrumentClusterRenderer {
+ private final static String TAG = DemoInstrumentClusterRenderer.class.getSimpleName();
+
private DemoInstrumentClusterView mView;
private Context mContext;
private CallStateMonitor mPhoneStatusMonitor;
private MediaStateMonitor mMediaStateMonitor;
private DemoPhoneRenderer mPhoneRenderer;
private DemoMediaRenderer mMediaRenderer;
+ private Presentation mPresentation;
@Override
public void onCreate(Context context) {
mContext = context;
+
+ final Display display = getInstrumentClusterDisplay(context);
+
+ if (display != null) {
+ runOnMainThread(() -> {
+ Log.d(TAG, "Initializing renderer in main thread.");
+ try {
+ mPresentation = new InstrumentClusterPresentation(mContext, display);
+
+ ViewGroup rootView = (ViewGroup) LayoutInflater.from(mContext).inflate(
+ R.layout.instrument_cluster, null);
+
+ mPresentation.setContentView(rootView);
+ View rendererView = createView();
+ rootView.addView(rendererView);
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage(), e);
+ throw e;
+ }
+ });
+ }
}
- @Override
- public View onCreateView(DisplayConfiguration displayConfiguration) {
+ private View createView() {
mView = new DemoInstrumentClusterView(mContext);
mPhoneRenderer = new DemoPhoneRenderer(mView);
mMediaRenderer = new DemoMediaRenderer(mView);
@@ -49,33 +78,55 @@
@Override
public void onStart() {
- mPhoneStatusMonitor = new CallStateMonitor(mContext, mPhoneRenderer);
- mMediaStateMonitor = new MediaStateMonitor(mContext, mMediaRenderer);
+ runOnMainThread(() -> {
+ Log.d(TAG, "onStart");
+ mPhoneStatusMonitor = new CallStateMonitor(mContext, mPhoneRenderer);
+ mMediaStateMonitor = new MediaStateMonitor(mContext, mMediaRenderer);
+ mPresentation.show();
+ });
}
@Override
public void onStop() {
- if (mPhoneStatusMonitor != null) {
- mPhoneStatusMonitor.release();
- mPhoneStatusMonitor = null;
- }
+ runOnMainThread(() -> {
+ if (mPhoneStatusMonitor != null) {
+ mPhoneStatusMonitor.release();
+ mPhoneStatusMonitor = null;
+ }
- if (mMediaStateMonitor != null) {
- mMediaStateMonitor.release();
- mMediaStateMonitor = null;
- }
- mPhoneRenderer = null;
- mMediaRenderer = null;
+ if (mMediaStateMonitor != null) {
+ mMediaStateMonitor.release();
+ mMediaStateMonitor = null;
+ }
+ mPhoneRenderer = null;
+ mMediaRenderer = null;
+ });
}
@Override
protected NavigationRenderer createNavigationRenderer() {
- return new DemoNavigationRenderer(mView);
+ return ThreadSafeNavigationRenderer.createFor(
+ Looper.getMainLooper(),
+ new DemoNavigationRenderer(mView));
}
- @Override
- public CarNavigationInstrumentCluster getNavigationProperties() {
- // TODO
+ private static Display getInstrumentClusterDisplay(Context context) {
+ DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+ Display[] displays = displayManager.getDisplays();
+
+ Log.d(TAG, "There are currently " + displays.length + " displays connected.");
+ for (Display display : displays) {
+ Log.d(TAG, " " + display);
+ }
+
+ if (displays.length > 1) {
+ // TODO: assuming that secondary display is instrument cluster. Put this into settings?
+ return displays[1];
+ }
return null;
}
+
+ private static void runOnMainThread(Runnable runnable) {
+ new Handler(Looper.getMainLooper()).post(runnable);
+ }
}
diff --git a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoNavigationRenderer.java b/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoNavigationRenderer.java
index 5ddb822..5588546 100644
--- a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoNavigationRenderer.java
+++ b/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/DemoNavigationRenderer.java
@@ -20,6 +20,7 @@
import static android.car.navigation.CarNavigationManager.TURN_TURN;
import android.car.cluster.renderer.NavigationRenderer;
+import android.car.navigation.CarNavigationInstrumentCluster;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
@@ -53,6 +54,12 @@
}
@Override
+ public CarNavigationInstrumentCluster getNavigationProperties() {
+ // TODO
+ return null;
+ }
+
+ @Override
public void onStartNavigation() {
mView.showNavigation();
}
diff --git a/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/InstrumentClusterPresentation.java b/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/InstrumentClusterPresentation.java
new file mode 100644
index 0000000..1547a24
--- /dev/null
+++ b/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/InstrumentClusterPresentation.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.car.cluster.demorenderer;
+
+import android.app.Presentation;
+import android.content.Context;
+import android.view.Display;
+import android.view.WindowManager;
+
+/**
+ * Presentation class.
+ */
+public class InstrumentClusterPresentation extends Presentation {
+ public InstrumentClusterPresentation(Context outerContext, Display display) {
+ super(outerContext, display);
+ getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ }
+}
diff --git a/service/src/com/android/car/cluster/renderer/RendererHandler.java b/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/RendererHandler.java
similarity index 96%
rename from service/src/com/android/car/cluster/renderer/RendererHandler.java
rename to car-cluster-demo-renderer/src/android/car/cluster/demorenderer/RendererHandler.java
index b999dde..6989a9c 100644
--- a/service/src/com/android/car/cluster/renderer/RendererHandler.java
+++ b/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/RendererHandler.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.car.cluster.renderer;
+package android.car.cluster.demorenderer;
import android.os.Handler;
import android.os.Looper;
diff --git a/service/src/com/android/car/cluster/renderer/ThreadSafeNavigationRenderer.java b/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/ThreadSafeNavigationRenderer.java
similarity index 76%
rename from service/src/com/android/car/cluster/renderer/ThreadSafeNavigationRenderer.java
rename to car-cluster-demo-renderer/src/android/car/cluster/demorenderer/ThreadSafeNavigationRenderer.java
index 33dde7a..e9327d8 100644
--- a/service/src/com/android/car/cluster/renderer/ThreadSafeNavigationRenderer.java
+++ b/car-cluster-demo-renderer/src/android/car/cluster/demorenderer/ThreadSafeNavigationRenderer.java
@@ -13,15 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.car.cluster.renderer;
+package android.car.cluster.demorenderer;
import android.annotation.Nullable;
import android.car.cluster.renderer.NavigationRenderer;
+import android.car.navigation.CarNavigationInstrumentCluster;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import java.util.concurrent.CountDownLatch;
+
/**
* A wrapper over {@link NavigationRenderer} that runs all its methods in the context of provided
* looper. It is guaranteed that all calls will be invoked in order they were called.
@@ -29,6 +32,7 @@
public class ThreadSafeNavigationRenderer extends NavigationRenderer {
private final Handler mHandler;
+ private final NavigationRenderer mRenderer;
private final static int MSG_NAV_START = 1;
private final static int MSG_NAV_STOP = 2;
@@ -42,10 +46,21 @@
}
private ThreadSafeNavigationRenderer(Looper looper, NavigationRenderer renderer) {
+ mRenderer = renderer;
mHandler = new NavigationRendererHandler(looper, renderer);
}
@Override
+ public CarNavigationInstrumentCluster getNavigationProperties() {
+ return runAndWaitResult(mHandler, new RunnableWithResult<CarNavigationInstrumentCluster>() {
+ @Override
+ protected CarNavigationInstrumentCluster createResult() {
+ return mRenderer.getNavigationProperties();
+ }
+ });
+ }
+
+ @Override
public void onStartNavigation() {
mHandler.sendMessage(mHandler.obtainMessage(MSG_NAV_START));
}
@@ -98,6 +113,20 @@
}
}
+ private static <E> E runAndWaitResult(Handler handler, RunnableWithResult<E> runnable) {
+ CountDownLatch latch = new CountDownLatch(1);
+ handler.post(() -> {
+ runnable.run();
+ latch.countDown();
+ });
+ try {
+ latch.wait();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ return runnable.getResult();
+ }
+
private static class NextTurn {
private final int event;
private final String road;
@@ -116,4 +145,19 @@
this.turnSide = turnSide;
}
}
+
+ public abstract class RunnableWithResult<T> implements Runnable {
+ private volatile T result;
+
+ protected abstract T createResult();
+
+ @Override
+ public void run() {
+ result = createResult();
+ }
+
+ public T getResult() {
+ return result;
+ }
+ }
}
diff --git a/car-cluster-logging-renderer/Android.mk b/car-cluster-logging-renderer/Android.mk
new file mode 100644
index 0000000..8fb7674
--- /dev/null
+++ b/car-cluster-logging-renderer/Android.mk
@@ -0,0 +1,34 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := android.car.cluster.loggingrenderer
+
+# Each update should be signed by OEMs
+LOCAL_CERTIFICATE := platform
+LOCAL_PRIVILEGED_MODULE := true
+
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_JAVA_LIBRARIES += android.car
+
+include $(BUILD_PACKAGE)
diff --git a/car-cluster-logging-renderer/AndroidManifest.xml b/car-cluster-logging-renderer/AndroidManifest.xml
new file mode 100644
index 0000000..20ee7d1
--- /dev/null
+++ b/car-cluster-logging-renderer/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.car.cluster.loggingrenderer"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <application android:label="@string/app_name" android:icon="@drawable/ic_launcher"/>
+</manifest>
diff --git a/car-cluster-logging-renderer/proguard.flags b/car-cluster-logging-renderer/proguard.flags
new file mode 100644
index 0000000..22cc22d
--- /dev/null
+++ b/car-cluster-logging-renderer/proguard.flags
@@ -0,0 +1,3 @@
+-verbose
+-keep @com.android.internal.annotations.VisibleForTesting class *
+
diff --git a/car-cluster-logging-renderer/res/drawable-hdpi/ic_launcher.png b/car-cluster-logging-renderer/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/car-cluster-logging-renderer/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/car-cluster-logging-renderer/res/drawable-ldpi/ic_launcher.png b/car-cluster-logging-renderer/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..9923872
--- /dev/null
+++ b/car-cluster-logging-renderer/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/car-cluster-logging-renderer/res/drawable-mdpi/ic_launcher.png b/car-cluster-logging-renderer/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/car-cluster-logging-renderer/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/car-cluster-logging-renderer/res/drawable-xhdpi/ic_launcher.png b/car-cluster-logging-renderer/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/car-cluster-logging-renderer/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/car-cluster-logging-renderer/res/values/strings.xml b/car-cluster-logging-renderer/res/values/strings.xml
new file mode 100644
index 0000000..f36db0d
--- /dev/null
+++ b/car-cluster-logging-renderer/res/values/strings.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <string name="app_name">LOGGING_INSTRUMENT_CLUSTER_RENDERER</string>
+</resources>
diff --git a/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/InstrumentClusterRendererFactory.java b/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/InstrumentClusterRendererFactory.java
new file mode 100644
index 0000000..d73ede4
--- /dev/null
+++ b/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/InstrumentClusterRendererFactory.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.car.cluster.loggingrenderer;
+
+import android.car.cluster.renderer.InstrumentClusterRenderer;
+
+/**
+ * This factory class will be requested by android.car using reflection in order to get an instance
+ * of {@link InstrumentClusterRenderer}.
+ */
+@SuppressWarnings("unused") /* Used by reflection. */
+public class InstrumentClusterRendererFactory {
+ public static InstrumentClusterRenderer createRenderer() {
+ return new LoggingInstrumentClusterRenderer();
+ }
+}
diff --git a/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/LoggingInstrumentClusterRenderer.java b/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/LoggingInstrumentClusterRenderer.java
new file mode 100644
index 0000000..1875792
--- /dev/null
+++ b/car-cluster-logging-renderer/src/android/car/cluster/loggingrenderer/LoggingInstrumentClusterRenderer.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.car.cluster.loggingrenderer;
+
+import android.car.cluster.renderer.InstrumentClusterRenderer;
+import android.car.cluster.renderer.NavigationRenderer;
+import android.car.navigation.CarNavigationInstrumentCluster;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.util.Log;
+
+/**
+ * Dummy implementation of {@code InstrumentClusterRenderer} that just traces all interaction.
+ */
+public class LoggingInstrumentClusterRenderer extends InstrumentClusterRenderer {
+
+ private final static String TAG = LoggingInstrumentClusterRenderer.class.getSimpleName();
+
+ @Override
+ public void onCreate(Context context) {
+ Log.i(TAG, "onCreate, context: " + context);
+ }
+
+ @Override
+ public void onStart() {
+ Log.i(TAG, "onStart");
+ }
+
+ @Override
+ public void onStop() {
+ Log.i(TAG, "onStop");
+ }
+
+ @Override
+ protected NavigationRenderer createNavigationRenderer() {
+ NavigationRenderer navigationRenderer = new NavigationRenderer() {
+ @Override
+ public CarNavigationInstrumentCluster getNavigationProperties() {
+ Log.i(TAG, "getNavigationProperties");
+ CarNavigationInstrumentCluster config =
+ CarNavigationInstrumentCluster.createCluster(1000);
+ Log.i(TAG, "getNavigationProperties, returns: " + config);
+ return config;
+ }
+
+
+ @Override
+ public void onStartNavigation() {
+ Log.i(TAG, "onStartNavigation");
+ }
+
+ @Override
+ public void onStopNavigation() {
+ Log.i(TAG, "onStopNavigation");
+ }
+
+ @Override
+ public void onNextTurnChanged(int event, String road, int turnAngle, int turnNumber,
+ Bitmap image, int turnSide) {
+ Log.i(TAG, "event: " + event + ", road: " + road + ", turnAngle: " + turnAngle
+ + ", turnNumber: " + turnNumber + ", image: " + image + ", turnSide: "
+ + turnSide);
+ }
+
+ @Override
+ public void onNextTurnDistanceChanged(int distanceMeters, int timeSeconds) {
+ Log.i(TAG, "onNextTurnDistanceChanged, distanceMeters: " + distanceMeters
+ + ", timeSeconds: " + timeSeconds);
+ }
+ };
+
+ Log.i(TAG, "createNavigationRenderer, returns: " + navigationRenderer);
+ return navigationRenderer;
+ }
+}
diff --git a/car-lib/api/current.txt b/car-lib/api/current.txt
index a24febf..56705c7 100644
--- a/car-lib/api/current.txt
+++ b/car-lib/api/current.txt
@@ -14,6 +14,7 @@
field public static final int CONNECTION_TYPE_EMBEDDED = 5; // 0x5
field public static final java.lang.String INFO_SERVICE = "info";
field public static final java.lang.String PACKAGE_SERVICE = "package";
+ field public static final java.lang.String PERMISSION_CAR_CONTROL_AUDIO_VOLUME = "android.car.permission.CAR_CONTROL_AUDIO_VOLUME";
field public static final java.lang.String PERMISSION_FUEL = "android.car.permission.CAR_FUEL";
field public static final java.lang.String PERMISSION_MILEAGE = "android.car.permission.CAR_MILEAGE";
field public static final java.lang.String PERMISSION_SPEED = "android.car.permission.CAR_SPEED";
@@ -329,8 +330,12 @@
public class CarAudioManager {
method public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes);
method public android.media.AudioAttributes getAudioAttributesForCarUsage(int);
+ method public int getStreamMaxVolume(int) throws android.car.CarNotConnectedException;
+ method public int getStreamMinVolume(int) throws android.car.CarNotConnectedException;
+ method public int getStreamVolume(int) throws android.car.CarNotConnectedException;
method public void onCarDisconnected();
method public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException;
+ method public void setStreamVolume(int, int, int) throws android.car.CarNotConnectedException;
field public static final int CAR_AUDIO_USAGE_ALARM = 6; // 0x6
field public static final int CAR_AUDIO_USAGE_DEFAULT = 0; // 0x0
field public static final int CAR_AUDIO_USAGE_MUSIC = 1; // 0x1
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index 18239c4..26d439c 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -17,6 +17,7 @@
field public static final java.lang.String INFO_SERVICE = "info";
field public static final java.lang.String PACKAGE_SERVICE = "package";
field public static final java.lang.String PERMISSION_CAR_CAMERA = "android.car.permission.CAR_CAMERA";
+ field public static final java.lang.String PERMISSION_CAR_CONTROL_AUDIO_VOLUME = "android.car.permission.CAR_CONTROL_AUDIO_VOLUME";
field public static final java.lang.String PERMISSION_CAR_HVAC = "android.car.permission.CAR_HVAC";
field public static final java.lang.String PERMISSION_CAR_PROJECTION = "android.car.permission.CAR_PROJECTION";
field public static final java.lang.String PERMISSION_CAR_RADIO = "android.car.permission.CAR_RADIO";
@@ -301,17 +302,16 @@
public abstract class InstrumentClusterRenderer {
ctor public InstrumentClusterRenderer();
method protected abstract android.car.cluster.renderer.NavigationRenderer createNavigationRenderer();
- method public abstract android.car.navigation.CarNavigationInstrumentCluster getNavigationProperties();
method public synchronized android.car.cluster.renderer.NavigationRenderer getNavigationRenderer();
method public final synchronized void initialize();
method public abstract void onCreate(android.content.Context);
- method public abstract android.view.View onCreateView(android.car.cluster.renderer.DisplayConfiguration);
method public abstract void onStart();
method public abstract void onStop();
}
public abstract class NavigationRenderer {
ctor public NavigationRenderer();
+ method public abstract android.car.navigation.CarNavigationInstrumentCluster getNavigationProperties();
method public abstract void onNextTurnChanged(int, java.lang.String, int, int, android.graphics.Bitmap, int);
method public abstract void onNextTurnDistanceChanged(int, int);
method public abstract void onStartNavigation();
@@ -689,8 +689,13 @@
public class CarAudioManager {
method public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes);
method public android.media.AudioAttributes getAudioAttributesForCarUsage(int);
+ method public int getStreamMaxVolume(int) throws android.car.CarNotConnectedException;
+ method public int getStreamMinVolume(int) throws android.car.CarNotConnectedException;
+ method public int getStreamVolume(int) throws android.car.CarNotConnectedException;
method public void onCarDisconnected();
method public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException;
+ method public void setStreamVolume(int, int, int) throws android.car.CarNotConnectedException;
+ method public void setVolumeController(android.media.IVolumeController) throws android.car.CarNotConnectedException;
field public static final int CAR_AUDIO_USAGE_ALARM = 6; // 0x6
field public static final int CAR_AUDIO_USAGE_DEFAULT = 0; // 0x0
field public static final int CAR_AUDIO_USAGE_MUSIC = 1; // 0x1
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index 5d56948..b6f7cc8 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -113,6 +113,12 @@
public static final String PERMISSION_SPEED = "android.car.permission.CAR_SPEED";
/**
+ * Permission necessary to change car audio volume through {@link CarAudioManager}.
+ */
+ public static final String PERMISSION_CAR_CONTROL_AUDIO_VOLUME =
+ "android.car.permission.CAR_CONTROL_AUDIO_VOLUME";
+
+ /**
* Permission necessary to use {@link CarNavigationManager}.
* @hide
*/
@@ -155,8 +161,9 @@
@SystemApi
public static final String PERMISSION_CAR_RADIO = "android.car.permission.CAR_RADIO";
+
/**
- * Permission necesary to access Car PRJECTION system APIs.
+ * Permission necessary to access Car PROJECTION system APIs.
* @hide
*/
@SystemApi
diff --git a/car-lib/src/android/car/CarAppContextManager.java b/car-lib/src/android/car/CarAppContextManager.java
index c7657ca..22e36bf 100644
--- a/car-lib/src/android/car/CarAppContextManager.java
+++ b/car-lib/src/android/car/CarAppContextManager.java
@@ -51,7 +51,7 @@
/**
* Lost ownership for the context, which happens when other app has set the context.
* The app losing context should stop the action associated with the context.
- * For example, navigaiton app currently running active navigation should stop navigation
+ * For example, navigation app currently running active navigation should stop navigation
* upon getting this for {@link CarAppContextManager#APP_CONTEXT_NAVIGATION}.
* @param context
*/
@@ -89,7 +89,7 @@
mService = IAppContext.Stub.asInterface(service);
mHandler = new Handler(looper);
mBinderListener = new IAppContextListenerImpl(this);
- mOwnershipListeners = new HashMap<Integer, AppContextOwnershipChangeListener>();
+ mOwnershipListeners = new HashMap<>();
}
/**
diff --git a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java
index 4e8a670..858613d 100644
--- a/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java
+++ b/car-lib/src/android/car/cluster/renderer/InstrumentClusterRenderer.java
@@ -20,7 +20,6 @@
import android.annotation.UiThread;
import android.car.navigation.CarNavigationInstrumentCluster;
import android.content.Context;
-import android.view.View;
/**
* Interface for instrument cluster rendering.
@@ -39,21 +38,10 @@
*/
abstract public void onCreate(Context context);
- @UiThread
- abstract public View onCreateView(DisplayConfiguration displayConfiguration);
-
- @UiThread
abstract public void onStart();
- @UiThread
abstract public void onStop();
- /**
- * Returns properties of instrument cluster for navigation.
- */
- abstract public CarNavigationInstrumentCluster getNavigationProperties();
-
- @UiThread
abstract protected NavigationRenderer createNavigationRenderer();
/** The method is thread-safe, callers should cache returned object. */
diff --git a/car-lib/src/android/car/cluster/renderer/NavigationRenderer.java b/car-lib/src/android/car/cluster/renderer/NavigationRenderer.java
index 67eac5e..3df2ab1 100644
--- a/car-lib/src/android/car/cluster/renderer/NavigationRenderer.java
+++ b/car-lib/src/android/car/cluster/renderer/NavigationRenderer.java
@@ -17,6 +17,7 @@
import android.annotation.SystemApi;
import android.annotation.UiThread;
+import android.car.navigation.CarNavigationInstrumentCluster;
import android.graphics.Bitmap;
/**
@@ -28,6 +29,11 @@
@SystemApi
@UiThread
public abstract class NavigationRenderer {
+ /**
+ * Returns properties of instrument cluster for navigation.
+ */
+ abstract public CarNavigationInstrumentCluster getNavigationProperties();
+
abstract public void onStartNavigation();
abstract public void onStopNavigation();
abstract public void onNextTurnChanged(int event, String road, int turnAngle, int turnNumber,
diff --git a/car-lib/src/android/car/media/CarAudioManager.java b/car-lib/src/android/car/media/CarAudioManager.java
index 0a92c04..be4bfba 100644
--- a/car-lib/src/android/car/media/CarAudioManager.java
+++ b/car-lib/src/android/car/media/CarAudioManager.java
@@ -16,13 +16,18 @@
package android.car.media;
import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.car.CarLibLog;
+import android.car.CarNotConnectedException;
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
+import android.media.IVolumeController;
import android.os.IBinder;
import android.os.RemoteException;
import android.car.CarManagerBase;
+import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -127,6 +132,103 @@
return mAudioManager.abandonAudioFocus(l, aa);
}
+ /**
+ * Sets the volume index for a particular stream.
+ *
+ * Requires {@link android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME} permission.
+ *
+ * @param streamType The stream whose volume index should be set.
+ * @param index The volume index to set. See
+ * {@link #getStreamMaxVolume(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})
+ */
+ @SystemApi
+ public void setStreamVolume(int streamType, int index, int flags)
+ throws CarNotConnectedException {
+ try {
+ mService.setStreamVolume(streamType, index, flags);
+ } catch (RemoteException e) {
+ Log.e(CarLibLog.TAG_CAR, "setStreamVolume failed", e);
+ throw new CarNotConnectedException(e);
+ }
+ }
+
+ /**
+ * Registers a global volume controller interface.
+ *
+ * Requires {@link android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME} permission.
+ *
+ * @hide
+ */
+ @SystemApi
+ public void setVolumeController(IVolumeController controller)
+ throws CarNotConnectedException {
+ try {
+ mService.setVolumeController(controller);
+ } catch (RemoteException e) {
+ Log.e(CarLibLog.TAG_CAR, "setVolumeController failed", e);
+ throw new CarNotConnectedException(e);
+ }
+ }
+
+ /**
+ * Returns the maximum volume index for a particular stream.
+ *
+ * Requires {@link android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME} permission.
+ *
+ * @param stream The stream type whose maximum volume index is returned.
+ * @return The maximum valid volume index for the stream.
+ */
+ @SystemApi
+ public int getStreamMaxVolume(int stream) throws CarNotConnectedException {
+ try {
+ return mService.getStreamMaxVolume(stream);
+ } catch (RemoteException e) {
+ Log.e(CarLibLog.TAG_CAR, "getStreamMaxVolume failed", e);
+ throw new CarNotConnectedException(e);
+ }
+ }
+
+ /**
+ * Returns the minimum volume index for a particular stream.
+ *
+ * Requires {@link android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME} permission.
+ *
+ * @param stream The stream type whose maximum volume index is returned.
+ * @return The maximum valid volume index for the stream.
+ */
+ @SystemApi
+ public int getStreamMinVolume(int stream) throws CarNotConnectedException {
+ try {
+ return mService.getStreamMinVolume(stream);
+ } catch (RemoteException e) {
+ Log.e(CarLibLog.TAG_CAR, "getStreamMaxVolume failed", e);
+ throw new CarNotConnectedException(e);
+ }
+ }
+
+ /**
+ * Returns the current volume index for a particular stream.
+ *
+ * Requires {@link android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME} permission.
+ *
+ * @param stream The stream type whose volume index is returned.
+ * @return The current volume index for the stream.
+ *
+ * @see #getStreamMaxVolume(int)
+ * @see #setStreamVolume(int, int, int)
+ */
+ @SystemApi
+ public int getStreamVolume(int stream) throws CarNotConnectedException {
+ try {
+ return mService.getStreamVolume(stream);
+ } catch (RemoteException e) {
+ Log.e(CarLibLog.TAG_CAR, "getStreamVolume failed", e);
+ throw new CarNotConnectedException(e);
+ }
+ }
+
@Override
public void onCarDisconnected() {
// TODO Auto-generated method stub
diff --git a/car-lib/src/android/car/media/ICarAudio.aidl b/car-lib/src/android/car/media/ICarAudio.aidl
index 7a5e43d..dfa2573 100644
--- a/car-lib/src/android/car/media/ICarAudio.aidl
+++ b/car-lib/src/android/car/media/ICarAudio.aidl
@@ -17,6 +17,7 @@
package android.car.media;
import android.media.AudioAttributes;
+import android.media.IVolumeController;
/**
* Binder interface for {@link android.car.media.CarAudioManager}.
@@ -26,4 +27,9 @@
*/
interface ICarAudio {
AudioAttributes getAudioAttributesForCarUsage(int carUsage) = 0;
+ void setStreamVolume(int streamType, int index, int flags) = 1;
+ void setVolumeController(IVolumeController controller) = 2;
+ int getStreamMaxVolume(int streamType) = 3;
+ int getStreamMinVolume(int streamType) = 4;
+ int getStreamVolume(int streamType) = 5;
}
diff --git a/car-support-lib/api/current.txt b/car-support-lib/api/current.txt
index 479f056..02e3f4f 100644
--- a/car-support-lib/api/current.txt
+++ b/car-support-lib/api/current.txt
@@ -18,6 +18,7 @@
field public static final int CONNECTION_TYPE_EMBEDDED = 5; // 0x5
field public static final int CONNECTION_TYPE_EMULATOR = 0; // 0x0
field public static final int CONNECTION_TYPE_ON_DEVICE_EMULATOR = 3; // 0x3
+ field public static final int CONNECTION_TYPE_UNKNOWN = -1; // 0xffffffff
field public static final int CONNECTION_TYPE_USB = 1; // 0x1
field public static final int CONNECTION_TYPE_WIFI = 2; // 0x2
field public static final java.lang.String INFO_SERVICE = "info";
@@ -60,6 +61,14 @@
method public abstract java.lang.Integer getInt(java.lang.String) throws android.support.car.CarNotConnectedException, java.lang.IllegalArgumentException;
method public abstract java.lang.Long getLong(java.lang.String) throws android.support.car.CarNotConnectedException, java.lang.IllegalArgumentException;
method public abstract java.lang.String getString(java.lang.String) throws android.support.car.CarNotConnectedException, java.lang.IllegalArgumentException;
+ field public static final int DRIVER_SIDE_CENTER = 2; // 0x2
+ field public static final int DRIVER_SIDE_LEFT = 0; // 0x0
+ field public static final int DRIVER_SIDE_RIGHT = 1; // 0x1
+ field public static final java.lang.String KEY_DRIVER_POSITION = "driverPosition";
+ field public static final java.lang.String KEY_HEAD_UNIT_MAKE = "headUnitMake";
+ field public static final java.lang.String KEY_HEAD_UNIT_MODEL = "headUnitModel";
+ field public static final java.lang.String KEY_HEAD_UNIT_SOFTWARE_BUILD = "headUnitSoftwareBuild";
+ field public static final java.lang.String KEY_HEAD_UNIT_SOFTWARE_VERSION = "headUnitSoftwareVersion";
field public static final java.lang.String KEY_MANUFACTURER = "manufacturer";
field public static final java.lang.String KEY_MODEL = "model";
field public static final java.lang.String KEY_MODEL_YEAR = "model-year";
diff --git a/car-support-lib/src/android/support/car/Car.java b/car-support-lib/src/android/support/car/Car.java
index e5739fd..d628e85 100644
--- a/car-support-lib/src/android/support/car/Car.java
+++ b/car-support-lib/src/android/support/car/Car.java
@@ -17,27 +17,22 @@
package android.support.car;
import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Looper;
-import android.os.RemoteException;
import android.support.car.content.pm.CarPackageManager;
import android.support.car.hardware.CarSensorManager;
import android.support.car.navigation.CarNavigationManager;
-import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
/**
* Top level car API.
@@ -80,6 +75,8 @@
public static final int CONNECTION_TYPE_ADB_EMULATOR = 4;
/** Type of car connection: platform runs directly in car. */
public static final int CONNECTION_TYPE_EMBEDDED = 5;
+ /** Unknown type. The support lib is likely out of date.*/
+ public static final int CONNECTION_TYPE_UNKNOWN = -1;
/**
* Type of car connection: platform runs directly in car but with mocked vehicle hal.
* This will only happen in testing environment.
@@ -89,7 +86,8 @@
/** @hide */
@IntDef({CONNECTION_TYPE_EMULATOR, CONNECTION_TYPE_USB, CONNECTION_TYPE_WIFI,
- CONNECTION_TYPE_ON_DEVICE_EMULATOR, CONNECTION_TYPE_ADB_EMULATOR, CONNECTION_TYPE_EMBEDDED})
+ CONNECTION_TYPE_ON_DEVICE_EMULATOR, CONNECTION_TYPE_ADB_EMULATOR,
+ CONNECTION_TYPE_EMBEDDED, CONNECTION_TYPE_UNKNOWN})
@Retention(RetentionPolicy.SOURCE)
public @interface ConnectionType {}
@@ -144,33 +142,33 @@
private int mConnectionState;
private final ServiceConnectionListener mServiceConnectionListener =
- new ServiceConnectionListener () {
- public void onServiceConnected(ComponentName name) {
- synchronized (Car.this) {
- mConnectionState = STATE_CONNECTED;
- }
- mServiceConnectionListenerClient.onServiceConnected(name);
- }
-
- public void onServiceDisconnected(ComponentName name) {
- synchronized (Car.this) {
- if (mConnectionState == STATE_DISCONNECTED) {
- return;
+ new ServiceConnectionListener() {
+ public void onServiceConnected(ComponentName name) {
+ synchronized (Car.this) {
+ mConnectionState = STATE_CONNECTED;
+ }
+ mServiceConnectionListenerClient.onServiceConnected(name);
}
- mConnectionState = STATE_DISCONNECTED;
- }
- mServiceConnectionListenerClient.onServiceDisconnected(name);
- connect();
- }
- public void onServiceSuspended(int cause) {
- mServiceConnectionListenerClient.onServiceSuspended(cause);
- }
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (Car.this) {
+ if (mConnectionState == STATE_DISCONNECTED) {
+ return;
+ }
+ mConnectionState = STATE_DISCONNECTED;
+ }
+ mServiceConnectionListenerClient.onServiceDisconnected(name);
+ connect();
+ }
- public void onServiceConnectionFailed(int cause) {
- mServiceConnectionListenerClient.onServiceConnectionFailed(cause);
- }
- };
+ public void onServiceSuspended(int cause) {
+ mServiceConnectionListenerClient.onServiceSuspended(cause);
+ }
+
+ public void onServiceConnectionFailed(int cause) {
+ mServiceConnectionListenerClient.onServiceConnectionFailed(cause);
+ }
+ };
private final ServiceConnectionListener mServiceConnectionListenerClient;
private final Object mCarManagerLock = new Object();
@@ -260,22 +258,22 @@
/**
* Car constructor when CarServiceLoader is already available.
- * @param context
- * @param serviceLoader
- * @param looper
+ * @param serviceLoader must be non-null and connected or {@link CarNotConnectedException} will
+ * be thrown.
*
* @hide
*/
- public Car(Context context, CarServiceLoader serviceLoader, @Nullable Looper looper) {
- mContext = context;
- if (looper == null) {
- mLooper = Looper.getMainLooper();
- } else {
- mLooper = looper;
+ public Car(@NonNull CarServiceLoader serviceLoader)
+ throws CarNotConnectedException {
+ if(!serviceLoader.isConnectedToCar()){
+ throw new CarNotConnectedException();
}
+ mCarServiceLoader = serviceLoader;
+ mLooper = serviceLoader.getLooper();
+ mContext = serviceLoader.getContext();
+
mEventHandler = new Handler(mLooper);
mConnectionState = STATE_CONNECTED;
- mCarServiceLoader = serviceLoader;
mServiceConnectionListenerClient = null;
}
diff --git a/car-support-lib/src/android/support/car/CarAppContextManager.java b/car-support-lib/src/android/support/car/CarAppContextManager.java
index 70616b9..64198eb 100644
--- a/car-support-lib/src/android/support/car/CarAppContextManager.java
+++ b/car-support-lib/src/android/support/car/CarAppContextManager.java
@@ -42,7 +42,7 @@
/**
* Lost ownership for the context, which happens when other app has set the context.
* The app losing context should stop the action associated with the context.
- * For example, navigaiton app currently running active navigation should stop navigation
+ * For example, navigation app currently running active navigation should stop navigation
* upon getting this for {@link CarAppContextManager#APP_CONTEXT_NAVIGATION}.
* @param context
*/
diff --git a/car-support-lib/src/android/support/car/CarAppContextManagerEmbedded.java b/car-support-lib/src/android/support/car/CarAppContextManagerEmbedded.java
index a610fe4..cd145ac 100644
--- a/car-support-lib/src/android/support/car/CarAppContextManagerEmbedded.java
+++ b/car-support-lib/src/android/support/car/CarAppContextManagerEmbedded.java
@@ -34,7 +34,7 @@
*/
CarAppContextManagerEmbedded(Object manager) {
mManager = (android.car.CarAppContextManager) manager;
- mOwnershipListeners = new HashMap<Integer, AppContextOwnershipChangeListenerProxy>();
+ mOwnershipListeners = new HashMap<>();
}
@Override
diff --git a/car-support-lib/src/android/support/car/CarInfoManager.java b/car-support-lib/src/android/support/car/CarInfoManager.java
index 78eacac..b40f7d3 100644
--- a/car-support-lib/src/android/support/car/CarInfoManager.java
+++ b/car-support-lib/src/android/support/car/CarInfoManager.java
@@ -54,22 +54,50 @@
@ValueTypeDef(type = String.class)
public static final String KEY_VEHICLE_ID = "vehicle-id";
+ /** Manufacturer of the head unit.*/
+ @ValueTypeDef(type = String.class)
+ public static final String KEY_HEAD_UNIT_MAKE = "headUnitMake";
+ /** Model of the head unit.*/
+ @ValueTypeDef(type = String.class)
+ public static final String KEY_HEAD_UNIT_MODEL = "headUnitModel";
+ /** Head Unit software build */
+ @ValueTypeDef(type = String.class)
+ public static final String KEY_HEAD_UNIT_SOFTWARE_BUILD = "headUnitSoftwareBuild";
+ /** Head Unit software version */
+ @ValueTypeDef(type = String.class)
+ public static final String KEY_HEAD_UNIT_SOFTWARE_VERSION = "headUnitSoftwareVersion";
+ /** Where is the driver's seat. One of the DRIVER_SIDE_* constants */
+ @ValueTypeDef(type = Integer.class)
+ public static final String KEY_DRIVER_POSITION = "driverPosition";
+
+ /** Location of the driver: left */
+ public static final int DRIVER_SIDE_LEFT = 0;
+ /** Location of the driver: right */
+ public static final int DRIVER_SIDE_RIGHT = 1;
+ /** Location of the driver: center */
+ public static final int DRIVER_SIDE_CENTER = 2;
+
/**
- * Retrieve floating point information for car.
- * @param key
- * @return null if the key is not supported.
- * @throws CarNotConnectedException
- * @throws IllegalArgumentException
+ * Returns the value for the given key or null if the key is not supported.
*/
public abstract Float getFloat(String key)
throws CarNotConnectedException, IllegalArgumentException;
+ /**
+ * Returns the value for the given key or null if the key is not supported.
+ */
public abstract Integer getInt(String key)
throws CarNotConnectedException, IllegalArgumentException;
+ /**
+ * Returns the value for the given key or null if the key is not supported.
+ */
public abstract Long getLong(String key)
throws CarNotConnectedException, IllegalArgumentException;
+ /**
+ * Returns the value for the given key or null if the key is not supported.
+ */
public abstract String getString(String key)
throws CarNotConnectedException, IllegalArgumentException;
@@ -78,9 +106,6 @@
* defined only for the car vendor. Vendor extension can be used for other APIs like
* getInt / getString, but this is for passing more complex data.
* @param key
- * @return
- * @throws CarNotConnectedException
- * @throws IllegalArgumentException
* @hide
*/
public abstract Bundle getBundle(String key)
diff --git a/car-support-lib/src/android/support/car/CarServiceLoaderEmbedded.java b/car-support-lib/src/android/support/car/CarServiceLoaderEmbedded.java
index fe0338e..f22b95a 100644
--- a/car-support-lib/src/android/support/car/CarServiceLoaderEmbedded.java
+++ b/car-support-lib/src/android/support/car/CarServiceLoaderEmbedded.java
@@ -79,7 +79,9 @@
@Override
public int getCarConnectionType() throws CarNotConnectedException {
- return mCar.getCarConnectionType();
+ @android.support.car.Car.ConnectionType
+ int carConnectionType = mCar.getCarConnectionType();
+ return carConnectionType;
}
@Override
@@ -113,20 +115,20 @@
}
// For publicly available versions, return wrapper version.
switch (serviceName) {
- case Car.AUDIO_SERVICE:
- return new CarAudioManagerEmbedded(manager);
- case Car.SENSOR_SERVICE:
- return new CarSensorManagerEmbedded(manager);
- case Car.INFO_SERVICE:
- return new CarInfoManagerEmbedded(manager);
- case Car.APP_CONTEXT_SERVICE:
- return new CarAppContextManagerEmbedded(manager);
- case Car.PACKAGE_SERVICE:
- return new CarPackageManagerEmbedded(manager);
- case Car.CAR_NAVIGATION_SERVICE:
- return new CarNavigationManagerEmbedded(manager);
- default:
- return manager;
+ case Car.AUDIO_SERVICE:
+ return new CarAudioManagerEmbedded(manager);
+ case Car.SENSOR_SERVICE:
+ return new CarSensorManagerEmbedded(manager, getContext());
+ case Car.INFO_SERVICE:
+ return new CarInfoManagerEmbedded(manager);
+ case Car.APP_CONTEXT_SERVICE:
+ return new CarAppContextManagerEmbedded(manager);
+ case Car.PACKAGE_SERVICE:
+ return new CarPackageManagerEmbedded(manager);
+ case Car.CAR_NAVIGATION_SERVICE:
+ return new CarNavigationManagerEmbedded(manager);
+ default:
+ return manager;
}
}
diff --git a/car-support-lib/src/android/support/car/app/CarProxyActivity.java b/car-support-lib/src/android/support/car/app/CarProxyActivity.java
index 933e2db..4e6d6a2 100644
--- a/car-support-lib/src/android/support/car/app/CarProxyActivity.java
+++ b/car-support-lib/src/android/support/car/app/CarProxyActivity.java
@@ -23,7 +23,6 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
-import android.os.IBinder;
import android.support.car.Car;
import android.support.car.ServiceConnectionListener;
import android.support.car.input.CarInputManager;
@@ -58,7 +57,7 @@
private CarInputManager mInputManager;
private final CopyOnWriteArrayList<Pair<Integer, Object[]>> mCmds =
- new CopyOnWriteArrayList<Pair<Integer, Object[]>>();
+ new CopyOnWriteArrayList<>();
private final ServiceConnectionListener mConnectionListener= new ServiceConnectionListener() {
@Override
@@ -375,8 +374,7 @@
mCarActivity.dispatchCmd(cmd, args);
} else {
// not connected yet. queue it and return.
- Pair<Integer, Object[]> cmdToQ =
- new Pair<Integer, Object[]>(Integer.valueOf(cmd), args);
+ Pair<Integer, Object[]> cmdToQ = new Pair<>(Integer.valueOf(cmd), args);
mCmds.add(cmdToQ);
}
}
diff --git a/car-support-lib/src/android/support/car/hardware/CarSensorManagerEmbedded.java b/car-support-lib/src/android/support/car/hardware/CarSensorManagerEmbedded.java
index e4a7a06..dbffb13 100644
--- a/car-support-lib/src/android/support/car/hardware/CarSensorManagerEmbedded.java
+++ b/car-support-lib/src/android/support/car/hardware/CarSensorManagerEmbedded.java
@@ -16,35 +16,71 @@
package android.support.car.hardware;
+import android.content.Context;
import android.support.car.CarNotConnectedException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Arrays;
import java.util.LinkedList;
+import java.util.Set;
/**
* @hide
*/
public class CarSensorManagerEmbedded extends CarSensorManager {
+ private static final String TAG = "CarSensorsProxy";
private final android.car.hardware.CarSensorManager mManager;
+ private final CarSensorsProxy mCarSensorsProxy;
private final LinkedList<CarSensorEventListenerProxy> mListeners = new LinkedList<>();
- public CarSensorManagerEmbedded(Object manager) {
+ public CarSensorManagerEmbedded(Object manager, Context context) {
mManager = (android.car.hardware.CarSensorManager) manager;
+ mCarSensorsProxy = new CarSensorsProxy(context);
}
@Override
public int[] getSupportedSensors() throws CarNotConnectedException {
try {
- return mManager.getSupportedSensors();
+ Set<Integer> sensorsSet = new HashSet<Integer>();
+ for (Integer sensor : mManager.getSupportedSensors()) {
+ sensorsSet.add(sensor);
+ }
+ for (Integer proxySensor : mCarSensorsProxy.getSupportedSensors()) {
+ sensorsSet.add(proxySensor);
+ }
+ return toIntArray(sensorsSet);
} catch (android.car.CarNotConnectedException e) {
throw new CarNotConnectedException(e);
}
}
+ private static int[] toIntArray(Collection<Integer> collection) {
+ int len = collection.size();
+ int[] arr = new int[len];
+ int arrIndex = 0;
+ for (Integer item : collection) {
+ arr[arrIndex] = item;
+ arrIndex++;
+ }
+ return arr;
+ }
+
@Override
public boolean isSensorSupported(int sensorType) throws CarNotConnectedException {
try {
- return mManager.isSensorSupported(sensorType);
+ return mManager.isSensorSupported(sensorType)
+ || mCarSensorsProxy.isSensorSupported(sensorType);
+ } catch (android.car.CarNotConnectedException e) {
+ throw new CarNotConnectedException(e);
+ }
+ }
+
+ private boolean isSensorProxied(int sensorType) throws CarNotConnectedException {
+ try {
+ return !mManager.isSensorSupported(sensorType)
+ && mCarSensorsProxy.isSensorSupported(sensorType);
} catch (android.car.CarNotConnectedException e) {
throw new CarNotConnectedException(e);
}
@@ -53,13 +89,17 @@
@Override
public boolean registerListener(CarSensorEventListener listener, int sensorType,
int rate) throws CarNotConnectedException, IllegalArgumentException {
+ if (isSensorProxied(sensorType)) {
+ return mCarSensorsProxy.registerSensorListener(listener, sensorType, rate);
+ }
CarSensorEventListenerProxy proxy = null;
synchronized (this) {
proxy = findListenerLocked(listener);
if (proxy == null) {
proxy = new CarSensorEventListenerProxy(listener, sensorType);
+ mListeners.add(proxy);
} else {
- proxy.sensors |= sensorType;
+ proxy.sensors.add(sensorType);
}
}
try {
@@ -72,6 +112,7 @@
@Override
public void unregisterListener(CarSensorEventListener listener)
throws CarNotConnectedException {
+ mCarSensorsProxy.unregisterSensorListener(listener);
CarSensorEventListenerProxy proxy = null;
synchronized (this) {
proxy = findListenerLocked(listener);
@@ -90,14 +131,15 @@
@Override
public void unregisterListener(CarSensorEventListener listener, int sensorType)
throws CarNotConnectedException {
+ mCarSensorsProxy.unregisterSensorListener(listener, sensorType);
CarSensorEventListenerProxy proxy = null;
synchronized (this) {
proxy = findListenerLocked(listener);
if (proxy == null) {
return;
}
- proxy.sensors &= ~sensorType;
- if (proxy.sensors == 0) {
+ proxy.sensors.remove(sensorType);
+ if (proxy.sensors.isEmpty()) {
mListeners.remove(proxy);
}
}
@@ -110,6 +152,9 @@
@Override
public CarSensorEvent getLatestSensorEvent(int type) throws CarNotConnectedException {
+ if (isSensorProxied(type)) {
+ return mCarSensorsProxy.getLatestSensorEvent(type);
+ }
try {
return convert(mManager.getLatestSensorEvent(type));
} catch (android.car.CarNotConnectedException e) {
@@ -143,11 +188,11 @@
android.car.hardware.CarSensorManager.CarSensorEventListener {
public final CarSensorEventListener listener;
- public int sensors;
+ public final Set<Integer> sensors = new HashSet<Integer>();
- public CarSensorEventListenerProxy(CarSensorEventListener listener, int sensors) {
+ CarSensorEventListenerProxy(CarSensorEventListener listener, int sensor) {
this.listener = listener;
- this.sensors = sensors;
+ this.sensors.add(sensor);
}
@Override
diff --git a/car-support-lib/src/android/support/car/hardware/CarSensorsProxy.java b/car-support-lib/src/android/support/car/hardware/CarSensorsProxy.java
new file mode 100644
index 0000000..83ccd5d
--- /dev/null
+++ b/car-support-lib/src/android/support/car/hardware/CarSensorsProxy.java
@@ -0,0 +1,492 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.car.hardware;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.location.GpsSatellite;
+import android.location.GpsStatus;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Set;
+
+/**
+ * CarSensorsProxy adds car sensors implementation for sensors that are not provided by vehicle HAL.
+ * @hide
+ */
+class CarSensorsProxy {
+ private static final String TAG = "CarSensorsProxy";
+ private static final int MSG_SENSORT_EVENT = 1;
+
+ // @GuardedBy("this")
+ private final Map<Integer, Set<CarSensorManager.CarSensorEventListener>> mListenersMultiMap;
+ private final LocationManager mLocationManager;
+ private final SensorManager mSensorManager;
+ private final Sensor mAccelerometerSensor;
+ private final Sensor mMagneticFieldSensor;
+ private final Sensor mGyroscopeSensor;
+ private final int[] mSupportedSensors;
+
+ // @GuardedBy("this")
+ private Location mLastLocation;
+ // @GuardedBy("this")
+ private GpsStatus mLastGpsStatus;
+ // @GuardedBy("this")
+ private float[] mLastAccelerometerData = new float[3];
+ // @GuardedBy("this")
+ private float[] mLastMagneticFieldData = new float[3];
+ // @GuardedBy("this")
+ private float[] mLastGyroscopeData = new float[3];
+ // @GuardedBy("this")
+ private float[] mR = new float[16];
+ // @GuardedBy("this")
+ private float[] mI = new float[16];
+ // @GuardedBy("this")
+ private float[] mOrientation = new float[3];
+ // @GuardedBy("this")
+ private long mLastLocationTime;
+ // @GuardedBy("this")
+ private long mLastGpsStatusTime;
+ // @GuardedBy("this")
+ private long mLastAccelerometerDataTime;
+ // @GuardedBy("this")
+ private long mLastMagneticFieldDataTime;
+ // @GuardedBy("this")
+ private long mLastGyroscopeDataTime;
+
+ private final GpsStatus.Listener mGpsStatusListener = new GpsStatus.Listener() {
+ @Override
+ public void onGpsStatusChanged(int event) {
+ if (event == GpsStatus.GPS_EVENT_SATELLITE_STATUS) {
+ synchronized (CarSensorsProxy.this) {
+ mLastGpsStatus = mLocationManager.getGpsStatus(mLastGpsStatus);
+ mLastGpsStatusTime = System.nanoTime();
+ }
+ pushSensorChanges(CarSensorManager.SENSOR_TYPE_GPS_SATELLITE);
+ }
+ }
+ };
+
+ private final LocationListener mLocationListener = new LocationListener() {
+ @Override
+ public void onLocationChanged(Location location) {
+ synchronized (CarSensorsProxy.this) {
+ mLastLocation = location;
+ mLastLocationTime = System.nanoTime();
+ }
+ pushSensorChanges(CarSensorManager.SENSOR_TYPE_LOCATION);
+ }
+
+ @Override
+ public void onProviderEnabled(String provider) {
+ }
+
+ @Override
+ public void onProviderDisabled(String provider) {
+ }
+
+ @Override
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+ }
+ };
+
+ private final SensorEventListener mSensorListener = new SensorEventListener() {
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ int type = event.sensor.getType();
+ synchronized (CarSensorsProxy.this) {
+ switch (type) {
+ case Sensor.TYPE_GYROSCOPE:
+ System.arraycopy(event.values, 0, mLastGyroscopeData, 0, 3);
+ mLastGyroscopeDataTime = System.nanoTime();
+ pushSensorChanges(CarSensorManager.SENSOR_TYPE_GYROSCOPE);
+ break;
+ case Sensor.TYPE_MAGNETIC_FIELD:
+ System.arraycopy(event.values, 0, mLastMagneticFieldData, 0, 3);
+ mLastMagneticFieldDataTime = System.nanoTime();
+ pushSensorChanges(CarSensorManager.SENSOR_TYPE_COMPASS);
+ break;
+ case Sensor.TYPE_ACCELEROMETER:
+ System.arraycopy(event.values, 0, mLastAccelerometerData, 0, 3);
+ mLastAccelerometerDataTime = System.nanoTime();
+ pushSensorChanges(CarSensorManager.SENSOR_TYPE_ACCELEROMETER);
+ pushSensorChanges(CarSensorManager.SENSOR_TYPE_COMPASS);
+ break;
+ default:
+ Log.w(TAG, "Unexpected sensor event type: " + type);
+ // Should never happen.
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {}
+ };
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SENSORT_EVENT:
+ int sensorType = msg.arg1;
+ Collection<CarSensorManager.CarSensorEventListener> listenersCollection;
+ synchronized (CarSensorsProxy.this) {
+ listenersCollection = mListenersMultiMap.get(sensorType);
+ }
+ CarSensorEvent event = (CarSensorEvent) msg.obj;
+ if (event != null) {
+ for (CarSensorManager.CarSensorEventListener listener :
+ listenersCollection) {
+ listener.onSensorChanged(event);
+ }
+ }
+ break;
+ default:
+ Log.w(TAG, "Unexpected msg dispatched. msg: " + msg);
+ super.handleMessage(msg);
+ }
+ }
+ };
+
+ CarSensorsProxy(Context context) {
+ mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
+ mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+ mAccelerometerSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ mMagneticFieldSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
+ mGyroscopeSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
+ mListenersMultiMap = new HashMap<Integer, Set<CarSensorManager.CarSensorEventListener>>();
+ mSupportedSensors = initSupportedSensors(context);
+
+ }
+
+ private int[] initSupportedSensors(Context context) {
+ Set<Integer> features = new HashSet<>();
+ PackageManager packageManager = context.getPackageManager();
+ if (packageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_COMPASS)
+ && packageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_ACCELEROMETER)) {
+ features.add(CarSensorManager.SENSOR_TYPE_COMPASS);
+ }
+ if (packageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_ACCELEROMETER)) {
+ features.add(CarSensorManager.SENSOR_TYPE_ACCELEROMETER);
+ }
+ if (packageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_GYROSCOPE)) {
+ features.add(CarSensorManager.SENSOR_TYPE_GYROSCOPE);
+ }
+ if (packageManager.hasSystemFeature(PackageManager.FEATURE_LOCATION)) {
+ features.add(CarSensorManager.SENSOR_TYPE_LOCATION);
+ features.add(CarSensorManager.SENSOR_TYPE_GPS_SATELLITE);
+ }
+ return toIntArray(features);
+ }
+
+ private static int[] toIntArray(Collection<Integer> collection) {
+ int len = collection.size();
+ int[] arr = new int[len];
+ int arrIndex = 0;
+ for (Integer item : collection) {
+ arr[arrIndex] = item;
+ arrIndex++;
+ }
+ return arr;
+ }
+
+
+ public boolean isSensorSupported(int sensorType) {
+ for (int sensor : mSupportedSensors) {
+ if (sensor == sensorType) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public int[] getSupportedSensors() {
+ return mSupportedSensors;
+ }
+
+ public boolean registerSensorListener(CarSensorManager.CarSensorEventListener listener,
+ int sensorType, int rate) {
+ // current implementation ignores rate.
+ boolean sensorSetChanged = false;
+ synchronized (this) {
+ if (mListenersMultiMap.get(sensorType) == null) {
+ mListenersMultiMap.put(sensorType,
+ new HashSet<CarSensorManager.CarSensorEventListener>());
+ sensorSetChanged = true;
+ }
+ mListenersMultiMap.get(sensorType).add(listener);
+ }
+
+ pushSensorChanges(sensorType);
+
+ if (sensorSetChanged) {
+ updateSensorListeners();
+ }
+ return true;
+ }
+
+ public void unregisterSensorListener(CarSensorManager.CarSensorEventListener listener,
+ int sensorType) {
+ if (listener == null) {
+ return;
+ }
+ boolean sensorSetChanged = false;
+ synchronized (this) {
+ Set<CarSensorManager.CarSensorEventListener> sensorTypeListeneres =
+ mListenersMultiMap.get(sensorType);
+ if (sensorTypeListeneres != null) {
+ sensorTypeListeneres.remove(listener);
+ if (sensorTypeListeneres.isEmpty()) {
+ mListenersMultiMap.remove(sensorType);
+ sensorSetChanged = true;
+ }
+ }
+ }
+ if (sensorSetChanged) {
+ updateSensorListeners();
+ };
+ }
+
+ public void unregisterSensorListener(CarSensorManager.CarSensorEventListener listener) {
+ if (listener == null) {
+ return;
+ }
+ boolean sensorSetChanged = false;
+ synchronized (this) {
+ for (Map.Entry<Integer, Set<CarSensorManager.CarSensorEventListener>> entry :
+ mListenersMultiMap.entrySet()) {
+ if (entry.getValue().contains(listener)) {
+ entry.getValue().remove(listener);
+ }
+ if (entry.getValue().isEmpty()) {
+ sensorSetChanged = true;
+ mListenersMultiMap.remove(entry.getKey());
+ }
+ }
+ }
+ if (sensorSetChanged) {
+ updateSensorListeners();
+ };
+ }
+
+ public CarSensorEvent getLatestSensorEvent(int type) {
+ return getSensorEvent(type);
+ }
+
+ private void pushSensorChanges(int sensorType) {
+ CarSensorEvent event = getSensorEvent(sensorType);
+ if (event == null) {
+ return;
+ }
+ Message msg = mHandler.obtainMessage(MSG_SENSORT_EVENT, sensorType, 0, event);
+ mHandler.sendMessage(msg);
+ }
+
+ private CarSensorEvent getSensorEvent(int sensorType) {
+ CarSensorEvent event = null;
+ synchronized (this) {
+ switch (sensorType) {
+ case CarSensorManager.SENSOR_TYPE_COMPASS:
+ if (mLastMagneticFieldDataTime != 0 && mLastAccelerometerDataTime != 0) {
+ event = new CarSensorEvent(sensorType, Math.max(mLastMagneticFieldDataTime,
+ mLastAccelerometerDataTime), 3, 0);
+ SensorManager.getRotationMatrix(mR, mI, mLastAccelerometerData,
+ mLastMagneticFieldData);
+ SensorManager.getOrientation(mR, mOrientation);
+ event.floatValues[CarSensorEvent.INDEX_COMPASS_BEARING] =
+ (float) Math.toDegrees(mOrientation[0]);
+ event.floatValues[CarSensorEvent.INDEX_COMPASS_PITCH] =
+ (float) Math.toDegrees(mOrientation[1]);
+ event.floatValues[CarSensorEvent.INDEX_COMPASS_ROLL] =
+ (float) Math.toDegrees(mOrientation[2]);
+ }
+ break;
+ case CarSensorManager.SENSOR_TYPE_LOCATION:
+ if (mLastLocationTime != 0) {
+ event = new CarSensorEvent(sensorType, mLastLocationTime, 6, 3);
+ populateLocationCarSensorEvent(event, mLastLocation);
+ }
+ break;
+ case CarSensorManager.SENSOR_TYPE_ACCELEROMETER:
+ if (mLastAccelerometerDataTime != 0) {
+ event = new CarSensorEvent(sensorType, mLastAccelerometerDataTime, 3, 0);
+ event.floatValues[CarSensorEvent.INDEX_ACCELEROMETER_X] =
+ mLastAccelerometerData[0];
+ event.floatValues[CarSensorEvent.INDEX_ACCELEROMETER_Y] =
+ mLastAccelerometerData[1];
+ event.floatValues[CarSensorEvent.INDEX_ACCELEROMETER_Z] =
+ mLastAccelerometerData[2];
+ }
+ break;
+ case CarSensorManager.SENSOR_TYPE_GPS_SATELLITE:
+ if (mLastGpsStatusTime != 0) {
+ event = createGpsStatusCarSensorEvent(mLastGpsStatus);
+ }
+ break;
+ case CarSensorManager.SENSOR_TYPE_GYROSCOPE:
+ if (mLastGyroscopeDataTime != 0) {
+ event = new CarSensorEvent(sensorType, mLastGyroscopeDataTime, 3, 0);
+ event.floatValues[CarSensorEvent.INDEX_GYROSCOPE_X] = mLastGyroscopeData[0];
+ event.floatValues[CarSensorEvent.INDEX_GYROSCOPE_Y] = mLastGyroscopeData[1];
+ event.floatValues[CarSensorEvent.INDEX_GYROSCOPE_Z] = mLastGyroscopeData[2];
+ }
+ break;
+ default:
+ // Should not happen.
+ Log.w(TAG, "[getSensorEvent]: Unsupported sensor type:" + sensorType);
+ return null;
+ }
+ }
+ return event;
+ }
+
+ private void populateLocationCarSensorEvent(CarSensorEvent event, Location location) {
+ if (location == null) {
+ return;
+ }
+ int present = 0;
+ present |= (0x1 << CarSensorEvent.INDEX_LOCATION_LONGITUDE);
+ event.intValues[CarSensorEvent.INDEX_LOCATION_LATITUDE_INTS] =
+ (int) (location.getLongitude() * 1E7);
+
+ present |= (0x1 << CarSensorEvent.INDEX_LOCATION_LATITUDE);
+ event.intValues[CarSensorEvent.INDEX_LOCATION_LATITUDE_INTS] =
+ (int) (location.getLatitude() * 1E7);
+
+ if (location.hasAccuracy()) {
+ present |= (0x1 << CarSensorEvent.INDEX_LOCATION_ACCURACY);
+ event.floatValues[CarSensorEvent.INDEX_LOCATION_ACCURACY] = location.getAccuracy();
+ }
+
+ if (location.hasAltitude()) {
+ present |= (0x1 << CarSensorEvent.INDEX_LOCATION_ALTITUDE);
+ event.floatValues[CarSensorEvent.INDEX_LOCATION_ALTITUDE] =
+ (float) location.getAltitude();
+ }
+
+ if (location.hasSpeed()) {
+ present |= (0x1 << CarSensorEvent.INDEX_LOCATION_SPEED);
+ event.floatValues[CarSensorEvent.INDEX_LOCATION_SPEED] = location.getSpeed();
+ }
+
+ if (location.hasBearing()) {
+ present |= (0x1 << CarSensorEvent.INDEX_LOCATION_BEARING);
+ event.floatValues[CarSensorEvent.INDEX_LOCATION_BEARING] = location.getBearing();
+ }
+
+ event.intValues[0] = present;
+ }
+
+ private CarSensorEvent createGpsStatusCarSensorEvent(GpsStatus gpsStatus) {
+ CarSensorEvent event = null;
+
+ if (gpsStatus == null) {
+ return event;
+ }
+
+ int numberInView = 0;
+ int numberInUse = 0;
+ for (GpsSatellite satellite : gpsStatus.getSatellites()) {
+ ++numberInView;
+ if (satellite.usedInFix()) {
+ ++numberInUse;
+ }
+ }
+ int floatValuesSize = CarSensorEvent.INDEX_GPS_SATELLITE_ARRAY_FLOAT_INTERVAL * numberInView
+ + CarSensorEvent.INDEX_GPS_SATELLITE_ARRAY_FLOAT_OFFSET;
+ int intValuesSize = CarSensorEvent.INDEX_GPS_SATELLITE_ARRAY_INT_INTERVAL * numberInView
+ + CarSensorEvent.INDEX_GPS_SATELLITE_ARRAY_INT_OFFSET;
+ event = new CarSensorEvent(CarSensorManager.SENSOR_TYPE_GPS_SATELLITE, mLastGpsStatusTime,
+ floatValuesSize, intValuesSize);
+ event.intValues[CarSensorEvent.INDEX_GPS_SATELLITE_NUMBER_IN_USE] = numberInUse;
+ event.intValues[CarSensorEvent.INDEX_GPS_SATELLITE_NUMBER_IN_VIEW] = numberInView;
+ int i = 0;
+ for (GpsSatellite satellite : gpsStatus.getSatellites()) {
+ int iInt = CarSensorEvent.INDEX_GPS_SATELLITE_ARRAY_INT_OFFSET
+ + CarSensorEvent.INDEX_GPS_SATELLITE_ARRAY_INT_INTERVAL * i;
+ int iFloat = CarSensorEvent.INDEX_GPS_SATELLITE_ARRAY_FLOAT_OFFSET
+ + CarSensorEvent.INDEX_GPS_SATELLITE_ARRAY_FLOAT_INTERVAL * i;
+ event.floatValues[iFloat + CarSensorEvent.INDEX_GPS_SATELLITE_PRN_OFFSET] =
+ satellite.getPrn();
+ event.floatValues[iFloat + CarSensorEvent.INDEX_GPS_SATELLITE_SNR_OFFSET] =
+ satellite.getSnr();
+ event.floatValues[iFloat + CarSensorEvent.INDEX_GPS_SATELLITE_AZIMUTH_OFFSET] =
+ satellite.getAzimuth();
+ event.floatValues[iFloat + CarSensorEvent.INDEX_GPS_SATELLITE_ELEVATION_OFFSET] =
+ satellite.getElevation();
+ event.intValues[iInt] = satellite.usedInFix() ? 1 : 0;
+ i++;
+ }
+ return event;
+ }
+
+ private void updateSensorListeners() {
+ Set<Integer> activeSensors;
+ synchronized (this) {
+ activeSensors = mListenersMultiMap.keySet();
+ }
+ if (activeSensors.contains(CarSensorManager.SENSOR_TYPE_LOCATION)) {
+ mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,
+ mLocationListener);
+ } else {
+ mLocationManager.removeUpdates(mLocationListener);
+ }
+
+ if (activeSensors.contains(CarSensorManager.SENSOR_TYPE_GPS_SATELLITE)) {
+ mLocationManager.addGpsStatusListener(mGpsStatusListener);
+ } else {
+ mLocationManager.removeGpsStatusListener(mGpsStatusListener);
+ }
+
+ if (activeSensors.contains(CarSensorManager.SENSOR_TYPE_ACCELEROMETER)
+ || activeSensors.contains(CarSensorManager.SENSOR_TYPE_COMPASS)) {
+ mSensorManager.registerListener(mSensorListener, mAccelerometerSensor,
+ SensorManager.SENSOR_DELAY_FASTEST);
+ } else {
+ mSensorManager.unregisterListener(mSensorListener, mAccelerometerSensor);
+ }
+
+ if (activeSensors.contains(CarSensorManager.SENSOR_TYPE_COMPASS)) {
+ mSensorManager.registerListener(mSensorListener, mMagneticFieldSensor,
+ SensorManager.SENSOR_DELAY_FASTEST);
+ } else {
+ mSensorManager.unregisterListener(mSensorListener, mMagneticFieldSensor);
+ }
+
+ if (activeSensors.contains(CarSensorManager.SENSOR_TYPE_GYROSCOPE)) {
+ mSensorManager.registerListener(mSensorListener, mGyroscopeSensor,
+ SensorManager.SENSOR_DELAY_FASTEST);
+ } else {
+ mSensorManager.unregisterListener(mSensorListener, mGyroscopeSensor);
+ }
+ }
+}
diff --git a/car_product/build/car.mk b/car_product/build/car.mk
index 3a7cfb1..aaed0d2 100644
--- a/car_product/build/car.mk
+++ b/car_product/build/car.mk
@@ -32,7 +32,7 @@
screenrecord
# This is for testing
-PRODUCT_PACKAGES +=
+PRODUCT_PACKAGES += \
EmbeddedKitchenSinkApp
PRODUCT_COPY_FILES := \
@@ -99,6 +99,10 @@
PRODUCT_LOCALES := en_US af_ZA am_ET ar_EG bg_BG bn_BD ca_ES cs_CZ da_DK de_DE el_GR en_AU en_GB en_IN es_ES es_US et_EE eu_ES fa_IR fi_FI fr_CA fr_FR gl_ES hi_IN hr_HR hu_HU hy_AM in_ID is_IS it_IT iw_IL ja_JP ka_GE km_KH ko_KR ky_KG lo_LA lt_LT lv_LV km_MH kn_IN mn_MN ml_IN mk_MK mr_IN ms_MY my_MM ne_NP nb_NO nl_NL pl_PL pt_BR pt_PT ro_RO ru_RU si_LK sk_SK sl_SI sr_RS sv_SE sw_TZ ta_IN te_IN th_TH tl_PH tr_TR uk_UA vi_VN zh_CN zh_HK zh_TW zu_ZA en_XA ar_XB
+# should add to BOOT_JARS only once
+ifeq (,$(INCLUDED_ANDROID_CAR_TO_PRODUCT_BOOT_JARS))
PRODUCT_BOOT_JARS += \
android.car
+INCLUDED_ANDROID_CAR_TO_PRODUCT_BOOT_JARS := yes
+endif
diff --git a/car_product/init/init.bootstat.rc b/car_product/init/init.bootstat.rc
new file mode 100644
index 0000000..8c93d67
--- /dev/null
+++ b/car_product/init/init.bootstat.rc
@@ -0,0 +1,36 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# 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.vns=running
+ exec - root root -- /system/bin/bootstat -r vns_running
+
+on property:init.svc.zygote=running
+ exec - root root -- /system/bin/bootstat -r zygote_running
diff --git a/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkConsts.java b/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkConsts.java
index 003451f..de792ed 100644
--- a/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkConsts.java
+++ b/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkConsts.java
@@ -407,6 +407,16 @@
}
}
+public static class VehicleAudioVolumeCapabilityFlag {
+public static final int VEHICLE_AUDIO_VOLUME_CAPABILITY_PERSISTENT_STORAGE = 0x1;
+public static String enumToString(int v) {
+switch(v) {
+case VEHICLE_AUDIO_VOLUME_CAPABILITY_PERSISTENT_STORAGE: return "VEHICLE_AUDIO_VOLUME_CAPABILITY_PERSISTENT_STORAGE";
+default: return "UNKNOWN";
+}
+}
+}
+
public static class VehicleAudioVolumeState {
public static final int VEHICLE_AUDIO_VOLUME_STATE_OK = 0;
public static final int VEHICLE_AUDIO_VOLUME_STATE_LIMIT_REACHED = 1;
@@ -458,10 +468,10 @@
}
public static class VehicleAudioHwVariantConfigFlag {
-public static final int VEHICLE_AUDIO_HW_VARIANT_FLAG_PASS_RADIO_AUDIO_FOCUS_FLAG = 0x1;
+public static final int VEHICLE_AUDIO_HW_VARIANT_FLAG_INTERNAL_RADIO_FLAG = 0x1;
public static String enumToString(int v) {
switch(v) {
-case VEHICLE_AUDIO_HW_VARIANT_FLAG_PASS_RADIO_AUDIO_FOCUS_FLAG: return "VEHICLE_AUDIO_HW_VARIANT_FLAG_PASS_RADIO_AUDIO_FOCUS_FLAG";
+case VEHICLE_AUDIO_HW_VARIANT_FLAG_INTERNAL_RADIO_FLAG: return "VEHICLE_AUDIO_HW_VARIANT_FLAG_INTERNAL_RADIO_FLAG";
default: return "UNKNOWN";
}
}
diff --git a/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkProtoUtil.java b/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkProtoUtil.java
index 5dc5a6f..ac2f393 100644
--- a/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkProtoUtil.java
+++ b/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehicleNetworkProtoUtil.java
@@ -18,6 +18,8 @@
import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfig;
import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValue;
+import java.util.Arrays;
+
public class VehicleNetworkProtoUtil {
public static String VehiclePropValueToString(VehiclePropValue value) {
StringBuilder sb = new StringBuilder();
@@ -25,6 +27,8 @@
sb.append(Integer.toHexString(value.getProp()));
sb.append(" type:0x");
sb.append(Integer.toHexString(value.getValueType()));
+ sb.append(" floatValues:" + Arrays.toString(value.getFloatValuesList().toArray()));
+ sb.append(" integerValues:" + Arrays.toString(value.getInt32ValuesList().toArray()));
return sb.toString();
}
diff --git a/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehiclePropConfigUtil.java b/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehiclePropConfigUtil.java
index 61e675b..2482f39 100644
--- a/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehiclePropConfigUtil.java
+++ b/libvehiclenetwork/java/src/com/android/car/vehiclenetwork/VehiclePropConfigUtil.java
@@ -21,6 +21,9 @@
import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleValueType;
import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfig;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Utility class to help creating VehiclePropConfig.
*/
@@ -66,6 +69,14 @@
public static VehiclePropConfig.Builder getBuilder(int property, int access, int changeMode,
int type, int permissionModel, int configFlags, float sampleRateMax,
float sampleRateMin) {
+ List<Integer> emptyList = new ArrayList<Integer>();
+ return getBuilder(property, access, changeMode, type, permissionModel, configFlags,
+ sampleRateMax, sampleRateMin, emptyList, emptyList);
+ }
+
+ public static VehiclePropConfig.Builder getBuilder(int property, int access, int changeMode,
+ int type, int permissionModel, int configFlags, float sampleRateMax,
+ float sampleRateMin, List<Integer> int32Maxs, List<Integer> int32Mins) {
return VehiclePropConfig.newBuilder().
setProp(property).
setAccess(access).
@@ -74,6 +85,8 @@
setPermissionModel(permissionModel).
addConfigArray(configFlags).
setSampleRateMax(sampleRateMax).
- setSampleRateMin(sampleRateMin);
+ setSampleRateMin(sampleRateMin)
+ .addAllInt32Maxs(int32Maxs)
+ .addAllInt32Mins(int32Mins);
}
}
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index 73dd518..4df72e4 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -88,6 +88,12 @@
android:label="@string/car_permission_label_control_app_blocking"
android:description="@string/car_permission_desc_control_app_blocking" />
+ <permission
+ android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"
+ android:protectionLevel="system|signature"
+ android:label="@string/car_permission_label_audio_volume"
+ android:description="@string/car_permission_desc_audio_volume" />
+
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.DEVICE_POWER" />
diff --git a/service/jni/Android.mk b/service/jni/Android.mk
index ed29126..012ddfa 100644
--- a/service/jni/Android.mk
+++ b/service/jni/Android.mk
@@ -32,6 +32,7 @@
LOCAL_CFLAGS := \
-Wno-unused-parameter \
+ -std=c++11
LOCAL_MODULE := libjni_car_service
LOCAL_MODULE_TAGS := optional
diff --git a/service/jni/com_android_car_CarInputService.cpp b/service/jni/com_android_car_CarInputService.cpp
index 1b02c12..a45d7e6 100644
--- a/service/jni/com_android_car_CarInputService.cpp
+++ b/service/jni/com_android_car_CarInputService.cpp
@@ -25,30 +25,42 @@
#include <android/keycodes.h>
#include <cutils/log.h>
#include <utils/Errors.h>
-
+#include <unordered_map>
namespace android {
static int androidKeyCodeToLinuxKeyCode(int androidKeyCode) {
- switch (androidKeyCode) {
- case AKEYCODE_VOLUME_UP:
- return KEY_VOLUMEUP;
- case AKEYCODE_VOLUME_DOWN:
- return KEY_VOLUMEDOWN;
- case AKEYCODE_CALL:
- return KEY_SEND;
- case AKEYCODE_ENDCALL:
- return KEY_END;
- /* TODO add more keys like these:
- case AKEYCODE_MEDIA_PLAY_PAUSE:
- case AKEYCODE_MEDIA_STOP:
- case AKEYCODE_MEDIA_NEXT:
- case AKEYCODE_MEDIA_PREVIOUS:*/
- case AKEYCODE_VOICE_ASSIST:
- return KEY_MICMUTE;
- default:
+ // Map Android Key Code to Linux Kernel codes
+ // according to frameworks/base/data/keyboards/Generic.kl
+
+ static const std::unordered_map<int, int> key_map {
+ { AKEYCODE_VOLUME_UP, KEY_VOLUMEUP },
+ { AKEYCODE_VOLUME_DOWN, KEY_VOLUMEDOWN },
+ { AKEYCODE_VOLUME_MUTE, KEY_MUTE },
+ { AKEYCODE_CALL, KEY_PHONE },
+ { AKEYCODE_ENDCALL, KEY_END }, // Currently not supported in Generic.kl
+ { AKEYCODE_MUSIC, KEY_SOUND },
+ { AKEYCODE_MEDIA_PLAY_PAUSE, KEY_PLAYPAUSE },
+ { AKEYCODE_MEDIA_PLAY, KEY_PLAY },
+ { AKEYCODE_BREAK, KEY_PAUSE },
+ { AKEYCODE_MEDIA_STOP, KEY_STOP },
+ { AKEYCODE_MEDIA_FAST_FORWARD, KEY_FASTFORWARD },
+ { AKEYCODE_MEDIA_REWIND, KEY_REWIND },
+ { AKEYCODE_MEDIA_NEXT, KEY_NEXTSONG },
+ { AKEYCODE_MEDIA_PREVIOUS, KEY_PREVIOUSSONG },
+ { AKEYCODE_CHANNEL_UP, KEY_CHANNELUP },
+ { AKEYCODE_CHANNEL_DOWN, KEY_CHANNELDOWN },
+ { AKEYCODE_VOICE_ASSIST, KEY_MICMUTE },
+ { AKEYCODE_HOME, KEY_HOME }
+ };
+
+ std::unordered_map<int, int>::const_iterator got = key_map.find(androidKeyCode);
+
+ if (got == key_map.end()) {
ALOGW("Unmapped android key code %d dropped", androidKeyCode);
return 0;
+ } else {
+ return got->second;
}
}
diff --git a/service/res/drawable-hdpi/car_ic_error.png b/service/res/drawable-hdpi/car_ic_error.png
new file mode 100644
index 0000000..ebb027d
--- /dev/null
+++ b/service/res/drawable-hdpi/car_ic_error.png
Binary files differ
diff --git a/service/res/drawable-mdpi/car_ic_error.png b/service/res/drawable-mdpi/car_ic_error.png
new file mode 100644
index 0000000..6d6c10f
--- /dev/null
+++ b/service/res/drawable-mdpi/car_ic_error.png
Binary files differ
diff --git a/service/res/drawable-xhdpi/car_ic_error.png b/service/res/drawable-xhdpi/car_ic_error.png
new file mode 100644
index 0000000..921555d
--- /dev/null
+++ b/service/res/drawable-xhdpi/car_ic_error.png
Binary files differ
diff --git a/service/res/drawable-xxhdpi/car_ic_error.png b/service/res/drawable-xxhdpi/car_ic_error.png
new file mode 100644
index 0000000..dfab63c
--- /dev/null
+++ b/service/res/drawable-xxhdpi/car_ic_error.png
Binary files differ
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 24ef730..3fd2a62 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -33,20 +33,30 @@
There is no "radio" as radio routing is outside android (for external module) or same as
music (for android internal module)
OEM can put multiple policies as item and VEHICLE_PROPERTY_AUDIO_HW_VARIANT in vehicle HAL
- can decide which policy to use for the given H/W. This allows OEMs to support multuple
+ can decide which policy to use for the given H/W. This allows OEMs to support multiple
audio policy from single android S/W by detecting system's audio capability in
vehicle HAL.-->
<string-array translatable="false" name="audioRoutingPolicy">
<!-- alll logical streams into single physical stream 0. -->
- <item>"0:call,media,nav_guidance,voice_command,alarm,notification,system,safety,unknown"</item>
+ <item>"0:call,media,radio,nav_guidance,voice_command,alarm,notification,system,safety,unknown"</item>
<!-- call and media to physical stream 0 while all others go to physical stream 1 -->
- <item>"0:call,media,unknown#1:nav_guidance,voice_command,alarm,notification,system,safety"</item>
+ <item>"0:call,media,radio,unknown#1:nav_guidance,voice_command,alarm,notification,system,safety"</item>
</string-array>
+ <!-- Timeout value in Ms for audio focus wait. Audio focus request not responsed within
+ this value will be treated as timeout and audio focus will be reset to LOSS state. -->
+ <integer name="audioFocusWaitTimeoutMs">1000</integer>
+ <!-- Configuration to enable usage of dynamic audio routing. If this is set to false,
+ dynamic audio routing is disabled and audio works in legacy mode. It may be useful
+ during initial development where audio hal does not support bus based addressing yet. -->
+ <bool name="audioUseDynamicRouting">true</bool>
+
+ <!-- Number of audio focus timeouts that indicate vehicle CAN bus failure. -->
+ <integer name="consecutiveHalFailures">3</integer>
<!-- This is kernel device node to allow input event injection for key inputs coming
from vehicle hal -->
<string name="inputInjectionDeviceNode">/dev/input/event2</string>
- <string name="instrumentClusterRendererPackage">android.car.cluster.demorenderer</string>
- <string name="instrumentClusterRendererFactoryClass">android.car.cluster.demorenderer.InstrumentClusterRendererFactory</string>
+ <string name="instrumentClusterRendererPackage">android.car.cluster.loggingrenderer</string>
+ <string name="instrumentClusterRendererFactoryClass">android.car.cluster.loggingrenderer.InstrumentClusterRendererFactory</string>
</resources>
diff --git a/service/res/values/strings.xml b/service/res/values/strings.xml
index 2fac6b4..3721fb3 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -50,12 +50,16 @@
<string name="car_permission_desc_radio">Access your car\'s 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-audio-volume [CHAR LIMIT=NONE] -->
+ <string name="car_permission_label_audio_volume">Car Audio Volume</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\'s vehicle HAL for internal
testing purpose.</string>
+ <!-- Permission text: can adjust the audio volume on your car [CHAR LIMIT=NONE] -->
+ <string name="car_permission_desc_audio_volume">Control your car\'s audio volume.</string>
<string name="car_permission_label_control_app_blocking">Application blocking</string>
<!-- Permission text: can emulate information from your car [CHAR LIMIT=NONE] -->
<string name="car_permission_desc_control_app_blocking">Control application blocking while
@@ -65,4 +69,11 @@
cluster [CHAR LIMIT=NONE] -->
<string name="car_permission_desc_car_navigation_manager">Report navigation data to instrument
cluster</string>
+
+ <!-- Notification messages -->
+ <!-- Notification text: Notification shown to the user when vehicle CAN bus fails -->
+ <string name="car_can_bus_failure">CAN bus failed</string>
+ <!-- Notification text: Notification description shown to the user when vehicle CAN bus fails -->
+ <string name="car_can_bus_failure_desc">CAN bus does not respond. Unplug and plug back headunit
+ box and restart the car</string>
</resources>
diff --git a/service/src/com/android/car/AudioRoutingPolicy.java b/service/src/com/android/car/AudioRoutingPolicy.java
index 39ad826..fdad5aa 100644
--- a/service/src/com/android/car/AudioRoutingPolicy.java
+++ b/service/src/com/android/car/AudioRoutingPolicy.java
@@ -44,13 +44,13 @@
}
private static int getStreamType(String str) {
- // no radio here as radio routing is outside android (for external module) or same as music
- // (for android internal module)
switch (str) {
case "call":
return CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL;
case "media":
return CarAudioManager.CAR_AUDIO_USAGE_MUSIC;
+ case "radio":
+ return CarAudioManager.CAR_AUDIO_USAGE_RADIO;
case "nav_guidance":
return CarAudioManager.CAR_AUDIO_USAGE_NAVIGATION_GUIDANCE;
case "voice_command":
@@ -101,20 +101,8 @@
}
for (int i = 0; i < mPhysicalStreamForLogicalStream.length; i++) {
if (mPhysicalStreamForLogicalStream[i] == USAGE_TYPE_INVALID) {
- if (i == CarAudioManager.CAR_AUDIO_USAGE_RADIO) {
- // set radio routing to be the same as music. For external radio, this does not
- // matter. For internal one, it should be the same as music.
- int musicPhysicalStream =
- mPhysicalStreamForLogicalStream[CarAudioManager.CAR_AUDIO_USAGE_MUSIC];
- if (musicPhysicalStream == USAGE_TYPE_INVALID) {
- musicPhysicalStream = defaultStreamType;
- }
- mPhysicalStreamForLogicalStream[i] = musicPhysicalStream;
- } else {
- Log.w(CarLog.TAG_AUDIO, "Audio routing policy did not cover logical stream " +
- i);
- mPhysicalStreamForLogicalStream[i] = defaultStreamType;
- }
+ Log.w(CarLog.TAG_AUDIO, "Audio routing policy did not cover logical stream " + i);
+ mPhysicalStreamForLogicalStream[i] = defaultStreamType;
}
}
}
diff --git a/service/src/com/android/car/CanBusErrorNotifier.java b/service/src/com/android/car/CanBusErrorNotifier.java
new file mode 100644
index 0000000..115dda0
--- /dev/null
+++ b/service/src/com/android/car/CanBusErrorNotifier.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Build;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Class used to notify user about CAN bus failure.
+ */
+final class CanBusErrorNotifier {
+ private static final String TAG = CarLog.TAG_CAN_BUS + ".ERROR.NOTIFIER";
+ private static final int NOTIFICATION_ID = 1;
+ private static final boolean IS_RELEASE_BUILD = "user".equals(Build.TYPE);
+
+ private final Context mContext;
+ private final NotificationManager mNotificationManager;
+
+ @GuardedBy("this")
+ private boolean mFailed;
+
+ @GuardedBy("this")
+ private Notification mNotification;
+
+ CanBusErrorNotifier(Context context) {
+ mNotificationManager = (NotificationManager) context.getSystemService(
+ Context.NOTIFICATION_SERVICE);
+ mContext = context;
+ }
+
+ public void setCanBusFailure(boolean failed) {
+ synchronized (this) {
+ if (mFailed == failed) {
+ return;
+ }
+ mFailed = failed;
+ }
+ Log.i(TAG, "Changing CAN bus failure state to " + failed);
+
+ if (failed) {
+ showNotification();
+ } else {
+ hideNotification();
+ }
+ }
+
+ private void showNotification() {
+ if (IS_RELEASE_BUILD) {
+ // TODO: for release build we should show message to take car to the dealer.
+ return;
+ }
+ Notification notification;
+ synchronized (this) {
+ if (mNotification == null) {
+ mNotification = new Notification.Builder(mContext)
+ .setContentTitle(mContext.getString(R.string.car_can_bus_failure))
+ .setContentText(mContext.getString(R.string.car_can_bus_failure_desc))
+ .setSmallIcon(R.drawable.car_ic_error)
+ .setOngoing(true)
+ .build();
+ }
+ notification = mNotification;
+ }
+ mNotificationManager.notify(TAG, NOTIFICATION_ID, notification);
+ }
+
+ private void hideNotification() {
+ if (IS_RELEASE_BUILD) {
+ // TODO: for release build we should show message to take car to the dealer.
+ return;
+ }
+ mNotificationManager.cancel(TAG, NOTIFICATION_ID);
+ }
+}
diff --git a/service/src/com/android/car/CarAudioService.java b/service/src/com/android/car/CarAudioService.java
index 5019fb4..b5a0a09 100644
--- a/service/src/com/android/car/CarAudioService.java
+++ b/service/src/com/android/car/CarAudioService.java
@@ -15,46 +15,71 @@
*/
package com.android.car;
+import android.car.Car;
+import android.car.VehicleZoneUtil;
+import android.app.AppGlobals;
import android.car.media.CarAudioManager;
import android.car.media.ICarAudio;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.media.AudioAttributes;
+import android.media.AudioDeviceInfo;
import android.media.AudioFocusInfo;
+import android.media.AudioFormat;
import android.media.AudioManager;
+import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
+import android.media.IVolumeController;
import android.media.audiopolicy.AudioPolicy;
import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
+import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
+import android.os.RemoteException;
import android.util.Log;
import com.android.car.hal.AudioHalService;
-import com.android.car.hal.AudioHalService.AudioHalListener;
+import com.android.car.hal.AudioHalService.AudioHalFocusListener;
import com.android.car.hal.VehicleHal;
import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
import java.util.LinkedList;
+public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
+ AudioHalFocusListener {
-public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, AudioHalListener {
+ public interface AudioContextChangeListener {
+ /**
+ * Notifies the current primary audio context (app holding focus).
+ * If there is no active context, context will be 0.
+ * Will use context like CarAudioManager.CAR_AUDIO_USAGE_*
+ */
+ void onContextChange(int primaryFocusContext, int primaryFocusPhysicalStream);
+ }
- private static final long FOCUS_RESPONSE_WAIT_TIMEOUT_MS = 1000;
+ private final long mFocusResponseWaitTimeoutMs;
+
+ private final int mNumConsecutiveHalFailuresForCanError;
private static final String TAG_FOCUS = CarLog.TAG_AUDIO + ".FOCUS";
private static final boolean DBG = true;
+ private static final boolean DBG_DYNAMIC_AUDIO_ROUTING = true;
private final AudioHalService mAudioHal;
private final Context mContext;
private final HandlerThread mFocusHandlerThread;
private final CarAudioFocusChangeHandler mFocusHandler;
- private final CarAudioVolumeHandler mVolumeHandler;
private final SystemFocusListener mSystemFocusListener;
- private AudioPolicy mAudioPolicy;
+ private final CarVolumeService mVolumeService;
private final Object mLock = new Object();
@GuardedBy("mLock")
+ private AudioPolicy mAudioPolicy;
+ @GuardedBy("mLock")
private FocusState mCurrentFocusState = FocusState.STATE_LOSS;
/** Focus state received, but not handled yet. Once handled, this will be set to null. */
@GuardedBy("mLock")
@@ -71,6 +96,7 @@
private AudioRoutingPolicy mAudioRoutingPolicy;
private final AudioManager mAudioManager;
+ private final CanBusErrorNotifier mCanBusErrorNotifier;
private final BottomAudioFocusListener mBottomAudioFocusHandler =
new BottomAudioFocusListener();
private final CarProxyAndroidFocusListener mCarProxyAudioFocusHandler =
@@ -83,6 +109,20 @@
private boolean mCallActive = false;
@GuardedBy("mLock")
private int mCurrentAudioContexts = 0;
+ @GuardedBy("mLock")
+ private int mCurrentPrimaryAudioContext = 0;
+ @GuardedBy("mLock")
+ private int mCurrentPrimaryPhysicalStream = 0;
+ @GuardedBy("mLock")
+ private AudioContextChangeListener mAudioContextChangeListener;
+ @GuardedBy("mLock")
+ private CarAudioContextChangeHandler mCarAudioContextChangeHandler;
+ @GuardedBy("mLock")
+ private boolean mIsRadioExternal;
+ @GuardedBy("mLock")
+ private int mNumConsecutiveHalFailures;
+
+ private final boolean mUseDynamicRouting;
private final AudioAttributes mAttributeBottom =
CarAudioAttributesUtil.getAudioAttributesForCarUsage(
@@ -91,15 +131,21 @@
CarAudioAttributesUtil.getAudioAttributesForCarUsage(
CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY);
- public CarAudioService(Context context) {
+ public CarAudioService(Context context, CarInputService inputService) {
mAudioHal = VehicleHal.getInstance().getAudioHal();
mContext = context;
mFocusHandlerThread = new HandlerThread(CarLog.TAG_AUDIO);
mSystemFocusListener = new SystemFocusListener();
mFocusHandlerThread.start();
mFocusHandler = new CarAudioFocusChangeHandler(mFocusHandlerThread.getLooper());
- mVolumeHandler = new CarAudioVolumeHandler(Looper.getMainLooper());
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ mCanBusErrorNotifier = new CanBusErrorNotifier(context);
+ Resources res = context.getResources();
+ mFocusResponseWaitTimeoutMs = (long) res.getInteger(R.integer.audioFocusWaitTimeoutMs);
+ mNumConsecutiveHalFailuresForCanError =
+ (int) res.getInteger(R.integer.consecutiveHalFailures);
+ mUseDynamicRouting = res.getBoolean(R.bool.audioUseDynamicRouting);
+ mVolumeService = new CarVolumeService(mContext, this, mAudioHal, inputService);
}
@Override
@@ -111,12 +157,9 @@
public void init() {
AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
builder.setLooper(Looper.getMainLooper());
- boolean isFocusSuported = mAudioHal.isFocusSupported();
- if (isFocusSuported) {
+ boolean isFocusSupported = mAudioHal.isFocusSupported();
+ if (isFocusSupported) {
builder.setAudioPolicyFocusListener(mSystemFocusListener);
- }
- mAudioPolicy = builder.build();
- if (isFocusSuported) {
FocusState currentState = FocusState.create(mAudioHal.getCurrentFocusState());
int r = mAudioManager.requestAudioFocus(mBottomAudioFocusHandler, mAttributeBottom,
AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
@@ -130,31 +173,177 @@
mCurrentAudioContexts = 0;
}
}
- int r = mAudioManager.registerAudioPolicy(mAudioPolicy);
- if (r != 0) {
- throw new RuntimeException("registerAudioPolicy failed " + r);
- }
- mAudioHal.setListener(this);
int audioHwVariant = mAudioHal.getHwVariant();
- mAudioRoutingPolicy = AudioRoutingPolicy.create(mContext, audioHwVariant);
- mAudioHal.setAudioRoutingPolicy(mAudioRoutingPolicy);
- //TODO set routing policy with new AudioPolicy API. This will control which logical stream
- // goes to which physical stream.
+ AudioRoutingPolicy audioRoutingPolicy = AudioRoutingPolicy.create(mContext, audioHwVariant);
+ if (mUseDynamicRouting) {
+ setupDynamicRoutng(audioRoutingPolicy, builder);
+ }
+ AudioPolicy audioPolicy = null;
+ if (isFocusSupported || mUseDynamicRouting) {
+ audioPolicy = builder.build();
+ int r = mAudioManager.registerAudioPolicy(audioPolicy);
+ if (r != 0) {
+ throw new RuntimeException("registerAudioPolicy failed " + r);
+ }
+ }
+ mAudioHal.setFocusListener(this);
+ mAudioHal.setAudioRoutingPolicy(audioRoutingPolicy);
+ synchronized (mLock) {
+ if (audioPolicy != null) {
+ mAudioPolicy = audioPolicy;
+ }
+ mAudioRoutingPolicy = audioRoutingPolicy;
+ mIsRadioExternal = mAudioHal.isRadioExternal();
+ }
+ mVolumeService.init();
+ }
+
+ private void setupDynamicRoutng(AudioRoutingPolicy audioRoutingPolicy,
+ AudioPolicy.Builder audioPolicyBuilder) {
+ AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+ if (deviceInfos.length == 0) {
+ Log.e(CarLog.TAG_AUDIO, "setupDynamicRoutng, no output device available, ignore");
+ return;
+ }
+ int numPhysicalStreams = audioRoutingPolicy.getPhysicalStreamsCount();
+ AudioDeviceInfo[] devicesToRoute = new AudioDeviceInfo[numPhysicalStreams];
+ for (AudioDeviceInfo info : deviceInfos) {
+ if (DBG_DYNAMIC_AUDIO_ROUTING) {
+ Log.v(CarLog.TAG_AUDIO, String.format(
+ "output device=%s id=%d name=%s addr=%s type=%s",
+ info.toString(), info.getId(), info.getProductName(), info.getAddress(),
+ info.getType()));
+ }
+ if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
+ int addressNumeric = parseDeviceAddress(info.getAddress());
+ if (addressNumeric >= 0 && addressNumeric < numPhysicalStreams) {
+ devicesToRoute[addressNumeric] = info;
+ Log.i(CarLog.TAG_AUDIO, String.format(
+ "valid bus found, devie=%s id=%d name=%s addr=%s",
+ info.toString(), info.getId(), info.getProductName(), info.getAddress())
+ );
+ }
+ }
+ }
+ for (int i = 0; i < numPhysicalStreams; i++) {
+ AudioDeviceInfo info = devicesToRoute[i];
+ if (info == null) {
+ Log.e(CarLog.TAG_AUDIO, "setupDynamicRoutng, cannot find device for address " + i);
+ return;
+ }
+ int sampleRate = getMaxSampleRate(info);
+ int channels = getMaxChannles(info);
+ AudioFormat mixFormat = new AudioFormat.Builder()
+ .setSampleRate(sampleRate)
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setChannelMask(channels)
+ .build();
+ Log.i(CarLog.TAG_AUDIO, String.format(
+ "Physical stream %d, sampleRate:%d, channles:0x%s", i, sampleRate,
+ Integer.toHexString(channels)));
+ int[] logicalStreams = audioRoutingPolicy.getLogicalStreamsForPhysicalStream(i);
+ AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
+ for (int logicalStream : logicalStreams) {
+ mixingRuleBuilder.addRule(
+ CarAudioAttributesUtil.getAudioAttributesForCarUsage(logicalStream),
+ AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
+ }
+ AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
+ .setFormat(mixFormat)
+ .setDevice(info)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
+ .build();
+ audioPolicyBuilder.addMix(audioMix);
+ }
+ }
+
+ /**
+ * Parse device address. Expected format is BUS%d_%s, address, usage hint
+ * @return valid address (from 0 to positive) or -1 for invalid address.
+ */
+ private int parseDeviceAddress(String address) {
+ String[] words = address.split("_");
+ int addressParsed = -1;
+ if (words[0].startsWith("BUS")) {
+ try {
+ addressParsed = Integer.parseInt(words[0].substring(3));
+ } catch (NumberFormatException e) {
+ //ignore
+ }
+ }
+ if (addressParsed < 0) {
+ return -1;
+ }
+ return addressParsed;
+ }
+
+ private int getMaxSampleRate(AudioDeviceInfo info) {
+ int[] sampleRates = info.getSampleRates();
+ if (sampleRates == null || sampleRates.length == 0) {
+ return 48000;
+ }
+ int sampleRate = sampleRates[0];
+ for (int i = 1; i < sampleRates.length; i++) {
+ if (sampleRates[i] > sampleRate) {
+ sampleRate = sampleRates[i];
+ }
+ }
+ return sampleRate;
+ }
+
+ private int getMaxChannles(AudioDeviceInfo info) {
+ int[] channelMasks = info.getChannelMasks();
+ if (channelMasks == null) {
+ return AudioFormat.CHANNEL_OUT_STEREO;
+ }
+ int channels = AudioFormat.CHANNEL_OUT_MONO;
+ int numChannels = 1;
+ for (int i = 0; i < channelMasks.length; i++) {
+ int currentNumChannles = VehicleZoneUtil.getNumberOfZones(channelMasks[i]);
+ if (currentNumChannles > numChannels) {
+ numChannels = currentNumChannles;
+ channels = channelMasks[i];
+ }
+ }
+ return channels;
}
@Override
public void release() {
- mAudioManager.unregisterAudioPolicyAsync(mAudioPolicy);
+ mFocusHandler.cancelAll();
mAudioManager.abandonAudioFocus(mBottomAudioFocusHandler);
mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
- mFocusHandler.cancelAll();
+ AudioPolicy audioPolicy;
synchronized (mLock) {
mCurrentFocusState = FocusState.STATE_LOSS;
mLastFocusRequestToCar = null;
mTopFocusInfo = null;
mPendingFocusChanges.clear();
mRadioActive = false;
+ if (mCarAudioContextChangeHandler != null) {
+ mCarAudioContextChangeHandler.cancelAll();
+ mCarAudioContextChangeHandler = null;
+ }
+ mAudioContextChangeListener = null;
+ mCurrentPrimaryAudioContext = 0;
+ audioPolicy = mAudioPolicy;
+ mAudioPolicy = null;
}
+ if (audioPolicy != null) {
+ mAudioManager.unregisterAudioPolicyAsync(audioPolicy);
+ }
+ }
+
+ public synchronized void setAudioContextChangeListener(Looper looper,
+ AudioContextChangeListener listener) {
+ if (looper == null || listener == null) {
+ throw new IllegalArgumentException("looper or listener null");
+ }
+ if (mCarAudioContextChangeHandler != null) {
+ mCarAudioContextChangeHandler.cancelAll();
+ }
+ mCarAudioContextChangeHandler = new CarAudioContextChangeHandler(looper);
+ mAudioContextChangeListener = listener;
}
@Override
@@ -164,6 +353,10 @@
" mLastFocusRequestToCar:" + mLastFocusRequestToCar);
writer.println(" mCurrentAudioContexts:0x" + Integer.toHexString(mCurrentAudioContexts));
writer.println(" mCallActive:" + mCallActive + " mRadioActive:" + mRadioActive);
+ writer.println(" mCurrentPrimaryAudioContext:" + mCurrentPrimaryAudioContext +
+ " mCurrentPrimaryPhysicalStream:" + mCurrentPrimaryPhysicalStream);
+ writer.println(" mIsRadioExternal:" + mIsRadioExternal);
+ writer.println(" mNumConsecutiveHalFailures:" + mNumConsecutiveHalFailures);
mAudioRoutingPolicy.dump(writer);
}
@@ -178,21 +371,52 @@
}
@Override
- public void onVolumeChange(int streamNumber, int volume, int volumeState) {
- mVolumeHandler.handleVolumeChange(new VolumeStateChangeEvent(streamNumber, volume,
- volumeState));
- }
-
- @Override
- public void onVolumeLimitChange(int streamNumber, int volume) {
- //TODO
- }
-
- @Override
public void onStreamStatusChange(int state, int streamNumber) {
mFocusHandler.handleStreamStateChange(state, streamNumber);
}
+ @Override
+ public void setStreamVolume(int streamType, int index, int flags) {
+ enforceAudioVolumePermission();
+ mVolumeService.setStreamVolume(streamType, index, flags);
+ }
+
+ @Override
+ public void setVolumeController(IVolumeController controller) {
+ enforceAudioVolumePermission();
+ mVolumeService.setVolumeController(controller);
+ }
+
+ @Override
+ public int getStreamMaxVolume(int streamType) {
+ enforceAudioVolumePermission();
+ return mVolumeService.getStreamMaxVolume(streamType);
+ }
+
+ @Override
+ public int getStreamMinVolume(int streamType) {
+ enforceAudioVolumePermission();
+ return mVolumeService.getStreamMinVolume(streamType);
+ }
+
+ @Override
+ public int getStreamVolume(int streamType) {
+ enforceAudioVolumePermission();
+ return mVolumeService.getStreamVolume(streamType);
+ }
+
+ public AudioRoutingPolicy getAudioRoutingPolicy() {
+ return mAudioRoutingPolicy;
+ }
+
+ private void enforceAudioVolumePermission() {
+ if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "requires permission " + Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
+ }
+ }
+
private void doHandleCarFocusChange() {
int newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_INVALID;
FocusState currentState;
@@ -299,14 +523,8 @@
shouldRequestProxyFocus = true;
}
if (isFocusFromCarProxy(topInfo)) {
- if ((currentState.externalFocus &
- (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG |
- AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) {
- // CarProxy in top, but no external focus: Drop it so that some other app
- // may pick up focus.
- mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
- return;
- }
+ // already car proxy is top. Nothing to do.
+ return;
} else if (!isFocusFromCarServiceBottom(topInfo)) {
shouldRequestProxyFocus = true;
}
@@ -333,10 +551,6 @@
androidFocus, flags, mAudioPolicy);
}
- private void doHandleVolumeChange(VolumeStateChangeEvent event) {
- //TODO
- }
-
private void doHandleStreamStatusChange(int streamNumber, int state) {
//TODO
}
@@ -368,8 +582,8 @@
return false;
}
- private boolean isFocusFromRadio(AudioFocusInfo info) {
- if (!mAudioHal.isRadioExternal()) {
+ private boolean isFocusFromExternalRadio(AudioFocusInfo info) {
+ if (!mIsRadioExternal) {
// if radio is not external, no special handling of radio is necessary.
return false;
}
@@ -430,6 +644,24 @@
int logicalStreamTypeForTop = CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib);
int physicalStreamTypeForTop = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
logicalStreamTypeForTop);
+
+ // update primary context and notify if necessary
+ int primaryContext = AudioHalService.logicalStreamToHalContextType(logicalStreamTypeForTop);
+ switch (logicalStreamTypeForTop) {
+ case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM:
+ case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY:
+ primaryContext = 0;
+ break;
+ }
+ if (mCurrentPrimaryAudioContext != primaryContext) {
+ mCurrentPrimaryAudioContext = primaryContext;
+ mCurrentPrimaryPhysicalStream = physicalStreamTypeForTop;
+ if (mCarAudioContextChangeHandler != null) {
+ mCarAudioContextChangeHandler.requestContextChangeNotification(
+ mAudioContextChangeListener, primaryContext, physicalStreamTypeForTop);
+ }
+ }
+
int audioContexts = 0;
if (logicalStreamTypeForTop == CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL) {
if (!mCallActive) {
@@ -440,7 +672,8 @@
if (mCallActive) {
mCallActive = false;
}
- audioContexts = AudioHalService.logicalStreamToHalContextType(logicalStreamTypeForTop);
+ audioContexts =
+ AudioHalService.logicalStreamToHalContextType(logicalStreamTypeForTop);
}
// other apps having focus
int focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE;
@@ -448,10 +681,8 @@
int streamsToRequest = 0x1 << physicalStreamTypeForTop;
switch (mTopFocusInfo.getGainRequest()) {
case AudioManager.AUDIOFOCUS_GAIN:
- if (isFocusFromRadio(mTopFocusInfo)) {
+ if (isFocusFromExternalRadio(mTopFocusInfo)) {
mRadioActive = true;
- // audio context sending is only for audio from android.
- audioContexts = 0;
} else {
mRadioActive = false;
}
@@ -494,9 +725,9 @@
// Most cars do not allow that, but if mixing is possible, it can take media stream.
// For now, assume no mixing capability.
int radioPhysicalStream = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
- CarAudioManager.CAR_AUDIO_USAGE_MUSIC);
- if (!isFocusFromRadio(mTopFocusInfo) &&
- (physicalStreamTypeForTop == radioPhysicalStream)) {
+ CarAudioManager.CAR_AUDIO_USAGE_RADIO);
+ if (!isFocusFromExternalRadio(mTopFocusInfo) &&
+ (physicalStreamTypeForTop == radioPhysicalStream) && mIsRadioExternal) {
Log.i(CarLog.TAG_AUDIO, "Top stream is taking the same stream:" +
physicalStreamTypeForTop + " as radio, stopping radio");
// stream conflict here. radio cannot be played
@@ -542,7 +773,7 @@
mAudioHal.requestAudioFocusChange(focusToRequest, streamsToRequest, extFocus,
audioContexts);
try {
- mLock.wait(FOCUS_RESPONSE_WAIT_TIMEOUT_MS);
+ mLock.wait(mFocusResponseWaitTimeoutMs);
} catch (InterruptedException e) {
//ignore
}
@@ -630,12 +861,18 @@
dumpAudioFocusInfo(newTopInfo) + " currentState:" + mCurrentFocusState);
}
}
- if (focusRequested && mFocusReceived == null) {
- Log.w(TAG_FOCUS, "focus response timed out, request sent" +
- mLastFocusRequestToCar);
- // no response. so reset to loss.
- mFocusReceived = FocusState.STATE_LOSS;
- mCurrentAudioContexts = 0;
+ if (focusRequested) {
+ if (mFocusReceived == null) {
+ Log.w(TAG_FOCUS, "focus response timed out, request sent"
+ + mLastFocusRequestToCar);
+ // no response. so reset to loss.
+ mFocusReceived = FocusState.STATE_LOSS;
+ mCurrentAudioContexts = 0;
+ mNumConsecutiveHalFailures++;
+ } else {
+ mNumConsecutiveHalFailures = 0;
+ }
+ checkCanStatus();
}
}
// handle it if there was response or force handle it for timeout.
@@ -657,7 +894,7 @@
mAudioHal.requestAudioFocusChange(
AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
try {
- mLock.wait(FOCUS_RESPONSE_WAIT_TIMEOUT_MS);
+ mLock.wait(mFocusResponseWaitTimeoutMs);
} catch (InterruptedException e) {
//ignore
}
@@ -671,6 +908,12 @@
}
}
+ private void checkCanStatus() {
+ // If CAN bus recovers, message will be removed.
+ mCanBusErrorNotifier.setCanBusFailure(
+ mNumConsecutiveHalFailures >= mNumConsecutiveHalFailuresForCanError);
+ }
+
private static boolean isAudioAttributesSame(AudioAttributes one, AudioAttributes two) {
if (one.getContentType() != two.getContentType()) {
return false;
@@ -748,6 +991,37 @@
}
}
+ private class CarAudioContextChangeHandler extends Handler {
+ private static final int MSG_CONTEXT_CHANGE = 0;
+
+ private CarAudioContextChangeHandler(Looper looper) {
+ super(looper);
+ }
+
+ private void requestContextChangeNotification(AudioContextChangeListener listener,
+ int primaryContext, int physicalStream) {
+ Message msg = obtainMessage(MSG_CONTEXT_CHANGE, primaryContext, physicalStream,
+ listener);
+ sendMessage(msg);
+ }
+
+ private void cancelAll() {
+ removeMessages(MSG_CONTEXT_CHANGE);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_CONTEXT_CHANGE: {
+ AudioContextChangeListener listener = (AudioContextChangeListener) msg.obj;
+ int context = msg.arg1;
+ int physicalStream = msg.arg2;
+ listener.onContextChange(context, physicalStream);
+ } break;
+ }
+ }
+ }
+
private class CarAudioFocusChangeHandler extends Handler {
private static final int MSG_FOCUS_CHANGE = 0;
private static final int MSG_STREAM_STATE_CHANGE = 1;
@@ -815,40 +1089,6 @@
}
}
- private class CarAudioVolumeHandler extends Handler {
- private static final int MSG_VOLUME_CHANGE = 0;
-
- private CarAudioVolumeHandler(Looper looper) {
- super(looper);
- }
-
- private void handleVolumeChange(VolumeStateChangeEvent event) {
- Message msg = obtainMessage(MSG_VOLUME_CHANGE, event);
- sendMessage(msg);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_VOLUME_CHANGE:
- doHandleVolumeChange((VolumeStateChangeEvent) msg.obj);
- break;
- }
- }
- }
-
- private static class VolumeStateChangeEvent {
- public final int stream;
- public final int volume;
- public final int state;
-
- public VolumeStateChangeEvent(int stream, int volume, int state) {
- this.stream = stream;
- this.volume = volume;
- this.state = state;
- }
- }
-
/** Wrapper class for holding the current focus state from car. */
private static class FocusState {
public final int focusState;
diff --git a/service/src/com/android/car/CarInputService.java b/service/src/com/android/car/CarInputService.java
index 8b0bd3d..27e72dc 100644
--- a/service/src/com/android/car/CarInputService.java
+++ b/service/src/com/android/car/CarInputService.java
@@ -35,18 +35,20 @@
public class CarInputService implements CarServiceBase, InputHalService.InputListener {
public interface KeyEventListener {
- void onKeyEvent(KeyEvent event);
+ boolean onKeyEvent(KeyEvent event);
}
private static final long VOICE_LONG_PRESS_TIME_MS = 1000;
private final Context mContext;
- private KeyEventListener mVoiceAssitantKeyListener;
- private KeyEventListener mLongVoiceAssitantKeyListener;
+ private KeyEventListener mVoiceAssistantKeyListener;
+ private KeyEventListener mLongVoiceAssistantKeyListener;
private long mLastVoiceKeyDownTime = 0;
- private KeyEventListener mInstumentClusterKeyListener;
+ private KeyEventListener mInstrumentClusterKeyListener;
+
+ private KeyEventListener mVolumeKeyListener;
private ParcelFileDescriptor mInjectionDeviceFd;
@@ -62,9 +64,9 @@
* If listener is set, short key press will lead into calling the listener.
* @param listener
*/
- public void setVoiceAssitantKeyListener(KeyEventListener listener) {
+ public void setVoiceAssistantKeyListener(KeyEventListener listener) {
synchronized (this) {
- mVoiceAssitantKeyListener = listener;
+ mVoiceAssistantKeyListener = listener;
}
}
@@ -74,15 +76,21 @@
* If listener is set, short long press will lead into calling the listener.
* @param listener
*/
- public void setLongVoiceAssitantKeyListener(KeyEventListener listener) {
+ public void setLongVoiceAssistantKeyListener(KeyEventListener listener) {
synchronized (this) {
- mLongVoiceAssitantKeyListener = listener;
+ mLongVoiceAssistantKeyListener = listener;
}
}
public void setInstrumentClusterKeyListener(KeyEventListener listener) {
synchronized (this) {
- mInstumentClusterKeyListener = listener;
+ mInstrumentClusterKeyListener = listener;
+ }
+ }
+
+ public void setVolumeKeyListener(KeyEventListener listener) {
+ synchronized (this) {
+ mVolumeKeyListener = listener;
}
}
@@ -112,9 +120,9 @@
@Override
public void release() {
synchronized (this) {
- mVoiceAssitantKeyListener = null;
- mLongVoiceAssitantKeyListener = null;
- mInstumentClusterKeyListener = null;
+ mVoiceAssistantKeyListener = null;
+ mLongVoiceAssistantKeyListener = null;
+ mInstrumentClusterKeyListener = null;
if (mInjectionDeviceFd != null) {
try {
mInjectionDeviceFd.close();
@@ -136,6 +144,10 @@
case KeyEvent.KEYCODE_VOICE_ASSIST:
handleVoiceAssistKey(event);
return;
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ handleVolumeKey(event);
+ return;
default:
break;
}
@@ -160,12 +172,12 @@
KeyEventListener longPressListener = null;
long downTime;
synchronized (this) {
- shortPressListener = mVoiceAssitantKeyListener;
- longPressListener = mLongVoiceAssitantKeyListener;
+ shortPressListener = mVoiceAssistantKeyListener;
+ longPressListener = mLongVoiceAssistantKeyListener;
downTime = mLastVoiceKeyDownTime;
}
if (shortPressListener == null && longPressListener == null) {
- launchDefaultVoiceAssitantHandler();
+ launchDefaultVoiceAssistantHandler();
} else {
long duration = SystemClock.elapsedRealtime() - downTime;
listener = (duration > VOICE_LONG_PRESS_TIME_MS
@@ -173,13 +185,13 @@
if (listener != null) {
listener.onKeyEvent(event);
} else {
- launchDefaultVoiceAssitantHandler();
+ launchDefaultVoiceAssistantHandler();
}
}
}
}
- private void launchDefaultVoiceAssitantHandler() {
+ private void launchDefaultVoiceAssistantHandler() {
Log.i(CarLog.TAG_INPUT, "voice key, launch default intent");
Intent voiceIntent =
new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
@@ -189,7 +201,7 @@
private void handleInstrumentClusterKey(KeyEvent event) {
KeyEventListener listener = null;
synchronized (this) {
- listener = mInstumentClusterKeyListener;
+ listener = mInstrumentClusterKeyListener;
}
if (listener == null) {
return;
@@ -197,6 +209,16 @@
listener.onKeyEvent(event);
}
+ private void handleVolumeKey(KeyEvent event) {
+ KeyEventListener listener;
+ synchronized (this) {
+ listener = mVolumeKeyListener;
+ }
+ if (listener != null) {
+ listener.onKeyEvent(event);
+ }
+ }
+
private void handleMainDisplayKey(KeyEvent event) {
int fd;
synchronized (this) {
diff --git a/service/src/com/android/car/CarLog.java b/service/src/com/android/car/CarLog.java
index 20c4b06..79ed606 100644
--- a/service/src/com/android/car/CarLog.java
+++ b/service/src/com/android/car/CarLog.java
@@ -33,4 +33,6 @@
public static final String TAG_INPUT = "CAR.INPUT";
public static final String TAG_PROJECTION = "CAR.PROJECTION";
public static final String TAG_CLUSTER = "CAR.CLUSTER";
+ public static final String TAG_CAN_BUS = "CAR.CAN_BUS";
+ public static final String TAG_SYS = "CAR.SYS";
}
diff --git a/service/src/com/android/car/CarPowerManagementService.java b/service/src/com/android/car/CarPowerManagementService.java
index 819e094..c6d0191 100644
--- a/service/src/com/android/car/CarPowerManagementService.java
+++ b/service/src/com/android/car/CarPowerManagementService.java
@@ -177,6 +177,7 @@
onApPowerStateChange(currentState);
} else {
Log.w(CarLog.TAG_POWER, "Vehicle hal does not support power state yet.");
+ onApPowerStateChange(new PowerState(PowerHalService.STATE_ON_FULL, 0));
mSystemInterface.switchToFullWakeLock();
}
mSystemInterface.startDisplayStateMonitoring(this);
@@ -407,9 +408,6 @@
listener.onSleepEntry();
}
int wakeupTimeSec = getWakeupTime();
- for (PowerEventProcessingHandlerWrapper wrapper : mPowerEventProcessingHandlers) {
- wrapper.resetPowerOnSent();
- }
mHal.sendSleepEntry();
synchronized (this) {
mLastSleepEntryTime = SystemClock.elapsedRealtime();
@@ -440,7 +438,7 @@
private void doHandleNotifyPowerOn() {
boolean displayOn = false;
synchronized (this) {
- if (mCurrentState != null && mCurrentState.state == PowerHalService.SET_DISPLAY_ON) {
+ if (mCurrentState != null && mCurrentState.state == PowerHalService.STATE_ON_FULL) {
displayOn = true;
}
}
@@ -784,6 +782,7 @@
private long mProcessingTime = 0;
private boolean mProcessingDone = true;
private boolean mPowerOnSent = false;
+ private int mLastDisplayState = -1;
public PowerEventProcessingHandlerWrapper(PowerEventProcessingHandler handler) {
this.handler = handler;
@@ -807,11 +806,13 @@
}
public void callOnPowerOn(boolean displayOn) {
+ int newDisplayState = displayOn ? 1 : 0;
boolean shouldCall = false;
synchronized (this) {
- if (!mPowerOnSent) {
+ if (!mPowerOnSent || (mLastDisplayState != newDisplayState)) {
shouldCall = true;
mPowerOnSent = true;
+ mLastDisplayState = newDisplayState;
}
}
if (shouldCall) {
@@ -819,10 +820,6 @@
}
}
- public synchronized void resetPowerOnSent() {
- mPowerOnSent = false;
- }
-
@Override
public String toString() {
return "PowerEventProcessingHandlerWrapper [handler=" + handler + ", mProcessingTime="
diff --git a/service/src/com/android/car/CarProjectionService.java b/service/src/com/android/car/CarProjectionService.java
index 66c75fa..1c5f15d 100644
--- a/service/src/com/android/car/CarProjectionService.java
+++ b/service/src/com/android/car/CarProjectionService.java
@@ -23,10 +23,7 @@
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
@@ -47,16 +44,18 @@
private final CarInputService.KeyEventListener mVoiceAssistantKeyListener =
new CarInputService.KeyEventListener() {
@Override
- public void onKeyEvent(KeyEvent event) {
+ public boolean onKeyEvent(KeyEvent event) {
handleVoiceAssitantRequest(false);
+ return true;
}
};
private final CarInputService.KeyEventListener mLongVoiceAssistantKeyListener =
new CarInputService.KeyEventListener() {
@Override
- public void onKeyEvent(KeyEvent event) {
+ public boolean onKeyEvent(KeyEvent event) {
handleVoiceAssitantRequest(true);
+ return true;
}
};
@@ -184,9 +183,9 @@
CarProjectionManager.PROJECTION_LONG_PRESS_VOICE_SEARCH);
}
}
- mCarInputService.setVoiceAssitantKeyListener(listenShortPress
+ mCarInputService.setVoiceAssistantKeyListener(listenShortPress
? mVoiceAssistantKeyListener : null);
- mCarInputService.setLongVoiceAssitantKeyListener(listenLongPress
+ mCarInputService.setLongVoiceAssistantKeyListener(listenLongPress
? mLongVoiceAssistantKeyListener : null);
}
diff --git a/service/src/com/android/car/CarService.java b/service/src/com/android/car/CarService.java
index e0feb8a..8986206 100644
--- a/service/src/com/android/car/CarService.java
+++ b/service/src/com/android/car/CarService.java
@@ -24,6 +24,7 @@
import android.car.Car;
import android.content.Intent;
import android.os.IBinder;
+import android.os.SystemProperties;
import android.util.Log;
public class CarService extends Service {
@@ -35,6 +36,7 @@
public void onCreate() {
Log.i(CarLog.TAG_SERVICE, "Service onCreate");
mICarImpl = ICarImpl.getInstance(this);
+ SystemProperties.set("boot.car_service_created", "1");
super.onCreate();
}
diff --git a/service/src/com/android/car/CarVolumeControllerFactory.java b/service/src/com/android/car/CarVolumeControllerFactory.java
new file mode 100644
index 0000000..72cf310
--- /dev/null
+++ b/service/src/com/android/car/CarVolumeControllerFactory.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.IAudioService;
+import android.media.IVolumeController;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.view.KeyEvent;
+
+import com.android.car.CarVolumeService.CarVolumeController;
+import com.android.car.hal.AudioHalService;
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * A factory class to create {@link com.android.car.CarVolumeService.CarVolumeController} based
+ * on car properties.
+ */
+public class CarVolumeControllerFactory {
+
+ public static CarVolumeController createCarVolumeController(Context context,
+ CarAudioService audioService, AudioHalService audioHal, CarInputService inputService) {
+ final boolean volumeSupported = audioHal.isAudioVolumeSupported();
+
+ // Case 1: Car Audio Module does not support volume controls
+ if (!volumeSupported) {
+ return new SimpleCarVolumeController(context);
+ }
+ return new CarExternalVolumeController(context, audioService, audioHal, inputService);
+ }
+
+ /**
+ * To control volumes through {@link android.media.AudioManager} when car audio module does not
+ * support volume controls.
+ */
+ public static final class SimpleCarVolumeController extends CarVolumeController {
+ private final AudioManager mAudioManager;
+ private final Context mContext;
+
+ public SimpleCarVolumeController(Context context) {
+ mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ mContext = context;
+ }
+
+ @Override
+ void init() {
+ }
+
+ @Override
+ public void setStreamVolume(int stream, int index, int flags) {
+ mAudioManager.setStreamVolume(stream, index, flags);
+ }
+
+ @Override
+ public int getStreamVolume(int stream) {
+ return mAudioManager.getStreamVolume(stream);
+ }
+
+ @Override
+ public void setVolumeController(IVolumeController controller) {
+ mAudioManager.setVolumeController(controller);
+ }
+
+ @Override
+ public int getStreamMaxVolume(int stream) {
+ return mAudioManager.getStreamMaxVolume(stream);
+ }
+
+ @Override
+ public int getStreamMinVolume(int stream) {
+ return mAudioManager.getStreamMinVolume(stream);
+ }
+
+ @Override
+ public boolean onKeyEvent(KeyEvent event) {
+ handleVolumeKeyDefault(event);
+ return true;
+ }
+
+ private void handleVolumeKeyDefault(KeyEvent event) {
+ if (event.getAction() != KeyEvent.ACTION_DOWN) {
+ return;
+ }
+
+ boolean volUp = event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_UP;
+ int flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND
+ | AudioManager.FLAG_FROM_KEY;
+ IAudioService audioService = getAudioService();
+ String pkgName = mContext.getOpPackageName();
+ try {
+ if (audioService != null) {
+ audioService.adjustSuggestedStreamVolume(
+ volUp ? AudioManager.ADJUST_RAISE : AudioManager.ADJUST_LOWER,
+ AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, CarLog.TAG_INPUT);
+ }
+ } catch (RemoteException e) {
+ Log.e(CarLog.TAG_INPUT, "Error calling android audio service.", e);
+ }
+ }
+
+ private static IAudioService getAudioService() {
+ IAudioService audioService = IAudioService.Stub.asInterface(
+ ServiceManager.checkService(Context.AUDIO_SERVICE));
+ if (audioService == null) {
+ Log.w(CarLog.TAG_INPUT, "Unable to find IAudioService interface.");
+ }
+ return audioService;
+ }
+ }
+
+ /**
+ * The car volume controller to use when the car audio modules supports volume controls.
+ *
+ * Depending on whether the car support audio context and has persistent memory, we need to
+ * handle per context volume change properly.
+ *
+ * Regardless whether car supports audio context or not, we need to keep per audio context
+ * volume internally. If we only support single channel, then we only send the volume change
+ * event when that stream is in focus; Otherwise, we need to adjust the stream volume either on
+ * software mixer level or send it the car audio module if the car support audio context
+ * and multi channel. TODO: Add support for multi channel.
+ *
+ * Per context volume should be persisted, so the volumes can stay the same across boots.
+ * Depending on the hardware property, this can be persisted on car side (or/and android side).
+ * TODO: we need to define one single source of truth if the car has memory.
+ */
+ public static class CarExternalVolumeController extends CarVolumeController
+ implements CarInputService.KeyEventListener, AudioHalService.AudioHalVolumeListener,
+ CarAudioService.AudioContextChangeListener {
+ private static final String TAG = CarLog.TAG_AUDIO + "ExtVolCtrl";
+ private static final int MSG_UPDATE_VOLUME = 0;
+ private static final int MSG_UPDATE_HAL = 1;
+
+ private final Context mContext;
+ private final AudioRoutingPolicy mPolicy;
+ private final AudioHalService mHal;
+ private final CarInputService mInputService;
+ private final CarAudioService mAudioService;
+
+ private int mSupportedAudioContext;
+
+ private boolean mHasExternalMemory;
+
+ @GuardedBy("this")
+ private int mCurrentContext = CarVolumeService.DEFAULT_CAR_AUDIO_CONTEXT;
+ // current logical volume, the key is android stream type
+ @GuardedBy("this")
+ private final SparseArray<Integer> mCurrentLogicalVolume =
+ new SparseArray<>(VolumeUtils.LOGICAL_STREAMS.length);
+ // stream volume limit, the key is android stream type
+ @GuardedBy("this")
+ private final SparseArray<Integer> mLogicalStreamVolumeMax =
+ new SparseArray<>(VolumeUtils.LOGICAL_STREAMS.length);
+ // stream volume limit, the key is android stream type
+ @GuardedBy("this")
+ private final SparseArray<Integer> mLogicalStreamVolumeMin =
+ new SparseArray<>(VolumeUtils.LOGICAL_STREAMS.length);
+
+ @GuardedBy("this")
+ private final RemoteCallbackList<IVolumeController> mVolumeControllers =
+ new RemoteCallbackList<>();
+
+ private final Handler mHandler = new VolumeHandler();
+
+ /**
+ * Convert an android logical stream to the car stream.
+ *
+ * @return If car supports audio context, then it returns the car audio context. Otherwise,
+ * it returns the physical stream that maps to this logical stream.
+ */
+ private int logicalStreamToCarStream(int logicalAndroidStream) {
+ if (mSupportedAudioContext == 0) {
+ int physicalStream = mPolicy.getPhysicalStreamForLogicalStream(
+ CarVolumeService.androidStreamToCarUsage(logicalAndroidStream));
+ return physicalStream;
+ } else {
+ int carContext = VolumeUtils.androidStreamToCarContext(logicalAndroidStream);
+ if ((carContext & mSupportedAudioContext) == 0) {
+ carContext = CarVolumeService.DEFAULT_CAR_AUDIO_CONTEXT;
+ }
+ return carContext;
+ }
+ }
+
+ /**
+ * All updates to external components should be posted to this handler to avoid holding
+ * the internal lock while sending updates.
+ */
+ private final class VolumeHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ int stream;
+ int volume;
+ switch (msg.what) {
+ case MSG_UPDATE_VOLUME:
+ stream = msg.arg1;
+ int flag = msg.arg2;
+ final int size = mVolumeControllers.beginBroadcast();
+ try {
+ for (int i = 0; i < size; i++) {
+ try {
+ mVolumeControllers.getBroadcastItem(i)
+ .volumeChanged(stream, flag);
+ } catch (RemoteException ignored) {
+ }
+ }
+ } finally {
+ mVolumeControllers.finishBroadcast();
+ }
+ break;
+ case MSG_UPDATE_HAL:
+ stream = msg.arg1;
+ volume = msg.arg2;
+ mHal.setStreamVolume(stream, volume);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ public CarExternalVolumeController(Context context, CarAudioService audioService,
+ AudioHalService hal, CarInputService inputService) {
+ mContext = context;
+ mAudioService = audioService;
+ mPolicy = audioService.getAudioRoutingPolicy();
+ mHal = hal;
+ mInputService = inputService;
+ }
+
+ @Override
+ void init() {
+ mSupportedAudioContext = mHal.getSupportedAudioVolumeContexts();
+ mHasExternalMemory = mHal.isExternalAudioVolumePersistent();
+ synchronized (this) {
+ initVolumeLimitLocked();
+ initCurrentVolumeLocked();
+ }
+ mInputService.setVolumeKeyListener(this);
+ mHal.setVolumeListener(this);
+ mAudioService.setAudioContextChangeListener(Looper.getMainLooper(), this);
+ }
+
+ private void initVolumeLimitLocked() {
+ for (int i : VolumeUtils.LOGICAL_STREAMS) {
+ int carStream = logicalStreamToCarStream(i);
+ Pair<Integer, Integer> volumeMinMax = mHal.getStreamVolumeLimit(carStream);
+ int max;
+ int min;
+ if (volumeMinMax == null) {
+ max = 0;
+ min = 0;
+ } else {
+ max = volumeMinMax.second >= 0 ? volumeMinMax.second : 0;
+ min = volumeMinMax.first >=0 ? volumeMinMax.first : 0;
+ }
+ // get default stream volume limit first.
+ mLogicalStreamVolumeMax.put(i, max);
+ mLogicalStreamVolumeMin.put(i, min);
+ }
+ }
+
+ private void initCurrentVolumeLocked() {
+ if (mHasExternalMemory) {
+ // TODO: read per context volume from audio hal
+ } else {
+ // TODO: read the Android side volume from Settings and pass it to the audio module
+ // Here we just set it to the physical stream volume temporarily.
+ for (int i : VolumeUtils.LOGICAL_STREAMS) {
+ mCurrentLogicalVolume.put(i, mHal.getStreamVolume(logicalStreamToCarStream(i)));
+ }
+ }
+ }
+
+ @Override
+ public void setStreamVolume(int stream, int index, int flags) {
+ synchronized (this) {
+ setStreamVolumeInternalLocked(stream, index, flags);
+ }
+ }
+
+ private void setStreamVolumeInternalLocked(int stream, int index, int flags) {
+ if (mLogicalStreamVolumeMax.get(stream) == null) {
+ Log.e(TAG, "Stream type not supported " + stream);
+ return;
+ }
+ int limit = mLogicalStreamVolumeMax.get(stream);
+ if (index > limit) {
+ Log.e(TAG, "Volume exceeds volume limit. stream: " + stream + " index: " + index
+ + " limit: " + limit);
+ index = limit;
+ }
+
+ if (index < 0) {
+ index = 0;
+ }
+
+ if (mCurrentLogicalVolume.get(stream) == index) {
+ return;
+ }
+
+ int carStream = logicalStreamToCarStream(stream);
+ int carContext = VolumeUtils.androidStreamToCarContext(stream);
+
+ // For single channel, only adjust the volume when the audio context is the current one.
+ if (mCurrentContext == carContext) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_HAL, carStream, index));
+ }
+ // Record the current volume internally.
+ mCurrentLogicalVolume.put(stream, index);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_VOLUME, stream,
+ getVolumeUpdateFlag()));
+ }
+
+ @Override
+ public int getStreamVolume(int stream) {
+ synchronized (this) {
+ if (mCurrentLogicalVolume.get(stream) == null) {
+ Log.d(TAG, "Invalid stream type " + stream);
+ return 0;
+ }
+ return mCurrentLogicalVolume.get(stream);
+ }
+ }
+
+ @Override
+ public void setVolumeController(IVolumeController controller) {
+ synchronized (this) {
+ mVolumeControllers.register(controller);
+ }
+ }
+
+ @Override
+ public void onVolumeChange(int carStream, int volume, int volumeState) {
+ int flag = getVolumeUpdateFlag();
+ synchronized (this) {
+ // Assume single channel here.
+ int currentLogicalStream = VolumeUtils.carContextToAndroidStream(mCurrentContext);
+ int currentCarStream = logicalStreamToCarStream(currentLogicalStream);
+ if (currentCarStream == carStream) {
+ mCurrentLogicalVolume.put(currentLogicalStream, volume);
+ mHandler.sendMessage(
+ mHandler.obtainMessage(MSG_UPDATE_VOLUME, currentLogicalStream, flag));
+ } else {
+ // Hal is telling us a car stream volume has changed, but it is not the current
+ // stream.
+ Log.w(TAG, "Car stream" + carStream
+ + " volume changed, but it is not current stream, ignored.");
+ }
+ }
+ }
+
+ private int getVolumeUpdateFlag() {
+ // TODO: Apply appropriate flags.
+ return AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND;
+ }
+
+ private void updateHalVolumeLocked(final int carStream, final int index) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_UPDATE_HAL, carStream, index));
+ }
+
+ @Override
+ public void onVolumeLimitChange(int streamNumber, int volume) {
+ // TODO: How should this update be sent to SystemUI? maybe send a volume update without
+ // showing UI.
+ synchronized (this) {
+ initVolumeLimitLocked();
+ }
+ }
+
+ @Override
+ public int getStreamMaxVolume(int stream) {
+ synchronized (this) {
+ if (mLogicalStreamVolumeMax.get(stream) == null) {
+ Log.e(TAG, "Stream type not supported " + stream);
+ return 0;
+ }
+ return mLogicalStreamVolumeMax.get(stream);
+ }
+ }
+
+ @Override
+ public int getStreamMinVolume(int stream) {
+ synchronized (this) {
+ if (mLogicalStreamVolumeMin.get(stream) == null) {
+ Log.e(TAG, "Stream type not supported " + stream);
+ return 0;
+ }
+ return mLogicalStreamVolumeMin.get(stream);
+ }
+ }
+
+ @Override
+ public boolean onKeyEvent(KeyEvent event) {
+ int logicalStream = VolumeUtils.carContextToAndroidStream(mCurrentContext);
+ final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
+ // TODO: properly handle long press on volume key
+ if (!down) {
+ return true;
+ }
+
+ synchronized (this) {
+ int currentVolume = mCurrentLogicalVolume.get(logicalStream);
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ setStreamVolumeInternalLocked(logicalStream, currentVolume + 1,
+ getVolumeUpdateFlag());
+ break;
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ setStreamVolumeInternalLocked(logicalStream, currentVolume - 1,
+ getVolumeUpdateFlag());
+ break;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void onContextChange(int primaryFocusContext, int primaryFocusPhysicalStream) {
+ synchronized (this) {
+ if (primaryFocusContext == mCurrentContext) {
+ return;
+ }
+ mCurrentContext = primaryFocusContext;
+
+ int currentVolume = mCurrentLogicalVolume.get(
+ VolumeUtils.carContextToAndroidStream(primaryFocusContext));
+ if (mSupportedAudioContext == 0) {
+ // Car does not support audio context, we need to reset the volume
+ updateHalVolumeLocked(primaryFocusPhysicalStream, currentVolume);
+ } else {
+ // car supports context, but does not have memory.
+ if (!mHasExternalMemory) {
+ updateHalVolumeLocked(primaryFocusContext, currentVolume);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/service/src/com/android/car/CarVolumeService.java b/service/src/com/android/car/CarVolumeService.java
new file mode 100644
index 0000000..18ab597
--- /dev/null
+++ b/service/src/com/android/car/CarVolumeService.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car;
+
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.IVolumeController;
+import android.view.KeyEvent;
+
+import com.android.car.hal.AudioHalService;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioContextFlag;
+
+/**
+ * Handles car volume controls.
+ *
+ * It delegates to a {@link com.android.car.CarVolumeService.CarVolumeController} to do proper
+ * volume controls based on different car properties.
+ *
+ * @hide
+ */
+public class CarVolumeService {
+ private static final String TAG = "CarVolumeService";
+
+ // TODO: need to have a policy to define the default context
+ public static int DEFAULT_CAR_AUDIO_CONTEXT =
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG;
+ private final Context mContext;
+ private final AudioHalService mAudioHal;
+ private final CarAudioService mAudioService;
+ private final CarInputService mInputService;
+
+ private CarVolumeController mCarVolumeController;
+
+ public static int androidStreamToCarUsage(int logicalAndroidStream) {
+ return CarAudioAttributesUtil.getCarUsageFromAudioAttributes(
+ new AudioAttributes.Builder()
+ .setLegacyStreamType(logicalAndroidStream).build());
+ }
+
+ public CarVolumeService(Context context, CarAudioService audioService, AudioHalService audioHal,
+ CarInputService inputService) {
+ mContext = context;
+ mAudioHal = audioHal;
+ mAudioService = audioService;
+ mInputService = inputService;
+ }
+
+ public void init() {
+ mCarVolumeController = CarVolumeControllerFactory.createCarVolumeController(mContext,
+ mAudioService, mAudioHal, mInputService);
+
+ mCarVolumeController.init();
+ mInputService.setVolumeKeyListener(mCarVolumeController);
+ }
+
+ public void setStreamVolume(int streamType, int index, int flags) {
+ mCarVolumeController.setStreamVolume(streamType, index, flags);
+ }
+
+ public void setVolumeController(IVolumeController controller) {
+ mCarVolumeController.setVolumeController(controller);
+ }
+
+ public int getStreamMaxVolume(int stream) {
+ return mCarVolumeController.getStreamMaxVolume(stream);
+ }
+
+ public int getStreamMinVolume(int stream) {
+ return mCarVolumeController.getStreamMinVolume(stream);
+ }
+
+ public int getStreamVolume(int stream) {
+ return mCarVolumeController.getStreamVolume(stream);
+ }
+
+ /**
+ * Abstraction layer for volume controls, so that we don't have if-else check for audio
+ * properties everywhere.
+ */
+ public static abstract class CarVolumeController implements CarInputService.KeyEventListener {
+ abstract void init();
+ abstract public void setStreamVolume(int stream, int index, int flags);
+ abstract public void setVolumeController(IVolumeController controller);
+ abstract public int getStreamMaxVolume(int stream);
+ abstract public int getStreamMinVolume(int stream);
+ abstract public int getStreamVolume(int stream);
+ }
+}
diff --git a/service/src/com/android/car/DayNightModePolicy.java b/service/src/com/android/car/DayNightModePolicy.java
index 38a5332..efe007a 100644
--- a/service/src/com/android/car/DayNightModePolicy.java
+++ b/service/src/com/android/car/DayNightModePolicy.java
@@ -16,15 +16,11 @@
package com.android.car;
-import android.car.Car;
import android.car.hardware.CarSensorEvent;
import android.car.hardware.CarSensorManager;
import android.content.Context;
-import android.os.SystemClock;
import android.util.Log;
-import com.android.car.hal.SensorHalServiceBase.SensorListener;
-
import java.io.PrintWriter;
//TODO
@@ -50,7 +46,10 @@
}
public static CarSensorEvent getDefaultValue(int sensorType) {
- return createEvent(true /* isNight */);
+ // There's a race condition and timestamp from vehicle HAL could be slightly less
+ // then current call to SystemClock.elapsedRealtimeNanos() will return.
+ // We want vehicle HAL value always override this default value so we set timestamp to 0.
+ return createEvent(true /* isNight */, 0 /* timestamp */);
}
@Override
@@ -91,9 +90,9 @@
// TODO Auto-generated method stub
}
- private static CarSensorEvent createEvent(boolean isNight) {
+ private static CarSensorEvent createEvent(boolean isNight, long timestamp) {
CarSensorEvent event = new CarSensorEvent(CarSensorManager.SENSOR_TYPE_NIGHT,
- SystemClock.elapsedRealtimeNanos(), 0, 1);
+ timestamp, 0, 1);
event.intValues[0] = isNight ? 1 : 0;
return event;
}
diff --git a/service/src/com/android/car/DrivingStatePolicy.java b/service/src/com/android/car/DrivingStatePolicy.java
index 2b2402f..f8baba2 100644
--- a/service/src/com/android/car/DrivingStatePolicy.java
+++ b/service/src/com/android/car/DrivingStatePolicy.java
@@ -24,8 +24,6 @@
import android.os.SystemClock;
import android.util.Log;
-import com.android.car.hal.SensorHalServiceBase.SensorListener;
-
import java.io.PrintWriter;
import java.util.List;
@@ -97,7 +95,10 @@
sensorType);
return null;
}
- return createEvent(CarSensorEvent.DRIVE_STATUS_FULLY_RESTRICTED);
+ // There's a race condition and timestamp from vehicle HAL could be slightly less
+ // then current call to SystemClock.elapsedRealtimeNanos() will return.
+ // We want vehicle HAL value always override this default value so we set timestamp to 0.
+ return createEvent(CarSensorEvent.DRIVE_STATUS_FULLY_RESTRICTED, 0 /* timestamp */);
}
@Override
@@ -202,8 +203,15 @@
}
private static CarSensorEvent createEvent(int drivingState) {
- CarSensorEvent event = new CarSensorEvent(CarSensorManager.SENSOR_TYPE_DRIVING_STATUS,
- SystemClock.elapsedRealtimeNanos(), 0, 1);
+ return createEvent(drivingState, SystemClock.elapsedRealtimeNanos());
+ }
+
+ private static CarSensorEvent createEvent(int drivingState, long timestamp) {
+ CarSensorEvent event = new CarSensorEvent(
+ CarSensorManager.SENSOR_TYPE_DRIVING_STATUS,
+ timestamp,
+ 0 /* float values */,
+ 1 /* int values */);
event.intValues[0] = drivingState;
return event;
}
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 43061db..87f827c 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -61,6 +61,7 @@
private final GarageModeService mGarageModeService;
private final CarNavigationService mCarNavigationService;
private final InstrumentClusterService mInstrumentClusterService;
+ private final SystemStateControllerService mSystemStateControllerService;
/** Test only service. Populate it only when necessary. */
@GuardedBy("this")
@@ -93,7 +94,7 @@
mCarInfoService = new CarInfoService(serviceContext);
mAppContextService = new AppContextService(serviceContext);
mCarSensorService = new CarSensorService(serviceContext);
- mCarAudioService = new CarAudioService(serviceContext);
+ mCarAudioService = new CarAudioService(serviceContext, mCarInputService);
mCarHvacService = new CarHvacService(serviceContext);
mCarRadioService = new CarRadioService(serviceContext);
mCarCameraService = new CarCameraService(serviceContext);
@@ -101,7 +102,9 @@
mCarPackageManagerService = new CarPackageManagerService(serviceContext);
mInstrumentClusterService = new InstrumentClusterService(serviceContext);
mCarNavigationService = new CarNavigationService(
- serviceContext, mAppContextService, mInstrumentClusterService);
+ mAppContextService, mInstrumentClusterService);
+ mSystemStateControllerService = new SystemStateControllerService(serviceContext,
+ mCarPowerManagementService, this);
// Be careful with order. Service depending on other service should be inited later.
mAllServices = new CarServiceBase[] {
@@ -120,6 +123,7 @@
mInstrumentClusterService,
mCarProjectionService,
mCarNavigationService,
+ mSystemStateControllerService
};
}
diff --git a/service/src/com/android/car/SystemStateControllerService.java b/service/src/com/android/car/SystemStateControllerService.java
new file mode 100644
index 0000000..b9a35e3
--- /dev/null
+++ b/service/src/com/android/car/SystemStateControllerService.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car;
+
+import android.car.media.CarAudioManager;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.util.Log;
+
+import com.android.car.CarPowerManagementService.PowerEventProcessingHandler;
+import com.android.car.CarPowerManagementService.PowerServiceEventListener;
+
+import java.io.PrintWriter;
+
+public class SystemStateControllerService implements CarServiceBase,
+ PowerServiceEventListener, PowerEventProcessingHandler {
+
+ private final CarPowerManagementService mCarPowerManagementService;
+ private final MediaSilencer mMediaSilencer;
+ private final ICarImpl mICarImpl;
+
+ public SystemStateControllerService(Context context,
+ CarPowerManagementService carPowerManagementSercvice, ICarImpl carImpl) {
+ mCarPowerManagementService = carPowerManagementSercvice;
+ AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ mMediaSilencer = new MediaSilencer(audioManager);
+ mICarImpl = carImpl;
+ }
+
+ @Override
+ public long onPrepareShutdown(boolean shuttingDown) {
+ //TODO add state saving here for things to restore on power on.
+ return 0;
+ }
+
+ @Override
+ public void onPowerOn(boolean displayOn) {
+ if (displayOn) {
+ Log.i(CarLog.TAG_SYS, "MediaSilencer unmute");
+ if (!mICarImpl.isInMocking()) {
+ mMediaSilencer.unmuteMedia();
+ }
+ } else {
+ Log.i(CarLog.TAG_SYS, "MediaSilencer mute");
+ if (!mICarImpl.isInMocking()) { // do not do this in mocking as it can affect test.
+ mMediaSilencer.muteMedia();
+ }
+ }
+ }
+
+ @Override
+ public int getWakeupTime() {
+ return 0;
+ }
+
+ @Override
+ public void onShutdown() {
+ // TODO
+ }
+
+ @Override
+ public void onSleepEntry() {
+ // TODO
+ }
+
+ @Override
+ public void onSleepExit() {
+ // TODO
+ }
+
+ @Override
+ public void init() {
+ mCarPowerManagementService.registerPowerEventListener(this);
+ mCarPowerManagementService.registerPowerEventProcessingHandler(this);
+ }
+
+ @Override
+ public void release() {
+ }
+
+ @Override
+ public void dump(PrintWriter writer) {
+ writer.println("Media silence, ismuted:" + mMediaSilencer.isMuted());
+ }
+
+ private class MediaSilencer implements AudioManager.OnAudioFocusChangeListener {
+
+ private final AudioManager mAudioManager;
+ private final AudioAttributes mAttribMusic;
+ private boolean mMuteMedia;
+
+ private MediaSilencer(AudioManager audioManager) {
+ mAudioManager = audioManager;
+ mAttribMusic = CarAudioAttributesUtil.getAudioAttributesForCarUsage(
+ CarAudioManager.CAR_AUDIO_USAGE_MUSIC);
+ }
+
+ private synchronized void muteMedia() {
+ mMuteMedia = true;
+ //TODO replace this into real mute request
+ mAudioManager.requestAudioFocus(this, mAttribMusic, AudioManager.AUDIOFOCUS_GAIN,
+ AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
+ }
+
+ private synchronized void unmuteMedia() {
+ mMuteMedia = false;
+ mAudioManager.abandonAudioFocus(this);
+ }
+
+ private synchronized boolean isMuted() {
+ return mMuteMedia;
+ }
+
+ @Override
+ public synchronized void onAudioFocusChange(int focusChange) {
+ if (mMuteMedia) {
+ if (focusChange == AudioManager.AUDIOFOCUS_LOSS ||
+ focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT ||
+ focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
+ Log.w(CarLog.TAG_SYS, "MediaSilencer got audio focus change:" + focusChange);
+ mAudioManager.requestAudioFocus(this, mAttribMusic,
+ AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
+ }
+ } else {
+ mAudioManager.abandonAudioFocus(this);
+ }
+ }
+ }
+}
diff --git a/service/src/com/android/car/VolumeUtils.java b/service/src/com/android/car/VolumeUtils.java
new file mode 100644
index 0000000..023d319
--- /dev/null
+++ b/service/src/com/android/car/VolumeUtils.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car;
+
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioContextFlag;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class VolumeUtils {
+ private static final String TAG = "VolumeUtils";
+
+ public static final int[] LOGICAL_STREAMS = {
+ AudioManager.STREAM_VOICE_CALL,
+ AudioManager.STREAM_SYSTEM,
+ AudioManager.STREAM_RING,
+ AudioManager.STREAM_MUSIC,
+ AudioManager.STREAM_ALARM,
+ AudioManager.STREAM_NOTIFICATION,
+ AudioManager.STREAM_DTMF,
+ };
+
+ public static final int[] CAR_AUDIO_CONTEXT = {
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG,
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NAVIGATION_FLAG,
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_VOICE_COMMAND_FLAG,
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_CALL_FLAG,
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_ALARM_FLAG,
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NOTIFICATION_FLAG,
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_UNKNOWN_FLAG,
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_SAFETY_ALERT_FLAG,
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_CD_ROM_FLAG,
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_AUX_AUDIO_FLAG,
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_SYSTEM_SOUND_FLAG,
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG
+ };
+
+ public static String streamToName(int stream) {
+ switch (stream) {
+ case AudioManager.STREAM_ALARM: return "Alarm";
+ case AudioManager.STREAM_MUSIC: return "Music";
+ case AudioManager.STREAM_NOTIFICATION: return "Notification";
+ case AudioManager.STREAM_RING: return "Ring";
+ case AudioManager.STREAM_VOICE_CALL: return "Call";
+ case AudioManager.STREAM_SYSTEM: return "System";
+ case AudioManager.STREAM_DTMF: return "DTMF";
+ default: return "Unknown";
+ }
+ }
+
+ public static int androidStreamToCarContext(int logicalAndroidStream) {
+ switch (logicalAndroidStream) {
+ case AudioManager.STREAM_VOICE_CALL:
+ return VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_CALL_FLAG;
+ case AudioManager.STREAM_SYSTEM:
+ return VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_SYSTEM_SOUND_FLAG;
+ case AudioManager.STREAM_RING:
+ return VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NOTIFICATION_FLAG;
+ case AudioManager.STREAM_MUSIC:
+ return VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG;
+ case AudioManager.STREAM_ALARM:
+ return VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_ALARM_FLAG;
+ case AudioManager.STREAM_NOTIFICATION:
+ return VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NOTIFICATION_FLAG;
+ case AudioManager.STREAM_DTMF:
+ return VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_SYSTEM_SOUND_FLAG;
+ default:
+ return VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_UNKNOWN_FLAG;
+ }
+ }
+
+ public static int carContextToAndroidStream(int carContext) {
+ switch (carContext) {
+ case VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_CALL_FLAG:
+ return AudioManager.STREAM_VOICE_CALL;
+ case VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_SYSTEM_SOUND_FLAG:
+ return AudioManager.STREAM_SYSTEM;
+ case VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NOTIFICATION_FLAG:
+ return AudioManager.STREAM_NOTIFICATION;
+ case VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG:
+ return AudioManager.STREAM_MUSIC;
+ case VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_ALARM_FLAG:
+ return AudioManager.STREAM_ALARM;
+ default:
+ return AudioManager.STREAM_MUSIC;
+ }
+ }
+
+ public static int androidStreamToCarUsage(int logicalAndroidStream) {
+ return CarAudioAttributesUtil.getCarUsageFromAudioAttributes(
+ new AudioAttributes.Builder()
+ .setLegacyStreamType(logicalAndroidStream).build());
+ }
+
+ private final SparseArray<Float[]> mStreamAmplLookup = new SparseArray<>(7);
+
+ private static final float LN_10 = 2.302585093f;
+ // From cs/#android/frameworks/av/media/libmedia/AudioSystem.cpp
+ private static final float DB_PER_STEP = -.5f;
+
+ private final AudioManager mAudioManager;
+
+ public VolumeUtils(AudioManager audioManager) {
+ mAudioManager = audioManager;
+ for(int i : LOGICAL_STREAMS) {
+ initStreamLookup(i);
+ }
+ }
+
+ private void initStreamLookup(int streamType) {
+ int maxIndex = mAudioManager.getStreamMaxVolume(streamType);
+ Float[] amplList = new Float[maxIndex + 1];
+
+ for (int i = 0; i <= maxIndex; i++) {
+ amplList[i] = volIndexToAmpl(i, maxIndex);
+ }
+ Log.d(TAG, streamToName(streamType) + ": " + Arrays.toString(amplList));
+ mStreamAmplLookup.put(streamType, amplList);
+ }
+
+
+ public static int closestIndex(float desired, Float[] list) {
+ float min = Float.MAX_VALUE;
+ int closestIndex = 0;
+
+ for (int i = 0; i < list.length; i++) {
+ float diff = Math.abs(list[i] - desired);
+ if (diff < min) {
+ min = diff;
+ closestIndex = i;
+ }
+ }
+ return closestIndex;
+ }
+
+ public void adjustStreamVol(int stream, int desired, int actual, int maxIndex) {
+ float gain = getTrackGain(desired, actual, maxIndex);
+ int index = closestIndex(gain, mStreamAmplLookup.get(stream));
+ if (index == mAudioManager.getStreamVolume(stream)) {
+ return;
+ } else {
+ mAudioManager.setStreamVolume(stream, index, 0 /*don't show UI*/);
+ }
+ }
+
+ /**
+ * Returns the gain which, when applied to an a stream with volume
+ * actualVolIndex, will make the output volume equivalent to a stream with a gain of
+ * 1.0 playing on a stream with volume desiredVolIndex.
+ *
+ * Computing this is non-trivial because the gain is applied on a linear scale while the volume
+ * indices map to a log (dB) scale.
+ *
+ * The computation is copied from cs/#android/frameworks/av/media/libmedia/AudioSystem.cpp
+ */
+ float getTrackGain(int desiredVolIndex, int actualVolIndex, int maxIndex) {
+ if (desiredVolIndex == actualVolIndex) {
+ return 1.0f;
+ }
+ return volIndexToAmpl(desiredVolIndex, maxIndex)
+ / volIndexToAmpl(actualVolIndex, maxIndex);
+ }
+
+ /**
+ * Returns the amplitude corresponding to volIndex. Guaranteed to return a non-negative value.
+ */
+ private float volIndexToAmpl(int volIndex, int maxIndex) {
+ // Normalize volIndex to be in the range [0, 100].
+ int volume = (int) ((float) volIndex / maxIndex * 100.0f);
+ return logToLinear(volumeToDecibels(volume));
+ }
+
+ /**
+ * volume is in the range [0, 100].
+ */
+ private static float volumeToDecibels(int volume) {
+ return (100 - volume) * DB_PER_STEP;
+ }
+
+ /**
+ * Corresponds to the function linearToLog in AudioSystem.cpp.
+ */
+ private static float logToLinear(float decibels) {
+ return decibels < 0.0f ? (float) Math.exp(decibels * LN_10 / 20.0f) : 1.0f;
+ }
+}
\ No newline at end of file
diff --git a/service/src/com/android/car/cluster/CarNavigationService.java b/service/src/com/android/car/cluster/CarNavigationService.java
index 311b42a..1e2887d 100644
--- a/service/src/com/android/car/cluster/CarNavigationService.java
+++ b/service/src/com/android/car/cluster/CarNavigationService.java
@@ -21,19 +21,15 @@
import android.car.navigation.CarNavigationManager;
import android.car.navigation.ICarNavigation;
import android.car.navigation.ICarNavigationEventListener;
-import android.content.Context;
import android.graphics.Bitmap;
import android.os.Binder;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import com.android.car.AppContextService;
import com.android.car.CarLog;
import com.android.car.CarServiceBase;
-import com.android.car.cluster.InstrumentClusterService.RendererInitializationListener;
-import com.android.car.cluster.renderer.ThreadSafeNavigationRenderer;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -42,21 +38,18 @@
/**
* Service that will push navigation event to navigation renderer in instrument cluster.
*/
-public class CarNavigationService extends ICarNavigation.Stub
- implements CarServiceBase, RendererInitializationListener {
+public class CarNavigationService extends ICarNavigation.Stub implements CarServiceBase {
private static final String TAG = CarLog.TAG_NAV;
private final List<CarNavigationEventListener> mListeners = new ArrayList<>();
private final AppContextService mAppContextService;
- private final Context mContext;
private final InstrumentClusterService mInstrumentClusterService;
private volatile CarNavigationInstrumentCluster mInstrumentClusterInfo = null;
private volatile NavigationRenderer mNavigationRenderer;
- public CarNavigationService(Context context, AppContextService appContextService,
+ public CarNavigationService(AppContextService appContextService,
InstrumentClusterService instrumentClusterService) {
- mContext = context;
mAppContextService = appContextService;
mInstrumentClusterService = instrumentClusterService;
}
@@ -64,7 +57,9 @@
@Override
public void init() {
Log.d(TAG, "init");
- mInstrumentClusterService.registerListener(this);
+ mNavigationRenderer = mInstrumentClusterService.getNavigationRenderer();
+ mInstrumentClusterInfo = mNavigationRenderer != null
+ ? mNavigationRenderer.getNavigationProperties() : null;
}
@Override
@@ -72,7 +67,6 @@
synchronized(mListeners) {
mListeners.clear();
}
- mInstrumentClusterService.unregisterListener(this);
}
@Override
@@ -195,23 +189,6 @@
return null;
}
- @Override
- public void onRendererInitSucceeded() {
- Log.d(TAG, "onRendererInitSucceeded");
- mNavigationRenderer = ThreadSafeNavigationRenderer.createFor(
- Looper.getMainLooper(),
- mInstrumentClusterService.getNavigationRenderer());
-
- // TODO: we need to obtain this information from InstrumentClusterRenderer.
- mInstrumentClusterInfo = CarNavigationInstrumentCluster.createCluster(1000);
-
- if (isRendererAvailable()) {
- for (CarNavigationEventListener listener : mListeners) {
- listener.onInstrumentClusterStart(mInstrumentClusterInfo);
- }
- }
- }
-
private class CarNavigationEventListener implements IBinder.DeathRecipient {
final ICarNavigationEventListener listener;
@@ -243,7 +220,7 @@
}
private boolean isRendererAvailable() {
- boolean available = mNavigationRenderer != null;
+ boolean available = mNavigationRenderer != null && mInstrumentClusterInfo != null;
if (!available) {
Log.w(TAG, "Instrument cluster renderer is not available.");
}
diff --git a/service/src/com/android/car/cluster/InstrumentClusterRendererLoader.java b/service/src/com/android/car/cluster/InstrumentClusterRendererLoader.java
index a986848..6d0a590 100644
--- a/service/src/com/android/car/cluster/InstrumentClusterRendererLoader.java
+++ b/service/src/com/android/car/cluster/InstrumentClusterRendererLoader.java
@@ -20,6 +20,7 @@
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.text.TextUtils;
import android.util.Log;
import com.android.car.CarLog;
@@ -34,7 +35,7 @@
* library.
*/
public class InstrumentClusterRendererLoader {
- private final static String TAG = CarLog.TAG_SERVICE;
+ private final static String TAG = CarLog.TAG_CLUSTER;
private final static String sCreateRendererMethod = "createRenderer";
@@ -42,11 +43,18 @@
* Returns true if instrument cluster renderer installed.
*/
public static boolean isRendererAvailable(Context context) {
+ String packageName = getRendererPackageName(context);
+ if (TextUtils.isEmpty(packageName)) {
+ Log.d(TAG, "Instrument cluster renderer was not configured.");
+ return false;
+ }
+
PackageManager packageManager = context.getPackageManager();
try {
- packageManager.getPackageInfo(getRendererPackageName(context), 0);
+ packageManager.getPackageInfo(packageName, 0);
return true;
} catch (NameNotFoundException e) {
+ Log.e(TAG, "Package not found: " + packageName);
return false;
}
}
diff --git a/service/src/com/android/car/cluster/InstrumentClusterService.java b/service/src/com/android/car/cluster/InstrumentClusterService.java
index 1ad9768..47f39fd 100644
--- a/service/src/com/android/car/cluster/InstrumentClusterService.java
+++ b/service/src/com/android/car/cluster/InstrumentClusterService.java
@@ -15,34 +15,17 @@
*/
package com.android.car.cluster;
-import static com.android.car.cluster.InstrumentClusterRendererLoader.createRenderer;
-import static com.android.car.cluster.InstrumentClusterRendererLoader.createRendererPackageContext;
-
-import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.car.cluster.renderer.InstrumentClusterRenderer;
import android.car.cluster.renderer.NavigationRenderer;
import android.content.Context;
-import android.hardware.display.DisplayManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
import android.util.Log;
-import android.view.Display;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
import com.android.car.CarLog;
import com.android.car.CarServiceBase;
-import com.android.car.CarServiceUtils;
-import com.android.car.R;
import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.concurrent.CopyOnWriteArrayList;
/**
* Service responsible for interaction with car's instrument cluster.
@@ -55,192 +38,47 @@
private static final String TAG = CarLog.TAG_CLUSTER + "."
+ InstrumentClusterService.class.getSimpleName();
- private static final int RENDERER_INIT_TIMEOUT_MS = 10 * 1000;
- private final static int MSG_TIMEOUT = 1;
-
private final Context mContext;
- private final Object mHalSync = new Object();
- private final CopyOnWriteArrayList<RendererInitializationListener>
- mRendererInitializationListeners = new CopyOnWriteArrayList<>();
private InstrumentClusterRenderer mRenderer;
- private final Handler mHandler;
-
public InstrumentClusterService(Context context) {
mContext = context;
- mHandler = new TimeoutHandler();
- }
-
- public interface RendererInitializationListener {
- void onRendererInitSucceeded();
}
@Override
public void init() {
Log.d(TAG, "init");
- if (getInstrumentClusterType() == InstrumentClusterType.GRAPHICS) {
- Display display = getInstrumentClusterDisplay(mContext);
- boolean rendererFound = InstrumentClusterRendererLoader.isRendererAvailable(mContext);
+ boolean rendererFound = InstrumentClusterRendererLoader.isRendererAvailable(mContext);
- if (display != null && rendererFound) {
- initRendererOnMainThread(display);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_TIMEOUT),
- RENDERER_INIT_TIMEOUT_MS);
- } else {
- mClusterType = InstrumentClusterType.NONE;
- Log.w(TAG, "Failed to initialize InstrumentClusterRenderer"
- + ", renderer found: " + rendererFound
- + ", secondary display: " + (display != null), new RuntimeException());
-
- return;
- }
+ if (rendererFound) {
+ mRenderer = InstrumentClusterRendererLoader.createRenderer(mContext);
+ mRenderer.onCreate(mContext);
+ mRenderer.initialize();
+ mRenderer.onStart();
}
}
- private void initRendererOnMainThread(final Display display) {
- CarServiceUtils.runOnMain(new Runnable() {
- @Override
- public void run() {
- Log.d(TAG, "initRendererOnMainThread");
- try {
- InstrumentClusterPresentation presentation =
- new InstrumentClusterPresentation(mContext, display);
-
- ViewGroup rootView = (ViewGroup) LayoutInflater.from(mContext).inflate(
- R.layout.instrument_cluster, null);
-
- presentation.setContentView(rootView);
- InstrumentClusterRenderer renderer = createRenderer(mContext);
- renderer.onCreate(createRendererPackageContext(mContext));
- View rendererView = renderer.onCreateView(null);
- renderer.initialize();
- rootView.addView(rendererView);
- presentation.show();
- renderer.onStart();
- initUiDone(renderer);
- } catch (Exception e) {
- Log.e(TAG, e.getMessage(), e);
- throw e;
- }
- }
- });
- }
-
- private void initUiDone(final InstrumentClusterRenderer renderer) {
- Log.d(TAG, "initUiDone");
- mHandler.removeMessages(MSG_TIMEOUT);
-
- // Call listeners in service thread.
- runOnServiceThread(new Runnable() {
- @Override
- public void run() {
- mRenderer = renderer;
-
- for (RendererInitializationListener listener : mRendererInitializationListeners) {
- listener.onRendererInitSucceeded();
- }
- }
- });
- }
-
@Override
public void release() {
Log.d(TAG, "release");
+ if (mRenderer != null) {
+ mRenderer.onStop();
+ mRenderer = null;
+ }
}
@Override
public void dump(PrintWriter writer) {
- // TODO
- }
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({
- InstrumentClusterType.NONE,
- InstrumentClusterType.METADATA,
- InstrumentClusterType.GRAPHICS
- })
- public @interface InstrumentClusterType {
- /**
- * For use privately in this class.
- * @hide
- */
- int UNDEFINED = -1;
-
- /** Access to instrument cluster is not available */
- int NONE = 0;
-
- /** Access to instrument cluster through vehicle HAL using meta-data. */
- int METADATA = 1;
-
- /** Access instrument cluster as a secondary display. */
- int GRAPHICS = 2;
- }
-
- @InstrumentClusterType private int mClusterType = InstrumentClusterType.UNDEFINED;
-
- public boolean isInstrumentClusterAvailable() {
- return mClusterType != InstrumentClusterType.NONE
- && mClusterType != InstrumentClusterType.UNDEFINED;
- }
-
- public int getInstrumentClusterType() {
- if (mClusterType == InstrumentClusterType.UNDEFINED) {
- synchronized (mHalSync) {
- // TODO: need to pull this information from the HAL
- mClusterType = getInstrumentClusterDisplay(mContext) != null
- ? InstrumentClusterType.GRAPHICS : InstrumentClusterType.NONE;
- }
- }
- return mClusterType;
+ writer.println("**" + getClass().getSimpleName() + "**");
+ writer.println("InstrumentClusterRenderer: " + mRenderer);
+ writer.println("NavigationRenderer: "
+ + (mRenderer != null ? mRenderer.getNavigationRenderer() : null));
}
@Nullable
public NavigationRenderer getNavigationRenderer() {
return mRenderer != null ? mRenderer.getNavigationRenderer() : null;
}
-
- public void registerListener(RendererInitializationListener listener) {
- mRendererInitializationListeners.add(listener);
- }
-
- public void unregisterListener(RendererInitializationListener listener) {
- mRendererInitializationListeners.remove(listener);
- }
-
- private static Display getInstrumentClusterDisplay(Context context) {
- DisplayManager displayManager = context.getSystemService(DisplayManager.class);
- Display[] displays = displayManager.getDisplays();
-
- Log.d(TAG, "There are currently " + displays.length + " displays connected.");
- for (Display display : displays) {
- Log.d(TAG, " " + display);
- }
-
- if (displays.length > 1) {
- // TODO: assuming that secondary display is instrument cluster. Put this into settings?
- return displays[1];
- }
- return null;
- }
-
- private void runOnServiceThread(final Runnable runnable) {
- if (Looper.myLooper() == mHandler.getLooper()) {
- runnable.run();
- } else {
- mHandler.post(runnable);
- }
- }
-
- private static class TimeoutHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == MSG_TIMEOUT) {
- Log.e(TAG, "Renderer initialization timeout.", new RuntimeException());
- } else {
- super.handleMessage(msg);
- }
- }
- }
}
diff --git a/service/src/com/android/car/hal/AudioHalService.java b/service/src/com/android/car/hal/AudioHalService.java
index 893fbff..0e97e4b 100644
--- a/service/src/com/android/car/hal/AudioHalService.java
+++ b/service/src/com/android/car/hal/AudioHalService.java
@@ -15,9 +15,11 @@
*/
package com.android.car.hal;
+import android.car.VehicleZoneUtil;
import android.car.media.CarAudioManager;
import android.os.ServiceSpecificException;
import android.util.Log;
+import android.util.Pair;
import com.android.car.AudioRoutingPolicy;
import com.android.car.CarAudioAttributesUtil;
@@ -33,10 +35,13 @@
import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioRoutingPolicyIndex;
import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioStreamState;
import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioStreamStateIndex;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioVolumeCapabilityFlag;
import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioVolumeIndex;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioVolumeLimitIndex;
import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfig;
import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfigs;
import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValue;
+import com.android.car.vehiclenetwork.VehiclePropValueUtil;
import java.io.PrintWriter;
import java.util.HashMap;
@@ -44,7 +49,6 @@
import java.util.List;
public class AudioHalService extends HalServiceBase {
-
public static final int VEHICLE_AUDIO_FOCUS_REQUEST_INVALID = -1;
public static final int VEHICLE_AUDIO_FOCUS_REQUEST_GAIN =
VehicleAudioFocusRequest.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
@@ -126,7 +130,7 @@
public static final int AUDIO_CONTEXT_SYSTEM_SOUND_FLAG =
VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_SYSTEM_SOUND_FLAG;
- public interface AudioHalListener {
+ public interface AudioHalFocusListener {
/**
* Audio focus change from car.
* @param focusState
@@ -136,6 +140,15 @@
*/
void onFocusChange(int focusState, int streams, int externalFocus);
/**
+ * Stream state change (start / stop) from android
+ * @param streamNumber
+ * @param state
+ */
+ void onStreamStatusChange(int streamNumber, int state);
+ }
+
+ public interface AudioHalVolumeListener {
+ /**
* Audio volume change from car.
* @param streamNumber
* @param volume
@@ -148,38 +161,25 @@
* @param volume
*/
void onVolumeLimitChange(int streamNumber, int volume);
- /**
- * Stream state change (start / stop) from android
- * @param streamNumber
- * @param state
- */
- void onStreamStatusChange(int streamNumber, int state);
}
private final VehicleHal mVehicleHal;
- private AudioHalListener mListener;
+ private AudioHalFocusListener mFocusListener;
+ private AudioHalVolumeListener mVolumeListener;
private int mVariant;
- private List<VehiclePropValue> mQueuedEvents;
-
private final HashMap<Integer, VehiclePropConfig> mProperties = new HashMap<>();
public AudioHalService(VehicleHal vehicleHal) {
mVehicleHal = vehicleHal;
}
- public void setListener(AudioHalListener listener) {
- List<VehiclePropValue> eventsToDispatch = null;
- synchronized (this) {
- mListener = listener;
- if (mQueuedEvents != null) {
- eventsToDispatch = mQueuedEvents;
- mQueuedEvents = null;
- }
- }
- if (eventsToDispatch != null) {
- dispatchEventToListener(listener, eventsToDispatch);
- }
+ public synchronized void setFocusListener(AudioHalFocusListener focusListener) {
+ mFocusListener = focusListener;
+ }
+
+ public synchronized void setVolumeListener(AudioHalVolumeListener volumeListener) {
+ mVolumeListener = volumeListener;
}
public void setAudioRoutingPolicy(AudioRoutingPolicy policy) {
@@ -207,6 +207,44 @@
}
/**
+ * Returns the volume limits of a stream in the form <min, max>.
+ */
+ public Pair<Integer, Integer> getStreamVolumeLimit(int stream) {
+ if (!isPropertySupportedLocked(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME)) {
+ throw new IllegalStateException("VEHICLE_PROPERTY_AUDIO_VOLUME not supported");
+ }
+ int supportedContext = getSupportedAudioVolumeContexts();
+ VehiclePropConfig config = mProperties.get(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME);
+ List<Integer> maxs = config.getInt32MaxsList();
+ List<Integer> mins = config.getInt32MinsList();
+
+ if (maxs.size() != mins.size()) {
+ Log.e(CarLog.TAG_AUDIO, "Invalid volume prop config");
+ return null;
+ }
+
+ Pair<Integer, Integer> result = null;
+ if (supportedContext != 0) {
+ int index = VehicleZoneUtil.zoneToIndex(supportedContext, stream);
+ if (index < maxs.size()) {
+ result = new Pair<>(mins.get(index), maxs.get(index));
+ }
+ } else {
+ if (stream < maxs.size()) {
+ result = new Pair<>(mins.get(stream), maxs.get(stream));
+ }
+ }
+
+ if (result == null) {
+ Log.e(CarLog.TAG_AUDIO, "No min/max volume found in vehicle" +
+ " prop config for stream: " + stream);
+ }
+
+ return result;
+ }
+
+ /**
* Convert car audio manager stream type (usage) into audio context type.
*/
public static int logicalStreamToHalContextType(int logicalStream) {
@@ -251,6 +289,37 @@
VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, payload);
}
+ public void setStreamVolume(int streamType, int index) {
+ int[] payload = {streamType, index, 0};
+ mVehicleHal.getVehicleNetwork().setIntVectorProperty(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME, payload);
+ }
+
+ public int getStreamVolume(int stream) {
+ int[] volume = {stream, 0, 0};
+ VehiclePropValue streamVolume =
+ VehiclePropValueUtil.createIntVectorValue(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME, volume, 0);
+ VehiclePropValue value = mVehicleHal.getVehicleNetwork().getProperty(streamVolume);
+
+ if (value.getInt32ValuesCount() != 3) {
+ Log.e(CarLog.TAG_AUDIO, "returned value not valid");
+ throw new IllegalStateException("Invalid preset returned from service: "
+ + value.getInt32ValuesList());
+ }
+
+ int retStreamNum = value.getInt32Values(0);
+ int retVolume = value.getInt32Values(1);
+ int retVolumeState = value.getInt32Values(2);
+
+ if (retStreamNum != stream) {
+ Log.e(CarLog.TAG_AUDIO, "Stream number is not the same: "
+ + stream + " vs " + retStreamNum);
+ throw new IllegalStateException("Stream number is not the same");
+ }
+ return retVolume;
+ }
+
public synchronized int getHwVariant() {
return mVariant;
}
@@ -262,7 +331,7 @@
return true;
}
return (config.getConfigArray(0) &
- VehicleAudioHwVariantConfigFlag.VEHICLE_AUDIO_HW_VARIANT_FLAG_PASS_RADIO_AUDIO_FOCUS_FLAG)
+ VehicleAudioHwVariantConfigFlag.VEHICLE_AUDIO_HW_VARIANT_FLAG_INTERNAL_RADIO_FLAG)
== 0;
}
@@ -270,6 +339,44 @@
return isPropertySupportedLocked(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS);
}
+ public synchronized boolean isAudioVolumeSupported() {
+ return isPropertySupportedLocked(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME);
+ }
+
+ public synchronized int getSupportedAudioVolumeContexts() {
+ if (!isPropertySupportedLocked(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME)) {
+ throw new IllegalStateException("VEHICLE_PROPERTY_AUDIO_VOLUME not supported");
+ }
+ VehiclePropConfig config = mProperties.get(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME);
+ return config.getConfigArray(0);
+ }
+
+ /**
+ * Whether external audio module can memorize logical audio volumes or not.
+ * @return
+ */
+ public synchronized boolean isExternalAudioVolumePersistent() {
+ if (!isPropertySupportedLocked(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME)) {
+ throw new IllegalStateException("VEHICLE_PROPERTY_AUDIO_VOLUME not supported");
+ }
+ VehiclePropConfig config = mProperties.get(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME);
+ if (config.getConfigArray(0) == 0) { // physical streams only
+ return false;
+ }
+ if ((config.getConfigArray(1) &
+ VehicleAudioVolumeCapabilityFlag.VEHICLE_AUDIO_VOLUME_CAPABILITY_PERSISTENT_STORAGE)
+ != 0) {
+ return true;
+ }
+ return false;
+ }
+
+ public synchronized boolean isAudioVolumeLimitSupported() {
+ return isPropertySupportedLocked(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT);
+ }
+
/**
* Get the current audio focus state.
* @return 0: focusState, 1: streams, 2: externalFocus
@@ -340,22 +447,18 @@
@Override
public void handleHalEvents(List<VehiclePropValue> values) {
- AudioHalListener listener = null;
+ AudioHalFocusListener focusListener = null;
+ AudioHalVolumeListener volumeListener = null;
synchronized (this) {
- listener = mListener;
- if (listener == null) {
- if (mQueuedEvents == null) {
- mQueuedEvents = new LinkedList<VehiclePropValue>();
- }
- mQueuedEvents.addAll(values);
- }
+ focusListener = mFocusListener;
+ volumeListener = mVolumeListener;
}
- if (listener != null) {
- dispatchEventToListener(listener, values);
- }
+ dispatchEventToListener(focusListener, volumeListener, values);
}
- private void dispatchEventToListener(AudioHalListener listener, List<VehiclePropValue> values) {
+ private void dispatchEventToListener(AudioHalFocusListener focusListener,
+ AudioHalVolumeListener volumeListener,
+ List<VehiclePropValue> values) {
for (VehiclePropValue v : values) {
switch (v.getProp()) {
case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS: {
@@ -365,7 +468,18 @@
VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_STREAMS);
int externalFocus = v.getInt32Values(
VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_EXTERNAL_FOCUS_STATE);
- listener.onFocusChange(focusState, streams, externalFocus);
+ if (focusListener != null) {
+ focusListener.onFocusChange(focusState, streams, externalFocus);
+ }
+ } break;
+ case VehicleNetworkConsts.VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE: {
+ int state = v.getInt32Values(
+ VehicleAudioStreamStateIndex.VEHICLE_AUDIO_STREAM_STATE_INDEX_STATE);
+ int streamNum = v.getInt32Values(
+ VehicleAudioStreamStateIndex.VEHICLE_AUDIO_STREAM_STATE_INDEX_STREAM);
+ if (focusListener != null) {
+ focusListener.onStreamStatusChange(streamNum, state);
+ }
} break;
case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME: {
int volume = v.getInt32Values(
@@ -374,20 +488,22 @@
VehicleAudioVolumeIndex.VEHICLE_AUDIO_VOLUME_INDEX_STREAM);
int volumeState = v.getInt32Values(
VehicleAudioVolumeIndex.VEHICLE_AUDIO_VOLUME_INDEX_STATE);
- listener.onVolumeChange(streamNum, volume, volumeState);
+ if (volumeListener != null) {
+ volumeListener.onVolumeChange(streamNum, volume, volumeState);
+ }
} break;
case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT: {
- //TODO
- } break;
- case VehicleNetworkConsts.VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE: {
- int state = v.getInt32Values(
- VehicleAudioStreamStateIndex.VEHICLE_AUDIO_STREAM_STATE_INDEX_STATE);
- int streamNum = v.getInt32Values(
- VehicleAudioStreamStateIndex.VEHICLE_AUDIO_STREAM_STATE_INDEX_STREAM);
- listener.onStreamStatusChange(streamNum, state);
+ int stream = v.getInt32Values(
+ VehicleAudioVolumeLimitIndex.VEHICLE_AUDIO_VOLUME_LIMIT_INDEX_STREAM);
+ int maxVolume = v.getInt32Values(
+ VehicleAudioVolumeLimitIndex.VEHICLE_AUDIO_VOLUME_LIMIT_INDEX_MAX_VOLUME);
+ if (volumeListener != null) {
+ volumeListener.onVolumeLimitChange(stream, maxVolume);
+ }
} break;
}
}
+ values.clear();
}
@Override
diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java
index 24d5a16..928a8de 100644
--- a/service/src/com/android/car/hal/VehicleHal.java
+++ b/service/src/com/android/car/hal/VehicleHal.java
@@ -17,6 +17,9 @@
package com.android.car.hal;
import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
@@ -95,6 +98,8 @@
private final HashMap<Integer, VehiclePropConfig> mUnclaimedProperties = new HashMap<>();
private final List<VehiclePropConfig> mAllProperties = new LinkedList<>();
+ private final HashMap<Integer, VehiclePropertyEventInfo> mEventLog = new HashMap<>();
+
private VehicleHal() {
mHandlerThread = new HandlerThread("VEHICLE-HAL");
mHandlerThread.start();
@@ -279,6 +284,13 @@
HalServiceBase service = mPropertyHandlers.get(v.getProp());
service.getDispatchList().add(v);
mServicesToDispatch.add(service);
+ VehiclePropertyEventInfo info = mEventLog.get(v.getProp());
+ if (info == null) {
+ info = new VehiclePropertyEventInfo(v);
+ mEventLog.put(v.getProp(), info);
+ } else {
+ info.addNewEvent(v);
+ }
}
}
for (HalServiceBase s : mServicesToDispatch) {
@@ -310,12 +322,12 @@
writer.println("**All properties**");
for (VehiclePropConfig config : mAllProperties) {
StringBuilder builder = new StringBuilder();
- builder.append("Property:" + Integer.toHexString(config.getProp()));
- builder.append(",access:" + Integer.toHexString(config.getAccess()));
- builder.append(",changeMode:" + Integer.toHexString(config.getChangeMode()));
- builder.append(",valueType:" + Integer.toHexString(config.getValueType()));
- builder.append(",permission:" + Integer.toHexString(config.getPermissionModel()));
- builder.append(",config:" + Integer.toHexString(config.getConfigArray(0)));
+ builder.append("Property:0x" + Integer.toHexString(config.getProp()));
+ builder.append(",access:0x" + Integer.toHexString(config.getAccess()));
+ builder.append(",changeMode:0x" + Integer.toHexString(config.getChangeMode()));
+ builder.append(",valueType:0x" + Integer.toHexString(config.getValueType()));
+ builder.append(",permission:0x" + Integer.toHexString(config.getPermissionModel()));
+ builder.append(",config:0x" + Integer.toHexString(config.getConfigArray(0)));
builder.append(",fs min:" + config.getSampleRateMin());
builder.append(",fs max:" + config.getSampleRateMax());
for (int i = 0; i < config.getFloatMaxsCount(); i++) {
@@ -332,5 +344,55 @@
}
writer.println(builder.toString());
}
+ writer.println(String.format("**All Events, now ns:%d**",
+ SystemClock.elapsedRealtimeNanos()));
+ for (VehiclePropertyEventInfo info : mEventLog.values()) {
+ writer.println(String.format("event count:%d, lastEvent:%s",
+ info.eventCount, dumpVehiclePropValue(info.lastEvent)));
+ }
+ }
+
+ public static String dumpVehiclePropValue(VehiclePropValue value) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Property:0x" + Integer.toHexString(value.getProp()));
+ sb.append(",timestamp:" + value.getTimestamp());
+ sb.append(",value type:0x" + Integer.toHexString(value.getValueType()));
+ sb.append(",zone:0x" + Integer.toHexString(value.getZone()));
+ if (value.getInt32ValuesCount() > 0) {
+ sb.append(",int32 values:");
+ for (int i = 0; i < value.getInt32ValuesCount(); i++) {
+ sb.append("," + value.getInt32Values(i));
+ }
+ }
+ if (value.hasInt64Value()) {
+ sb.append(",int64 value:" + value.getInt64Value());
+ }
+ if (value.getFloatValuesCount() > 0) {
+ sb.append(",float values:");
+ for (int i = 0; i < value.getFloatValuesCount(); i++) {
+ sb.append("," + value.getFloatValues(i));
+ }
+ }
+ if (value.hasStringValue()) {
+ sb.append(",string value:" + value.getStringValue());
+ }
+ if (value.hasBytesValue()) {
+ sb.append(",bytes value:" + value.getBytesValue());
+ }
+ return sb.toString();
+ }
+ private static class VehiclePropertyEventInfo {
+ private int eventCount;
+ private VehiclePropValue lastEvent;
+
+ private VehiclePropertyEventInfo(VehiclePropValue event) {
+ eventCount = 1;
+ lastEvent = event;
+ }
+
+ private void addNewEvent(VehiclePropValue event) {
+ eventCount++;
+ lastEvent = event;
+ }
}
}
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index a626a86..a7a0013 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -21,11 +21,16 @@
android:minSdkVersion="22"
android:targetSdkVersion='23'/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.car.permission.CAR_SPEED" />
+ <uses-permission android:name="android.car.permission.CAR_MILEAGE" />
+ <uses-permission android:name="android.car.permission.CAR_FUEL" />
<uses-permission android:name="android.car.permission.CAR_HVAC" />
<uses-permission android:name="android.car.permission.CAR_MOCK_VEHICLE_HAL" />
<uses-permission android:name="android.car.permission.CAR_CAMERA" />
<uses-permission android:name="android.car.permission.CAR_NAVIGATION_MANAGER"/>
+ <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application android:label="@string/app_title"
android:icon="@drawable/ic_launcher">
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/audio.xml b/tests/EmbeddedKitchenSinkApp/res/layout/audio.xml
index 445a14f..a407c7e 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/audio.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/audio.xml
@@ -170,6 +170,16 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/microphone_off" />
+ <Button
+ android:id="@+id/button_wav_play_start"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/play_wav" />
+ <Button
+ android:id="@+id/button_wav_play_stop"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/stop_wav" />
</LinearLayout>
<TextView
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/input_test.xml b/tests/EmbeddedKitchenSinkApp/res/layout/input_test.xml
index f347ce9..e730b03 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/input_test.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/input_test.xml
@@ -24,24 +24,11 @@
android:orientation="vertical"
android:layout_weight="1" />
<LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_weight="1" >
- <Button
- android:id="@+id/button_volume_up"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/volume_up" />
- <Button
- android:id="@+id/button_volume_down"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/volume_down" />
- <Button
- android:id="@+id/button_voice"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/voice" />
+ android:orientation="vertical"
+ android:layout_weight="1"
+ android:id="@+id/input_buttons">
+ <!-- Filled at runtime. -->
</LinearLayout>
</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml b/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml
index aebb27da..a92f608 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/instrument_cluster.xml
@@ -17,7 +17,7 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_marginTop="40dp"
+ android:layout_marginTop="100dp"
android:layout_marginLeft="40dp">
<LinearLayout
@@ -38,13 +38,5 @@
android:text="@string/cluster_stop" android:id="@+id/cluster_stop_button"
android:layout_column="0" android:textSize="32sp"/>
</LinearLayout>
- <EditText
- android:layout_width="match_parent"
- android:layout_height="fill_parent"
- android:inputType="textMultiLine"
- android:ems="10"
- android:id="@+id/cluster_log"
- android:scrollIndicators="right" android:gravity="top"
- android:editable="false"/>
</LinearLayout>
</LinearLayout>
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/keyboard_test.xml b/tests/EmbeddedKitchenSinkApp/res/layout/keyboard_test.xml
index c617094..7eac97c 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/keyboard_test.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/keyboard_test.xml
@@ -20,6 +20,12 @@
android:layout_marginLeft="96dp">
<TextView
+ android:id="@+id/driving_status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="20dp"/>
+
+ <TextView
android:id="@+id/search_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/radio.xml b/tests/EmbeddedKitchenSinkApp/res/layout/radio.xml
new file mode 100644
index 0000000..e5b1ed2
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/radio.xml
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+ <!-- dummy one for top area -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="70dp"
+ android:orientation="vertical"
+ android:layout_weight="1" />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_weight="1" >
+ <Button
+ android:id="@+id/button_open_radio"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/radio_open" />
+ <Button
+ android:id="@+id/button_close_radio"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/radio_close" />
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_weight="1" >
+ <Button
+ android:id="@+id/button_get_radio_focus"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/radio_get_radio_focus" />
+ <Button
+ android:id="@+id/button_release_radio_focus"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/radio_release_radio_focus" />
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_weight="1" >
+ <Button
+ android:id="@+id/button_get_focus_in_radio"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/radio_get_focus" />
+ <Button
+ android:id="@+id/button_release_focus_in_radio"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/radio_release_focus" />
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_weight="1" >
+ <ToggleButton
+ android:id="@+id/button_band_selection"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:checked="true"
+ android:textOn="@string/radio_fm"
+ android:textOff="@string/radio_am" />
+ <Button
+ android:id="@+id/button_radio_next"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/radio_next" />
+ <Button
+ android:id="@+id/button_radio_prev"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/radio_prev" />
+ <Button
+ android:id="@+id/button_radio_scan_cancel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/radio_scan_cancel" />
+ <Button
+ android:id="@+id/button_radio_get_program_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/radio_get_program_info" />
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_weight="1" >
+ <TextView
+ android:id="@+id/radio_station_info"
+ android:layout_marginRight="@dimen/radioInfoMargin"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <TextView
+ android:id="@+id/radio_channel_info"
+ android:layout_marginRight="@dimen/radioInfoMargin"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <TextView
+ android:id="@+id/radio_song_info"
+ android:layout_marginRight="@dimen/radioInfoMargin"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ <TextView
+ android:id="@+id/radio_artist_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_weight="1" >
+ <TextView
+ android:id="@+id/radio_log"
+ android:maxLines="@integer/radio_log_lines"
+ android:scrollbars="vertical"
+ android:gravity="bottom"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/sensors.xml b/tests/EmbeddedKitchenSinkApp/res/layout/sensors.xml
new file mode 100644
index 0000000..6ee854a
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/sensors.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+ <!-- dummy one for top area -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="50dp"
+ android:orientation="vertical"
+ android:layout_weight="1" />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_weight="1" >
+ <TextView
+ android:id="@+id/sensor_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/volume_item.xml b/tests/EmbeddedKitchenSinkApp/res/layout/volume_item.xml
new file mode 100644
index 0000000..f62e527
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/volume_item.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2016 Google Inc. All Rights Reserved. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <TextView
+ android:id="@+id/stream_id"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:textSize="20sp"
+ android:layout_weight="2">
+ </TextView>
+ <TextView
+ android:id="@+id/volume_limit"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="10dp"
+ android:gravity="center"
+ android:textSize="20sp"
+ android:layout_weight="2">
+ </TextView>
+ <TextView
+ android:id="@+id/current_volume"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="10dp"
+ android:textSize="20sp"
+ android:layout_weight="2">
+ </TextView>
+ <TextView
+ android:id="@+id/logical_max"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:textSize="20dp"
+ android:layout_weight="2"/>
+ <TextView
+ android:id="@+id/logical_volume"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:textSize="20dp"
+ android:layout_weight="2"/>
+ <Button
+ android:id="@+id/volume_up"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="20dp"
+ android:text="@string/volume_up_logical" />
+ <Button
+ android:id="@+id/volume_down"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/volume_down_logical" />
+ <Button
+ android:id="@+id/request"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/request" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/volume_test.xml b/tests/EmbeddedKitchenSinkApp/res/layout/volume_test.xml
new file mode 100644
index 0000000..238d318
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/volume_test.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal" android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginTop="96dp"
+ android:layout_marginLeft="96dp">
+ <ListView
+ android:id="@+id/volume_list"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="3">
+ </ListView>
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="20dp"
+ android:orientation="vertical"
+ android:layout_weight="1">
+
+ <Button
+ android:id="@+id/refresh"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/refresh_volume"/>
+
+ <Button
+ android:id="@+id/volume_up"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/volume_up"/>
+
+ <Button
+ android:id="@+id/volume_down"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/volume_down"/>
+ </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/raw/free_flight.wav b/tests/EmbeddedKitchenSinkApp/res/raw/free_flight.wav
new file mode 100644
index 0000000..a4e14aa
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/raw/free_flight.wav
Binary files differ
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/dimens.xml b/tests/EmbeddedKitchenSinkApp/res/values/dimens.xml
index e35ee3d..0d23c73 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/dimens.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/dimens.xml
@@ -18,4 +18,6 @@
<dimen name="rvcBtnWidth">150dp</dimen>
<dimen name="rvcTextSize">10dp</dimen>
<dimen name="rvcTvHeight">80dp</dimen>
-</resources>
\ No newline at end of file
+ <dimen name="radioInfoMargin">5dp</dimen>
+ <dimen name="sensorInfoMargin">5dp</dimen>
+</resources>
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/integers.xml b/tests/EmbeddedKitchenSinkApp/res/values/integers.xml
new file mode 100644
index 0000000..e8b418f
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/values/integers.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <integer name="radio_log_lines">5</integer>
+</resources>
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index 9eeeaa9..a654d54 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -44,6 +44,8 @@
<string name="play">Play</string>
<string name="play_pcm_once">Play Once</string>
<string name="stop">Stop</string>
+ <string name="play_wav">Play WAV</string>
+ <string name="stop_wav">Stop WAV</string>
<string name="audio_focus">Audio Focus</string>
<string name="focus_gain">Gain</string>
<string name="focus_gain_transient_duck">Gain,Transient,Duck</string>
@@ -105,5 +107,62 @@
<!-- input test -->
<string name="volume_up">Volume +</string>
<string name="volume_down">Volume -</string>
+ <string name="volume_mute">Mute</string>
<string name="voice">Voice</string>
+ <string name="mock_vehicle_hal">Mock HAL</string>
+ <string name="mock_vehicle_hal_off">Mock HAL OFF</string>
+ <string name="mock_vehicle_hal_on">Mock HAL ON</string>
+ <string name="music">Music</string>
+ <string name="call_send">Call</string>
+ <string name="call_end">Call end</string>
+ <string name="home">Home</string>
+ <string name="next_song">Next song</string>
+ <string name="prev_song">Prev song</string>
+ <string name="tune_right">Tune +</string>
+ <string name="tune_left">Tune -</string>
+ <string name="music_play">Play</string>
+ <string name="music_stop">Stop</string>
+
+ <!-- radio test -->
+ <string name="radio_open">Open</string>
+ <string name="radio_close">Close</string>
+ <string name="radio_get_radio_focus">Get Radio focus</string>
+ <string name="radio_release_radio_focus">Release Radio focus</string>
+ <string name="radio_get_focus">Get Audio focus</string>
+ <string name="radio_release_focus">Release Audio focus</string>
+ <string name="radio_next">Next</string>
+ <string name="radio_prev">Previous</string>
+ <string name="radio_scan_cancel">Cancel scan</string>
+ <string name="radio_get_program_info">getProgramInformation</string>
+ <string name="radio_am">AM</string>
+ <string name="radio_fm">FM</string>
+ <string name="radio_station_info">Station info: %1$s</string>
+ <string name="radio_channel_info">Channel info: %1$s kHz</string>
+ <string name="radio_song_info">Song info: %1$s</string>
+ <string name="radio_artist_info">Artist info: %1$s</string>
+ <string name="radio_na">N/A</string>
+
+ <!-- sensorts test -->
+ <string name="sensor_na">N/A</string>
+
+ <string name="sensor_environment">Environment[%1$s]: temperature=%2$s, pressure=%3$s</string>
+ <string name="sensor_night">Night[%1$s]: isNight=%2$s</string>
+ <string name="sensor_gear">Gear[%1$s]: gear=%2$s</string>
+ <string name="sensor_parking_break">Parking break[%1$s]: isEngaged=%2$s</string>
+ <string name="sensor_fuel_level">Fuel level[%1$s]: lebel=%2$s, range=%3$s, lowFuelWarning=%4$s</string>
+ <string name="sensor_odometer">Odometer[%1$s]: kms=%2$s</string>
+ <string name="sensor_rpm">Rpm[%1$s]: rpm=%2$s</string>
+ <string name="sensor_speed">Speed[%1$s]: speed=%2$s</string>
+ <string name="sensor_driving_status">Driving status[%1$s]: status=%2$s [bin=%3$s]</string>
+ <string name="sensor_compass">Compass[%1$s]: bear=%2$s, pitch=%3$s, roll=%4$s</string>
+ <string name="sensor_accelerometer">Accelerometer[%1$s]: x=%2$s, y=%3$s, z=%4$s</string>
+ <string name="sensor_gyroscope">Gyroscpoe[%1$s]: x=%2$s, y=%3$s, z=%4$s</string>
+ <string name="sensor_location">Location[%1$s]: lat=%2$s, lon=%3$s, accuracy=%4$s, altitude=%5$s, speed=%6$s, bearing=%7$s</string>
+ <string name="sensor_gps">GPS Satellites[%1$s]: inView: %2$s, inUse: %3$s. %4$s</string>
+ <string name="sensor_single_gps_satellite">(%1$s): usedInFix: %2$s, prn: %3$s, snr: %4$s, azimuth: %5$s, elevation: %6$s</string>
+
+ <string name="volume_test">Volume Test</string>
+ <string name="volume_up_logical">Vol +</string>
+ <string name="volume_down_logical">Vol -</string>
+ <string name="refresh_volume">Refresh volumes</string>
</resources>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/CarEmulator.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/CarEmulator.java
index 29aad2c..ad3ac73 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/CarEmulator.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/CarEmulator.java
@@ -20,13 +20,12 @@
import android.car.test.VehicleHalEmulator.VehicleHalPropertyHandler;
import android.car.test.VehicleHalEmulator;
import android.os.SystemClock;
+import android.view.KeyEvent;
import com.android.car.vehiclenetwork.VehicleNetworkConsts;
-import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioExtFocusFlag;
import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioFocusIndex;
import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioFocusRequest;
import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioFocusState;
-import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleAudioStream;
import com.android.car.vehiclenetwork.VehiclePropConfigUtil;
import com.android.car.vehiclenetwork.VehiclePropValueUtil;
import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePermissionModel;
@@ -35,6 +34,9 @@
import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleValueType;
import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValue;
+import java.util.ArrayList;
+import java.util.List;
+
public class CarEmulator {
private final Car mCar;
@@ -44,19 +46,33 @@
new AudioFocusPropertyHandler();
private final AudioStreamStatePropertyHandler mAudioStreamStatePropertyHandler =
new AudioStreamStatePropertyHandler();
+ private final AudioVolumePropertyHandler mAudioVolumePropertyHandler =
+ new AudioVolumePropertyHandler();
+ private final AudioVolumeLimitPropertyHandler mAudioVolumeLimitPropertyHandler =
+ new AudioVolumeLimitPropertyHandler();
+ private static final int STREAM_COUNT = 2;
+ private static final int[] STREAM_MAX_VOLUME = {30, 30};
+ private static final int[] STREAM_MIN_VOLUME = {0, 0};
public CarEmulator(Car car) {
mCar = car;
mHalEmulator = new VehicleHalEmulator(car);
+ List<Integer> maxVols = new ArrayList<>();
+ List<Integer> minVols = new ArrayList<>();
+ for(int i = 0; i < STREAM_COUNT; i++) {
+ maxVols.add(STREAM_MAX_VOLUME[i]);
+ minVols.add(STREAM_MIN_VOLUME[i]);
+ }
+
mHalEmulator.addProperty(
VehiclePropConfigUtil.getBuilder(
VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS,
VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE,
VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE,
- VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC3,
+ VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC4,
VehiclePermissionModel.VEHICLE_PERMISSION_SYSTEM_APP_ONLY,
0 /*configFlags*/, 0 /*sampleRateMax*/, 0 /*sampleRateMin*/).build(),
- mAudioFocusPropertyHandler);
+ mAudioFocusPropertyHandler);
mHalEmulator.addProperty(
VehiclePropConfigUtil.getBuilder(
VehicleNetworkConsts.VEHICLE_PROPERTY_INTERNAL_AUDIO_STREAM_STATE,
@@ -66,6 +82,34 @@
VehiclePermissionModel.VEHICLE_PERMISSION_SYSTEM_APP_ONLY,
0 /*configFlags*/, 0 /*sampleRateMax*/, 0 /*sampleRateMin*/).build(),
mAudioStreamStatePropertyHandler);
+ mHalEmulator.addProperty(
+ VehiclePropConfigUtil.getBuilder(VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME,
+ VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE,
+ VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE,
+ VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC3,
+ VehiclePermissionModel.VEHICLE_PERMISSION_SYSTEM_APP_ONLY,
+ 0 /*configFlags*/, 0 /*sampleRateMax*/, 0 /*sampleRateMin*/,
+ minVols, maxVols).build(),
+ mAudioVolumePropertyHandler);
+ mHalEmulator.addProperty(
+ VehiclePropConfigUtil.getBuilder(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT,
+ VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE,
+ VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE,
+ VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC2,
+ VehiclePermissionModel.VEHICLE_PERMISSION_SYSTEM_APP_ONLY,
+ 0 /*configFlags*/, 0 /*sampleRateMax*/, 0 /*sampleRateMin*/).build(),
+ mAudioVolumeLimitPropertyHandler);
+
+ mHalEmulator.addProperty(
+ VehiclePropConfigUtil.getBuilder(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_HW_KEY_INPUT,
+ VehiclePropAccess.VEHICLE_PROP_ACCESS_READ,
+ VehiclePropChangeMode.VEHICLE_PROP_CHANGE_MODE_ON_CHANGE,
+ VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC4,
+ VehiclePermissionModel.VEHICLE_PERMISSION_SYSTEM_APP_ONLY,
+ 0 /*configFlags*/, 0 /*sampleRateMax*/, 0 /*sampleRateMin*/).build(),
+ null);
}
public void start() {
@@ -80,6 +124,14 @@
mAudioFocusPropertyHandler.setAudioFocusControl(reject);
}
+ public void setStreamVolume(int stream, int volume) {
+ mAudioVolumePropertyHandler.setStreamVolume(stream, volume);
+ }
+
+ public void injectVolumeKey(boolean up) {
+ mAudioVolumePropertyHandler.injectVolumeKey(up);
+ }
+
private class AudioFocusPropertyHandler implements VehicleHalPropertyHandler {
private boolean mRejectFocus;
private int mCurrentFocusState = VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
@@ -97,7 +149,7 @@
mCurrentFocusExtState = 0;
mRejectFocus = true;
int[] values = { mCurrentFocusState, mCurrentFocusStreams,
- mCurrentFocusExtState };
+ mCurrentFocusExtState, 0};
injectValue = VehiclePropValueUtil.createIntVectorValue(
VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, values,
SystemClock.elapsedRealtimeNanos());
@@ -108,7 +160,7 @@
mCurrentFocusStreams = 0;
mCurrentFocusExtState = 0;
int[] values = { mCurrentFocusState, mCurrentFocusStreams,
- mCurrentFocusExtState };
+ mCurrentFocusExtState, 0};
injectValue = VehiclePropValueUtil.createIntVectorValue(
VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, values,
SystemClock.elapsedRealtimeNanos());
@@ -127,7 +179,7 @@
synchronized (this) {
if (mRejectFocus) {
int[] values = { mCurrentFocusState, mCurrentFocusStreams,
- mCurrentFocusExtState };
+ mCurrentFocusExtState, 0};
injectValue = VehiclePropValueUtil.createIntVectorValue(
VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, values,
SystemClock.elapsedRealtimeNanos());
@@ -156,7 +208,7 @@
mCurrentFocusStreams = requestedStreams;
mCurrentFocusExtState = requestedExtFocus;
int[] values = { mCurrentFocusState, mCurrentFocusStreams,
- mCurrentFocusExtState };
+ mCurrentFocusExtState, 0};
injectValue = VehiclePropValueUtil.createIntVectorValue(
VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, values,
SystemClock.elapsedRealtimeNanos());
@@ -170,7 +222,7 @@
@Override
public synchronized VehiclePropValue onPropertyGet(VehiclePropValue value) {
int[] values = { mCurrentFocusState, mCurrentFocusStreams,
- mCurrentFocusExtState };
+ mCurrentFocusExtState, 0};
return VehiclePropValueUtil.createIntVectorValue(
VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_FOCUS, values,
SystemClock.elapsedRealtimeNanos());
@@ -204,4 +256,108 @@
public void onPropertyUnsubscribe(int property) {
}
}
+
+ private static class AudioVolumeLimitPropertyHandler implements VehicleHalPropertyHandler {
+ @Override
+ public void onPropertySet(VehiclePropValue value) {
+ }
+
+ @Override
+ public VehiclePropValue onPropertyGet(VehiclePropValue value) {
+ int stream = value.getInt32Values(
+ VehicleNetworkConsts.VehicleAudioVolumeLimitIndex
+ .VEHICLE_AUDIO_VOLUME_LIMIT_INDEX_STREAM);
+
+ int[] values = {stream, 20};
+ VehiclePropValue propValue = VehiclePropValueUtil.createIntVectorValue(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT, values,
+ SystemClock.elapsedRealtimeNanos());
+ return propValue;
+ }
+
+ @Override
+ public void onPropertySubscribe(int property, float sampleRate, int zones) {
+ }
+
+ @Override
+ public void onPropertyUnsubscribe(int property) {
+ }
+ }
+
+ private class AudioVolumePropertyHandler implements VehicleHalPropertyHandler {
+ private int[] mStreamVolume = {0, 0};
+
+ public void setStreamVolume(int stream, int index) {
+ if (stream > 1) {
+ return;
+ }
+ mStreamVolume[stream] = index;
+ VehiclePropValue injectValue = null;
+ int[] values = { stream, index, 0 };
+ injectValue = VehiclePropValueUtil.createIntVectorValue(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME, values,
+ SystemClock.elapsedRealtimeNanos());
+ mHalEmulator.injectEvent(injectValue);
+ }
+
+ public void injectVolumeKey(boolean up) {
+ int[] values = {
+ VehicleNetworkConsts.VehicleHwKeyInputAction.VEHICLE_HW_KEY_INPUT_ACTION_DOWN,
+ up ? KeyEvent.KEYCODE_VOLUME_UP : KeyEvent.KEYCODE_VOLUME_DOWN, 0, 0 };
+
+ VehiclePropValue injectValue = VehiclePropValueUtil.createIntVectorValue(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_HW_KEY_INPUT, values,
+ SystemClock.elapsedRealtimeNanos());
+
+ mHalEmulator.injectEvent(injectValue);
+
+ int[] upValues = {
+ VehicleNetworkConsts.VehicleHwKeyInputAction.VEHICLE_HW_KEY_INPUT_ACTION_UP,
+ up ? KeyEvent.KEYCODE_VOLUME_UP : KeyEvent.KEYCODE_VOLUME_DOWN, 0, 0 };
+
+ injectValue = VehiclePropValueUtil.createIntVectorValue(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_HW_KEY_INPUT, upValues,
+ SystemClock.elapsedRealtimeNanos());
+
+ mHalEmulator.injectEvent(injectValue);
+ }
+
+ @Override
+ public void onPropertySet(VehiclePropValue value) {
+ int stream = value.getInt32Values(
+ VehicleNetworkConsts.VehicleAudioVolumeIndex.VEHICLE_AUDIO_VOLUME_INDEX_STREAM);
+ int volume = value.getInt32Values(
+ VehicleNetworkConsts.VehicleAudioVolumeIndex.VEHICLE_AUDIO_VOLUME_INDEX_VOLUME);
+ int state = value.getInt32Values(
+ VehicleNetworkConsts.VehicleAudioVolumeIndex.VEHICLE_AUDIO_VOLUME_INDEX_STATE);
+
+ int[] values = {stream, volume, state};
+ VehiclePropValue injectValue = VehiclePropValueUtil.createIntVectorValue(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME, values,
+ SystemClock.elapsedRealtimeNanos());
+ mStreamVolume[stream] = volume;
+ mHalEmulator.injectEvent(injectValue);
+ }
+
+ @Override
+ public VehiclePropValue onPropertyGet(VehiclePropValue value) {
+ int stream = value.getInt32Values(
+ VehicleNetworkConsts.VehicleAudioVolumeIndex.VEHICLE_AUDIO_VOLUME_INDEX_STREAM);
+
+ int volume = mStreamVolume[stream];
+ int[] values = {stream, volume, 0};
+ VehiclePropValue propValue = VehiclePropValueUtil.createIntVectorValue(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME, values,
+ SystemClock.elapsedRealtimeNanos());
+ return propValue;
+ }
+
+ @Override
+ public void onPropertySubscribe(int property, float sampleRate, int zones) {
+ }
+
+ @Override
+ public void onPropertyUnsubscribe(int property) {
+ }
+ }
}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
index d227486..c7f15c6 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
@@ -44,6 +44,9 @@
import com.google.android.car.kitchensink.input.InputTestFragment;
import com.google.android.car.kitchensink.job.JobSchedulerFragment;
import com.google.android.car.kitchensink.keyboard.KeyboardFragment;
+import com.google.android.car.kitchensink.radio.RadioTestFragment;
+import com.google.android.car.kitchensink.sensor.SensorsTestFragment;
+import com.google.android.car.kitchensink.volume.VolumeTestFragment;
import java.util.ArrayList;
import java.util.List;
@@ -59,6 +62,9 @@
private static final String MENU_KEYBOARD = "keyboard";
private static final String MENU_CLUSTER = "inst cluster";
private static final String MENU_INPUT_TEST = "input test";
+ private static final String MENU_RADIO = "radio";
+ private static final String MENU_SENSORS = "sensors";
+ private static final String MENU_VOLUME_TEST = "volume test";
private Car mCarApi;
private CarCameraManager mCameraManager;
@@ -69,12 +75,15 @@
private AudioTestFragment mAudioTestFragment;
+ private RadioTestFragment mRadioTestFragment;
+ private SensorsTestFragment mSensorsTestFragment;
private CameraTestFragment mCameraTestFragment;
private HvacTestFragment mHvacTestFragment;
private JobSchedulerFragment mJobFragment;
private KeyboardFragment mKeyboardFragment;
private InstrumentClusterFragment mInstrumentClusterFragment;
private InputTestFragment mInputTestFragment;
+ private VolumeTestFragment mVolumeTestFragment;
private final CarSensorManager.CarSensorEventListener mListener =
new CarSensorManager.CarSensorEventListener() {
@@ -213,8 +222,8 @@
List<CarMenu.Item> items = new ArrayList<>();
if (parentId.equals(ROOT)) {
String[] allMenus = {
- MENU_AUDIO, MENU_CAMERA, MENU_HVAC, MENU_JOB, MENU_KEYBOARD, MENU_CLUSTER,
- MENU_INPUT_TEST, MENU_QUIT
+ MENU_AUDIO, MENU_RADIO, MENU_CAMERA, MENU_HVAC, MENU_JOB, MENU_KEYBOARD,
+ MENU_CLUSTER, MENU_INPUT_TEST, MENU_SENSORS, MENU_VOLUME_TEST, MENU_QUIT
};
for (String menu : allMenus) {
items.add(new CarMenu.Builder(menu).setText(menu).build());
@@ -231,6 +240,16 @@
mAudioTestFragment = new AudioTestFragment();
}
setContentFragment(mAudioTestFragment);
+ } else if (id.equals(MENU_RADIO)) {
+ if (mRadioTestFragment == null) {
+ mRadioTestFragment = new RadioTestFragment();
+ }
+ setContentFragment(mRadioTestFragment);
+ } else if (id.equals(MENU_SENSORS)) {
+ if (mSensorsTestFragment == null) {
+ mSensorsTestFragment = new SensorsTestFragment();
+ }
+ setContentFragment(mSensorsTestFragment);
} else if (id.equals(MENU_CAMERA)) {
if (mCameraManager != null) {
if (mCameraTestFragment == null) {
@@ -271,6 +290,11 @@
mInputTestFragment = new InputTestFragment();
}
setContentFragment(mInputTestFragment);
+ } else if (id.equals(MENU_VOLUME_TEST)) {
+ if (mVolumeTestFragment == null) {
+ mVolumeTestFragment = new VolumeTestFragment();
+ }
+ setContentFragment(mVolumeTestFragment);
} else if (id.equals(MENU_QUIT)) {
finish();
}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
index 16e2e57..6cd0638 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/audio/AudioTestFragment.java
@@ -60,6 +60,8 @@
private Button mMediaPlay;
private Button mMediaPlayOnce;
private Button mMediaStop;
+ private Button mWavPlay;
+ private Button mWavStop;
private Button mNavStart;
private Button mNavEnd;
private Button mVrStart;
@@ -78,6 +80,7 @@
private AudioPlayer mNavGuidancePlayer;
private AudioPlayer mVrPlayer;
private AudioPlayer mSystemPlayer;
+ private AudioPlayer mWavPlayer;
private AudioPlayer[] mAllPlayers;
private Handler mHandler;
@@ -171,12 +174,15 @@
mVrAudioAttrib);
mSystemPlayer = new AudioPlayer(mContext, R.raw.ring_classic_01,
mSystemSoundAudioAttrib);
+ mWavPlayer = new AudioPlayer(mContext, R.raw.free_flight,
+ mMusicAudioAttrib);
mAllPlayers = new AudioPlayer[] {
mMusicPlayer,
mMusicPlayerShort,
mNavGuidancePlayer,
mVrPlayer,
- mSystemPlayer
+ mSystemPlayer,
+ mWavPlayer
};
}
@Override
@@ -225,6 +231,20 @@
mMusicPlayer.stop();
}
});
+ mWavPlay = (Button) view.findViewById(R.id.button_wav_play_start);
+ mWavPlay.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mWavPlayer.start(true, true, AudioManager.AUDIOFOCUS_GAIN);
+ }
+ });
+ mWavStop = (Button) view.findViewById(R.id.button_wav_play_stop);
+ mWavStop.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mWavPlayer.stop();
+ }
+ });
mNavPlayOnce = (Button) view.findViewById(R.id.button_nav_play_once);
mNavPlayOnce.setOnClickListener(new OnClickListener() {
@Override
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
index 91f8d39..421bb42 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/cluster/InstrumentClusterFragment.java
@@ -27,7 +27,6 @@
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.ViewGroup;
import com.google.android.car.kitchensink.R;
@@ -53,20 +52,10 @@
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.instrument_cluster, container);
+ View view = inflater.inflate(R.layout.instrument_cluster, container, false);
- view.findViewById(R.id.cluster_start_button).setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- initCluster();
- }
- });
- view.findViewById(R.id.cluster_turn_left_button).setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- turnLeft();
- }
- });
+ view.findViewById(R.id.cluster_start_button).setOnClickListener(v -> initCluster());
+ view.findViewById(R.id.cluster_turn_left_button).setOnClickListener(v -> turnLeft());
return super.onCreateView(inflater, container, savedInstanceState);
}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/input/InputTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/input/InputTestFragment.java
index 64ede01..a177c00 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/input/InputTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/input/InputTestFragment.java
@@ -16,6 +16,7 @@
package com.google.android.car.kitchensink.input;
import android.annotation.Nullable;
+import android.annotation.StringRes;
import android.car.Car;
import android.car.CarNotConnectedException;
import android.car.test.CarTestManager;
@@ -34,6 +35,17 @@
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.Button;
+import android.widget.LinearLayout;
+
+import com.google.android.car.kitchensink.R;
+
+import com.android.car.vehiclenetwork.VehicleNetworkConsts;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleHwKeyInputAction;
+import com.android.car.vehiclenetwork.VehiclePropValueUtil;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import com.android.car.vehiclenetwork.VehicleNetworkConsts;
import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleHwKeyInputAction;
@@ -49,11 +61,11 @@
private static final String TAG = "CAR.INPUT.KS";
+ private static final Button BREAK_LINE = null;
+
private Car mCar;
private CarTestManager mTestManager;
- private Button mVolumeUp;
- private Button mVolumeDown;
- private Button mVoice;
+ private final List<View> mButtons = new ArrayList<>();
@Nullable
@Override
@@ -61,32 +73,27 @@
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.input_test, container, false);
- // Single touch + key event does not work as touch is happening in other window
- // at the same time. But long press will work.
- mVolumeUp = (Button) view.findViewById(R.id.button_volume_up);
- mVolumeUp.setOnTouchListener(new OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- handleTouchEvent(event, KeyEvent.KEYCODE_VOLUME_UP);
- return true;
- }
- });
- mVolumeDown = (Button) view.findViewById(R.id.button_volume_down);
- mVolumeDown.setOnTouchListener(new OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- handleTouchEvent(event, KeyEvent.KEYCODE_VOLUME_DOWN);
- return true;
- }
- });
- mVoice = (Button) view.findViewById(R.id.button_voice);
- mVoice.setOnTouchListener(new OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- handleTouchEvent(event, KeyEvent.KEYCODE_VOICE_ASSIST);
- return true;
- }
- });
+ Collections.addAll(mButtons,
+ BREAK_LINE,
+ createButton(R.string.home, KeyEvent.KEYCODE_HOME),
+ createButton(R.string.volume_up, KeyEvent.KEYCODE_VOLUME_UP),
+ createButton(R.string.volume_down, KeyEvent.KEYCODE_VOLUME_DOWN),
+ createButton(R.string.volume_mute, KeyEvent.KEYCODE_VOLUME_MUTE),
+ createButton(R.string.voice, KeyEvent.KEYCODE_VOICE_ASSIST),
+ BREAK_LINE,
+ createButton(R.string.music, KeyEvent.KEYCODE_MUSIC),
+ createButton(R.string.music_play, KeyEvent.KEYCODE_MEDIA_PLAY),
+ createButton(R.string.music_stop, KeyEvent.KEYCODE_MEDIA_STOP),
+ createButton(R.string.next_song, KeyEvent.KEYCODE_MEDIA_NEXT),
+ createButton(R.string.prev_song, KeyEvent.KEYCODE_MEDIA_PREVIOUS),
+ createButton(R.string.tune_right, KeyEvent.KEYCODE_CHANNEL_UP),
+ createButton(R.string.tune_left, KeyEvent.KEYCODE_CHANNEL_DOWN),
+ BREAK_LINE,
+ createButton(R.string.call_send, KeyEvent.KEYCODE_CALL),
+ createButton(R.string.call_end, KeyEvent.KEYCODE_ENDCALL)
+ );
+
+ addButtonsToPanel((LinearLayout) view.findViewById(R.id.input_buttons), mButtons);
mCar = Car.createCar(getContext(), new ServiceConnection() {
@Override
@@ -97,12 +104,16 @@
} catch (CarNotConnectedException e) {
throw new RuntimeException("Failed to create test service manager", e);
}
- if (!mTestManager.isPropertySupported(
- VehicleNetworkConsts.VEHICLE_PROPERTY_HW_KEY_INPUT)) {
+ boolean hwKeySupported = mTestManager.isPropertySupported(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_HW_KEY_INPUT);
+ if (!hwKeySupported) {
Log.w(TAG, "VEHICLE_PROPERTY_HW_KEY_INPUT not supported");
- mVolumeUp.setEnabled(false);
- mVolumeDown.setEnabled(false);
- mVoice.setEnabled(false);
+ }
+
+ for (View v : mButtons) {
+ if (v != null) {
+ v.setEnabled(hwKeySupported);
+ }
}
}
@@ -114,6 +125,34 @@
return view;
}
+ private Button createButton(@StringRes int textResId, int keyCode) {
+ Button button = new Button(getContext());
+ button.setText(getContext().getString(textResId));
+ button.setTextSize(32f);
+ // Single touch + key event does not work as touch is happening in other window
+ // at the same time. But long press will work.
+ button.setOnTouchListener((v, event) -> {
+ handleTouchEvent(event, keyCode);
+ return true;
+ });
+
+ return button;
+ }
+
+ private void checkHwKeyInputSupported() {
+ boolean hwKeyInputSupported = mTestManager.isPropertySupported(
+ VehicleNetworkConsts.VEHICLE_PROPERTY_HW_KEY_INPUT);
+ if (!hwKeyInputSupported) {
+ Log.w(TAG, "VEHICLE_PROPERTY_HW_KEY_INPUT not supported");
+ }
+
+ for (View v : mButtons) {
+ if (v != null) {
+ v.setEnabled(hwKeyInputSupported);
+ }
+ }
+ }
+
private void handleTouchEvent(MotionEvent event, int keyCode) {
int action = event.getActionMasked();
Log.i(TAG, "handleTouchEvent, action:" + action + ",keyCode:" + keyCode);
@@ -139,4 +178,17 @@
super.onDestroyView();
mCar.disconnect();
}
+
+ private void addButtonsToPanel(LinearLayout root, List<View> buttons) {
+ LinearLayout panel = null;
+ for (View button : buttons) {
+ if (button == BREAK_LINE) {
+ panel = new LinearLayout(getContext());
+ panel.setOrientation(LinearLayout.HORIZONTAL);
+ root.addView(panel);
+ } else {
+ panel.addView(button);
+ }
+ }
+ }
}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/keyboard/KeyboardFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/keyboard/KeyboardFragment.java
index 4ee0b16..3acb5cf 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/keyboard/KeyboardFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/keyboard/KeyboardFragment.java
@@ -18,9 +18,15 @@
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
+import android.support.car.Car;
+import android.support.car.CarNotConnectedException;
+import android.support.car.CarNotSupportedException;
import android.support.car.app.menu.CarDrawerActivity;
import android.support.car.app.menu.SearchBoxEditListener;
+import android.support.car.hardware.CarSensorEvent;
+import android.support.car.hardware.CarSensorManager;
import android.support.v4.app.Fragment;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -30,16 +36,19 @@
import com.google.android.car.kitchensink.R;
public class KeyboardFragment extends Fragment {
+ private static final String TAG = "KitchenSinkKeyboard";
public static final int CARD = 0xfffafafa;
public static final int TEXT_PRIMARY_DAY = 0xde000000;
public static final int TEXT_SECONDARY_DAY = 0x8a000000;
+ private TextView mDrivingStatus;
private Button mImeButton;
private Button mCloseImeButton;
private Button mShowHideInputButton;
private CarDrawerActivity mActivity;
private TextView mOnSearchText;
private TextView mOnEditText;
+ private CarSensorManager mSensorManager;
private final Handler mHandler = new Handler();
@@ -81,10 +90,57 @@
mOnEditText = (TextView) v.findViewById(R.id.edit_text);
resetInput();
mActivity.setSearchBoxEndView(View.inflate(getContext(), R.layout.keyboard_end_view, null));
-
+ mDrivingStatus = (TextView) v.findViewById(R.id.driving_status);
return v;
}
+ @Override
+ public void onResume() {
+ super.onResume();
+ try {
+ mSensorManager = (CarSensorManager)
+ mActivity.getCar().getCarManager(Car.SENSOR_SERVICE);
+ mSensorManager.registerListener(mCarSensorListener,
+ CarSensorManager.SENSOR_TYPE_DRIVING_STATUS,
+ CarSensorManager.SENSOR_RATE_FASTEST);
+ } catch (CarNotSupportedException | CarNotConnectedException e) {
+ Log.e(TAG, "Car not connected or not supported", e);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (mSensorManager != null) {
+ try {
+ mSensorManager.unregisterListener(mCarSensorListener);
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car not connected", e);
+ }
+ }
+ }
+
+ private final CarSensorManager.CarSensorEventListener mCarSensorListener =
+ new CarSensorManager.CarSensorEventListener() {
+ @Override
+ public void onSensorChanged(CarSensorEvent event) {
+ if (event.sensorType != CarSensorManager.SENSOR_TYPE_DRIVING_STATUS) {
+ return;
+ }
+ int drivingStatus = event.getDrivingStatusData(null).status;
+
+ boolean keyboardEnabled =
+ (drivingStatus & CarSensorEvent.DRIVE_STATUS_NO_KEYBOARD_INPUT) == 0;
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mDrivingStatus.setText("Driving status: " + drivingStatus
+ + " Keyboard " + (keyboardEnabled ? "enabled" : "disabled"));
+ }
+ });
+ }
+ };
+
private void resetInput() {
mActivity.showSearchBox(new View.OnClickListener() {
@Override
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/radio/RadioTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/radio/RadioTestFragment.java
new file mode 100644
index 0000000..b127e2b
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/radio/RadioTestFragment.java
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.kitchensink.radio;
+
+import android.annotation.Nullable;
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.media.CarAudioManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioMetadata;
+import android.hardware.radio.RadioTuner;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.support.v4.app.Fragment;
+import android.text.TextUtils;
+import android.text.method.ScrollingMovementMethod;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.TextView;
+import android.widget.ToggleButton;
+
+import com.google.android.car.kitchensink.R;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+public class RadioTestFragment extends Fragment {
+ private static final String TAG = "CAR.RADIO.KS";
+ private static final boolean DBG = true;
+ private static final int MAX_LOG_MESSAGES = 100;
+
+ private final AudioManager.OnAudioFocusChangeListener mRadioFocusListener =
+ new AudioManager.OnAudioFocusChangeListener() {
+ @Override
+ public void onAudioFocusChange(int focusChange) {
+ addLog(Log.INFO, "Radio focus change:" + focusChange);
+ }
+ };
+
+ private final AudioManager.OnAudioFocusChangeListener mSecondaryFocusListener =
+ new AudioManager.OnAudioFocusChangeListener() {
+ @Override
+ public void onAudioFocusChange(int focusChange) {
+ addLog(Log.INFO, "Secondary focus change:" + focusChange);
+ }
+ };
+
+ private final RadioTuner.Callback mRadioCallback = new RadioTuner.Callback() {
+ @Override
+ public void onError(int status) {
+ addLog(Log.WARN, "Radio tuner error " + status);
+ }
+
+ @Override
+ public void onConfigurationChanged(RadioManager.BandConfig config) {
+ addLog(Log.INFO, "Radio tuner configuration changed. config:" + config);
+ }
+
+ @Override
+ public void onMetadataChanged(RadioMetadata metadata) {
+ addLog(Log.INFO, "Radio tuner metadata changed. metadata:" + metadata);
+ if (metadata == null) {
+ resetMessages();
+ updateMessages();
+ return;
+ }
+ mArtist = metadata.getString(RadioMetadata.METADATA_KEY_ARTIST);
+ mSong = metadata.getString(RadioMetadata.METADATA_KEY_TITLE);
+ mStation = metadata.getString(RadioMetadata.METADATA_KEY_RDS_PI);
+ updateMessages();
+ }
+
+ @Override
+ public void onProgramInfoChanged(RadioManager.ProgramInfo info) {
+ addLog(Log.INFO, "Radio tuner program info. info:" + info);
+ mChannel = String.valueOf(info.getChannel());
+ onMetadataChanged(info.getMetadata());
+ updateMessages();
+ }
+
+ };
+ private final LinkedList<String> mLogMessages = new LinkedList<>();
+
+ private Button mOpenRadio;
+ private Button mCloseRadio;
+ private Button mGetRadioFocus;
+ private Button mReleaseRadioFocus;
+ private Button mGetFocus;
+ private Button mReleaseFocus;
+ private Button mRadioNext;
+ private Button mRadioPrev;
+ private Button mRadioScanCancel;
+ private Button mRadioGetProgramInfo;
+ private ToggleButton mRadioBand;
+ private TextView mStationInfo;
+ private TextView mChannelInfo;
+ private TextView mSongInfo;
+ private TextView mArtistInfo;
+ private TextView mLog;
+
+ private Car mCar;
+ private CarAudioManager mCarAudioManager;
+ private AudioAttributes mRadioAudioAttrib;
+ private AudioManager mAudioManager;
+ private boolean mHasRadioFocus;
+ private boolean mHasSecondaryFocus;
+ private RadioTuner mRadioTuner;
+ private RadioManager mRadioManager;
+ private RadioManager.FmBandDescriptor mFmDescriptor;
+ private RadioManager.AmBandDescriptor mAmDescriptor;
+ private String mStation;
+ private String mChannel;
+ private String mSong;
+ private String mArtist;
+ private String mNaString;
+
+ private RadioManager.BandConfig mFmConfig;
+ private RadioManager.BandConfig mAmConfig;
+
+ private final List<RadioManager.ModuleProperties> mModules = new ArrayList<>();
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ if (DBG) {
+ Log.i(TAG, "onCreateView");
+ }
+
+ init();
+ View view = inflater.inflate(R.layout.radio, container, false);
+
+ mOpenRadio = (Button) view.findViewById(R.id.button_open_radio);
+ mCloseRadio = (Button) view.findViewById(R.id.button_close_radio);
+ mGetRadioFocus = (Button) view.findViewById(R.id.button_get_radio_focus);
+ mReleaseRadioFocus = (Button) view.findViewById(R.id.button_release_radio_focus);
+ mGetFocus = (Button) view.findViewById(R.id.button_get_focus_in_radio);
+ mReleaseFocus = (Button) view.findViewById(R.id.button_release_focus_in_radio);
+ mRadioNext = (Button) view.findViewById(R.id.button_radio_next);
+ mRadioPrev = (Button) view.findViewById(R.id.button_radio_prev);
+ mRadioScanCancel = (Button) view.findViewById(R.id.button_radio_scan_cancel);
+ mRadioGetProgramInfo = (Button) view.findViewById(R.id.button_radio_get_program_info);
+ mRadioBand = (ToggleButton) view.findViewById(R.id.button_band_selection);
+
+ mStationInfo = (TextView) view.findViewById(R.id.radio_station_info);
+ mChannelInfo = (TextView) view.findViewById(R.id.radio_channel_info);
+ mSongInfo = (TextView) view.findViewById(R.id.radio_song_info);
+ mArtistInfo = (TextView) view.findViewById(R.id.radio_artist_info);
+
+ mLog = (TextView) view.findViewById(R.id.radio_log);
+ mLog.setMovementMethod(new ScrollingMovementMethod());
+
+ mNaString = getContext().getString(R.string.radio_na);
+
+ addHandlers();
+ updateStates();
+
+ return view;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ resetMessages();
+ updateStates();
+ updateMessages();
+ resetLog();
+ }
+
+ private void init() {
+ mCar = Car.createCar(getContext(), new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ try {
+ mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
+ mRadioAudioAttrib = mCarAudioManager.getAudioAttributesForCarUsage(
+ CarAudioManager.CAR_AUDIO_USAGE_RADIO);
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car not connected", e);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
+ }, Looper.getMainLooper());
+ mCar.connect();
+ mAudioManager = (AudioManager) getContext().getSystemService(
+ Context.AUDIO_SERVICE);
+ initializeRadio();
+ }
+
+ private void initializeRadio() {
+ mRadioManager = (RadioManager) getContext().getSystemService(Context.RADIO_SERVICE);
+
+ if (mRadioManager == null) {
+ throw new IllegalStateException("RadioManager could not be loaded.");
+ }
+
+ int status = mRadioManager.listModules(mModules);
+ if (status != RadioManager.STATUS_OK) {
+ throw new IllegalStateException("Load modules failed with status: " + status);
+ }
+
+ if (mModules.size() == 0) {
+ throw new IllegalStateException("No radio modules on device.");
+ }
+
+ boolean isDebugLoggable = Log.isLoggable(TAG, Log.DEBUG);
+
+ // Load the possible radio bands. For now, just accept FM and AM bands.
+ for (RadioManager.BandDescriptor band : mModules.get(0).getBands()) {
+ if (isDebugLoggable) {
+ Log.d(TAG, "loading band: " + band.toString());
+ }
+
+ if (mFmDescriptor == null && band.getType() == RadioManager.BAND_FM) {
+ mFmDescriptor = (RadioManager.FmBandDescriptor) band;
+ }
+
+ if (mAmDescriptor == null && band.getType() == RadioManager.BAND_AM) {
+ mAmDescriptor = (RadioManager.AmBandDescriptor) band;
+ }
+ }
+
+ if (mFmDescriptor == null && mAmDescriptor == null) {
+ throw new IllegalStateException("No AM and FM radio bands could be loaded.");
+ }
+
+ mFmConfig = new RadioManager.FmBandConfig.Builder(mFmDescriptor)
+ .setStereo(true)
+ .build();
+ mAmConfig = new RadioManager.AmBandConfig.Builder(mAmDescriptor)
+ .setStereo(true)
+ .build();
+ }
+
+ private void addHandlers() {
+ mOpenRadio.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ handleRadioStart();
+ updateStates();
+ }
+ });
+ mCloseRadio.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ handleRadioEnd();
+ updateStates();
+ }
+ });
+ mGetRadioFocus.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (DBG) {
+ Log.i(TAG, "Get radio focus");
+ }
+ mCarAudioManager.requestAudioFocus(mRadioFocusListener, mRadioAudioAttrib,
+ AudioManager.AUDIOFOCUS_GAIN, 0);
+ mHasRadioFocus = true;
+ updateStates();
+ }
+ });
+ mReleaseRadioFocus.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (DBG) {
+ Log.i(TAG, "Release radio focus");
+ }
+ mCarAudioManager.abandonAudioFocus(mRadioFocusListener, mRadioAudioAttrib);
+ mHasRadioFocus = false;
+ updateStates();
+ }
+ });
+ mGetFocus.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (DBG) {
+ Log.i(TAG, "Get secondary focus");
+ }
+ mAudioManager.requestAudioFocus(mSecondaryFocusListener,
+ AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
+ mHasSecondaryFocus = true;
+ updateStates();
+ }
+ });
+ mReleaseFocus.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (DBG) {
+ Log.i(TAG, "Release secondary focus");
+ }
+ mAudioManager.abandonAudioFocus(mSecondaryFocusListener);
+ mHasSecondaryFocus = false;
+ updateStates();
+ }
+ });
+ mRadioNext.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (DBG) {
+ Log.i(TAG, "Next radio station");
+ }
+ if (mRadioTuner != null) {
+ mRadioTuner.scan(RadioTuner.DIRECTION_UP, true);
+ }
+ updateStates();
+ }
+ });
+ mRadioPrev.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (DBG) {
+ Log.i(TAG, "Previous radio station");
+ }
+ if (mRadioTuner != null) {
+ mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, true);
+ }
+ updateStates();
+ }
+ });
+ mRadioScanCancel.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (DBG) {
+ Log.i(TAG, "Cancel radio scan");
+ }
+ if (mRadioTuner != null) {
+ mRadioTuner.cancel();
+ }
+ updateStates();
+ }
+ });
+ mRadioGetProgramInfo.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (DBG) {
+ Log.i(TAG, "getProgramInformation");
+ }
+ if (mRadioTuner != null) {
+ RadioManager.ProgramInfo[] programInfos = new RadioManager.ProgramInfo[1];
+ mRadioTuner.getProgramInformation(programInfos);
+ addLog(Log.INFO, "mRadioTuner.getProgramInformation() =>" + programInfos[0]);
+ }
+ }
+ });
+ mRadioBand.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (DBG) {
+ Log.i(TAG, "Changing radio band");
+ }
+ if (mRadioTuner != null) {
+ mRadioTuner.setConfiguration(mRadioBand.isChecked() ? mFmConfig : mAmConfig);
+ }
+ resetMessages();
+ updateMessages();
+ updateStates();
+ }
+ });
+ }
+
+ private void updateStates() {
+ mOpenRadio.setEnabled(mRadioTuner == null);
+ mCloseRadio.setEnabled(mRadioTuner != null);
+ mGetRadioFocus.setEnabled(!mHasRadioFocus);
+ mReleaseRadioFocus.setEnabled(mHasRadioFocus);
+ mGetFocus.setEnabled(!mHasSecondaryFocus);
+ mReleaseFocus.setEnabled(mHasSecondaryFocus);
+ mRadioNext.setEnabled(mRadioTuner != null);
+ mRadioPrev.setEnabled(mRadioTuner != null);
+ mRadioBand.setEnabled(mRadioTuner != null);
+ }
+
+ private void updateMessages() {
+ mStationInfo.setText(getContext().getString
+ (R.string.radio_station_info, mStation == null ? mNaString : mStation));
+ mChannelInfo.setText(getContext().getString
+ (R.string.radio_channel_info, mChannel == null ? mNaString : mChannel));
+ mArtistInfo.setText(getContext().getString
+ (R.string.radio_artist_info, mArtist == null ? mNaString : mArtist));
+ mSongInfo.setText(getContext().getString
+ (R.string.radio_song_info, mSong == null ? mNaString : mSong));
+ }
+
+ private void resetMessages() {
+ mStation = null;
+ mChannel = null;
+ mSong = null;
+ mArtist = null;
+ }
+
+ private void handleRadioStart() {
+ if (mCarAudioManager == null) {
+ return;
+ }
+ if (DBG) {
+ Log.i(TAG, "Radio start");
+ }
+ if (mRadioTuner != null) {
+ Log.w(TAG, "Radio tuner already open");
+ mRadioTuner.close();
+ mRadioTuner = null;
+ }
+ mRadioTuner = mRadioManager.openTuner(mModules.get(0).getId(),
+ mRadioBand.isChecked() ? mFmConfig : mAmConfig,
+ true, mRadioCallback /* callback */, null /* handler */);
+ }
+
+ private void handleRadioEnd() {
+ if (mCarAudioManager == null) {
+ return;
+ }
+ if (DBG) {
+ Log.i(TAG, "Radio end");
+ }
+ mRadioTuner.close();
+ mRadioTuner = null;
+ }
+
+ private void resetLog() {
+ synchronized (this) {
+ mLogMessages.clear();
+ }
+ }
+
+ private void addLog(int priority, String message) {
+ Log.println(priority, TAG, message);
+ synchronized (this) {
+ mLogMessages.add(message);
+ if (mLogMessages.size() > MAX_LOG_MESSAGES) {
+ mLogMessages.poll();
+ }
+ mLog.setText(TextUtils.join("\n", mLogMessages));
+ }
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/sensor/SensorsTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/sensor/SensorsTestFragment.java
new file mode 100644
index 0000000..468ac39
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/sensor/SensorsTestFragment.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.car.kitchensink.sensor;
+
+import android.Manifest;
+import android.annotation.Nullable;
+import android.car.Car;
+import android.content.pm.PackageManager;
+import android.location.Location;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.car.CarNotConnectedException;
+import android.support.car.CarNotSupportedException;
+import android.support.car.app.menu.CarDrawerActivity;
+import android.support.car.hardware.CarSensorEvent;
+import android.support.car.hardware.CarSensorManager;
+import android.support.v4.app.Fragment;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.google.android.car.kitchensink.R;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class SensorsTestFragment extends Fragment {
+ private static final String TAG = "CAR.SENSOR.KS";
+ private static final boolean DBG = true;
+ private static final boolean DBG_VERBOSE = false;
+ private static final int KS_PERMISSIONS_REQUEST = 1;
+
+ private final static String[] REQUIRED_PERMISSIONS = new String[]{
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ Manifest.permission.ACCESS_COARSE_LOCATION,
+ Car.PERMISSION_MILEAGE,
+ Car.PERMISSION_FUEL,
+ Car.PERMISSION_SPEED
+ };
+
+ private final CarSensorManager.CarSensorEventListener mCarSensorListener =
+ new CarSensorManager.CarSensorEventListener() {
+ @Override
+ public void onSensorChanged(CarSensorEvent event) {
+ if (DBG_VERBOSE) {
+ Log.v(TAG, "New car sensor event: " + event);
+ }
+ synchronized (SensorsTestFragment.this) {
+ mEventMap.put(event.sensorType, event);
+ }
+ refreshUi();
+ }
+ };
+ private final Handler mHandler = new Handler();
+ private final Map<Integer, CarSensorEvent> mEventMap = new ConcurrentHashMap<>();
+ private final DateFormat mDateFormat = SimpleDateFormat.getDateTimeInstance();
+
+ private CarDrawerActivity mActivity;
+ private TextView mSensorInfo;
+ private Car mCar;
+ private CarSensorManager mSensorManager;
+ private String mNaString;
+ private int[] supportedSensors = new int[0];
+ private Set<String> mActivePermissions = new HashSet<String>();
+
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ if (DBG) {
+ Log.i(TAG, "onCreateView");
+ }
+
+ View view = inflater.inflate(R.layout.sensors, container, false);
+ mActivity = (CarDrawerActivity) getHost();
+
+ mSensorInfo = (TextView) view.findViewById(R.id.sensor_info);
+ mNaString = getContext().getString(R.string.sensor_na);
+
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ initPermissions();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (mSensorManager != null) {
+ try {
+ mSensorManager.unregisterListener(mCarSensorListener);
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car not connected", e);
+ }
+ }
+ }
+
+ private void initSensors() {
+ try {
+ mSensorManager = (CarSensorManager)
+ mActivity.getCar().getCarManager(Car.SENSOR_SERVICE);
+ supportedSensors = mSensorManager.getSupportedSensors();
+ for (Integer sensor : supportedSensors) {
+ if ((sensor == CarSensorManager.SENSOR_TYPE_LOCATION
+ || sensor == CarSensorManager.SENSOR_TYPE_GPS_SATELLITE)
+ && !mActivePermissions.contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
+ continue;
+ }
+ mSensorManager.registerListener(mCarSensorListener, sensor,
+ CarSensorManager.SENSOR_RATE_NORMAL);
+ }
+ } catch (CarNotSupportedException | CarNotConnectedException e) {
+ Log.e(TAG, "Car not connected or not supported", e);
+ }
+ }
+
+ private void initPermissions() {
+ Set<String> missingPermissions = checkExistingPermissions();
+ if (!missingPermissions.isEmpty()) {
+ requestPermissions(missingPermissions);
+ } else {
+ initSensors();
+ }
+ }
+
+ private Set<String> checkExistingPermissions() {
+ Set<String> missingPermissions = new HashSet<String>();
+ for (String permission : REQUIRED_PERMISSIONS) {
+ if (mActivity.getContext().checkSelfPermission(permission)
+ == PackageManager.PERMISSION_GRANTED) {
+ mActivePermissions.add(permission);
+ } else {
+ missingPermissions.add(permission);
+ }
+ }
+ return missingPermissions;
+ }
+
+ private void requestPermissions(Set<String> permissions) {
+ Log.d(TAG, "requesting additional permissions=" + permissions);
+
+ requestPermissions(permissions.toArray(new String[permissions.size()]),
+ KS_PERMISSIONS_REQUEST);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions,
+ int[] grantResults) {
+ Log.d(TAG, "onRequestPermissionsResult reqCode=" + requestCode);
+ if (KS_PERMISSIONS_REQUEST == requestCode) {
+ for (int i=0; i<permissions.length; i++) {
+ if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
+ mActivePermissions.add(permissions[i]);
+ }
+ }
+ initSensors();
+ }
+ }
+
+
+
+ private void refreshUi() {
+ String summaryString;
+ synchronized (this) {
+ List<String> summary = new ArrayList<>();
+ for (Integer i : supportedSensors) {
+ CarSensorEvent event = mEventMap.get(i);
+ switch (i) {
+ case CarSensorManager.SENSOR_TYPE_COMPASS:
+ summary.add(getCompassString(event));
+ break;
+ case CarSensorManager.SENSOR_TYPE_CAR_SPEED:
+ summary.add(getContext().getString(R.string.sensor_speed,
+ getTimestamp(event),
+ event == null ? mNaString : event.getCarSpeedData(null).carSpeed));
+ break;
+ case CarSensorManager.SENSOR_TYPE_RPM:
+ summary.add(getContext().getString(R.string.sensor_rpm,
+ getTimestamp(event),
+ event == null ? mNaString : event.getRpmData(null).rpm));
+ break;
+ case CarSensorManager.SENSOR_TYPE_ODOMETER:
+ summary.add(getContext().getString(R.string.sensor_odometer,
+ getTimestamp(event),
+ event == null ? mNaString : event.getOdometerData(null).kms));
+ break;
+ case CarSensorManager.SENSOR_TYPE_FUEL_LEVEL:
+ String level = mNaString;
+ String range = mNaString;
+ String lowFuelWarning = mNaString;
+ if (event != null) {
+ CarSensorEvent.FuelLevelData fuelData = event.getFuelLevelData(null);
+ level = fuelData.level == -1 ? level : String.valueOf(fuelData.level);
+ range = fuelData.range == -1 ? range : String.valueOf(fuelData.range);
+ lowFuelWarning = String.valueOf(fuelData.lowFuelWarning);
+ }
+ summary.add(getContext().getString(R.string.sensor_fuel_level,
+ getTimestamp(event), level, range, lowFuelWarning));
+ break;
+ case CarSensorManager.SENSOR_TYPE_PARKING_BRAKE:
+ summary.add(getContext().getString(R.string.sensor_parking_break,
+ getTimestamp(event),
+ event == null ? mNaString :
+ event.getParkingBrakeData(null).isEngaged));
+ break;
+ case CarSensorManager.SENSOR_TYPE_GEAR:
+ summary.add(getContext().getString(R.string.sensor_gear,
+ getTimestamp(event),
+ event == null ? mNaString : event.getGearData(null).gear));
+ break;
+ case CarSensorManager.SENSOR_TYPE_NIGHT:
+ summary.add(getContext().getString(R.string.sensor_night,
+ getTimestamp(event),
+ event == null ? mNaString : event.getNightData(null).isNightMode));
+ break;
+ case CarSensorManager.SENSOR_TYPE_LOCATION:
+ summary.add(getLocationString(event));
+ break;
+ case CarSensorManager.SENSOR_TYPE_DRIVING_STATUS:
+ String drivingStatus = mNaString;
+ String binDrivingStatus = mNaString;
+ if (event != null) {
+ CarSensorEvent.DrivingStatusData drivingStatusData =
+ event.getDrivingStatusData(null);
+ drivingStatus = String.valueOf(drivingStatusData.status);
+ binDrivingStatus = Integer.toBinaryString(drivingStatusData.status);
+ }
+ summary.add(getContext().getString(R.string.sensor_driving_status,
+ getTimestamp(event), drivingStatus, binDrivingStatus));
+ break;
+ case CarSensorManager.SENSOR_TYPE_ENVIRONMENT:
+ String temperature = mNaString;
+ String pressure = mNaString;
+ if (event != null) {
+ CarSensorEvent.EnvironmentData env = event.getEnvironmentData(null);
+ temperature = Float.isNaN(env.temperature) ? temperature :
+ String.valueOf(env.temperature);
+ pressure = Float.isNaN(env.pressure) ? pressure :
+ String.valueOf(env.pressure);
+ }
+ summary.add(getContext().getString(R.string.sensor_environment,
+ getTimestamp(event), temperature, pressure));
+ break;
+ case CarSensorManager.SENSOR_TYPE_ACCELEROMETER:
+ summary.add(getAccelerometerString(event));
+ break;
+ case CarSensorManager.SENSOR_TYPE_GPS_SATELLITE:
+ summary.add(getGpsSatteliteString(event));
+ break;
+ case CarSensorManager.SENSOR_TYPE_GYROSCOPE:
+ summary.add(getGyroscopeString(event));
+ default:
+ // Should never happen.
+ Log.w(TAG, "Unrecognized event type: " + i);
+ }
+ }
+ summaryString = TextUtils.join("\n", summary);
+ }
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSensorInfo.setText(summaryString);
+ }
+ });
+ }
+
+ private String getTimestamp(CarSensorEvent event) {
+ if (event == null) {
+ return mNaString;
+ }
+ return mDateFormat.format(new Date(event.timeStampNs / 1000L));
+ }
+
+ private String getCompassString(CarSensorEvent event) {
+ String bear = mNaString;
+ String pitch = mNaString;
+ String roll = mNaString;
+ if (event != null) {
+ CarSensorEvent.CompassData compass = event.getCompassData(null);
+ bear = Float.isNaN(compass.bearing) ? bear : String.valueOf(compass.bearing);
+ pitch = Float.isNaN(compass.pitch) ? pitch : String.valueOf(compass.pitch);
+ roll = Float.isNaN(compass.roll) ? roll : String.valueOf(compass.roll);
+ }
+ return getContext().getString(R.string.sensor_compass,
+ getTimestamp(event), bear, pitch, roll);
+ }
+
+ private String getGyroscopeString(CarSensorEvent event) {
+ String x = mNaString;
+ String y = mNaString;
+ String z = mNaString;
+ if (event != null) {
+ CarSensorEvent.GyroscopeData gyro = event.getGyroscopeData(null);
+ x = Float.isNaN(gyro.x) ? x : String.valueOf(gyro.x);
+ y = Float.isNaN(gyro.y) ? y : String.valueOf(gyro.y);
+ z = Float.isNaN(gyro.z) ? z : String.valueOf(gyro.z);
+ }
+ return getContext().getString(R.string.sensor_gyroscope,
+ getTimestamp(event), x, y, z);
+ }
+
+ private String getAccelerometerString(CarSensorEvent event) {
+ String x = mNaString;
+ String y = mNaString;
+ String z = mNaString;
+ if (event != null) {
+ CarSensorEvent.AccelerometerData gyro = event.getAccelerometerData(null);
+ x = Float.isNaN(gyro.x) ? x : String.valueOf(gyro.x);
+ y = Float.isNaN(gyro.y) ? y : String.valueOf(gyro.y);
+ z = Float.isNaN(gyro.z) ? z : String.valueOf(gyro.z);
+ }
+ return getContext().getString(R.string.sensor_accelerometer,
+ getTimestamp(event), x, y, z);
+ }
+
+ private String getLocationString(CarSensorEvent event) {
+ String lat = mNaString;
+ String lon = mNaString;
+ String accuracy = mNaString;
+ String alt = mNaString;
+ String speed = mNaString;
+ String bearing = mNaString;
+ if (event != null) {
+ Location location = event.getLocation(null);
+ lat = String.valueOf(location.getLatitude());
+ lon = String.valueOf(location.getLongitude());
+ accuracy = location.hasAccuracy() ? String.valueOf(location.getAccuracy()) : accuracy;
+ alt = location.hasAltitude() ? String.valueOf(location.getAltitude()) : alt;
+ speed = location.hasSpeed() ? String.valueOf(location.getSpeed()) : speed;
+ bearing = location.hasBearing() ? String.valueOf(location.getBearing()) : bearing;
+ }
+ return getContext().getString(R.string.sensor_location,
+ getTimestamp(event), lat, lon, accuracy, alt, speed, bearing);
+ }
+
+ private String getGpsSatteliteString(CarSensorEvent event) {
+ String inUse = mNaString;
+ String inView = mNaString;
+ String perSattelite = "";
+ if (event != null) {
+ CarSensorEvent.GpsSatelliteData gpsData = event.getGpsSatelliteData(null, true);
+ inUse = gpsData.numberInUse != -1 ? String.valueOf(gpsData.numberInUse) : inUse;
+ inView = gpsData.numberInView != -1 ? String.valueOf(gpsData.numberInView) : inView;
+ List<String> perSatteliteList = new ArrayList<>();
+ int num = gpsData.usedInFix.length;
+ for (int i=0; i<num; i++) {
+ perSatteliteList.add(getContext().getString(R.string.sensor_single_gps_satellite,
+ i+1, gpsData.usedInFix[i], gpsData.prn[i], gpsData.snr[i],
+ gpsData.azimuth[i], gpsData.elevation[i]));
+ }
+ perSattelite = TextUtils.join(", ", perSatteliteList);
+ }
+ return getContext().getString(R.string.sensor_gps,
+ getTimestamp(event), inView, inUse, perSattelite);
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeAdapter.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeAdapter.java
new file mode 100644
index 0000000..c66d242
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeAdapter.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.car.kitchensink.volume;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.google.android.car.kitchensink.R;
+import com.google.android.car.kitchensink.volume.VolumeTestFragment.VolumeInfo;
+
+
+public class VolumeAdapter extends ArrayAdapter<VolumeInfo> {
+
+ private final Context mContext;
+ private VolumeInfo[] mVolumeList;
+ private final int mLayoutResourceId;
+ private VolumeTestFragment mFragment;
+
+
+ public VolumeAdapter(Context c, int layoutResourceId, VolumeInfo[] locations,
+ VolumeTestFragment fragment) {
+ super(c, layoutResourceId, locations);
+ mFragment = fragment;
+ mContext = c;
+ this.mLayoutResourceId = layoutResourceId;
+ this.mVolumeList = locations;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ViewHolder vh = new ViewHolder();
+ if (convertView == null) {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ convertView = inflater.inflate(mLayoutResourceId, parent, false);
+ vh.id = (TextView) convertView.findViewById(R.id.stream_id);
+ vh.maxVolume = (TextView) convertView.findViewById(R.id.volume_limit);
+ vh.currentVolume = (TextView) convertView.findViewById(R.id.current_volume);
+ vh.logicalVolume = (TextView) convertView.findViewById(R.id.logical_volume);
+ vh.logicalMax = (TextView) convertView.findViewById(R.id.logical_max);
+ vh.upButton = (Button) convertView.findViewById(R.id.volume_up);
+ vh.downButton = (Button) convertView.findViewById(R.id.volume_down);
+ vh.requestButton = (Button) convertView.findViewById(R.id.request);
+ convertView.setTag(vh);
+ } else {
+ vh = (ViewHolder) convertView.getTag();
+ }
+ if (mVolumeList[position] != null) {
+ vh.id.setText(mVolumeList[position].mId);
+ vh.maxVolume.setText(String.valueOf(mVolumeList[position].mMax));
+ vh.currentVolume.setText(String.valueOf(mVolumeList[position].mCurrent));
+ vh.logicalVolume.setText(String.valueOf(mVolumeList[position].mLogicalCurrent));
+ vh.logicalMax.setText(String.valueOf(mVolumeList[position].mLogicalMax));
+ int color = mVolumeList[position].mHasFocus ? Color.GREEN : Color.GRAY;
+ vh.requestButton.setBackgroundColor(color);
+ if (position == 0) {
+ vh.upButton.setVisibility(View.INVISIBLE);
+ vh.downButton.setVisibility(View.INVISIBLE);
+ vh.requestButton.setVisibility(View.INVISIBLE);
+ } else {
+ vh.upButton.setVisibility(View.VISIBLE);
+ vh.downButton.setVisibility(View.VISIBLE);
+ vh.requestButton.setVisibility(View.VISIBLE);
+ vh.upButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mFragment.adjustVolumeByOne(mVolumeList[position].logicalStream, true);
+ }
+ });
+
+ vh.downButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mFragment.adjustVolumeByOne(mVolumeList[position].logicalStream, false);
+ }
+ });
+
+ vh.requestButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mFragment.requestFocus(mVolumeList[position].logicalStream);
+ }
+ });
+ }
+ }
+ return convertView;
+ }
+
+ @Override
+ public int getCount() {
+ return mVolumeList.length;
+ }
+
+
+ public void refreshVolumes(VolumeInfo[] volumes) {
+ mVolumeList = volumes;
+ notifyDataSetChanged();
+ }
+
+ static class ViewHolder {
+ TextView id;
+ TextView maxVolume;
+ TextView currentVolume;
+ TextView logicalMax;
+ TextView logicalVolume;
+ Button upButton;
+ Button downButton;
+ Button requestButton;
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeTestFragment.java
new file mode 100644
index 0000000..4655f3c
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/volume/VolumeTestFragment.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.car.kitchensink.volume;
+
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.media.CarAudioManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.media.AudioManager;
+import android.media.IVolumeController;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.util.SparseIntArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ListView;
+
+import com.google.android.car.kitchensink.CarEmulator;
+import com.google.android.car.kitchensink.R;
+
+public class VolumeTestFragment extends Fragment{
+ private static final String TAG = "CarVolumeTest";
+ private static final int MSG_VOLUME_CHANGED = 0;
+ private static final int MSG_REQUEST_FOCUS = 1;
+ private static final int MSG_FOCUS_CHANGED= 2;
+
+ private ListView mVolumeList;
+ private Button mRefreshButton;
+ private AudioManager mAudioManager;
+ private VolumeAdapter mAdapter;
+
+ private CarAudioManager mCarAudioManager;
+ private Car mCar;
+ private CarEmulator mCarEmulator;
+
+ private Button mVolumeUp;
+ private Button mVolumeDown;
+
+ private final VolumeController mVolumeController = new VolumeController();
+ private final Handler mHandler = new VolumeHandler();
+
+ private class VolumeHandler extends Handler {
+ private AudioFocusListener mFocusListener;
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_VOLUME_CHANGED:
+ initVolumeInfo();
+ break;
+ case MSG_REQUEST_FOCUS:
+ int stream = msg.arg1;
+ if (mFocusListener != null) {
+ mAudioManager.abandonAudioFocus(mFocusListener);
+ mVolumeInfos[mStreamIndexMap.get(stream)].mHasFocus = false;
+ mAdapter.notifyDataSetChanged();
+ }
+
+ mFocusListener = new AudioFocusListener(stream);
+ mAudioManager.requestAudioFocus(mFocusListener, stream,
+ AudioManager.AUDIOFOCUS_GAIN);
+ break;
+ case MSG_FOCUS_CHANGED:
+ int focusStream = msg.arg1;
+ mVolumeInfos[mStreamIndexMap.get(focusStream)].mHasFocus = true;
+ mAdapter.refreshVolumes(mVolumeInfos);
+ break;
+
+ }
+ }
+ }
+
+ private VolumeInfo[] mVolumeInfos = new VolumeInfo[LOGICAL_STREAMS.length + 1];
+ private SparseIntArray mStreamIndexMap = new SparseIntArray(LOGICAL_STREAMS.length);
+
+ private class AudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
+ private final int mStream;
+ public AudioFocusListener(int stream) {
+ mStream = stream;
+ }
+ @Override
+ public void onAudioFocusChange(int focusChange) {
+ if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_FOCUS_CHANGED, mStream, 0));
+ } else {
+ Log.e(TAG, "Audio focus request failed");
+ }
+ }
+ }
+
+ public static class VolumeInfo {
+ public int logicalStream;
+ public String mId;
+ public String mMax;
+ public String mCurrent;
+ public String mLogicalMax;
+ public String mLogicalCurrent;
+ public boolean mHasFocus;
+ }
+
+ private static final int LOGICAL_STREAMS[] = {
+ AudioManager.STREAM_MUSIC,
+ AudioManager.STREAM_ALARM,
+ AudioManager.STREAM_NOTIFICATION,
+ AudioManager.STREAM_RING,
+ AudioManager.STREAM_VOICE_CALL,
+ AudioManager.STREAM_SYSTEM
+ // AudioManager.STREAM_DTMF,
+ };
+
+ private static String streamToName (int stream) {
+ switch (stream) {
+ case AudioManager.STREAM_ALARM: return "Alarm";
+ case AudioManager.STREAM_MUSIC: return "Music";
+ case AudioManager.STREAM_NOTIFICATION: return "Notification";
+ case AudioManager.STREAM_RING: return "Ring";
+ case AudioManager.STREAM_VOICE_CALL: return "Call";
+ case AudioManager.STREAM_SYSTEM: return "System";
+ default: return "Unknown";
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.volume_test, container, false);
+
+ mVolumeList = (ListView) v.findViewById(R.id.volume_list);
+ mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+
+ mRefreshButton = (Button) v.findViewById(R.id.refresh);
+ mAdapter = new VolumeAdapter(getContext(), R.layout.volume_item, mVolumeInfos, this);
+ mVolumeList.setAdapter(mAdapter);
+
+ mRefreshButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ initVolumeInfo();
+ }
+ });
+
+ mCar = Car.createCar(getContext(), new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mCarEmulator = new CarEmulator(mCar);
+ mCarEmulator.start();
+ try {
+ mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
+ initVolumeInfo();
+ mCarAudioManager.setVolumeController(mVolumeController);
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car not connected!");
+
+ }
+ }
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
+ }, Looper.getMainLooper());
+ mCar.connect();
+ mVolumeUp = (Button) v.findViewById(R.id.volume_up);
+ mVolumeDown = (Button) v.findViewById(R.id.volume_down);
+
+ mVolumeUp.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mCarEmulator.injectVolumeKey(true);
+ }
+ });
+
+ mVolumeDown.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mCarEmulator.injectVolumeKey(false);
+ }
+ });
+
+ return v;
+ }
+
+ public void adjustVolumeByOne(int logicalStream, boolean up) {
+ if (mCarAudioManager == null) {
+ Log.e(TAG, "CarAudioManager is null");
+ return;
+ }
+ int current = 0;
+ try {
+ current = mCarAudioManager.getStreamVolume(logicalStream);
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "car not connected", e);
+ }
+ setStreamVolume(logicalStream, current + (up ? 1 : -1));
+ }
+
+ public void requestFocus(int logicalStream) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_REQUEST_FOCUS, logicalStream));
+ }
+
+ public void setStreamVolume(int logicalStream, int volume) {
+ if (mCarAudioManager == null) {
+ Log.e(TAG, "CarAudioManager is null");
+ return;
+ }
+ try {
+ mCarAudioManager.setStreamVolume(logicalStream, volume, 0);
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "car not connected", e);
+ }
+
+ Log.d(TAG, "Set stream " + logicalStream + " volume " + volume);
+ }
+
+
+ private void initVolumeInfo() {
+ if (mVolumeInfos[0] == null) {
+ mVolumeInfos[0] = new VolumeInfo();
+ mVolumeInfos[0].mId = "Stream";
+ mVolumeInfos[0].mCurrent = "Current";
+ mVolumeInfos[0].mMax = "Max";
+ mVolumeInfos[0].mLogicalMax = "Android_Max";
+ mVolumeInfos[0].mLogicalCurrent = "Android_Current";
+
+ }
+ int i = 1;
+ for (int stream : LOGICAL_STREAMS) {
+ if (mVolumeInfos[i] == null) {
+ mVolumeInfos[i] = new VolumeInfo();
+ }
+ mVolumeInfos[i].logicalStream = stream;
+ mStreamIndexMap.put(stream, i);
+ mVolumeInfos[i].mId = streamToName(stream);
+
+ int current = 0;
+ int max = 0;
+ try {
+ current = mCarAudioManager.getStreamVolume(stream);
+ max = mCarAudioManager.getStreamMaxVolume(stream);
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "car not connected", e);
+ }
+
+ mVolumeInfos[i].mCurrent = String.valueOf(current);
+ mVolumeInfos[i].mMax = String.valueOf(max);
+
+ mVolumeInfos[i].mLogicalMax = String.valueOf(mAudioManager.getStreamMaxVolume(stream));
+ mVolumeInfos[i].mLogicalCurrent = String.valueOf(mAudioManager.getStreamVolume(stream));
+
+ Log.d(TAG, stream + " max: " + mVolumeInfos[i].mMax + " current: "
+ + mVolumeInfos[i].mCurrent);
+ i++;
+ }
+ mAdapter.refreshVolumes(mVolumeInfos);
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mCar != null) {
+ mCar.disconnect();
+ }
+ super.onDestroy();
+ }
+
+ private class VolumeController extends IVolumeController.Stub {
+
+ @Override
+ public void displaySafeVolumeWarning(int flags) throws RemoteException {}
+
+ @Override
+ public void volumeChanged(int streamType, int flags) throws RemoteException {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_VOLUME_CHANGED, streamType));
+ }
+
+ @Override
+ public void masterMuteChanged(int flags) throws RemoteException {}
+
+ @Override
+ public void setLayoutDirection(int layoutDirection) throws RemoteException {
+ }
+
+ @Override
+ public void dismiss() throws RemoteException {
+ }
+ }
+}
diff --git a/tests/carservice_test/src/com/android/car/test/AudioRoutingPolicyTest.java b/tests/carservice_test/src/com/android/car/test/AudioRoutingPolicyTest.java
index 72e97e0..371a9a5 100644
--- a/tests/carservice_test/src/com/android/car/test/AudioRoutingPolicyTest.java
+++ b/tests/carservice_test/src/com/android/car/test/AudioRoutingPolicyTest.java
@@ -117,6 +117,7 @@
VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_ALARM_FLAG |
VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_CALL_FLAG |
VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG |
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG |
VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NAVIGATION_FLAG |
VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NOTIFICATION_FLAG |
VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_UNKNOWN_FLAG |
@@ -138,6 +139,7 @@
assertEquals(
VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_CALL_FLAG |
VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_MUSIC_FLAG |
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG |
VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_UNKNOWN_FLAG,
v.getInt32Values(
VehicleAudioRoutingPolicyIndex.VEHICLE_AUDIO_ROUTING_POLICY_INDEX_CONTEXTS)
diff --git a/tests/carservice_test/src/com/android/car/test/CarAudioFocusTest.java b/tests/carservice_test/src/com/android/car/test/CarAudioFocusTest.java
index 180b3ef..29a020e 100644
--- a/tests/carservice_test/src/com/android/car/test/CarAudioFocusTest.java
+++ b/tests/carservice_test/src/com/android/car/test/CarAudioFocusTest.java
@@ -361,8 +361,7 @@
assertEquals(0, request[1]);
assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG,
request[2]);
- // no android side context for radio
- assertEquals(0, request[3]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG, request[3]);
mAudioFocusPropertyHandler.sendAudioFocusState(
VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
0,
@@ -382,7 +381,8 @@
assertEquals(0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM1, request[1]);
assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG,
request[2]);
- assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NAVIGATION_FLAG, request[3]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_NAVIGATION_FLAG |
+ VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG, request[3]);
mAudioFocusPropertyHandler.sendAudioFocusState(
VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
0x1 << VehicleAudioStream.VEHICLE_AUDIO_STREAM1,
@@ -396,7 +396,7 @@
assertEquals(0, request[1]);
assertEquals(VehicleAudioExtFocusFlag.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG,
request[2]);
- assertEquals(0, request[3]);
+ assertEquals(VehicleAudioContextFlag.VEHICLE_AUDIO_CONTEXT_RADIO_FLAG, request[3]);
mAudioFocusPropertyHandler.sendAudioFocusState(
VehicleAudioFocusState.VEHICLE_AUDIO_FOCUS_STATE_GAIN,
0,
diff --git a/tests/carservice_test/src/com/android/car/test/CarHvacManagerTest.java b/tests/carservice_test/src/com/android/car/test/CarHvacManagerTest.java
index ef16acd..0b28e8b 100644
--- a/tests/carservice_test/src/com/android/car/test/CarHvacManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/test/CarHvacManagerTest.java
@@ -189,7 +189,9 @@
@Override
public synchronized VehiclePropValue onPropertyGet(VehiclePropValue value) {
- return mMap.get(value.getProp());
+ VehiclePropValue currentValue = mMap.get(value.getProp());
+ // VNS will call getProperty method when subscribe is called, just return empty value.
+ return currentValue != null ? currentValue : value;
}
@Override
diff --git a/tests/carservice_test/src/com/android/car/test/CarRadioManagerTest.java b/tests/carservice_test/src/com/android/car/test/CarRadioManagerTest.java
index 36a8cfc..70c5474 100644
--- a/tests/carservice_test/src/com/android/car/test/CarRadioManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/test/CarRadioManagerTest.java
@@ -89,7 +89,11 @@
value.getInt32ValuesList().toArray(valueList);
// Get the actual preset.
- if (valueList[0] < 1 || valueList[0] > NUM_PRESETS) return null;
+ if (valueList[0] < 1 || valueList[0] > NUM_PRESETS) {
+ // VNS will call getProperty method when subscribe is called, just return an empty
+ // value.
+ return value;
+ }
CarRadioPreset preset = mRadioPresets.get(valueList[0]);
VehiclePropValue v =
VehiclePropValueUtil.createIntVectorValue(
@@ -171,9 +175,9 @@
}
public void testSubscribe() throws Exception {
- EventListener l = new EventListener();
+ EventListener listener = new EventListener();
assertEquals("Lock should be freed by now.", 0, mAvailable.availablePermits());
- mCarRadioManager.registerListener(l);
+ mCarRadioManager.registerListener(listener);
// Wait for acquire to be available again, fail if timeout.
boolean success = mAvailable.tryAcquire(5L, TimeUnit.SECONDS);
diff --git a/tests/vehicle_hal_test/Android.mk b/tests/vehicle_hal_test/Android.mk
new file mode 100644
index 0000000..77bc6f8
--- /dev/null
+++ b/tests/vehicle_hal_test/Android.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := AndroidVehicleHalTests
+
+# for system|priviledged permission.
+LOCAL_CERTIFICATE := platform
+
+LOCAL_MODULE_TAGS := tests
+
+# When built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_STATIC_JAVA_LIBRARIES := libvehiclenetwork-java
+
+LOCAL_JAVA_LIBRARIES := android.car android.test.runner
+
+include $(BUILD_PACKAGE)
diff --git a/tests/vehicle_hal_test/AndroidManifest.xml b/tests/vehicle_hal_test/AndroidManifest.xml
new file mode 100644
index 0000000..5fbb62d
--- /dev/null
+++ b/tests/vehicle_hal_test/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ package="com.android.car.vehiclenetwork.haltest"
+ android:sharedUserId="android.uid.system" >
+
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.car.vehiclenetwork.haltest"
+ android:label="Tests for vehicle hal using vehicle network service"/>
+
+ <application android:label="VehicleHalTest">
+ <uses-library android:name="android.test.runner" />
+ <activity android:name=".TestCarProxyActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/vehicle_hal_test/src/com/android/car/vehiclenetwork/haltest/HvacVnsHalTest.java b/tests/vehicle_hal_test/src/com/android/car/vehiclenetwork/haltest/HvacVnsHalTest.java
new file mode 100644
index 0000000..6111746
--- /dev/null
+++ b/tests/vehicle_hal_test/src/com/android/car/vehiclenetwork/haltest/HvacVnsHalTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.vehiclenetwork.haltest;
+
+import static com.android.car.vehiclenetwork.VehicleNetworkConsts.VEHICLE_PROPERTY_HVAC_AC_ON;
+import static com.android.car.vehiclenetwork.VehicleNetworkConsts.VEHICLE_PROPERTY_HVAC_DEFROSTER;
+import static com.android.car.vehiclenetwork.VehicleNetworkConsts.VEHICLE_PROPERTY_HVAC_FAN_DIRECTION;
+import static com.android.car.vehiclenetwork.VehicleNetworkConsts.VEHICLE_PROPERTY_HVAC_FAN_SPEED;
+import static com.android.car.vehiclenetwork.VehicleNetworkConsts.VEHICLE_PROPERTY_HVAC_MAX_AC_ON;
+import static com.android.car.vehiclenetwork.VehicleNetworkConsts.VEHICLE_PROPERTY_HVAC_RECIRC_ON;
+
+import android.util.Log;
+
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleHvacFanDirection;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehiclePropAccess;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleValueType;
+import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfig;
+
+/**
+ * Test HVAC vehicle HAL using vehicle network service.
+ */
+public class HvacVnsHalTest extends VnsHalBaseTestCase {
+
+ public void testAcProperty() throws Exception {
+ int propertyId = VEHICLE_PROPERTY_HVAC_AC_ON;
+ if (!isPropertyAvailable(propertyId)) {
+ return;
+ }
+
+ VehiclePropConfig config = mConfigsMap.get(propertyId);
+ assertEquals(VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_BOOLEAN, config.getValueType());
+ assertEquals(VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE, config.getAccess());
+
+ int zone = getFirstZoneForProperty(propertyId);
+ setPropertyAndVerify(propertyId, zone, false);
+
+ mVehicleNetwork.subscribe(propertyId, 0, 0);
+ mListener.reset();
+ mListener.addExpectedValues(propertyId, zone, true);
+ setPropertyAndVerify(propertyId, zone, true);
+ mListener.waitAndVerifyValues();
+ }
+
+ public void testRecirculateProperty() {
+ int propertyId = VEHICLE_PROPERTY_HVAC_RECIRC_ON;
+ if (!isPropertyAvailable(propertyId)) {
+ return;
+ }
+ VehiclePropConfig config = mConfigsMap.get(propertyId);
+ assertEquals(VehicleValueType.VEHICLE_VALUE_TYPE_BOOLEAN, config.getValueType());
+ assertEquals(VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE, config.getAccess());
+
+ // Verify subsequent calls ends-up with right value.
+ mVehicleNetwork.setBooleanProperty(propertyId, false);
+ mVehicleNetwork.setBooleanProperty(propertyId, true);
+ mVehicleNetwork.setBooleanProperty(propertyId, false);
+ mVehicleNetwork.setBooleanProperty(propertyId, true);
+ verifyValue(propertyId, true);
+
+ // Verify subsequent calls ends-up with right value.
+ mVehicleNetwork.setBooleanProperty(propertyId, false);
+ mVehicleNetwork.setBooleanProperty(propertyId, true);
+ mVehicleNetwork.setBooleanProperty(propertyId, false);
+ verifyValue(propertyId, false);
+
+ setPropertyAndVerify(propertyId, false);
+ setPropertyAndVerify(propertyId, true);
+ setPropertyAndVerify(propertyId, false);
+ }
+
+ public void testMaxAcProperty() throws Exception {
+ if (!isPropertyAvailable(
+ VEHICLE_PROPERTY_HVAC_MAX_AC_ON,
+ VEHICLE_PROPERTY_HVAC_AC_ON,
+ VEHICLE_PROPERTY_HVAC_RECIRC_ON)) {
+ return;
+ }
+ VehiclePropConfig config = mConfigsMap.get(VEHICLE_PROPERTY_HVAC_MAX_AC_ON);
+ assertEquals(VehicleValueType.VEHICLE_VALUE_TYPE_BOOLEAN, config.getValueType());
+ assertEquals(VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE, config.getAccess());
+
+ int acZone = getFirstZoneForProperty(VEHICLE_PROPERTY_HVAC_AC_ON);
+
+ // Turn off related properties.
+ setPropertyAndVerify(VEHICLE_PROPERTY_HVAC_MAX_AC_ON, false);
+ setPropertyAndVerify(VEHICLE_PROPERTY_HVAC_AC_ON, acZone, false);
+ setPropertyAndVerify(VEHICLE_PROPERTY_HVAC_RECIRC_ON, false);
+
+ // Now turning max A/C and verify that other related HVAC props turned on.
+ mVehicleNetwork.subscribe(VEHICLE_PROPERTY_HVAC_AC_ON, 0f, acZone);
+ mVehicleNetwork.subscribe(VEHICLE_PROPERTY_HVAC_RECIRC_ON, 0f);
+ mListener.reset();
+ mListener.addExpectedValues(VEHICLE_PROPERTY_HVAC_AC_ON, acZone, true);
+ mListener.addExpectedValues(VEHICLE_PROPERTY_HVAC_RECIRC_ON, 0, true);
+ setPropertyAndVerify(VEHICLE_PROPERTY_HVAC_MAX_AC_ON, true);
+ verifyValue(VEHICLE_PROPERTY_HVAC_AC_ON, acZone, true);
+ verifyValue(VEHICLE_PROPERTY_HVAC_RECIRC_ON, true);
+ mListener.waitAndVerifyValues();
+
+ // When max A/C turned off, A/C should remain to be turned on, but circulation should has
+ // the value it had before turning max A/C on, which if OFF in this case.
+ setPropertyAndVerify(VEHICLE_PROPERTY_HVAC_MAX_AC_ON, false);
+ verifyValue(VEHICLE_PROPERTY_HVAC_AC_ON, acZone, true);
+ verifyValue(VEHICLE_PROPERTY_HVAC_RECIRC_ON, false);
+ }
+
+ public void testDefroster() {
+ final int propertyId = VEHICLE_PROPERTY_HVAC_DEFROSTER;
+ if (!isPropertyAvailable(propertyId)) {
+ return;
+ }
+
+ VehiclePropConfig config = mConfigsMap.get(propertyId);
+ assertEquals(VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_BOOLEAN, config.getValueType());
+ assertEquals(VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE, config.getAccess());
+
+ iterateOnZones(config, (message, zone, minValue, maxValue) -> {
+ Log.i(TAG, "testDefroster, " + message);
+ setPropertyAndVerify(propertyId, zone, false);
+ setPropertyAndVerify(propertyId, zone, true);
+ });
+ }
+
+ public void testFanSpeed() throws Exception {
+ if (!isPropertyAvailable(VEHICLE_PROPERTY_HVAC_FAN_SPEED)) {
+ return;
+ }
+
+ VehiclePropConfig config = mConfigsMap.get(VEHICLE_PROPERTY_HVAC_FAN_SPEED);
+ assertEquals(VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32, config.getValueType());
+ assertEquals(VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE, config.getAccess());
+
+ verifyIntZonedProperty(VEHICLE_PROPERTY_HVAC_FAN_SPEED);
+ }
+
+ public void testFanDirection() throws Exception {
+ final int propertyId = VEHICLE_PROPERTY_HVAC_FAN_DIRECTION;
+
+ if (!isPropertyAvailable(propertyId)) {
+ return;
+ }
+
+ VehiclePropConfig config = mConfigsMap.get(propertyId);
+ assertEquals(VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32, config.getValueType());
+ assertEquals(VehiclePropAccess.VEHICLE_PROP_ACCESS_READ_WRITE, config.getAccess());
+
+ // Assert setting edge-case values.
+ iterateOnZones(config, (message, zone, minValue, maxValue) -> {
+ setPropertyAndVerify(message, propertyId, zone, minValue);
+ setPropertyAndVerify(message, propertyId, zone, maxValue);
+ });
+
+ iterateOnZones(config, ((message, zone, minValue, maxValue)
+ -> setPropertyAndVerify(message, propertyId, zone,
+ VehicleHvacFanDirection.VEHICLE_HVAC_FAN_DIRECTION_DEFROST_AND_FLOOR)));
+ }
+}
diff --git a/tests/vehicle_hal_test/src/com/android/car/vehiclenetwork/haltest/RecordingVehicleNetworkListener.java b/tests/vehicle_hal_test/src/com/android/car/vehiclenetwork/haltest/RecordingVehicleNetworkListener.java
new file mode 100644
index 0000000..c10dbd0
--- /dev/null
+++ b/tests/vehicle_hal_test/src/com/android/car/vehiclenetwork/haltest/RecordingVehicleNetworkListener.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.vehiclenetwork.haltest;
+
+import static java.lang.Integer.toHexString;
+import static junit.framework.Assert.assertTrue;
+
+import android.util.Log;
+
+import com.android.car.vehiclenetwork.VehicleNetwork.VehicleNetworkListener;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleValueType;
+import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValue;
+import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropValues;
+import com.android.car.vehiclenetwork.VehicleNetworkProtoUtil;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * This class must be used in testing environment only. Here's an example of usage:
+ * <ul>
+ * <li>listener.reset();
+ * <li>listener.addExpectedValues(myPropertyId, myZone, 10, 20, 30)
+ * <li>... set values through VehicleNetworkService ...
+ * <li>listener.waitAndVerifyValues()
+ *</ul>
+ *
+ */
+class RecordingVehicleNetworkListener implements VehicleNetworkListener {
+
+ private final static String TAG = VnsHalBaseTestCase.TAG;
+ private final static int EVENTS_WAIT_TIMEOUT_MS = 2000;
+
+ private final Set<NormalizedValue> mExpectedValues = new HashSet<>();
+ // Using Set here instead of List as we probably shouldn't assert the order event was received.
+ private final Set<NormalizedValue> mRecordedEvents = new HashSet<>();
+
+ synchronized void reset() {
+ mExpectedValues.clear();
+ mRecordedEvents.clear();
+ }
+
+ void addExpectedValues(int propertyId, int zone, Object... values) {
+ for (Object value : values) {
+ mExpectedValues.add(NormalizedValue.createFor(propertyId, zone, value));
+ }
+ }
+
+ /**
+ * Waits for events to come for #EVENTS_WAIT_TIMEOUT_MS milliseconds and asserts that recorded
+ * values match with expected.
+ * */
+ synchronized void waitAndVerifyValues() throws InterruptedException {
+ long currentTime = System.currentTimeMillis();
+ long deadline = currentTime + EVENTS_WAIT_TIMEOUT_MS;
+ while (currentTime < deadline && !isExpectedMatchedRecorded()) {
+ wait(deadline - currentTime);
+ currentTime = System.currentTimeMillis();
+ }
+ assertTrue("Expected values: " + Arrays.toString(mExpectedValues.toArray())
+ + " doesn't match recorded: " + Arrays.toString(mRecordedEvents.toArray()),
+ isExpectedMatchedRecorded());
+ }
+
+ private boolean isExpectedMatchedRecorded() {
+ for (NormalizedValue expectedValue : mExpectedValues) {
+ if (!mRecordedEvents.contains(expectedValue)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void onVehicleNetworkEvents(VehiclePropValues values) {
+ for (VehiclePropValue value : values.getValuesList()) {
+ Log.d(TAG, "onVehicleNetworkEvents, value: "
+ + VehicleNetworkProtoUtil.VehiclePropValueToString(value));
+
+ synchronized (this) {
+ mRecordedEvents.add(NormalizedValue.createFor(value));
+ notifyAll();
+ }
+ }
+ }
+
+ @Override
+ public void onHalError(int errorCode, int property, int operation) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void onHalRestart(boolean inMocking) {
+ // TODO Auto-generated method stub
+ }
+
+ // To be used to compare expected vs recorded values.
+ private static class NormalizedValue {
+ private final int propertyId;
+ private final int zone;
+ private final List<Object> value;
+
+ static NormalizedValue createFor(VehiclePropValue value) {
+ return new NormalizedValue(value.getProp(), value.getZone(), getObjectValue(value));
+ }
+
+ static NormalizedValue createFor(int propertyId, int zone, Object value) {
+ return new NormalizedValue(propertyId, zone, wrapSingleObjectToList(value));
+ }
+
+ // Do not call this ctor directly, use appropriate factory methods to create an object.
+ private NormalizedValue(int propertyId, int zone, List<Object> value) {
+ this.propertyId = propertyId;
+ this.zone = zone;
+ this.value = value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ NormalizedValue propValue = (NormalizedValue) o;
+ return propertyId == propValue.propertyId &&
+ zone == propValue.zone &&
+ Objects.equals(value, propValue.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(propertyId, zone, value);
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + " { "
+ + "prop: 0x" + toHexString(propertyId)
+ + ", zone: 0x" + toHexString(zone)
+ + ", value: " + Arrays.toString(value.toArray())
+ + " }";
+ }
+
+ private static List<Object> wrapSingleObjectToList(Object value) {
+ if (value instanceof Integer
+ || value instanceof Float) {
+ List<Object> list = new ArrayList<>(1);
+ list.add(value);
+ return list;
+ } else if (value instanceof Boolean) {
+ List<Object> list = new ArrayList<>(1);
+ list.add((Boolean)value ? 1 : 0);
+ return list;
+ } else if (value instanceof Collection<?>) {
+ return new ArrayList<>((Collection<?>) value);
+ } else {
+ throw new IllegalArgumentException("Unexpected type: " + value);
+ }
+ }
+
+ // Converts any VehiclePropValue to either ArrayList<Integer> or ArrayList<Float>
+ private static List<Object> getObjectValue(VehiclePropValue val) {
+ switch (val.getValueType()) {
+ case VehicleValueType.VEHICLE_VALUE_TYPE_BOOLEAN:
+ case VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_BOOLEAN:
+ case VehicleValueType.VEHICLE_VALUE_TYPE_INT32:
+ case VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC2:
+ case VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC3:
+ case VehicleValueType.VEHICLE_VALUE_TYPE_INT32_VEC4:
+ case VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32:
+ case VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32_VEC2:
+ case VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32_VEC3:
+ case VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_INT32_VEC4:
+ return new ArrayList<>(val.getInt32ValuesList());
+ case VehicleValueType.VEHICLE_VALUE_TYPE_FLOAT:
+ case VehicleValueType.VEHICLE_VALUE_TYPE_FLOAT_VEC2:
+ case VehicleValueType.VEHICLE_VALUE_TYPE_FLOAT_VEC3:
+ case VehicleValueType.VEHICLE_VALUE_TYPE_FLOAT_VEC4:
+ case VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_FLOAT:
+ case VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_FLOAT_VEC2:
+ case VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_FLOAT_VEC3:
+ case VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_FLOAT_VEC4:
+ return new ArrayList<>(val.getFloatValuesList());
+ default:
+ throw new IllegalArgumentException("Unexpected type: " + val.getValueType());
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/vehicle_hal_test/src/com/android/car/vehiclenetwork/haltest/VnsHalBaseTestCase.java b/tests/vehicle_hal_test/src/com/android/car/vehiclenetwork/haltest/VnsHalBaseTestCase.java
new file mode 100644
index 0000000..adcc336
--- /dev/null
+++ b/tests/vehicle_hal_test/src/com/android/car/vehiclenetwork/haltest/VnsHalBaseTestCase.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.vehiclenetwork.haltest;
+
+import static java.lang.Integer.toHexString;
+
+import android.car.VehicleZoneUtil;
+import android.os.HandlerThread;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.car.vehiclenetwork.VehicleNetwork;
+import com.android.car.vehiclenetwork.VehicleNetworkConsts.VehicleValueType;
+import com.android.car.vehiclenetwork.VehicleNetworkProto.VehiclePropConfig;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Base class for testing vehicle HAL using vehicle network service (VNS).
+ */
+public class VnsHalBaseTestCase extends AndroidTestCase {
+
+ protected static final String TAG = "VnsHalTest";
+
+ protected static final int PROP_TIMEOUT_MS = 5000;
+ protected static final int NO_ZONE = -1;
+
+ private final HandlerThread mHandlerThread =
+ new HandlerThread(VnsHalBaseTestCase.class.getSimpleName());
+ protected VehicleNetwork mVehicleNetwork;
+ protected RecordingVehicleNetworkListener mListener;
+
+ protected Map<Integer, VehiclePropConfig> mConfigsMap;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mHandlerThread.start();
+ mListener = new RecordingVehicleNetworkListener();
+ mVehicleNetwork = VehicleNetwork.createVehicleNetwork(mListener,
+ mHandlerThread.getLooper());
+ setupConfigsMap();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mHandlerThread.quit();
+ }
+
+ private void setupConfigsMap() {
+ mConfigsMap = new HashMap<>();
+ for (VehiclePropConfig config : mVehicleNetwork.listProperties().getConfigsList()) {
+ mConfigsMap.put(config.getProp(), config);
+ }
+ assertTrue(mConfigsMap.size() > 0);
+ }
+
+ protected boolean isPropertyAvailable(int... properties) {
+ assertTrue(properties.length > 0);
+
+ for (int propertyId : properties) {
+ if (!mConfigsMap.containsKey(propertyId)) {
+ // Property is not supported by vehicle HAL, nothing to test.
+ Log.w(TAG, "Property: 0x" + Integer.toHexString(propertyId)
+ + " is not available, ignoring...");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ protected void setPropertyAndVerify(int propertyId, boolean switchOn) {
+ setPropertyAndVerify(propertyId, NO_ZONE, switchOn);
+ }
+
+ protected void setPropertyAndVerify(int propertyId, int zone, boolean switchOn) {
+ int val = switchOn ? 1 : 0;
+
+ if (zone == NO_ZONE) {
+ mVehicleNetwork.setBooleanProperty(propertyId, switchOn);
+ } else {
+ mVehicleNetwork.setZonedBooleanProperty(propertyId, zone, switchOn);
+ }
+ verifyValue(propertyId, zone, val);
+ }
+
+ protected void setPropertyAndVerify(String message, int propertyId, int zone, int value) {
+ if (zone == NO_ZONE) {
+ mVehicleNetwork.setIntProperty(propertyId, value);
+ } else {
+ mVehicleNetwork.setZonedIntProperty(propertyId, zone, value);
+ }
+ verifyValue(message, propertyId, zone, value);
+ }
+
+ protected void verifyValue(int propertyId, boolean val) {
+ verifyValue(propertyId, NO_ZONE, val);
+ }
+
+ protected void verifyValue(int propertyId, int zone, boolean val) {
+ int intVal = val ? 1 : 0;
+ assertEquals(intVal, waitForIntValue(propertyId, zone, intVal));
+ }
+
+ protected void verifyValue(int propertyId, int zone, int val) {
+ assertEquals(val, waitForIntValue(propertyId, zone, val));
+ }
+
+ protected void verifyValue(String message, int propertyId, int zone, int val) {
+ assertEquals(message, val, waitForIntValue(propertyId, zone, val));
+ }
+
+ protected int getFirstZoneForProperty(int propertyId) {
+ return VehicleZoneUtil.getFirstZone(mConfigsMap.get(propertyId).getZones());
+ }
+
+ protected void verifyIntZonedProperty(int propertyId) throws InterruptedException {
+ if (!isPropertyAvailable(propertyId)) {
+ return;
+ }
+
+ VehiclePropConfig config = mConfigsMap.get(propertyId);
+
+ // Assert setting edge-case values.
+ iterateOnZones(config, (message, zone, minValue, maxValue) -> {
+ setPropertyAndVerify(message, propertyId, zone, minValue);
+ setPropertyAndVerify(message, propertyId, zone, maxValue);
+ });
+
+ // Setting out of the range values.
+ iterateOnZones(config, ((message, zone, minValue, maxValue) -> {
+ mVehicleNetwork.setZonedIntProperty(propertyId, zone, minValue - 1);
+ verifyValue(message, propertyId, zone, minValue);
+
+ mVehicleNetwork.setZonedIntProperty(propertyId, zone, maxValue + 20);
+ verifyValue(message, propertyId, zone, maxValue);
+ }));
+
+ // Verify that subsequent SET calls will result in correct value at the end.
+ mVehicleNetwork.subscribe(propertyId, 0f, config.getZones());
+ int zone = VehicleZoneUtil.getFirstZone(config.getZones());
+ int minValue = config.getInt32Mins(0);
+ int maxValue = config.getInt32Maxs(0);
+ int finalValue = (minValue + maxValue) / 2;
+ mListener.reset();
+ // We can only expect to see finalValue in the events as vehicle HAL may batch
+ // set commands and use only last value.
+ mListener.addExpectedValues(propertyId, zone, finalValue);
+ mVehicleNetwork.setZonedIntProperty(propertyId, zone, minValue);
+ mVehicleNetwork.setZonedIntProperty(propertyId, zone, maxValue);
+ mVehicleNetwork.setZonedIntProperty(propertyId, zone, finalValue);
+ verifyValue(propertyId, zone, finalValue);
+ mListener.waitAndVerifyValues();
+ mVehicleNetwork.unsubscribe(propertyId);
+ }
+
+ protected int waitForIntValue(int propertyId, int zone, int expectedValue) {
+ int actualValue = mVehicleNetwork.getZonedIntProperty(propertyId, zone);
+ long deadline = System.currentTimeMillis() + PROP_TIMEOUT_MS;
+
+ while (System.currentTimeMillis() <= deadline && actualValue != expectedValue) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ex) {
+ throw new RuntimeException(ex);
+ }
+ actualValue = mVehicleNetwork.getZonedIntProperty(propertyId, zone);
+ }
+
+ return actualValue;
+ }
+
+ protected void iterateOnZones(VehiclePropConfig config, IntZonesFunctor f) {
+ int[] zones = VehicleZoneUtil.listAllZones(config.getZones());
+ int zoneIndex = 0;
+ boolean isBooleanType = config.getValueType() ==
+ VehicleValueType.VEHICLE_VALUE_TYPE_ZONED_BOOLEAN;
+
+ for (int zone : zones) {
+ int minValue = isBooleanType ? 0 : config.getInt32Mins(zoneIndex);
+ int maxValue = isBooleanType ? 1 : config.getInt32Maxs(zoneIndex);
+ String message = "PropertyId: 0x" + toHexString(config.getProp())
+ + ", zone[" + zoneIndex + "]: 0x" + toHexString(zone);
+ f.func(message, zone, minValue, maxValue);
+ zoneIndex++;
+ }
+
+ }
+
+ protected interface IntZonesFunctor {
+ void func(String message, int zone, int minValue, int maxValue);
+ }
+
+
+}
diff --git a/vehicle_network_service/VehicleNetworkService.cpp b/vehicle_network_service/VehicleNetworkService.cpp
index 4e0f77c..c13d3fc 100644
--- a/vehicle_network_service/VehicleNetworkService.cpp
+++ b/vehicle_network_service/VehicleNetworkService.cpp
@@ -151,6 +151,11 @@
}
}
+void VehicleHalMessageHandler::dump(String8& msg) {
+ msg.appendFormat("mFreeListIndex:%d, mLastDispatchTime:%" PRId64 "\n",
+ mFreeListIndex, mLastDispatchTime);
+}
+
// ----------------------------------------------------------------------------
void MockDeathHandler::binderDied(const wp<IBinder>& who) {
@@ -218,6 +223,8 @@
return NO_ERROR;
}
msg.append("MockingEnabled=%d\n", mMockingEnabled ? 1 : 0);
+ msg.appendFormat("*Handler, now in ms:%" PRId64 "\n", elapsedRealtime());
+ mHandler->dump(msg);
msg.append("*Properties\n");
for (auto& prop : mProperties->getList()) {
VechilePropertyUtil::dumpProperty(msg, *prop);
@@ -231,8 +238,7 @@
}
msg.append("*Active clients*\n");
for (size_t i = 0; i < mBinderToClientMap.size(); i++) {
- msg.appendFormat("pid %d uid %d\n", mBinderToClientMap.valueAt(i)->getPid(),
- mBinderToClientMap.valueAt(i)->getUid());
+ mBinderToClientMap.valueAt(i)->dump(msg);
}
msg.append("*Active clients per property*\n");
for (size_t i = 0; i < mPropertyToClientsMap.size(); i++) {
@@ -249,15 +255,19 @@
msg.appendFormat("prop 0x%x, sample rate %f Hz, zones 0x%x\n", mSubscriptionInfos.keyAt(i),
info.sampleRate, info.zones);
}
- msg.append("*Event counts per property*\n");
- for (size_t i = 0; i < mEventsCount.size(); i++) {
- msg.appendFormat("prop 0x%x: %d\n", mEventsCount.keyAt(i),
- mEventsCount.valueAt(i));
+ msg.appendFormat("*Event info per property, now in ns:%" PRId64 " *\n", elapsedRealtimeNano());
+ for (size_t i = 0; i < mEventInfos.size(); i++) {
+ const EventInfo& info = mEventInfos.valueAt(i);
+ msg.appendFormat("prop 0x%x, event counts:%d, last timestamp: %" PRId64 "\n",
+ mEventInfos.keyAt(i), info.eventCount, info.lastTimestamp);
}
+ msg.appendFormat(" Events dropped while in mocking:%d, last dropped time %" PRId64 "\n",
+ mDroppedEventsWhileInMocking, mLastEventDropTimeWhileInMocking);
msg.append("*Vehicle Network Service Permissions*\n");
mVehiclePropertyAccessControl.dump(msg);
-
+ msg.append("*Vehicle HAL dump*\n");
write(fd, msg.string(), msg.size());
+ mDevice->dump(mDevice, fd);
return NO_ERROR;
}
@@ -273,7 +283,9 @@
VehicleNetworkService::VehicleNetworkService()
: mModule(NULL),
- mMockingEnabled(false) {
+ mMockingEnabled(false),
+ mDroppedEventsWhileInMocking(0),
+ mLastEventDropTimeWhileInMocking(0) {
sInstance = this;
// Load vehicle network services policy file
@@ -601,12 +613,14 @@
bool shouldSubscribe = false;
bool inMock = false;
int32_t newZones = zones;
+ vehicle_prop_config_t const * config = NULL;
+ sp<HalClient> client;
do {
Mutex::Autolock autoLock(mLock);
if (!isSubscribableLocked(prop)) {
return BAD_VALUE;
}
- vehicle_prop_config_t const * config = findConfigLocked(prop);
+ config = findConfigLocked(prop);
if (config->change_mode == VEHICLE_PROP_CHANGE_MODE_ON_CHANGE) {
if (sampleRate != 0) {
ALOGW("Sample rate set to non-zeo for on change type. Ignore it");
@@ -635,7 +649,7 @@
}
sp<IBinder> ibinder = IInterface::asBinder(listener);
LOG_VERBOSE("subscribe, binder 0x%x prop 0x%x", ibinder.get(), prop);
- sp<HalClient> client = findOrCreateClientLocked(ibinder, listener);
+ client = findOrCreateClientLocked(ibinder, listener);
if (client.get() == NULL) {
ALOGE("subscribe, no memory, cannot create HalClient");
return NO_MEMORY;
@@ -673,22 +687,73 @@
}
} while (false);
if (shouldSubscribe) {
- status_t r;
if (inMock) {
- r = mHalMock->onPropertySubscribe(prop, sampleRate, newZones);
+ status_t r = mHalMock->onPropertySubscribe(prop, sampleRate, newZones);
if (r != NO_ERROR) {
ALOGW("subscribe 0x%x failed, mock returned %d", prop, r);
+ return r;
}
} else {
LOG_VERBOSE("subscribe to HAL, prop 0x%x sample rate:%f zones:0x%x", prop, sampleRate,
newZones);
- r = mDevice->subscribe(mDevice, prop, sampleRate, newZones);
+ status_t r = mDevice->subscribe(mDevice, prop, sampleRate, newZones);
if (r != NO_ERROR) {
ALOGW("subscribe 0x%x failed, HAL returned %d", prop, r);
+ return r;
}
}
- return r;
}
+ if (config->change_mode == VEHICLE_PROP_CHANGE_MODE_ON_CHANGE) {
+ status_t r = notifyClientWithCurrentValue(inMock, config, zones);
+ if (r != NO_ERROR) {
+ return r;
+ }
+ }
+ return NO_ERROR;
+}
+
+status_t VehicleNetworkService::notifyClientWithCurrentValue(bool isMocking,
+ const vehicle_prop_config_t *config, int32_t zones) {
+ status_t r;
+ int32_t prop = config->prop;
+ int32_t valueType = config->value_type;
+ if (isZonedProperty(config)) {
+ int32_t requestedZones = (zones == 0) ? config->vehicle_zone_flags : zones;
+ for (int i = 0, zone = 1; i < 32; i++, zone <<= 1) {
+ if ((zone & requestedZones) == zone) {
+ r = notifyClientWithCurrentValue(isMocking, prop, valueType, zone);
+ if (r != NO_ERROR) {
+ return r;
+ }
+ }
+ }
+ } else {
+ r = notifyClientWithCurrentValue(isMocking, prop, valueType, 0 /*no zones*/);
+ if (r != NO_ERROR) {
+ return r;
+ }
+ }
+ return NO_ERROR;
+}
+
+status_t VehicleNetworkService::notifyClientWithCurrentValue(bool isMocking,
+ int32_t prop, int32_t valueType, int32_t zone) {
+ vehicle_prop_value_t value;
+ value.prop = prop;
+ value.value_type = valueType;
+ value.zone = zone;
+ status_t r = isMocking ? mHalMock->onPropertyGet(&value) : mDevice->get(mDevice, &value);
+ if (r != NO_ERROR) {
+ if (r == -EAGAIN) {
+ LOG_VERBOSE("value is not ready:0x%x, mock:%d", prop, isMocking);
+ return NO_ERROR;
+ } else {
+ ALOGW("failed to get current value prop:0x%x, mock:%d, error:%d", prop, isMocking, r);
+ return r;
+ }
+ }
+
+ onHalEvent(&value, false);
return NO_ERROR;
}
@@ -872,7 +937,7 @@
// all subscriptions are invalid
mPropertyToClientsMap.clear();
mSubscriptionInfos.clear();
- mEventsCount.clear();
+ mEventInfos.clear();
List<sp<HalClient> > clientsToRemove;
for (size_t i = 0; i < mBinderToClientMap.size(); i++) {
sp<HalClient> client = mBinderToClientMap.valueAt(i);
@@ -961,16 +1026,19 @@
if (!isInjection) {
if (mMockingEnabled) {
// drop real HAL event if mocking is enabled
+ mDroppedEventsWhileInMocking++;
+ mLastEventDropTimeWhileInMocking = elapsedRealtimeNano();
return NO_ERROR;
}
}
- ssize_t index = mEventsCount.indexOfKey(eventData->prop);
+ ssize_t index = mEventInfos.indexOfKey(eventData->prop);
if (index < 0) {
- mEventsCount.add(eventData->prop, 1);
+ EventInfo info(eventData->timestamp, 1);
+ mEventInfos.add(eventData->prop, info);
} else {
- int count = mEventsCount.valueAt(index);
- count++;
- mEventsCount.add(eventData->prop, count);
+ EventInfo& info = mEventInfos.editValueAt(index);
+ info.eventCount++;
+ info.lastTimestamp = eventData->timestamp;
}
handler = mHandler;
} while (false);
@@ -1027,9 +1095,10 @@
} while (false);
EVENT_LOG("dispatchHalEvents num events %d, active clients:%d", events.size(),
activeClients.size());
+ int64_t now = elapsedRealtimeNano();
for (size_t i = 0; i < activeClients.size(); i++) {
sp<HalClient> client = activeClients.editItemAt(i);
- client->dispatchEvents();
+ client->dispatchEvents(now);
}
activeClients.clear();
}
diff --git a/vehicle_network_service/VehicleNetworkService.h b/vehicle_network_service/VehicleNetworkService.h
index 2116f55..5798e7f 100644
--- a/vehicle_network_service/VehicleNetworkService.h
+++ b/vehicle_network_service/VehicleNetworkService.h
@@ -18,6 +18,7 @@
#define CAR_VEHICLE_NETWORK_SERVICE_H_
#include <stdint.h>
+#include <inttypes.h>
#include <sys/types.h>
#include <memory>
@@ -74,6 +75,7 @@
void handleHalEvent(vehicle_prop_value_t *eventData);
void handleHalError(VehicleHalError* error);
void handleMockStart();
+ void dump(String8& msg);
private:
void handleMessage(const Message& message);
@@ -106,6 +108,21 @@
};
// ----------------------------------------------------------------------------
+class EventInfo {
+public:
+ int64_t lastTimestamp;
+ int eventCount;
+ EventInfo()
+ : lastTimestamp(0),
+ eventCount(0) {};
+ EventInfo(int64_t aLastTimestamp, int aEventCount)
+ : lastTimestamp(aLastTimestamp),
+ eventCount(aEventCount) {};
+ EventInfo(const EventInfo& info)
+ : lastTimestamp(info.lastTimestamp),
+ eventCount(info.eventCount) {};
+};
+// ----------------------------------------------------------------------------
class HalClient : public virtual RefBase {
public:
@@ -114,7 +131,10 @@
mPid(pid),
mUid(uid),
mMonitoringHalRestart(false),
- mMonitoringHalError(false) {
+ mMonitoringHalError(false),
+ mLastDispatchedEventCounts(0),
+ mTotalDispatchedEvents(0),
+ mLastDispatchTime(0) {
}
~HalClient() {
@@ -204,11 +224,14 @@
}
// no lock here as this should be called only from single event looper thread
- status_t dispatchEvents(){
+ status_t dispatchEvents(const int64_t& timestamp){
ALOGV("dispatchEvents, num Events:%d", mEvents.size());
sp<VehiclePropValueListHolder> events(new VehiclePropValueListHolder(&mEvents,
false /*deleteInDestructor */));
ASSERT_OR_HANDLE_NO_MEMORY(events.get(), return NO_MEMORY);
+ mLastDispatchTime = timestamp;
+ mLastDispatchedEventCounts = mEvents.size();
+ mTotalDispatchedEvents += mLastDispatchedEventCounts;
mListener->onEvents(events);
mEvents.clear();
return NO_ERROR;
@@ -222,6 +245,12 @@
mListener->onHalRestart(inMocking);
}
+ void dump(String8& msg) {
+ msg.appendFormat("pid:%d, uid:%d, mLastDispatchedEventCounts:%d, mTotalDispatchedEvents:%d"
+ ", mLastDispatchTime:%" PRId64 "\n",
+ mPid, mUid, mLastDispatchedEventCounts, mTotalDispatchedEvents, mLastDispatchTime);
+ }
+
private:
mutable Mutex mLock;
const sp<IVehicleNetworkListener> mListener;
@@ -231,6 +260,9 @@
List<vehicle_prop_value_t*> mEvents;
bool mMonitoringHalRestart;
bool mMonitoringHalError;
+ int mLastDispatchedEventCounts;
+ int mTotalDispatchedEvents;
+ int64_t mLastDispatchTime;
};
class HalClientSpVector : public SortedVector<sp<HalClient> >, public RefBase {
@@ -322,6 +354,7 @@
bool isGettableLocked(int32_t property);
bool isSettableLocked(int32_t property, int32_t valueType);
bool isSubscribableLocked(int32_t property);
+ status_t getProperty(vehicle_prop_value_t *data, bool retry);
static bool isZonedProperty(vehicle_prop_config_t const * config);
sp<HalClient> findClientLocked(sp<IBinder>& ibinder);
sp<HalClient> findOrCreateClientLocked(sp<IBinder>& ibinder,
@@ -331,6 +364,11 @@
bool removePropertyFromClientLocked(sp<IBinder>& ibinder, sp<HalClient>& client,
int32_t property);
void handleHalRestartAndGetClientsToDispatchLocked(List<sp<HalClient> >& clientsToDispatch);
+ status_t notifyClientWithCurrentValue(bool isMocking, const vehicle_prop_config_t *config,
+ int32_t zones);
+ status_t notifyClientWithCurrentValue(bool isMocking, int32_t prop, int32_t valueType,
+ int32_t zone);
+
static int eventCallback(const vehicle_prop_value_t *eventData);
static int errorCallback(int32_t errorCode, int32_t property, int32_t operation);
@@ -350,9 +388,11 @@
// client subscribing properties
KeyedVector<int32_t, sp<HalClientSpVector> > mPropertyToClientsMap;
KeyedVector<int32_t, SubscriptionInfo> mSubscriptionInfos;
- KeyedVector<int32_t, int> mEventsCount;
+ KeyedVector<int32_t, EventInfo> mEventInfos;
PropertyValueCache mCache;
bool mMockingEnabled;
+ int mDroppedEventsWhileInMocking;
+ int64_t mLastEventDropTimeWhileInMocking;
sp<IVehicleNetworkHalMock> mHalMock;
sp<VehiclePropertiesHolder> mPropertiesForMocking;
sp<MockDeathHandler> mHalMockDeathHandler;