plumbing for volume support
- add volume key listening to CarInputService
- separate volume property monitoring from AudioHalService
with additional APIs for volume capability check
- add audio context listening to CarAudioService
bug: 27744968
Change-Id: I5cebe141115ce86dc07fa8d590874ff5bb9bf66b
diff --git a/service/src/com/android/car/CarAudioService.java b/service/src/com/android/car/CarAudioService.java
index 5019fb4..82e5610 100644
--- a/service/src/com/android/car/CarAudioService.java
+++ b/service/src/com/android/car/CarAudioService.java
@@ -30,7 +30,7 @@
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;
@@ -38,7 +38,17 @@
import java.util.LinkedList;
-public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, AudioHalListener {
+public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
+ AudioHalFocusListener {
+
+ 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;
@@ -50,7 +60,6 @@
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 Object mLock = new Object();
@@ -83,6 +92,14 @@
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;
private final AudioAttributes mAttributeBottom =
CarAudioAttributesUtil.getAudioAttributesForCarUsage(
@@ -98,7 +115,6 @@
mSystemFocusListener = new SystemFocusListener();
mFocusHandlerThread.start();
mFocusHandler = new CarAudioFocusChangeHandler(mFocusHandlerThread.getLooper());
- mVolumeHandler = new CarAudioVolumeHandler(Looper.getMainLooper());
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
}
@@ -134,7 +150,7 @@
if (r != 0) {
throw new RuntimeException("registerAudioPolicy failed " + r);
}
- mAudioHal.setListener(this);
+ mAudioHal.setFocusListener(this);
int audioHwVariant = mAudioHal.getHwVariant();
mAudioRoutingPolicy = AudioRoutingPolicy.create(mContext, audioHwVariant);
mAudioHal.setAudioRoutingPolicy(mAudioRoutingPolicy);
@@ -154,9 +170,27 @@
mTopFocusInfo = null;
mPendingFocusChanges.clear();
mRadioActive = false;
+ if (mCarAudioContextChangeHandler != null) {
+ mCarAudioContextChangeHandler.cancelAll();
+ mCarAudioContextChangeHandler = null;
+ }
+ mAudioContextChangeListener = null;
+ mCurrentPrimaryAudioContext = 0;
}
}
+ 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
public void dump(PrintWriter writer) {
writer.println("*CarAudioService*");
@@ -164,6 +198,8 @@
" mLastFocusRequestToCar:" + mLastFocusRequestToCar);
writer.println(" mCurrentAudioContexts:0x" + Integer.toHexString(mCurrentAudioContexts));
writer.println(" mCallActive:" + mCallActive + " mRadioActive:" + mRadioActive);
+ writer.println(" mCurrentPrimaryAudioContext:" + mCurrentPrimaryAudioContext +
+ " mCurrentPrimaryPhysicalStream:" + mCurrentPrimaryPhysicalStream);
mAudioRoutingPolicy.dump(writer);
}
@@ -178,17 +214,6 @@
}
@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);
}
@@ -333,10 +358,6 @@
androidFocus, flags, mAudioPolicy);
}
- private void doHandleVolumeChange(VolumeStateChangeEvent event) {
- //TODO
- }
-
private void doHandleStreamStatusChange(int streamNumber, int state) {
//TODO
}
@@ -430,6 +451,31 @@
int logicalStreamTypeForTop = CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib);
int physicalStreamTypeForTop = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
logicalStreamTypeForTop);
+
+ // update primary context and notify if necessary
+ int primaryContext = logicalStreamTypeForTop;
+ switch (logicalStreamTypeForTop) {
+ case CarAudioAttributesUtil.CAR_AUDIO_TYPE_CARSERVICE_BOTTOM:
+ case CarAudioAttributesUtil.CAR_AUDIO_TYPE_CARSERVICE_CAR_PROXY:
+ primaryContext = 0;
+ break;
+ }
+ if (mCurrentPrimaryAudioContext != primaryContext) {
+ mCurrentPrimaryAudioContext = primaryContext;
+ if (primaryContext == CarAudioManager.CAR_AUDIO_USAGE_RADIO) {
+ // policy does not know radio. treat it same as music.
+ mCurrentPrimaryPhysicalStream =
+ mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
+ CarAudioManager.CAR_AUDIO_USAGE_MUSIC);
+ } else {
+ 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 +486,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;
@@ -748,6 +795,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 +893,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..af7f38c 100644
--- a/service/src/com/android/car/CarInputService.java
+++ b/service/src/com/android/car/CarInputService.java
@@ -48,6 +48,8 @@
private KeyEventListener mInstumentClusterKeyListener;
+ private KeyEventListener mVolumeKeyListener;
+
private ParcelFileDescriptor mInjectionDeviceFd;
private int mKeyEventCount = 0;
@@ -86,6 +88,12 @@
}
}
+ public void setVolumeKeyListener(KeyEventListener listener) {
+ synchronized (this) {
+ mVolumeKeyListener = listener;
+ }
+ }
+
@Override
public void init() {
InputHalService hal = VehicleHal.getInstance().getInputHal();
@@ -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;
}
@@ -197,6 +209,17 @@
listener.onKeyEvent(event);
}
+ private void handleVolumeKey(KeyEvent event) {
+ KeyEventListener listener = null;
+ synchronized (this) {
+ listener = mVolumeKeyListener;
+ }
+ if (listener == null) {
+ return;
+ }
+ listener.onKeyEvent(event);
+ }
+
private void handleMainDisplayKey(KeyEvent event) {
int fd;
synchronized (this) {
diff --git a/service/src/com/android/car/hal/AudioHalService.java b/service/src/com/android/car/hal/AudioHalService.java
index 893fbff..16d3d38 100644
--- a/service/src/com/android/car/hal/AudioHalService.java
+++ b/service/src/com/android/car/hal/AudioHalService.java
@@ -33,7 +33,9 @@
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;
@@ -126,7 +128,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 +138,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,16 +159,11 @@
* @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;
@@ -168,17 +174,41 @@
mVehicleHal = vehicleHal;
}
- public void setListener(AudioHalListener listener) {
+ public void setFocusListener(AudioHalFocusListener focusListener) {
List<VehiclePropValue> eventsToDispatch = null;
+ AudioHalVolumeListener volumeListener;
+ boolean shouldDispatch = false;
synchronized (this) {
- mListener = listener;
- if (mQueuedEvents != null) {
+ mFocusListener = focusListener;
+ volumeListener = mVolumeListener;
+ shouldDispatch = canDispatchEventLocked();
+ if (shouldDispatch) {
+ if (mQueuedEvents != null) {
+ eventsToDispatch = mQueuedEvents;
+ mQueuedEvents = null;
+ }
+ }
+ }
+ if (shouldDispatch && eventsToDispatch != null) {
+ dispatchEventToListener(focusListener, volumeListener, eventsToDispatch);
+ }
+ }
+
+ public void setVolumeListener(AudioHalVolumeListener volumeListener) {
+ List<VehiclePropValue> eventsToDispatch = null;
+ AudioHalFocusListener focusListener;
+ boolean shouldDispatch = false;
+ synchronized (this) {
+ mVolumeListener = volumeListener;
+ focusListener = mFocusListener;
+ shouldDispatch = canDispatchEventLocked();
+ if (shouldDispatch) {
eventsToDispatch = mQueuedEvents;
mQueuedEvents = null;
}
}
- if (eventsToDispatch != null) {
- dispatchEventToListener(listener, eventsToDispatch);
+ if (shouldDispatch && eventsToDispatch != null) {
+ dispatchEventToListener(focusListener, volumeListener, eventsToDispatch);
}
}
@@ -270,6 +300,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 +408,38 @@
@Override
public void handleHalEvents(List<VehiclePropValue> values) {
- AudioHalListener listener = null;
+ AudioHalFocusListener focusListener = null;
+ AudioHalVolumeListener volumeListener = null;
+ boolean shouldDispatch = false;
synchronized (this) {
- listener = mListener;
- if (listener == null) {
+ focusListener = mFocusListener;
+ volumeListener = mVolumeListener;
+ shouldDispatch = canDispatchEventLocked();
+ if (!shouldDispatch) {
if (mQueuedEvents == null) {
mQueuedEvents = new LinkedList<VehiclePropValue>();
}
mQueuedEvents.addAll(values);
}
}
- if (listener != null) {
- dispatchEventToListener(listener, values);
+ if (shouldDispatch) {
+ dispatchEventToListener(focusListener, volumeListener, values);
}
}
- private void dispatchEventToListener(AudioHalListener listener, List<VehiclePropValue> values) {
+ private boolean canDispatchEventLocked() {
+ if (mFocusListener == null && isFocusSupported()) {
+ return false;
+ }
+ if (mVolumeListener == null && isAudioVolumeSupported()) {
+ return false;
+ }
+ return true;
+ }
+
+ 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 +449,7 @@
VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_STREAMS);
int externalFocus = v.getInt32Values(
VehicleAudioFocusIndex.VEHICLE_AUDIO_FOCUS_INDEX_EXTERNAL_FOCUS_STATE);
- listener.onFocusChange(focusState, streams, externalFocus);
+ focusListener.onFocusChange(focusState, streams, externalFocus);
} break;
case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME: {
int volume = v.getInt32Values(
@@ -374,20 +458,25 @@
VehicleAudioVolumeIndex.VEHICLE_AUDIO_VOLUME_INDEX_STREAM);
int volumeState = v.getInt32Values(
VehicleAudioVolumeIndex.VEHICLE_AUDIO_VOLUME_INDEX_STATE);
- listener.onVolumeChange(streamNum, volume, volumeState);
+ volumeListener.onVolumeChange(streamNum, volume, volumeState);
} break;
case VehicleNetworkConsts.VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT: {
- //TODO
+ int stream = v.getInt32Values(
+ VehicleAudioVolumeLimitIndex.VEHICLE_AUDIO_VOLUME_LIMIT_INDEX_STREAM);
+ int maxVolume = v.getInt32Values(
+ VehicleAudioVolumeLimitIndex.VEHICLE_AUDIO_VOLUME_LIMIT_INDEX_MAX_VOLUME);
+ volumeListener.onVolumeLimitChange(stream, maxVolume);
} 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);
+ focusListener.onStreamStatusChange(streamNum, state);
} break;
}
}
+ values.clear();
}
@Override