Merge changes I67883374,I945c79ac
* changes:
Temporary disconnects for Bluetooth profiles.
Make ConnectionParams an immutable object.
diff --git a/car-lib/src/android/car/trust/CarTrustAgentEnrollmentManager.java b/car-lib/src/android/car/trust/CarTrustAgentEnrollmentManager.java
index 48ee384..6b27b5e 100644
--- a/car-lib/src/android/car/trust/CarTrustAgentEnrollmentManager.java
+++ b/car-lib/src/android/car/trust/CarTrustAgentEnrollmentManager.java
@@ -25,9 +25,13 @@
import android.car.CarManagerBase;
import android.car.CarNotConnectedException;
import android.content.Context;
+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.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -48,8 +52,8 @@
* <li> startEnrollmentAdvertising()
* <li> wait for onEnrollmentAdvertisingStarted() or
* <li> wait for onBleEnrollmentDeviceConnected() and check if the device connected is the right
- * one.
- * <li> initiateEnrollmentHandhake()
+ * one.
+ * <li> initiateEnrollmentHandshake()
* <li> wait for onAuthStringAvailable() to get the pairing code to display to the user
* <li> enrollmentHandshakeAccepted() after user confirms the pairing code
* <li> wait for onEscrowTokenAdded()
@@ -64,6 +68,19 @@
@SystemApi
public final class CarTrustAgentEnrollmentManager implements CarManagerBase {
private static final String TAG = "CarTrustEnrollMgr";
+ private static final String KEY_HANDLE = "handle";
+ private static final String KEY_ACTIVE = "active";
+ private static final String KEY_SUCCESS = "success";
+ private static final int MSG_ENROLL_ADVERTISING_STARTED = 0;
+ private static final int MSG_ENROLL_ADVERTISING_FAILED = 1;
+ private static final int MSG_ENROLL_DEVICE_CONNECTED = 2;
+ private static final int MSG_ENROLL_DEVICE_DISCONNECTED = 3;
+ private static final int MSG_ENROLL_HANDSHAKE_FAILURE = 4;
+ private static final int MSG_ENROLL_AUTH_STRING_AVAILABLE = 5;
+ private static final int MSG_ENROLL_TOKEN_ADDED = 6;
+ private static final int MSG_ENROLL_TOKEN_REVOKED = 7;
+ private static final int MSG_ENROLL_TOKEN_STATE_CHANGED = 8;
+
private final Context mContext;
private final ICarTrustAgentEnrollment mEnrollmentService;
private Object mListenerLock = new Object();
@@ -75,12 +92,14 @@
private final ListenerToEnrollmentService mListenerToEnrollmentService =
new ListenerToEnrollmentService(this);
private final ListenerToBleService mListenerToBleService = new ListenerToBleService(this);
+ private final EventCallbackHandler mEventCallbackHandler;
/** @hide */
public CarTrustAgentEnrollmentManager(IBinder service, Context context, Handler handler) {
mContext = context;
mEnrollmentService = ICarTrustAgentEnrollment.Stub.asInterface(service);
+ mEventCallbackHandler = new EventCallbackHandler(this, handler.getLooper());
}
/** @hide */
@@ -284,6 +303,10 @@
}
}
+ private Handler getEventCallbackHandler() {
+ return mEventCallbackHandler;
+ }
+
/**
* Callback interface for Trusted device enrollment applications to implement. The applications
* get notified on various enrollment state change events.
@@ -292,7 +315,7 @@
/**
* Communicate about failure/timeouts in the handshake process.
*
- * @param device the remote device trying to enroll
+ * @param device the remote device trying to enroll
* @param errorCode information on what failed.
*/
void onEnrollmentHandshakeFailure(BluetoothDevice device, int errorCode);
@@ -300,7 +323,7 @@
/**
* Present the pairing/authentication string to the user.
*
- * @param device the remote device trying to enroll
+ * @param device the remote device trying to enroll
* @param authString the authentication string to show to the user to confirm across
* both devices
*/
@@ -358,9 +381,8 @@
void onEnrollmentAdvertisingFailed(int errorCode);
}
- // TODO(ramperry) - call the client callbacks from the methods of this listener to the service
- // callbacks.
- private class ListenerToEnrollmentService extends ICarTrustAgentEnrollmentCallback.Stub {
+ private static final class ListenerToEnrollmentService extends
+ ICarTrustAgentEnrollmentCallback.Stub {
private final WeakReference<CarTrustAgentEnrollmentManager> mMgr;
ListenerToEnrollmentService(CarTrustAgentEnrollmentManager mgr) {
@@ -370,38 +392,87 @@
/**
* Communicate about failure/timeouts in the handshake process.
*/
+ @Override
public void onEnrollmentHandshakeFailure(BluetoothDevice device, int errorCode) {
+ CarTrustAgentEnrollmentManager enrollmentManager = mMgr.get();
+ if (enrollmentManager == null) {
+ return;
+ }
+ enrollmentManager.getEventCallbackHandler().sendMessage(
+ enrollmentManager.getEventCallbackHandler().obtainMessage(
+ MSG_ENROLL_HANDSHAKE_FAILURE, new AuthInfo(device, null, errorCode)));
}
/**
* Present the pairing/authentication string to the user.
*/
- public void onAuthStringAvailable(BluetoothDevice device, byte[] authString) {
+ @Override
+ public void onAuthStringAvailable(BluetoothDevice device, String authString) {
+ CarTrustAgentEnrollmentManager enrollmentManager = mMgr.get();
+ if (enrollmentManager == null) {
+ return;
+ }
+ enrollmentManager.getEventCallbackHandler().sendMessage(
+ enrollmentManager.getEventCallbackHandler().obtainMessage(
+ MSG_ENROLL_AUTH_STRING_AVAILABLE, new AuthInfo(device, authString, 0)));
}
/**
* Escrow token was received and the Trust Agent framework has generated a corresponding
* handle.
*/
+ @Override
public void onEscrowTokenAdded(long handle) {
+ CarTrustAgentEnrollmentManager enrollmentManager = mMgr.get();
+ if (enrollmentManager == null) {
+ return;
+ }
+ Message message = enrollmentManager.getEventCallbackHandler().obtainMessage(
+ MSG_ENROLL_TOKEN_ADDED);
+ Bundle data = new Bundle();
+ data.putLong(KEY_HANDLE, handle);
+ message.setData(data);
+ enrollmentManager.getEventCallbackHandler().sendMessage(message);
}
/**
* Escrow token corresponding to the given handle has been removed.
*/
+ @Override
public void onTrustRevoked(long handle, boolean success) {
+ CarTrustAgentEnrollmentManager enrollmentManager = mMgr.get();
+ if (enrollmentManager == null) {
+ return;
+ }
+ Message message = enrollmentManager.getEventCallbackHandler().obtainMessage(
+ MSG_ENROLL_TOKEN_REVOKED);
+ Bundle data = new Bundle();
+ data.putLong(KEY_HANDLE, handle);
+ data.putBoolean(KEY_SUCCESS, success);
+ message.setData(data);
+ enrollmentManager.getEventCallbackHandler().sendMessage(message);
}
/**
* Escrow token's active state changed.
*/
+ @Override
public void onEscrowTokenActiveStateChanged(long handle, boolean active) {
+ CarTrustAgentEnrollmentManager enrollmentManager = mMgr.get();
+ if (enrollmentManager == null) {
+ return;
+ }
+ Message message = enrollmentManager.getEventCallbackHandler().obtainMessage(
+ MSG_ENROLL_TOKEN_STATE_CHANGED);
+ Bundle data = new Bundle();
+ data.putLong(KEY_HANDLE, handle);
+ data.putBoolean(KEY_ACTIVE, active);
+ message.setData(data);
+ enrollmentManager.getEventCallbackHandler().sendMessage(message);
}
}
- // TODO(ramperry) - call the client callbacks from the methods of this listener to the service
- // callbacks.
- private class ListenerToBleService extends ICarTrustAgentBleCallback.Stub {
+ private static final class ListenerToBleService extends ICarTrustAgentBleCallback.Stub {
private final WeakReference<CarTrustAgentEnrollmentManager> mMgr;
ListenerToBleService(CarTrustAgentEnrollmentManager mgr) {
@@ -413,6 +484,13 @@
* enrollment.
*/
public void onEnrollmentAdvertisingStarted() {
+ CarTrustAgentEnrollmentManager enrollmentManager = mMgr.get();
+ if (enrollmentManager == null) {
+ return;
+ }
+ enrollmentManager.getEventCallbackHandler().sendMessage(
+ enrollmentManager.getEventCallbackHandler().obtainMessage(
+ MSG_ENROLL_ADVERTISING_STARTED));
}
/**
@@ -420,18 +498,179 @@
* see AdvertiseCallback#ADVERTISE_FAILED_* for possible error codes.
*/
public void onEnrollmentAdvertisingFailed(int errorCode) {
+ CarTrustAgentEnrollmentManager enrollmentManager = mMgr.get();
+ if (enrollmentManager == null) {
+ return;
+ }
+ enrollmentManager.getEventCallbackHandler().sendMessage(
+ enrollmentManager.getEventCallbackHandler().obtainMessage(
+ MSG_ENROLL_ADVERTISING_FAILED, errorCode));
}
/**
* Called when a remote device is connected on BLE.
*/
public void onBleEnrollmentDeviceConnected(BluetoothDevice device) {
+ CarTrustAgentEnrollmentManager enrollmentManager = mMgr.get();
+ if (enrollmentManager == null) {
+ return;
+ }
+ enrollmentManager.getEventCallbackHandler().sendMessage(
+ enrollmentManager.getEventCallbackHandler().obtainMessage(
+ MSG_ENROLL_DEVICE_CONNECTED, device));
}
/**
* Called when a remote device is disconnected on BLE.
*/
public void onBleEnrollmentDeviceDisconnected(BluetoothDevice device) {
+ CarTrustAgentEnrollmentManager enrollmentManager = mMgr.get();
+ if (enrollmentManager == null) {
+ return;
+ }
+ enrollmentManager.getEventCallbackHandler().sendMessage(
+ enrollmentManager.getEventCallbackHandler().obtainMessage(
+ MSG_ENROLL_DEVICE_DISCONNECTED, device));
+ }
+ }
+
+ /**
+ * Callback Handler to handle dispatching the enrollment state changes to the corresponding
+ * listeners
+ */
+ private static final class EventCallbackHandler extends Handler {
+ private final WeakReference<CarTrustAgentEnrollmentManager> mEnrollmentManager;
+
+ EventCallbackHandler(CarTrustAgentEnrollmentManager manager, Looper looper) {
+ super(looper);
+ mEnrollmentManager = new WeakReference<>(manager);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ CarTrustAgentEnrollmentManager enrollmentManager = mEnrollmentManager.get();
+ if (enrollmentManager == null) {
+ return;
+ }
+ switch (message.what) {
+ case MSG_ENROLL_ADVERTISING_STARTED:
+ case MSG_ENROLL_ADVERTISING_FAILED:
+ case MSG_ENROLL_DEVICE_CONNECTED:
+ case MSG_ENROLL_DEVICE_DISCONNECTED:
+ enrollmentManager.dispatchBleCallback(message);
+ break;
+ case MSG_ENROLL_HANDSHAKE_FAILURE:
+ case MSG_ENROLL_AUTH_STRING_AVAILABLE:
+ case MSG_ENROLL_TOKEN_ADDED:
+ case MSG_ENROLL_TOKEN_REVOKED:
+ case MSG_ENROLL_TOKEN_STATE_CHANGED:
+ enrollmentManager.dispatchEnrollmentCallback(message);
+ break;
+ default:
+ Log.e(TAG, "Unknown message:" + message.what);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Dispatch BLE related state change callbacks
+ *
+ * @param message Message to handle and dispatch
+ */
+ private void dispatchBleCallback(Message message) {
+ CarTrustAgentBleCallback bleCallback;
+ synchronized (mListenerLock) {
+ bleCallback = mBleCallback;
+ }
+ if (bleCallback == null) {
+ return;
+ }
+ switch (message.what) {
+ case MSG_ENROLL_ADVERTISING_STARTED:
+ bleCallback.onEnrollmentAdvertisingStarted();
+ break;
+ case MSG_ENROLL_ADVERTISING_FAILED:
+ bleCallback.onEnrollmentAdvertisingFailed((int) message.obj);
+ break;
+ case MSG_ENROLL_DEVICE_CONNECTED:
+ bleCallback.onBleEnrollmentDeviceConnected((BluetoothDevice) message.obj);
+ break;
+ case MSG_ENROLL_DEVICE_DISCONNECTED:
+ bleCallback.onBleEnrollmentDeviceDisconnected((BluetoothDevice) message.obj);
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Dispatch Enrollment related state changes to the listener.
+ *
+ * @param message Message to handle and dispatch
+ */
+ private void dispatchEnrollmentCallback(Message message) {
+ CarTrustAgentEnrollmentCallback enrollmentCallback;
+ synchronized (mListenerLock) {
+ enrollmentCallback = mEnrollmentCallback;
+ }
+ if (enrollmentCallback == null) {
+ return;
+ }
+ AuthInfo auth;
+ Bundle data;
+ switch (message.what) {
+ case MSG_ENROLL_HANDSHAKE_FAILURE:
+ auth = (AuthInfo) message.obj;
+ enrollmentCallback.onEnrollmentHandshakeFailure(auth.mDevice, auth.mErrorCode);
+ break;
+ case MSG_ENROLL_AUTH_STRING_AVAILABLE:
+ auth = (AuthInfo) message.obj;
+ if (auth.mDevice != null && auth.mAuthString != null) {
+ enrollmentCallback.onAuthStringAvailable(auth.mDevice, auth.mAuthString);
+ }
+ break;
+ case MSG_ENROLL_TOKEN_ADDED:
+ data = message.getData();
+ if (data == null) {
+ break;
+ }
+ enrollmentCallback.onEscrowTokenAdded(data.getLong(KEY_HANDLE));
+ break;
+ case MSG_ENROLL_TOKEN_REVOKED:
+ data = message.getData();
+ if (data == null) {
+ break;
+ }
+ enrollmentCallback.onTrustRevoked(data.getLong(KEY_HANDLE),
+ data.getBoolean(KEY_SUCCESS));
+ break;
+ case MSG_ENROLL_TOKEN_STATE_CHANGED:
+ data = message.getData();
+ if (data == null) {
+ break;
+ }
+ enrollmentCallback.onEscrowTokenActiveStateChanged(data.getLong(KEY_HANDLE),
+ data.getBoolean(KEY_ACTIVE));
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Container class to pass information through a Message to the handler.
+ */
+ private static class AuthInfo {
+ final BluetoothDevice mDevice;
+ @Nullable
+ final String mAuthString;
+ final int mErrorCode;
+
+ AuthInfo(BluetoothDevice device, @Nullable String authString, int errorCode) {
+ mDevice = device;
+ mAuthString = authString;
+ mErrorCode = errorCode;
}
}
}
diff --git a/car-lib/src/android/car/trust/ICarTrustAgentEnrollmentCallback.aidl b/car-lib/src/android/car/trust/ICarTrustAgentEnrollmentCallback.aidl
index 9457fec..62cef9c 100644
--- a/car-lib/src/android/car/trust/ICarTrustAgentEnrollmentCallback.aidl
+++ b/car-lib/src/android/car/trust/ICarTrustAgentEnrollmentCallback.aidl
@@ -32,7 +32,7 @@
/**
* Present the pairing/authentication string to the user.
*/
- void onAuthStringAvailable(in BluetoothDevice device, in byte[] authString);
+ void onAuthStringAvailable(in BluetoothDevice device, in String authString);
/**
* Escrow token was received and the Trust Agent framework has generated a corresponding handle.
diff --git a/car_product/build/car.mk b/car_product/build/car.mk
index bfe12d6..cdad5b0 100644
--- a/car_product/build/car.mk
+++ b/car_product/build/car.mk
@@ -47,9 +47,8 @@
BOARD_SEPOLICY_DIRS += packages/services/Car/car_product/sepolicy/test
endif
-PRODUCT_COPY_FILES := \
- frameworks/av/media/libeffects/data/audio_effects.conf:system/etc/audio_effects.conf \
- packages/services/Car/car_product/preloaded-classes-car:system/etc/preloaded-classes \
+PRODUCT_COPY_FILES += \
+ frameworks/av/media/libeffects/data/audio_effects.conf:system/etc/audio_effects.conf
PRODUCT_PROPERTY_OVERRIDES += \
ro.carrier=unknown \
diff --git a/car_product/preloaded-classes-car b/car_product/preloaded-classes-car
deleted file mode 100644
index 962099f..0000000
--- a/car_product/preloaded-classes-car
+++ /dev/null
@@ -1 +0,0 @@
-# Classes which are preloaded by com.android.internal.os.ZygoteInit.
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 3b05e29..e8c520a 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -25,13 +25,6 @@
during initial development where audio hal does not support bus based addressing yet. -->
<bool name="audioUseDynamicRouting">false</bool>
- <!-- Configuration to use the unified audio configuration.
- This flag has no effect if audioUseDynamicRouting is set to false.
- When audioUseDynamicRouting is enabled
- - car_volume_groups.xml will be picked if this flag is false
- - car_audio_configuration.xml will be used otherwise. -->
- <bool name="audioUseUnifiedConfiguration">false</bool>
-
<!-- Configuration to persist master mute state. If this is set to true,
Android will restore the master mute state on boot. -->
<bool name="audioPersistMasterMuteState">true</bool>
diff --git a/service/res/values/strings.xml b/service/res/values/strings.xml
index 2a7bcaa..69b9467 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -14,8 +14,7 @@
limitations under the License.
-->
<resources>
- <string name="app_title">Car service</string>
-
+ <string name="app_title" translatable="false">Car service</string>
<!-- For permissions -->
<!-- Permission text: can access your car's information [CHAR LIMIT=NONE] -->
<string name="car_permission_label">Car information</string>
diff --git a/service/res/xml/car_audio_configuration.xml b/service/res/xml/car_audio_configuration.xml
deleted file mode 100644
index e3fd8f6..0000000
--- a/service/res/xml/car_audio_configuration.xml
+++ /dev/null
@@ -1,88 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<!--
- Defines the audio configuration in a car, including
- - Audio zones
- - Display to audio zone mappings
- - Context to audio bus mappings
- - Volume groups
- in the car environment.
--->
-<carAudioConfiguration
- xmlns:car="http://schemas.android.com/apk/res-auto"
- car:version="1">
- <zones>
- <zone car:name="primary zone" car:isPrimary="true">
- <volumeGroups>
- <group>
- <device car:address="bus0_media_out">
- <context car:context="music"/>
- </device>
- <device car:address="bus3_call_ring_out">
- <context car:context="call_ring"/>
- </device>
- <device car:address="bus6_notification_out">
- <context car:context="notification"/>
- </device>
- <device car:address="bus7_system_sound_out">
- <context car:context="system_sound"/>
- </device>
- </group>
- <group>
- <device car:address="bus1_navigation_out">
- <context car:context="navigation"/>
- </device>
- <device car:address="bus2_voice_command_out">
- <context car:context="voice_command"/>
- </device>
- </group>
- <group>
- <device car:address="bus4_call_out">
- <context car:context="call"/>
- </device>
- </group>
- <group>
- <device car:address="bus5_alarm_out">
- <context car:context="alarm"/>
- </device>
- </group>
- </volumeGroups>
- <displays>
- <display car:display="primary_display"/>
- </displays>
- </zone>
- <zone car:name="rear seat zone">
- <volumeGroups>
- <group>
- <device car:address="bus100_rear_seat">
- <context car:context="music"/>
- <context car:context="navigation"/>
- <context car:context="voice_command"/>
- <context car:context="call_ring"/>
- <context car:context="call"/>
- <context car:context="alarm"/>
- <context car:context="notification"/>
- <context car:context="system_sound"/>
- </device>
- </group>
- </volumeGroups>
- <displays>
- <display car:display="rear_seat_display"/>
- </displays>
- </zone>
- </zones>
-</carAudioConfiguration>
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index 3c5434d..b19fda5 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -56,6 +56,7 @@
import com.android.car.R;
import com.android.internal.util.Preconditions;
+import java.io.File;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -83,6 +84,14 @@
// allows listening for both GROUP/MEDIA and GROUP/NAVIGATION.
private static final String VOLUME_SETTINGS_KEY_FOR_GROUP_PREFIX = "android.car.VOLUME_GROUP/";
+ // CarAudioService reads configuration from the following paths respectively.
+ // If the first one is found, all others are ignored.
+ // If no one is found, it fallbacks to car_volume_groups.xml resource file.
+ private static final String[] AUDIO_CONFIGURATION_PATHS = new String[] {
+ "/vendor/etc/car_audio_configuration.xml",
+ "/system/etc/car_audio_configuration.xml"
+ };
+
/**
* Gets the key to persist volume for a volume group in settings
*
@@ -101,7 +110,6 @@
private final TelephonyManager mTelephonyManager;
private final AudioManager mAudioManager;
private final boolean mUseDynamicRouting;
- private final boolean mUseUnifiedConfiguration;
private final boolean mPersistMasterMuteState;
private final AudioPolicy.AudioPolicyVolumeCallback mAudioPolicyVolumeCallback =
@@ -175,6 +183,7 @@
private AudioPolicy mAudioPolicy;
private CarAudioFocus mFocusHandler;
+ private String mCarAudioConfigurationPath;
private CarAudioZone[] mCarAudioZones;
public CarAudioService(Context context) {
@@ -182,8 +191,6 @@
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mUseDynamicRouting = mContext.getResources().getBoolean(R.bool.audioUseDynamicRouting);
- mUseUnifiedConfiguration = mContext.getResources().getBoolean(
- R.bool.audioUseUnifiedConfiguration);
mPersistMasterMuteState = mContext.getResources().getBoolean(
R.bool.audioPersistMasterMuteState);
}
@@ -254,9 +261,11 @@
public void dump(PrintWriter writer) {
writer.println("*CarAudioService*");
writer.println("\tRun in legacy mode? " + (!mUseDynamicRouting));
- writer.println("\tUse unified configuration? " + mUseUnifiedConfiguration);
writer.println("\tPersist master mute state? " + mPersistMasterMuteState);
writer.println("\tMaster muted? " + mAudioManager.isMasterMute());
+ if (mCarAudioConfigurationPath != null) {
+ writer.println("\tCar audio configuration path: " + mCarAudioConfigurationPath);
+ }
// Empty line for comfortable reading
writer.println();
if (mUseDynamicRouting) {
@@ -407,8 +416,9 @@
builder.setLooper(Looper.getMainLooper());
final CarAudioZonesLoader zonesLoader;
- if (mUseUnifiedConfiguration) {
- zonesLoader = new CarAudioZonesHelper(mContext, R.xml.car_audio_configuration,
+ mCarAudioConfigurationPath = getAudioConfigurationPath();
+ if (mCarAudioConfigurationPath != null) {
+ zonesLoader = new CarAudioZonesHelper(mContext, mCarAudioConfigurationPath,
busToCarAudioDeviceInfo);
} else {
// In legacy mode, context -> bus mapping is done by querying IAudioControl HAL.
@@ -459,6 +469,21 @@
}
/**
+ * Read from {@link #AUDIO_CONFIGURATION_PATHS} respectively.
+ * @return File path of the first hit in {@link #AUDIO_CONFIGURATION_PATHS}
+ */
+ @Nullable
+ private String getAudioConfigurationPath() {
+ for (String path : AUDIO_CONFIGURATION_PATHS) {
+ File configuration = new File(path);
+ if (configuration.exists()) {
+ return path;
+ }
+ }
+ return null;
+ }
+
+ /**
* @return Context number for a given audio usage, 0 if the given usage is unrecognized.
*/
int getContextForUsage(int audioUsage) {
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelper.java b/service/src/com/android/car/audio/CarAudioZonesHelper.java
index ba7b93c..0acae73 100644
--- a/service/src/com/android/car/audio/CarAudioZonesHelper.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelper.java
@@ -16,30 +16,32 @@
package com.android.car.audio;
import android.annotation.NonNull;
-import android.annotation.XmlRes;
import android.car.media.CarAudioManager;
import android.content.Context;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.util.AttributeSet;
+import android.hardware.automotive.audiocontrol.V1_0.ContextNumber;
import android.util.Log;
import android.util.SparseArray;
import android.util.Xml;
import com.android.car.CarLog;
-import com.android.car.R;
+import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* A helper class loads all audio zones from the configuration XML file.
*/
/* package */ class CarAudioZonesHelper implements CarAudioService.CarAudioZonesLoader {
+ private static final String NAMESPACE = null;
private static final String TAG_ROOT = "carAudioConfiguration";
private static final String TAG_AUDIO_ZONES = "zones";
private static final String TAG_AUDIO_ZONE = "zone";
@@ -47,18 +49,37 @@
private static final String TAG_VOLUME_GROUP = "group";
private static final String TAG_AUDIO_DEVICE = "device";
private static final String TAG_CONTEXT = "context";
+ private static final String ATTR_VERSION = "version";
+ private static final String ATTR_IS_PRIMARY = "isPrimary";
+ private static final String ATTR_ZONE_NAME = "name";
+ private static final String ATTR_DEVICE_ADDRESS = "address";
+ private static final String ATTR_CONTEXT_NAME = "context";
private static final int SUPPORTED_VERSION = 1;
+ private static final Map<String, Integer> CONTEXT_NAME_MAP;
+
+ static {
+ CONTEXT_NAME_MAP = new HashMap<>();
+ CONTEXT_NAME_MAP.put("music", ContextNumber.MUSIC);
+ CONTEXT_NAME_MAP.put("navigation", ContextNumber.NAVIGATION);
+ CONTEXT_NAME_MAP.put("voice_command", ContextNumber.VOICE_COMMAND);
+ CONTEXT_NAME_MAP.put("call_ring", ContextNumber.CALL_RING);
+ CONTEXT_NAME_MAP.put("call", ContextNumber.CALL);
+ CONTEXT_NAME_MAP.put("alarm", ContextNumber.ALARM);
+ CONTEXT_NAME_MAP.put("notification", ContextNumber.NOTIFICATION);
+ CONTEXT_NAME_MAP.put("system_sound", ContextNumber.SYSTEM_SOUND);
+ }
+
private final Context mContext;
- private final int mXmlConfiguration;
+ private final String mXmlConfigurationPath;
private final SparseArray<CarAudioDeviceInfo> mBusToCarAudioDeviceInfo;
private int mNextSecondaryZoneId;
- CarAudioZonesHelper(Context context, @XmlRes int xmlConfiguration,
+ CarAudioZonesHelper(Context context, @NonNull String xmlConfigurationPath,
@NonNull SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo) {
mContext = context;
- mXmlConfiguration = xmlConfiguration;
+ mXmlConfigurationPath = xmlConfigurationPath;
mBusToCarAudioDeviceInfo = busToCarAudioDeviceInfo;
mNextSecondaryZoneId = CarAudioManager.PRIMARY_AUDIO_ZONE + 1;
@@ -67,133 +88,137 @@
@Override
public CarAudioZone[] loadAudioZones() {
List<CarAudioZone> carAudioZones = new ArrayList<>();
- try (XmlResourceParser parser = mContext.getResources().getXml(mXmlConfiguration)) {
- AttributeSet attrs = Xml.asAttributeSet(parser);
- int type;
- // Traverse to the first start tag, <carAudioConfiguration> in this case
- while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
- && type != XmlResourceParser.START_TAG) {
- // ignored
- }
- if (!TAG_ROOT.equals(parser.getName())) {
- throw new RuntimeException("Meta-data does not start with " + TAG_ROOT);
- }
+ try (InputStream stream = new FileInputStream(mXmlConfigurationPath)) {
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, NAMESPACE != null);
+ parser.setInput(stream, null);
+
+ // Ensure <carAudioConfiguration> is the root
+ parser.nextTag();
+ parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_ROOT);
// Version check
- TypedArray c = mContext.getResources().obtainAttributes(
- attrs, R.styleable.carAudioConfiguration);
- final int versionNumber = c.getInt(R.styleable.carAudioConfiguration_version, -1);
+ final int versionNumber = Integer.parseInt(
+ parser.getAttributeValue(NAMESPACE, ATTR_VERSION));
if (versionNumber != SUPPORTED_VERSION) {
throw new RuntimeException("Support version:"
+ SUPPORTED_VERSION + " only, got version:" + versionNumber);
}
- c.recycle();
- // And follows with the <zones> tag
- while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
- && type != XmlResourceParser.START_TAG) {
- // ignored
- }
- if (!TAG_AUDIO_ZONES.equals(parser.getName())) {
- throw new RuntimeException("Configuration should begin with a <zones> tag");
- }
- int outerDepth = parser.getDepth();
- while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
- && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlResourceParser.END_TAG) {
- continue;
- }
- if (TAG_AUDIO_ZONE.equals(parser.getName())) {
- carAudioZones.add(parseAudioZone(attrs, parser));
+ // Get all zones configured under <zones> tag
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+ if (TAG_AUDIO_ZONES.equals(parser.getName())) {
+ parseAudioZones(parser, carAudioZones);
+ } else {
+ skip(parser);
}
}
} catch (Exception e) {
Log.e(CarLog.TAG_AUDIO, "Error parsing unified car audio configuration", e);
-
}
return carAudioZones.toArray(new CarAudioZone[0]);
}
- private CarAudioZone parseAudioZone(AttributeSet attrs, XmlResourceParser parser)
+ private void parseAudioZones(XmlPullParser parser, List<CarAudioZone> carAudioZones)
throws XmlPullParserException, IOException {
- TypedArray c = mContext.getResources().obtainAttributes(
- attrs, R.styleable.carAudioConfiguration);
- final boolean isPrimary = c.getBoolean(R.styleable.carAudioConfiguration_isPrimary, false);
- final String zoneName = c.getString(R.styleable.carAudioConfiguration_name);
- c.recycle();
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+ if (TAG_AUDIO_ZONE.equals(parser.getName())) {
+ carAudioZones.add(parseAudioZone(parser));
+ } else {
+ skip(parser);
+ }
+ }
+ }
+
+ private CarAudioZone parseAudioZone(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ final boolean isPrimary = Boolean.parseBoolean(
+ parser.getAttributeValue(NAMESPACE, ATTR_IS_PRIMARY));
+ final String zoneName = parser.getAttributeValue(NAMESPACE, ATTR_ZONE_NAME);
CarAudioZone zone = new CarAudioZone(
isPrimary ? CarAudioManager.PRIMARY_AUDIO_ZONE : getNextSecondaryZoneId(),
zoneName);
- int type;
- // Traverse to the first start tag, <volumeGroups> in this case
- while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
- && type != XmlResourceParser.START_TAG) {
- // ignored
- }
-
- if (!TAG_VOLUME_GROUPS.equals(parser.getName())) {
- throw new RuntimeException("Audio zone does not start with <volumeGroups> tag");
- }
- int outerDepth = parser.getDepth();
- int groupId = 0;
- while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
- && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlResourceParser.END_TAG) {
- continue;
- }
- if (TAG_VOLUME_GROUP.equals(parser.getName())) {
- zone.addVolumeGroup(parseVolumeGroup(zone.getId(), groupId, attrs, parser));
- groupId += 1;
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+ // Expect one <volumeGroups> in one audio zone
+ if (TAG_VOLUME_GROUPS.equals(parser.getName())) {
+ parseVolumeGroups(parser, zone);
+ } else {
+ skip(parser);
}
}
return zone;
}
- private CarVolumeGroup parseVolumeGroup(
- int zoneId, int groupId, AttributeSet attrs, XmlResourceParser parser)
+ private void parseVolumeGroups(XmlPullParser parser, CarAudioZone zone)
+ throws XmlPullParserException, IOException {
+ int groupId = 0;
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+ if (TAG_VOLUME_GROUP.equals(parser.getName())) {
+ zone.addVolumeGroup(parseVolumeGroup(parser, zone.getId(), groupId));
+ groupId++;
+ } else {
+ skip(parser);
+ }
+ }
+ }
+
+ private CarVolumeGroup parseVolumeGroup(XmlPullParser parser, int zoneId, int groupId)
throws XmlPullParserException, IOException {
final CarVolumeGroup group = new CarVolumeGroup(mContext, zoneId, groupId);
- int type;
- int outerDepth = parser.getDepth();
- while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
- && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlResourceParser.END_TAG) {
- continue;
- }
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) continue;
if (TAG_AUDIO_DEVICE.equals(parser.getName())) {
- TypedArray c = mContext.getResources().obtainAttributes(
- attrs, R.styleable.carAudioConfiguration);
- final String address = c.getString(R.styleable.carAudioConfiguration_address);
- parseVolumeGroupContexts(group,
- CarAudioDeviceInfo.parseDeviceAddress(address), attrs, parser);
- c.recycle();
+ String address = parser.getAttributeValue(NAMESPACE, ATTR_DEVICE_ADDRESS);
+ parseVolumeGroupContexts(parser, group,
+ CarAudioDeviceInfo.parseDeviceAddress(address));
+ } else {
+ skip(parser);
}
}
return group;
}
private void parseVolumeGroupContexts(
- CarVolumeGroup group, int busNumber, AttributeSet attrs, XmlResourceParser parser)
+ XmlPullParser parser, CarVolumeGroup group, int busNumber)
throws XmlPullParserException, IOException {
- int type;
- int innerDepth = parser.getDepth();
- while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
- && (type != XmlResourceParser.END_TAG || parser.getDepth() > innerDepth)) {
- if (type == XmlResourceParser.END_TAG) {
- continue;
- }
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) continue;
if (TAG_CONTEXT.equals(parser.getName())) {
- TypedArray c = mContext.getResources().obtainAttributes(
- attrs, R.styleable.volumeGroups_context);
- final int contextNumber = c.getInt(
- R.styleable.volumeGroups_context_context, -1);
- c.recycle();
- group.bind(contextNumber, busNumber, mBusToCarAudioDeviceInfo.get(busNumber));
+ group.bind(
+ parseContextNumber(parser.getAttributeValue(NAMESPACE, ATTR_CONTEXT_NAME)),
+ busNumber, mBusToCarAudioDeviceInfo.get(busNumber));
+ }
+ // Always skip to upper level since we're at the lowest.
+ skip(parser);
+ }
+ }
+
+ private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ throw new IllegalStateException();
+ }
+ int depth = 1;
+ while (depth != 0) {
+ switch (parser.next()) {
+ case XmlPullParser.END_TAG:
+ depth--;
+ break;
+ case XmlPullParser.START_TAG:
+ depth++;
+ break;
}
}
}
+ private int parseContextNumber(String context) {
+ return CONTEXT_NAME_MAP.getOrDefault(context.toLowerCase(), ContextNumber.INVALID);
+ }
+
private int getNextSecondaryZoneId() {
int zoneId = mNextSecondaryZoneId;
mNextSecondaryZoneId += 1;
diff --git a/tests/DirectRenderingClusterSample/AndroidManifest.xml b/tests/DirectRenderingClusterSample/AndroidManifest.xml
index 660f460..2dcdc58 100644
--- a/tests/DirectRenderingClusterSample/AndroidManifest.xml
+++ b/tests/DirectRenderingClusterSample/AndroidManifest.xml
@@ -46,6 +46,7 @@
<uses-permission android:name="android.car.permission.CAR_POWERTRAIN"/>
<uses-permission android:name="android.car.permission.CAR_INFO"/>
<uses-permission android:name="android.car.permission.CAR_SPEED"/>
+ <uses-permission android:name="android.car.permission.CAR_ENGINE_DETAILED"/>
<application android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterViewModel.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterViewModel.java
index 8a67856..2efe765 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterViewModel.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/ClusterViewModel.java
@@ -74,23 +74,8 @@
try {
Log.i(TAG, "onServiceConnected, name: " + name + ", service: " + service);
- // Listen navigation focus state
- mCarAppFocusManager = (CarAppFocusManager) mCar.getCarManager(
- Car.APP_FOCUS_SERVICE);
- if (mCarAppFocusManager == null) {
- Log.e(TAG, "onServiceConnected: unable to obtain CarAppFocusManager");
- return;
- }
- mCarAppFocusManager.addFocusListener(
- (appType, active) -> setNavigationFocus(active),
- CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
-
- // Listen property value changes
- mCarPropertyManager = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
- for (Integer propertyId : Sensors.getInstance().getPropertyIds()) {
- mCarPropertyManager.registerListener(mCarPropertyEventListener,
- propertyId, PROPERTIES_REFRESH_RATE_UI);
- }
+ registerAppFocusListener();
+ registerCarPropertiesListener();
} catch (CarNotConnectedException e) {
Log.e(TAG, "onServiceConnected: error obtaining manager", e);
}
@@ -104,6 +89,33 @@
}
};
+ private void registerAppFocusListener() throws CarNotConnectedException {
+ mCarAppFocusManager = (CarAppFocusManager) mCar.getCarManager(
+ Car.APP_FOCUS_SERVICE);
+ if (mCarAppFocusManager != null) {
+ mCarAppFocusManager.addFocusListener(
+ (appType, active) -> setNavigationFocus(active),
+ CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);
+ } else {
+ Log.e(TAG, "onServiceConnected: unable to obtain CarAppFocusManager");
+ }
+ }
+
+ private void registerCarPropertiesListener() throws CarNotConnectedException {
+ Sensors sensors = Sensors.getInstance();
+ mCarPropertyManager = (CarPropertyManager) mCar.getCarManager(Car.PROPERTY_SERVICE);
+ for (Integer propertyId : sensors.getPropertyIds()) {
+ try {
+ mCarPropertyManager.registerListener(mCarPropertyEventListener,
+ propertyId, PROPERTIES_REFRESH_RATE_UI);
+ } catch (SecurityException ex) {
+ Log.e(TAG, "onServiceConnected: Unable to listen to car property: " + propertyId
+ + " sensors: " + sensors.getSensorsForPropertyId(propertyId), ex);
+ }
+ }
+ }
+
+
private CarPropertyManager.CarPropertyEventListener mCarPropertyEventListener =
new CarPropertyManager.CarPropertyEventListener() {
@Override
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
index e138a6b..cf38e1f 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
@@ -429,11 +429,9 @@
* have a default navigation activity selected yet.
*/
private void tryLaunchNavigationActivity() {
- int userHandle = ActivityManager.getCurrentUser();
- if (userHandle == UserHandle.USER_SYSTEM || mNavigationDisplayId == NO_DISPLAY) {
+ if (mNavigationDisplayId == NO_DISPLAY) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, String.format("Launch activity ignored (user: %d, display: %d)",
- userHandle, mNavigationDisplayId));
+ Log.d(TAG, String.format("Launch activity ignored (no display yet)"));
}
// Not ready to launch yet.
return;
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensor.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensor.java
index 54cc7f8..8f01cd7 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensor.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/sensors/Sensor.java
@@ -57,4 +57,9 @@
mExpectedPropertyType = expectedPropertyType;
mAdapter = adapter;
}
+
+ @Override
+ public String toString() {
+ return mName;
+ }
}