Merge changes from topic "muteAwaitConnection"

* changes:
  AudioManager: test API for device connection
  AudioManager: add API for muting playback until device connects
This commit is contained in:
TreeHugger Robot
2021-12-08 15:51:23 +00:00
committed by Android (Google) Code Review
9 changed files with 736 additions and 19 deletions

View File

@@ -5403,6 +5403,7 @@ package android.media {
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException;
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForCapturePresetChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener) throws java.lang.SecurityException;
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener) throws java.lang.SecurityException;
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void cancelMuteAwaitConnection(@NonNull android.media.AudioDeviceAttributes) throws java.lang.IllegalStateException;
method public void clearAudioServerStateCallback();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean clearPreferredDevicesForCapturePreset(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
@@ -5416,6 +5417,7 @@ package android.media {
method @IntRange(from=0) public long getMaxAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAttributes getMutingExpectedDevice();
method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAttributes getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForCapturePreset(int);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
@@ -5424,7 +5426,9 @@ package android.media {
method public boolean isAudioServerRunning();
method public boolean isHdmiSystemAudioSupported();
method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void muteAwaitConnection(@NonNull int[], @NonNull android.media.AudioDeviceAttributes, long, @NonNull java.util.concurrent.TimeUnit) throws java.lang.IllegalStateException;
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void registerMuteAwaitConnectionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForCapturePresetChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener);
@@ -5444,6 +5448,7 @@ package android.media {
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setVolumeIndexForAttributes(@NonNull android.media.AudioAttributes, int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicyAsync(@NonNull android.media.audiopolicy.AudioPolicy);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterMuteAwaitConnectionCallback(@NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
method public void unregisterVolumeGroupCallback(@NonNull android.media.AudioManager.VolumeGroupCallback);
field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1
field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4
@@ -5463,6 +5468,15 @@ package android.media {
method public void onAudioServerUp();
}
public abstract static class AudioManager.MuteAwaitConnectionCallback {
ctor public AudioManager.MuteAwaitConnectionCallback();
method public void onMutedUntilConnection(@NonNull android.media.AudioDeviceAttributes, @NonNull int[]);
method public void onUnmutedEvent(int, @NonNull android.media.AudioDeviceAttributes, @NonNull int[]);
field public static final int EVENT_CANCEL = 3; // 0x3
field public static final int EVENT_CONNECTION = 1; // 0x1
field public static final int EVENT_TIMEOUT = 2; // 0x2
}
@Deprecated public static interface AudioManager.OnPreferredDeviceForStrategyChangedListener {
method @Deprecated public void onPreferredDeviceForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @Nullable android.media.AudioDeviceAttributes);
}

View File

@@ -1458,6 +1458,7 @@ package android.media {
method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int requestAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String, int, int);
method public void setRampingRingerEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setTestDeviceConnectionState(@NonNull android.media.AudioDeviceAttributes, boolean);
}
public static final class AudioRecord.MetricsConstants {

View File

@@ -89,6 +89,7 @@ import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* AudioManager provides access to volume and ringer mode control.
@@ -5774,6 +5775,23 @@ public class AudioManager {
}
}
/**
* Indicate wired accessory connection state change.
* @param device {@link AudioDeviceAttributes} of the device to "fake-connect"
* @param connected true for connected, false for disconnected
* {@hide}
*/
@TestApi
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device,
boolean connected) {
try {
getService().setTestDeviceConnectionState(device, connected);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Indicate Bluetooth profile connection state change.
* Configuration changes for A2DP are indicated by having the same <code>newDevice</code> and
@@ -7967,6 +7985,231 @@ public class AudioManager {
}
}
//---------------------------------------------------------
// audio device connection-dependent muting
/**
* @hide
* Mute a set of playback use cases until a given audio device is connected.
* Automatically unmute upon connection of the device, or after the given timeout, whichever
* happens first.
* @param usagesToMute non-empty array of {@link AudioAttributes} usages (for example
* {@link AudioAttributes#USAGE_MEDIA}) to mute until the
* device connects
* @param device the audio device expected to connect within the timeout duration
* @param timeout the maximum amount of time to wait for the device connection
* @param timeUnit the unit for the timeout
* @throws IllegalStateException when trying to issue the command while another is already in
* progress and hasn't been cancelled by
* {@link #cancelMuteAwaitConnection(AudioDeviceAttributes)}. See
* {@link #getMutingExpectedDevice()} to check if a muting command is active.
* @see #registerMuteAwaitConnectionCallback(Executor, AudioManager.MuteAwaitConnectionCallback)
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public void muteAwaitConnection(@NonNull int[] usagesToMute,
@NonNull AudioDeviceAttributes device,
long timeout, @NonNull TimeUnit timeUnit) throws IllegalStateException {
if (timeout <= 0) {
throw new IllegalArgumentException("Timeout must be greater than 0");
}
Objects.requireNonNull(usagesToMute);
if (usagesToMute.length == 0) {
throw new IllegalArgumentException("Array of usages to mute cannot be empty");
}
Objects.requireNonNull(device);
Objects.requireNonNull(timeUnit);
try {
getService().muteAwaitConnection(usagesToMute, device, timeUnit.toMillis(timeout));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* @hide
* Query which audio device, if any, is causing some playback use cases to be muted until it
* connects.
* @return the audio device used in
* {@link #muteAwaitConnection(int[], AudioDeviceAttributes, long, TimeUnit)}, or null
* if there is no active muting command (either because the muting command was not issued
* or because it timed out)
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public @Nullable AudioDeviceAttributes getMutingExpectedDevice() {
try {
return getService().getMutingExpectedDevice();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* @hide
* Cancel a {@link #muteAwaitConnection(int[], AudioDeviceAttributes, long, TimeUnit)}
* command.
* @param device the device whose connection was expected when the {@code muteAwaitConnection}
* command was issued.
* @throws IllegalStateException when trying to issue the command for a device whose connection
* is not anticipated by a previous call to
* {@link #muteAwaitConnection(int[], AudioDeviceAttributes, long, TimeUnit)}
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public void cancelMuteAwaitConnection(@NonNull AudioDeviceAttributes device)
throws IllegalStateException {
Objects.requireNonNull(device);
try {
getService().cancelMuteAwaitConnection(device);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* @hide
* A callback class to receive events about the muting and unmuting of playback use cases
* conditional on the upcoming connection of an audio device.
* @see #registerMuteAwaitConnectionCallback(Executor, AudioManager.MuteAwaitConnectionCallback)
*/
@SystemApi
public abstract static class MuteAwaitConnectionCallback {
/**
* An event where the expected audio device connected
* @see MuteAwaitConnectionCallback#onUnmutedEvent(int, AudioDeviceAttributes, int[])
*/
public static final int EVENT_CONNECTION = 1;
/**
* An event where the expected audio device failed connect before the timeout happened
* @see MuteAwaitConnectionCallback#onUnmutedEvent(int, AudioDeviceAttributes, int[])
*/
public static final int EVENT_TIMEOUT = 2;
/**
* An event where the {@code muteAwaitConnection()} command
* was cancelled with {@link #cancelMuteAwaitConnection(AudioDeviceAttributes)}
* @see MuteAwaitConnectionCallback#onUnmutedEvent(int, AudioDeviceAttributes, int[])
*/
public static final int EVENT_CANCEL = 3;
/** @hide */
@IntDef(flag = false, prefix = "EVENT_", value = {
EVENT_CONNECTION,
EVENT_TIMEOUT,
EVENT_CANCEL }
)
@Retention(RetentionPolicy.SOURCE)
public @interface UnmuteEvent {}
/**
* Called when a number of playback use cases are muted in response to a call to
* {@link #muteAwaitConnection(int[], AudioDeviceAttributes, long, TimeUnit)}.
* @param device the audio device whose connection is expected. Playback use cases are
* unmuted when that device connects
* @param mutedUsages an array of {@link AudioAttributes} usages that describe the affected
* playback use cases.
*/
public void onMutedUntilConnection(
@NonNull AudioDeviceAttributes device,
@NonNull int[] mutedUsages) {}
/**
* Called when an event occurred that caused playback uses cases to be unmuted
* @param unmuteEvent the nature of the event
* @param device the device that was expected to connect
* @param mutedUsages the array of {@link AudioAttributes} usages that were muted until
* the event occurred
*/
public void onUnmutedEvent(
@UnmuteEvent int unmuteEvent,
@NonNull AudioDeviceAttributes device, @NonNull int[] mutedUsages) {}
}
/**
* @hide
* Register a callback to receive updates on the playback muting conditional on a specific
* audio device connection.
* @param executor the {@link Executor} handling the callback
* @param callback the callback to register
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public void registerMuteAwaitConnectionCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull MuteAwaitConnectionCallback callback) {
synchronized (mMuteAwaitConnectionListenerLock) {
final Pair<ArrayList<ListenerInfo<MuteAwaitConnectionCallback>>,
MuteAwaitConnectionDispatcherStub> res =
CallbackUtil.addListener("registerMuteAwaitConnectionCallback",
executor, callback, mMuteAwaitConnectionListeners,
mMuteAwaitConnDispatcherStub,
() -> new MuteAwaitConnectionDispatcherStub(),
stub -> stub.register(true));
mMuteAwaitConnectionListeners = res.first;
mMuteAwaitConnDispatcherStub = res.second;
}
}
/**
* @hide
* Unregister a previously registered callback for playback muting conditional on device
* connection.
* @param callback the callback to unregister
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public void unregisterMuteAwaitConnectionCallback(
@NonNull MuteAwaitConnectionCallback callback) {
synchronized (mMuteAwaitConnectionListenerLock) {
final Pair<ArrayList<ListenerInfo<MuteAwaitConnectionCallback>>,
MuteAwaitConnectionDispatcherStub> res =
CallbackUtil.removeListener("unregisterMuteAwaitConnectionCallback",
callback, mMuteAwaitConnectionListeners, mMuteAwaitConnDispatcherStub,
stub -> stub.register(false));
mMuteAwaitConnectionListeners = res.first;
mMuteAwaitConnDispatcherStub = res.second;
}
}
private final Object mMuteAwaitConnectionListenerLock = new Object();
@GuardedBy("mMuteAwaitConnectionListenerLock")
private @Nullable ArrayList<ListenerInfo<MuteAwaitConnectionCallback>>
mMuteAwaitConnectionListeners;
@GuardedBy("mMuteAwaitConnectionListenerLock")
private MuteAwaitConnectionDispatcherStub mMuteAwaitConnDispatcherStub;
private final class MuteAwaitConnectionDispatcherStub
extends IMuteAwaitConnectionCallback.Stub {
public void register(boolean register) {
try {
getService().registerMuteAwaitConnectionDispatcher(this, register);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@Override
@SuppressLint("GuardedBy") // lock applied inside callListeners method
public void dispatchOnMutedUntilConnection(AudioDeviceAttributes device,
int[] mutedUsages) {
CallbackUtil.callListeners(mMuteAwaitConnectionListeners,
mMuteAwaitConnectionListenerLock,
(listener) -> listener.onMutedUntilConnection(device, mutedUsages));
}
@Override
@SuppressLint("GuardedBy") // lock applied inside callListeners method
public void dispatchOnUnmutedEvent(int event, AudioDeviceAttributes device,
int[] mutedUsages) {
CallbackUtil.callListeners(mMuteAwaitConnectionListeners,
mMuteAwaitConnectionListenerLock,
(listener) -> listener.onUnmutedEvent(event, device, mutedUsages));
}
}
//---------------------------------------------------------
// Inner classes
//--------------------

View File

@@ -32,6 +32,7 @@ import android.media.IAudioRoutesObserver;
import android.media.IAudioServerStateDispatcher;
import android.media.ICapturePresetDevicesRoleDispatcher;
import android.media.ICommunicationDeviceDispatcher;
import android.media.IMuteAwaitConnectionCallback;
import android.media.IPlaybackConfigDispatcher;
import android.media.IRecordingConfigDispatcher;
import android.media.IRingtonePlayer;
@@ -447,4 +448,16 @@ interface IAudioService {
boolean isVolumeFixed();
boolean isPstnCallAudioInterceptable();
oneway void muteAwaitConnection(in int[] usagesToMute, in AudioDeviceAttributes dev,
long timeOutMs);
oneway void cancelMuteAwaitConnection(in AudioDeviceAttributes dev);
AudioDeviceAttributes getMutingExpectedDevice();
void registerMuteAwaitConnectionDispatcher(in IMuteAwaitConnectionCallback cb,
boolean register);
void setTestDeviceConnectionState(in AudioDeviceAttributes device, boolean connected);
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2021 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.media;
import android.media.AudioDeviceAttributes;
/**
* AIDL for the AudioService to signal mute events tied to audio device connections.
*
* {@hide}
*/
oneway interface IMuteAwaitConnectionCallback {
void dispatchOnMutedUntilConnection(in AudioDeviceAttributes device, in int[] mutedUsages);
void dispatchOnUnmutedEvent(int event, in AudioDeviceAttributes device, in int[] mutedUsages);
}

View File

@@ -493,6 +493,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
return isDeviceActiveForCommunication(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
}
/*package*/ boolean isDeviceConnected(@NonNull AudioDeviceAttributes device) {
synchronized (mDeviceStateLock) {
return mDeviceInventory.isDeviceConnected(device);
}
}
/*package*/ void setWiredDeviceConnectionState(int type,
@AudioService.ConnectionState int state, String address, String name,
String caller) {
@@ -502,6 +508,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
}
/*package*/ void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device,
@AudioService.ConnectionState int state) {
synchronized (mDeviceStateLock) {
mDeviceInventory.setTestDeviceConnectionState(device, state);
}
}
/*package*/ static final class BleVolumeInfo {
final int mIndex;
final int mMaxIndex;
@@ -1002,7 +1015,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
/*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
String deviceName) {
synchronized (mDeviceStateLock) {
return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName);
return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName,
false /*for test*/);
}
}

View File

@@ -224,6 +224,7 @@ public class AudioDeviceInventory {
public final String mAddress;
public final String mName;
public final String mCaller;
public boolean mForTest = false;
/*package*/ WiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
String address, String name, String caller) {
@@ -521,7 +522,7 @@ public class AudioDeviceInventory {
}
if (!handleDeviceConnection(wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED,
wdcs.mType, wdcs.mAddress, wdcs.mName)) {
wdcs.mType, wdcs.mAddress, wdcs.mName, wdcs.mForTest)) {
// change of connection state failed, bailout
mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed")
.record();
@@ -593,7 +594,7 @@ public class AudioDeviceInventory {
}
//------------------------------------------------------------
//
// preferred device(s)
/*package*/ int setPreferredDevicesForStrategySync(int strategy,
@NonNull List<AudioDeviceAttributes> devices) {
@@ -674,16 +675,34 @@ public class AudioDeviceInventory {
mDevRoleCapturePresetDispatchers.unregister(dispatcher);
}
//-----------------------------------------------------------------------
/**
* Check if a device is in the list of connected devices
* @param device the device whose connection state is queried
* @return true if connected
*/
// called with AudioDeviceBroker.mDeviceStateLock lock held
public boolean isDeviceConnected(@NonNull AudioDeviceAttributes device) {
final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(),
device.getAddress());
synchronized (mDevicesLock) {
return (mConnectedDevices.get(key) != null);
}
}
/**
* Implements the communication with AudioSystem to (dis)connect a device in the native layers
* @param connect true if connection
* @param device the device type
* @param address the address of the device
* @param deviceName human-readable name of device
* @param isForTesting if true, not calling AudioSystem for the connection as this is
* just for testing
* @return false if an error was reported by AudioSystem
*/
/*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
String deviceName) {
String deviceName, boolean isForTesting) {
if (AudioService.DEBUG_DEVICES) {
Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:"
+ Integer.toHexString(device) + " address:" + address
@@ -706,9 +725,14 @@ public class AudioDeviceInventory {
Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected);
}
if (connect && !isConnected) {
final int res = mAudioSystem.setDeviceConnectionState(device,
AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
AudioSystem.AUDIO_FORMAT_DEFAULT);
final int res;
if (isForTesting) {
res = AudioSystem.AUDIO_STATUS_OK;
} else {
res = mAudioSystem.setDeviceConnectionState(device,
AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
AudioSystem.AUDIO_FORMAT_DEFAULT);
}
if (res != AudioSystem.AUDIO_STATUS_OK) {
final String reason = "not connecting device 0x" + Integer.toHexString(device)
+ " due to command error " + res;
@@ -914,6 +938,15 @@ public class AudioDeviceInventory {
}
}
/*package*/ void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device,
@AudioService.ConnectionState int state) {
final WiredDeviceConnectionState connection = new WiredDeviceConnectionState(
device.getInternalType(), state, device.getAddress(),
"test device", "com.android.server.audio");
connection.mForTest = true;
onSetWiredDeviceConnectionState(connection);
}
//-------------------------------------------------------------------
// Internal utilities

View File

@@ -35,6 +35,7 @@ import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -92,6 +93,7 @@ import android.media.IAudioServerStateDispatcher;
import android.media.IAudioService;
import android.media.ICapturePresetDevicesRoleDispatcher;
import android.media.ICommunicationDeviceDispatcher;
import android.media.IMuteAwaitConnectionCallback;
import android.media.IPlaybackConfigDispatcher;
import android.media.IRecordingConfigDispatcher;
import android.media.IRingtonePlayer;
@@ -1027,7 +1029,8 @@ public class AudioService extends IAudioService.Stub
readUserRestrictions();
mPlaybackMonitor =
new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM],
device -> onMuteAwaitConnectionTimeout(device));
mPlaybackMonitor.registerPlaybackCallback(mVoicePlaybackActivityMonitor, true);
mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
@@ -1048,6 +1051,9 @@ public class AudioService extends IAudioService.Stub
mHasSpatializerEffect = SystemProperties.getBoolean("ro.audio.spatializer_enabled", false);
// monitor routing updates coming from native
mAudioSystem.setRoutingListener(this);
// done with service initialization, continue additional work in our Handler thread
queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_STREAMS_VOLUMES,
0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
@@ -1251,20 +1257,22 @@ public class AudioService extends IAudioService.Stub
// routing monitoring from AudioSystemAdapter
@Override
public void onRoutingUpdatedFromNative() {
if (!mHasSpatializerEffect) {
return;
}
sendMsg(mAudioHandler,
MSG_ROUTING_UPDATED,
SENDMSG_REPLACE, 0, 0, null,
/*delay*/ 0);
}
void monitorRoutingChanges(boolean enabled) {
mAudioSystem.setRoutingListener(enabled ? this : null);
/**
* called when handling MSG_ROUTING_UPDATED
*/
void onRoutingUpdatedFromAudioThread() {
if (mHasSpatializerEffect) {
mSpatializerHelper.onRoutingUpdated();
}
checkMuteAwaitConnection();
}
//-----------------------------------------------------------------
RoleObserver mRoleObserver;
@@ -1452,7 +1460,6 @@ public class AudioService extends IAudioService.Stub
if (mHasSpatializerEffect) {
mSpatializerHelper.reset(/* featureEnabled */ isSpatialAudioEnabled());
monitorRoutingChanges(true);
}
onIndicateSystemReady();
@@ -6357,6 +6364,20 @@ public class AudioService extends IAudioService.Stub
mDeviceBroker.setWiredDeviceConnectionState(type, state, address, name, caller);
}
/** @see AudioManager#setTestDeviceConnectionState(AudioDeviceAttributes, boolean) */
public void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device,
boolean connected) {
Objects.requireNonNull(device);
enforceModifyAudioRoutingPermission();
mDeviceBroker.setTestDeviceConnectionState(device,
connected ? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED);
// simulate a routing update from native
sendMsg(mAudioHandler,
MSG_ROUTING_UPDATED,
SENDMSG_REPLACE, 0, 0, null,
/*delay*/ 0);
}
/**
* @hide
* The states that can be used with AudioService.setBluetoothHearingAidDeviceConnectionState()
@@ -7652,7 +7673,6 @@ public class AudioService extends IAudioService.Stub
mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect);
if (mHasSpatializerEffect) {
mSpatializerHelper.setFeatureEnabled(isSpatialAudioEnabled());
monitorRoutingChanges(true);
}
mAudioEventWakeLock.release();
break;
@@ -7791,7 +7811,7 @@ public class AudioService extends IAudioService.Stub
break;
case MSG_ROUTING_UPDATED:
mSpatializerHelper.onRoutingUpdated();
onRoutingUpdatedFromAudioThread();
break;
case MSG_PERSIST_SPATIAL_AUDIO_ENABLED:
@@ -8611,6 +8631,171 @@ public class AudioService extends IAudioService.Stub
com.android.internal.R.bool.config_camera_sound_forced);
}
//==========================================================================================
private final Object mMuteAwaitConnectionLock = new Object();
/**
* The device that is expected to be connected soon, and causes players to be muted until
* its connection, or it times out.
* Null when no active muting command, or it has timed out.
*/
@GuardedBy("mMuteAwaitConnectionLock")
private AudioDeviceAttributes mMutingExpectedDevice;
@GuardedBy("mMuteAwaitConnectionLock")
private @Nullable int[] mMutedUsagesAwaitingConnection;
/** @see AudioManager#muteAwaitConnection */
@SuppressLint("EmptyCatch") // callback exception caught inside dispatchMuteAwaitConnection
public void muteAwaitConnection(@NonNull int[] usages,
@NonNull AudioDeviceAttributes device, long timeOutMs) {
Objects.requireNonNull(usages);
Objects.requireNonNull(device);
enforceModifyAudioRoutingPermission();
if (timeOutMs <= 0 || usages.length == 0) {
throw new IllegalArgumentException("Invalid timeOutMs/usagesToMute");
}
if (mDeviceBroker.isDeviceConnected(device)) {
// not throwing an exception as there could be a race between a connection (server-side,
// notification of connection in flight) and a mute operation (client-side)
Log.i(TAG, "muteAwaitConnection ignored, device (" + device + ") already connected");
return;
}
synchronized (mMuteAwaitConnectionLock) {
if (mMutingExpectedDevice != null) {
Log.e(TAG, "muteAwaitConnection ignored, another in progress for device:"
+ mMutingExpectedDevice);
throw new IllegalStateException("muteAwaitConnection already in progress");
}
mMutingExpectedDevice = device;
mMutedUsagesAwaitingConnection = usages;
mPlaybackMonitor.muteAwaitConnection(usages, device, timeOutMs);
}
dispatchMuteAwaitConnection(cb -> { try {
cb.dispatchOnMutedUntilConnection(device, usages); } catch (RemoteException e) { } });
}
/** @see AudioManager#getMutingExpectedDevice */
public @Nullable AudioDeviceAttributes getMutingExpectedDevice() {
enforceModifyAudioRoutingPermission();
synchronized (mMuteAwaitConnectionLock) {
return mMutingExpectedDevice;
}
}
/** @see AudioManager#cancelMuteAwaitConnection */
@SuppressLint("EmptyCatch") // callback exception caught inside dispatchMuteAwaitConnection
public void cancelMuteAwaitConnection(@NonNull AudioDeviceAttributes device) {
Objects.requireNonNull(device);
enforceModifyAudioRoutingPermission();
Log.i(TAG, "cancelMuteAwaitConnection for device:" + device);
final int[] mutedUsages;
synchronized (mMuteAwaitConnectionLock) {
if (mMutingExpectedDevice == null) {
// not throwing an exception as there could be a race between a timeout
// (server-side) and a cancel operation (client-side)
Log.i(TAG, "cancelMuteAwaitConnection ignored, no expected device");
return;
}
if (!device.equals(mMutingExpectedDevice)) {
Log.e(TAG, "cancelMuteAwaitConnection ignored, got " + device
+ "] but expected device is" + mMutingExpectedDevice);
throw new IllegalStateException("cancelMuteAwaitConnection for wrong device");
}
mutedUsages = mMutedUsagesAwaitingConnection;
mMutingExpectedDevice = null;
mMutedUsagesAwaitingConnection = null;
mPlaybackMonitor.cancelMuteAwaitConnection();
}
dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent(
AudioManager.MuteAwaitConnectionCallback.EVENT_CANCEL, device, mutedUsages);
} catch (RemoteException e) { } });
}
final RemoteCallbackList<IMuteAwaitConnectionCallback> mMuteAwaitConnectionDispatchers =
new RemoteCallbackList<IMuteAwaitConnectionCallback>();
/** @see AudioManager#registerMuteAwaitConnectionCallback */
public void registerMuteAwaitConnectionDispatcher(@NonNull IMuteAwaitConnectionCallback cb,
boolean register) {
enforceModifyAudioRoutingPermission();
if (register) {
mMuteAwaitConnectionDispatchers.register(cb);
} else {
mMuteAwaitConnectionDispatchers.unregister(cb);
}
}
@SuppressLint("EmptyCatch") // callback exception caught inside dispatchMuteAwaitConnection
void checkMuteAwaitConnection() {
final AudioDeviceAttributes device;
final int[] mutedUsages;
synchronized (mMuteAwaitConnectionLock) {
if (mMutingExpectedDevice == null) {
return;
}
device = mMutingExpectedDevice;
mutedUsages = mMutedUsagesAwaitingConnection;
if (!mDeviceBroker.isDeviceConnected(device)) {
return;
}
mMutingExpectedDevice = null;
mMutedUsagesAwaitingConnection = null;
Log.i(TAG, "muteAwaitConnection device " + device + " connected, unmuting");
mPlaybackMonitor.cancelMuteAwaitConnection();
}
dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent(
AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION, device, mutedUsages);
} catch (RemoteException e) { } });
}
/**
* Called by PlaybackActivityMonitor when the timeout hit for the mute on device connection
*/
@SuppressLint("EmptyCatch") // callback exception caught inside dispatchMuteAwaitConnection
void onMuteAwaitConnectionTimeout(@NonNull AudioDeviceAttributes timedOutDevice) {
final int[] mutedUsages;
synchronized (mMuteAwaitConnectionLock) {
if (!timedOutDevice.equals(mMutingExpectedDevice)) {
return;
}
Log.i(TAG, "muteAwaitConnection timeout, clearing expected device "
+ mMutingExpectedDevice);
mutedUsages = mMutedUsagesAwaitingConnection;
mMutingExpectedDevice = null;
mMutedUsagesAwaitingConnection = null;
}
dispatchMuteAwaitConnection(cb -> { try {
cb.dispatchOnUnmutedEvent(
AudioManager.MuteAwaitConnectionCallback.EVENT_TIMEOUT,
timedOutDevice, mutedUsages);
} catch (RemoteException e) { } });
}
private void dispatchMuteAwaitConnection(
java.util.function.Consumer<IMuteAwaitConnectionCallback> callback) {
final int nbDispatchers = mMuteAwaitConnectionDispatchers.beginBroadcast();
// lazy initialization as errors unlikely
ArrayList<IMuteAwaitConnectionCallback> errorList = null;
for (int i = 0; i < nbDispatchers; i++) {
try {
callback.accept(mMuteAwaitConnectionDispatchers.getBroadcastItem(i));
} catch (Exception e) {
if (errorList == null) {
errorList = new ArrayList<>(1);
}
errorList.add(mMuteAwaitConnectionDispatchers.getBroadcastItem(i));
}
}
if (errorList != null) {
for (IMuteAwaitConnectionCallback errorItem : errorList) {
mMuteAwaitConnectionDispatchers.unregister(errorItem);
}
}
mMuteAwaitConnectionDispatchers.finishBroadcast();
}
//==========================================================================================
// Device orientation
//==========================================================================================

View File

@@ -17,9 +17,11 @@
package com.android.server.audio;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
import android.media.AudioSystem;
@@ -27,21 +29,27 @@ import android.media.IPlaybackConfigDispatcher;
import android.media.PlayerBase;
import android.media.VolumeShaper;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
/**
* Class to receive and dispatch updates from AudioSystem about recording configurations.
@@ -54,6 +62,7 @@ public final class PlaybackActivityMonitor
/*package*/ static final boolean DEBUG = false;
/*package*/ static final int VOLUME_SHAPER_SYSTEM_DUCK_ID = 1;
/*package*/ static final int VOLUME_SHAPER_SYSTEM_FADEOUT_ID = 2;
/*package*/ static final int VOLUME_SHAPER_SYSTEM_MUTE_AWAIT_CONNECTION_ID = 3;
private static final VolumeShaper.Configuration DUCK_VSHAPE =
new VolumeShaper.Configuration.Builder()
@@ -73,6 +82,18 @@ public final class PlaybackActivityMonitor
.createIfNeeded()
.build();
private static final long UNMUTE_DURATION_MS = 100;
private static final VolumeShaper.Configuration MUTE_AWAIT_CONNECTION_VSHAPE =
new VolumeShaper.Configuration.Builder()
.setId(VOLUME_SHAPER_SYSTEM_MUTE_AWAIT_CONNECTION_ID)
.setCurve(new float[] { 0.f, 1.f } /* times */,
new float[] { 1.f, 0.f } /* volumes */)
.setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
// even though we specify a duration, it's only used for the unmute,
// for muting this volume shaper is run with PLAY_SKIP_RAMP
.setDuration(UNMUTE_DURATION_MS)
.build();
// TODO support VolumeShaper on those players
private static final int[] UNDUCKABLE_PLAYER_TYPES = {
AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO,
@@ -90,6 +111,7 @@ public final class PlaybackActivityMonitor
private boolean mHasPublicClients = false;
private final Object mPlayerLock = new Object();
@GuardedBy("mPlayerLock")
private final HashMap<Integer, AudioPlaybackConfiguration> mPlayers =
new HashMap<Integer, AudioPlaybackConfiguration>();
@@ -97,12 +119,16 @@ public final class PlaybackActivityMonitor
private int mSavedAlarmVolume = -1;
private final int mMaxAlarmVolume;
private int mPrivilegedAlarmActiveCount = 0;
private final Consumer<AudioDeviceAttributes> mMuteAwaitConnectionTimeoutCb;
PlaybackActivityMonitor(Context context, int maxAlarmVolume) {
PlaybackActivityMonitor(Context context, int maxAlarmVolume,
Consumer<AudioDeviceAttributes> muteTimeoutCallback) {
mContext = context;
mMaxAlarmVolume = maxAlarmVolume;
PlayMonitorClient.sListenerDeathMonitor = this;
AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
mMuteAwaitConnectionTimeoutCb = muteTimeoutCallback;
initEventHandler();
}
//=================================================================
@@ -170,6 +196,7 @@ public final class PlaybackActivityMonitor
sEventLogger.log(new NewPlayerEvent(apc));
synchronized(mPlayerLock) {
mPlayers.put(newPiid, apc);
maybeMutePlayerAwaitingConnection(apc);
}
return newPiid;
}
@@ -323,6 +350,7 @@ public final class PlaybackActivityMonitor
mPlayers.remove(new Integer(piid));
mDuckingManager.removeReleased(apc);
mFadingManager.removeReleased(apc);
mMutedPlayersAwaitingConnection.remove(Integer.valueOf(piid));
checkVolumeForPrivilegedAlarm(apc, AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
change = apc.handleStateEvent(AudioPlaybackConfiguration.PLAYER_STATE_RELEASED,
AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID);
@@ -451,7 +479,7 @@ public final class PlaybackActivityMonitor
pw.println("\n faded out players piids:");
mFadingManager.dump(pw);
// players muted due to the device ringing or being in a call
pw.print("\n muted player piids:");
pw.print("\n muted player piids due to call/ring:");
for (int piid : mMutedPlayers) {
pw.print(" " + piid);
}
@@ -462,6 +490,12 @@ public final class PlaybackActivityMonitor
pw.print(" " + uid);
}
pw.println("\n");
// muted players:
pw.print("\n muted players (piids) awaiting device connection: BL3 ####");
for (int piid : mMutedPlayersAwaitingConnection) {
pw.print(" " + piid);
}
pw.println("\n");
// log
sEventLogger.dump(pw);
}
@@ -1100,6 +1134,155 @@ public final class PlaybackActivityMonitor
}
}
private static final class MuteAwaitConnectionEvent extends AudioEventLogger.Event {
private final @NonNull int[] mUsagesToMute;
MuteAwaitConnectionEvent(@NonNull int[] usagesToMute) {
mUsagesToMute = usagesToMute;
}
@Override
public String eventToString() {
return "muteAwaitConnection muting usages " + Arrays.toString(mUsagesToMute);
}
}
static final AudioEventLogger sEventLogger = new AudioEventLogger(100,
"playback activity as reported through PlayerBase");
//==========================================================================================
// Mute conditional on device connection
//==========================================================================================
void muteAwaitConnection(@NonNull int[] usagesToMute,
@NonNull AudioDeviceAttributes dev, long timeOutMs) {
synchronized (mPlayerLock) {
mutePlayersExpectingDevice(usagesToMute);
// schedule timeout (remove previously scheduled first)
mEventHandler.removeMessages(MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION);
mEventHandler.sendMessageDelayed(
mEventHandler.obtainMessage(MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION, dev),
timeOutMs);
}
}
void cancelMuteAwaitConnection() {
synchronized (mPlayerLock) {
// cancel scheduled timeout, ignore device, only one expected device at a time
mEventHandler.removeMessages(MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION);
// unmute immediately
unmutePlayersExpectingDevice();
}
}
/**
* List of the piids of the players that are muted until a specific audio device connects
*/
@GuardedBy("mPlayerLock")
private final ArrayList<Integer> mMutedPlayersAwaitingConnection = new ArrayList<Integer>();
/**
* List of AudioAttributes usages to mute until a specific audio device connects
*/
@GuardedBy("mPlayerLock")
private @Nullable int[] mMutedUsagesAwaitingConnection = null;
@GuardedBy("mPlayerLock")
private void mutePlayersExpectingDevice(@NonNull int[] usagesToMute) {
sEventLogger.log(new MuteAwaitConnectionEvent(usagesToMute));
mMutedUsagesAwaitingConnection = usagesToMute;
final Set<Integer> piidSet = mPlayers.keySet();
final Iterator<Integer> piidIterator = piidSet.iterator();
// find which players to mute
while (piidIterator.hasNext()) {
final Integer piid = piidIterator.next();
final AudioPlaybackConfiguration apc = mPlayers.get(piid);
if (apc == null) {
continue;
}
maybeMutePlayerAwaitingConnection(apc);
}
}
@GuardedBy("mPlayerLock")
private void maybeMutePlayerAwaitingConnection(@NonNull AudioPlaybackConfiguration apc) {
if (mMutedUsagesAwaitingConnection == null) {
return;
}
for (int usage : mMutedUsagesAwaitingConnection) {
if (usage == apc.getAudioAttributes().getUsage()) {
try {
sEventLogger.log((new AudioEventLogger.StringEvent(
"awaiting connection: muting piid:"
+ apc.getPlayerInterfaceId()
+ " uid:" + apc.getClientUid())).printLog(TAG));
apc.getPlayerProxy().applyVolumeShaper(
MUTE_AWAIT_CONNECTION_VSHAPE,
PLAY_CREATE_IF_NEEDED);
mMutedPlayersAwaitingConnection.add(apc.getPlayerInterfaceId());
} catch (Exception e) {
Log.e(TAG, "awaiting connection: error muting player "
+ apc.getPlayerInterfaceId(), e);
}
}
}
}
@GuardedBy("mPlayerLock")
private void unmutePlayersExpectingDevice() {
if (mMutedPlayersAwaitingConnection.isEmpty()) {
return;
}
for (int piid : mMutedPlayersAwaitingConnection) {
final AudioPlaybackConfiguration apc = mPlayers.get(piid);
if (apc == null) {
continue;
}
try {
sEventLogger.log(new AudioEventLogger.StringEvent(
"unmuting piid:" + piid).printLog(TAG));
apc.getPlayerProxy().applyVolumeShaper(MUTE_AWAIT_CONNECTION_VSHAPE,
VolumeShaper.Operation.REVERSE);
} catch (Exception e) {
Log.e(TAG, "Error unmuting player " + piid + " uid:"
+ apc.getClientUid(), e);
}
}
mMutedPlayersAwaitingConnection.clear();
mMutedUsagesAwaitingConnection = null;
}
//=================================================================
// Message handling
private Handler mEventHandler;
private HandlerThread mEventThread;
/**
* timeout for a mute awaiting a device connection
* args:
* msg.obj: the audio device being expected
* type: AudioDeviceAttributes
*/
private static final int MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION = 1;
private void initEventHandler() {
mEventThread = new HandlerThread(TAG);
mEventThread.start();
mEventHandler = new Handler(mEventThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION:
Log.i(TAG, "Timeout for muting waiting for "
+ (AudioDeviceAttributes) msg.obj + ", unmuting");
synchronized (mPlayerLock) {
unmutePlayersExpectingDevice();
}
mMuteAwaitConnectionTimeoutCb.accept((AudioDeviceAttributes) msg.obj);
break;
default:
break;
}
}
};
}
}