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:
committed by
Android (Google) Code Review
commit
715d08a2e2
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
//--------------------
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
31
media/java/android/media/IMuteAwaitConnectionCallback.aidl
Normal file
31
media/java/android/media/IMuteAwaitConnectionCallback.aidl
Normal 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);
|
||||
}
|
||||
@@ -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*/);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
//==========================================================================================
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user