Merge "AudioService: new interface for absolute volume control"
This commit is contained in:
committed by
Android (Google) Code Review
commit
a22c7e6be6
192
media/java/android/media/AudioDeviceVolumeManager.java
Normal file
192
media/java/android/media/AudioDeviceVolumeManager.java
Normal file
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.annotation.CallbackExecutor;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.content.Context;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* AudioDeviceVolumeManager provides access to audio device volume control.
|
||||
*/
|
||||
public class AudioDeviceVolumeManager {
|
||||
|
||||
// define when using Log.*
|
||||
//private static final String TAG = "AudioDeviceVolumeManager";
|
||||
private static IAudioService sService;
|
||||
|
||||
private final String mPackageName;
|
||||
|
||||
public AudioDeviceVolumeManager(Context context) {
|
||||
mPackageName = context.getApplicationContext().getOpPackageName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Interface to receive volume changes on a device that behaves in absolute volume mode.
|
||||
* @see #setDeviceAbsoluteMultiVolumeBehavior(AudioDeviceAttributes, List, Executor,
|
||||
* OnAudioDeviceVolumeChangeListener)
|
||||
* @see #setDeviceAbsoluteVolumeBehavior(AudioDeviceAttributes, VolumeInfo, Executor,
|
||||
* OnAudioDeviceVolumeChangeListener)
|
||||
*/
|
||||
public interface OnAudioDeviceVolumeChangedListener {
|
||||
/**
|
||||
* Called the device for the given audio device has changed.
|
||||
* @param device the audio device whose volume has changed
|
||||
* @param vol the new volume for the device
|
||||
*/
|
||||
void onAudioDeviceVolumeChanged(
|
||||
@NonNull AudioDeviceAttributes device,
|
||||
@NonNull VolumeInfo vol);
|
||||
}
|
||||
|
||||
static class ListenerInfo {
|
||||
final @NonNull OnAudioDeviceVolumeChangedListener mListener;
|
||||
final @NonNull Executor mExecutor;
|
||||
final @NonNull AudioDeviceAttributes mDevice;
|
||||
|
||||
ListenerInfo(@NonNull OnAudioDeviceVolumeChangedListener listener, @NonNull Executor exe,
|
||||
@NonNull AudioDeviceAttributes device) {
|
||||
mListener = listener;
|
||||
mExecutor = exe;
|
||||
mDevice = device;
|
||||
}
|
||||
}
|
||||
|
||||
private final Object mDeviceVolumeListenerLock = new Object();
|
||||
/**
|
||||
* List of listeners for volume changes, the associated device, and their associated Executor.
|
||||
* List is lazy-initialized on first registration
|
||||
*/
|
||||
@GuardedBy("mDeviceVolumeListenerLock")
|
||||
private @Nullable ArrayList<ListenerInfo> mDeviceVolumeListeners;
|
||||
|
||||
@GuardedBy("mDeviceVolumeListenerLock")
|
||||
private DeviceVolumeDispatcherStub mDeviceVolumeDispatcherStub;
|
||||
|
||||
final class DeviceVolumeDispatcherStub extends IAudioDeviceVolumeDispatcher.Stub {
|
||||
/**
|
||||
* Register / unregister the stub
|
||||
* @param register true for registering, false for unregistering
|
||||
* @param device device for which volume is monitored
|
||||
*/
|
||||
public void register(boolean register, @NonNull AudioDeviceAttributes device,
|
||||
@NonNull List<VolumeInfo> volumes) {
|
||||
try {
|
||||
getService().registerDeviceVolumeDispatcherForAbsoluteVolume(register,
|
||||
this, mPackageName,
|
||||
Objects.requireNonNull(device), Objects.requireNonNull(volumes));
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchDeviceVolumeChanged(
|
||||
@NonNull AudioDeviceAttributes device, @NonNull VolumeInfo vol) {
|
||||
final ArrayList<ListenerInfo> volumeListeners;
|
||||
synchronized (mDeviceVolumeListenerLock) {
|
||||
volumeListeners = (ArrayList<ListenerInfo>) mDeviceVolumeListeners.clone();
|
||||
}
|
||||
for (ListenerInfo listenerInfo : volumeListeners) {
|
||||
if (listenerInfo.mDevice.equals(device)) {
|
||||
listenerInfo.mExecutor.execute(
|
||||
() -> listenerInfo.mListener.onAudioDeviceVolumeChanged(device, vol));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Configures a device to use absolute volume model, and registers a listener for receiving
|
||||
* volume updates to apply on that device
|
||||
* @param device the audio device set to absolute volume mode
|
||||
* @param volume the type of volume this device responds to
|
||||
* @param executor the Executor used for receiving volume updates through the listener
|
||||
* @param vclistener the callback for volume updates
|
||||
*/
|
||||
@RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED })
|
||||
public void setDeviceAbsoluteVolumeBehavior(
|
||||
@NonNull AudioDeviceAttributes device,
|
||||
@NonNull VolumeInfo volume,
|
||||
@NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull OnAudioDeviceVolumeChangedListener vclistener) {
|
||||
final ArrayList<VolumeInfo> volumes = new ArrayList<>(1);
|
||||
volumes.add(volume);
|
||||
setDeviceAbsoluteMultiVolumeBehavior(device, volumes, executor, vclistener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Configures a device to use absolute volume model applied to different volume types, and
|
||||
* registers a listener for receiving volume updates to apply on that device
|
||||
* @param device the audio device set to absolute multi-volume mode
|
||||
* @param volumes the list of volumes the given device responds to
|
||||
* @param executor the Executor used for receiving volume updates through the listener
|
||||
* @param vclistener the callback for volume updates
|
||||
*/
|
||||
@RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED })
|
||||
public void setDeviceAbsoluteMultiVolumeBehavior(
|
||||
@NonNull AudioDeviceAttributes device,
|
||||
@NonNull List<VolumeInfo> volumes,
|
||||
@NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull OnAudioDeviceVolumeChangedListener vclistener) {
|
||||
Objects.requireNonNull(device);
|
||||
Objects.requireNonNull(volumes);
|
||||
Objects.requireNonNull(executor);
|
||||
Objects.requireNonNull(vclistener);
|
||||
|
||||
// TODO verify not already registered
|
||||
//final ListenerInfo listenerInfo = new ListenerInfo(vclistener, executor, device);
|
||||
synchronized (mDeviceVolumeListenerLock) {
|
||||
if (mDeviceVolumeListeners == null) {
|
||||
mDeviceVolumeListeners = new ArrayList<>();
|
||||
}
|
||||
if (mDeviceVolumeListeners.size() == 0) {
|
||||
if (mDeviceVolumeDispatcherStub == null) {
|
||||
mDeviceVolumeDispatcherStub = new DeviceVolumeDispatcherStub();
|
||||
}
|
||||
}
|
||||
mDeviceVolumeDispatcherStub.register(true, device, volumes);
|
||||
}
|
||||
}
|
||||
|
||||
private static IAudioService getService() {
|
||||
if (sService != null) {
|
||||
return sService;
|
||||
}
|
||||
IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
|
||||
sService = IAudioService.Stub.asInterface(b);
|
||||
return sService;
|
||||
}
|
||||
}
|
||||
31
media/java/android/media/IAudioDeviceVolumeDispatcher.aidl
Normal file
31
media/java/android/media/IAudioDeviceVolumeDispatcher.aidl
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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;
|
||||
import android.media.VolumeInfo;
|
||||
|
||||
/**
|
||||
* AIDL for the AudioService to signal audio device volume changes.
|
||||
*
|
||||
* {@hide}
|
||||
*/
|
||||
oneway interface IAudioDeviceVolumeDispatcher {
|
||||
|
||||
void dispatchDeviceVolumeChanged(in AudioDeviceAttributes device, in VolumeInfo vol);
|
||||
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import android.media.AudioPlaybackConfiguration;
|
||||
import android.media.AudioRecordingConfiguration;
|
||||
import android.media.AudioRoutesInfo;
|
||||
import android.media.BluetoothProfileConnectionInfo;
|
||||
import android.media.IAudioDeviceVolumeDispatcher;
|
||||
import android.media.IAudioFocusDispatcher;
|
||||
import android.media.IAudioModeDispatcher;
|
||||
import android.media.IAudioRoutesObserver;
|
||||
@@ -44,6 +45,7 @@ import android.media.ISpatializerOutputCallback;
|
||||
import android.media.IVolumeController;
|
||||
import android.media.IVolumeController;
|
||||
import android.media.PlayerBase;
|
||||
import android.media.VolumeInfo;
|
||||
import android.media.VolumePolicy;
|
||||
import android.media.audiopolicy.AudioPolicyConfig;
|
||||
import android.media.audiopolicy.AudioProductStrategy;
|
||||
@@ -456,6 +458,8 @@ interface IAudioService {
|
||||
|
||||
boolean isVolumeFixed();
|
||||
|
||||
VolumeInfo getDefaultVolumeInfo();
|
||||
|
||||
boolean isPstnCallAudioInterceptable();
|
||||
|
||||
oneway void muteAwaitConnection(in int[] usagesToMute, in AudioDeviceAttributes dev,
|
||||
@@ -474,6 +478,7 @@ interface IAudioService {
|
||||
|
||||
boolean sendFocusLoss(in AudioFocusInfo focusLoser, in IAudioPolicyCallback apcb);
|
||||
|
||||
|
||||
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)")
|
||||
void addAssistantServicesUids(in int[] assistantUID);
|
||||
|
||||
@@ -488,4 +493,10 @@ interface IAudioService {
|
||||
|
||||
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)")
|
||||
int[] getActiveAssistantServiceUids();
|
||||
|
||||
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)")
|
||||
void registerDeviceVolumeDispatcherForAbsoluteVolume(boolean register,
|
||||
in IAudioDeviceVolumeDispatcher cb,
|
||||
in String packageName,
|
||||
in AudioDeviceAttributes device, in List<VolumeInfo> volumes);
|
||||
}
|
||||
|
||||
18
media/java/android/media/VolumeInfo.aidl
Normal file
18
media/java/android/media/VolumeInfo.aidl
Normal file
@@ -0,0 +1,18 @@
|
||||
/* Copyright 2022, 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;
|
||||
|
||||
parcelable VolumeInfo;
|
||||
380
media/java/android/media/VolumeInfo.java
Normal file
380
media/java/android/media/VolumeInfo.java
Normal file
@@ -0,0 +1,380 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.media.audiopolicy.AudioVolumeGroup;
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* A class to represent type of volume information.
|
||||
* Can be used to represent volume associated with a stream type or {@link AudioVolumeGroup}.
|
||||
* Volume index is optional when used to represent a category of volume.
|
||||
* Index ranges are supported too, making the representation of volume changes agnostic to the
|
||||
* range (e.g. can be used to map BT A2DP absolute volume range to internal range).
|
||||
*
|
||||
* Note: this class is not yet part of the SystemApi but is intended to be gradually introduced
|
||||
* particularly in parts of the audio framework that suffer from code ambiguity when
|
||||
* dealing with different volume ranges / units.
|
||||
*/
|
||||
public final class VolumeInfo implements Parcelable {
|
||||
private static final String TAG = "VolumeInfo";
|
||||
|
||||
private final boolean mUsesStreamType; // false implies AudioVolumeGroup is used
|
||||
private final boolean mIsMuted;
|
||||
private final int mVolIndex;
|
||||
private final int mMinVolIndex;
|
||||
private final int mMaxVolIndex;
|
||||
private final int mVolGroupId;
|
||||
private final int mStreamType;
|
||||
|
||||
private static IAudioService sService;
|
||||
private static VolumeInfo sDefaultVolumeInfo;
|
||||
|
||||
private VolumeInfo(boolean usesStreamType, boolean isMuted, int volIndex,
|
||||
int minVolIndex, int maxVolIndex,
|
||||
int volGroupId, int streamType) {
|
||||
mUsesStreamType = usesStreamType;
|
||||
mIsMuted = isMuted;
|
||||
mVolIndex = volIndex;
|
||||
mMinVolIndex = minVolIndex;
|
||||
mMaxVolIndex = maxVolIndex;
|
||||
mVolGroupId = volGroupId;
|
||||
mStreamType = streamType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this instance has a stream type associated to it.
|
||||
* Note this method returning true implies {@link #hasVolumeGroup()} returns false.
|
||||
* (e.g. {@link AudioManager#STREAM_MUSIC}).
|
||||
* @return true if it has stream type information
|
||||
*/
|
||||
public boolean hasStreamType() {
|
||||
return mUsesStreamType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the associated stream type, or will throw if {@link #hasStreamType()} returned false.
|
||||
* @return a stream type value, see AudioManager.STREAM_*
|
||||
*/
|
||||
public int getStreamType() {
|
||||
if (!mUsesStreamType) {
|
||||
throw new IllegalStateException("VolumeInfo doesn't use stream types");
|
||||
}
|
||||
return mStreamType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this instance has a {@link AudioVolumeGroup} associated to it.
|
||||
* Note this method returning true implies {@link #hasStreamType()} returns false.
|
||||
* @return true if it has volume group information
|
||||
*/
|
||||
public boolean hasVolumeGroup() {
|
||||
return !mUsesStreamType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the associated volume group, or will throw if {@link #hasVolumeGroup()} returned
|
||||
* false.
|
||||
* @return the volume group corresponding to this VolumeInfo, or null if an error occurred
|
||||
* in the volume group management
|
||||
*/
|
||||
public @Nullable AudioVolumeGroup getVolumeGroup() {
|
||||
if (mUsesStreamType) {
|
||||
throw new IllegalStateException("VolumeInfo doesn't use AudioVolumeGroup");
|
||||
}
|
||||
List<AudioVolumeGroup> volGroups = AudioVolumeGroup.getAudioVolumeGroups();
|
||||
for (AudioVolumeGroup group : volGroups) {
|
||||
if (group.getId() == mVolGroupId) {
|
||||
return group;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this instance is conveying a mute state.
|
||||
* @return true if the volume state is muted
|
||||
*/
|
||||
public boolean isMuted() {
|
||||
return mIsMuted;
|
||||
}
|
||||
|
||||
/**
|
||||
* A value used to express no volume index has been set.
|
||||
*/
|
||||
public static final int INDEX_NOT_SET = -100;
|
||||
|
||||
/**
|
||||
* Returns the volume index.
|
||||
* @return a volume index, or {@link #INDEX_NOT_SET} if no index was set, in which case this
|
||||
* instance is used to express a volume representation type (stream vs group) and
|
||||
* optionally its volume range
|
||||
*/
|
||||
public int getVolumeIndex() {
|
||||
return mVolIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum volume index.
|
||||
* @return the minimum volume index, or {@link #INDEX_NOT_SET} if no minimum index was set.
|
||||
*/
|
||||
public int getMinVolumeIndex() {
|
||||
return mMinVolIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum volume index.
|
||||
* @return the maximum volume index, or {@link #INDEX_NOT_SET} if no maximum index was
|
||||
* set.
|
||||
*/
|
||||
public int getMaxVolumeIndex() {
|
||||
return mMaxVolIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default info for the platform, typically initialized
|
||||
* to STREAM_MUSIC with min/max initialized to the associated range
|
||||
* @return the default VolumeInfo for the device
|
||||
*/
|
||||
public static @NonNull VolumeInfo getDefaultVolumeInfo() {
|
||||
if (sService == null) {
|
||||
IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
|
||||
sService = IAudioService.Stub.asInterface(b);
|
||||
}
|
||||
if (sDefaultVolumeInfo == null) {
|
||||
try {
|
||||
sDefaultVolumeInfo = sService.getDefaultVolumeInfo();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Error calling getDefaultVolumeInfo", e);
|
||||
// return a valid value, but don't cache it
|
||||
return new VolumeInfo.Builder(AudioManager.STREAM_MUSIC).build();
|
||||
}
|
||||
}
|
||||
return sDefaultVolumeInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* The builder class for creating and initializing, or copying and modifying VolumeInfo
|
||||
* instances
|
||||
*/
|
||||
public static final class Builder {
|
||||
private boolean mUsesStreamType = true; // false implies AudioVolumeGroup is used
|
||||
private int mStreamType = AudioManager.STREAM_MUSIC;
|
||||
private boolean mIsMuted = false;
|
||||
private int mVolIndex = INDEX_NOT_SET;
|
||||
private int mMinVolIndex = INDEX_NOT_SET;
|
||||
private int mMaxVolIndex = INDEX_NOT_SET;
|
||||
private int mVolGroupId = -Integer.MIN_VALUE;
|
||||
|
||||
/**
|
||||
* Builder constructor for stream type-based VolumeInfo
|
||||
*/
|
||||
public Builder(int streamType) {
|
||||
// TODO validate stream type
|
||||
mUsesStreamType = true;
|
||||
mStreamType = streamType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder constructor for volume group-based VolumeInfo
|
||||
*/
|
||||
public Builder(@NonNull AudioVolumeGroup volGroup) {
|
||||
Objects.requireNonNull(volGroup);
|
||||
mUsesStreamType = false;
|
||||
mStreamType = -Integer.MIN_VALUE;
|
||||
mVolGroupId = volGroup.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder constructor to copy a given VolumeInfo.
|
||||
* Note you can't change the stream type or volume group later.
|
||||
*/
|
||||
public Builder(@NonNull VolumeInfo info) {
|
||||
Objects.requireNonNull(info);
|
||||
mUsesStreamType = info.mUsesStreamType;
|
||||
mStreamType = info.mStreamType;
|
||||
mIsMuted = info.mIsMuted;
|
||||
mVolIndex = info.mVolIndex;
|
||||
mMinVolIndex = info.mMinVolIndex;
|
||||
mMaxVolIndex = info.mMaxVolIndex;
|
||||
mVolGroupId = info.mVolGroupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the volume is in a muted state
|
||||
* @param isMuted
|
||||
* @return the same builder instance
|
||||
*/
|
||||
public @NonNull Builder setMuted(boolean isMuted) {
|
||||
mIsMuted = isMuted;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the volume index
|
||||
* @param volIndex a 0 or greater value, or {@link #INDEX_NOT_SET} if unknown
|
||||
* @return the same builder instance
|
||||
*/
|
||||
// TODO should we allow muted true + volume index set? (useful when toggling mute on/off?)
|
||||
public @NonNull Builder setVolumeIndex(int volIndex) {
|
||||
if (volIndex != INDEX_NOT_SET && volIndex < 0) {
|
||||
throw new IllegalArgumentException("Volume index cannot be negative");
|
||||
}
|
||||
mVolIndex = volIndex;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum volume index
|
||||
* @param minIndex a 0 or greater value, or {@link #INDEX_NOT_SET} if unknown
|
||||
* @return the same builder instance
|
||||
*/
|
||||
public @NonNull Builder setMinVolumeIndex(int minIndex) {
|
||||
if (minIndex != INDEX_NOT_SET && minIndex < 0) {
|
||||
throw new IllegalArgumentException("Min volume index cannot be negative");
|
||||
}
|
||||
mMinVolIndex = minIndex;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum volume index
|
||||
* @param maxIndex a 0 or greater value, or {@link #INDEX_NOT_SET} if unknown
|
||||
* @return the same builder instance
|
||||
*/
|
||||
public @NonNull Builder setMaxVolumeIndex(int maxIndex) {
|
||||
if (maxIndex != INDEX_NOT_SET && maxIndex < 0) {
|
||||
throw new IllegalArgumentException("Max volume index cannot be negative");
|
||||
}
|
||||
mMaxVolIndex = maxIndex;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the VolumeInfo with the data given to the builder
|
||||
* @return the new VolumeInfo instance
|
||||
*/
|
||||
public @NonNull VolumeInfo build() {
|
||||
if (mVolIndex != INDEX_NOT_SET) {
|
||||
if (mMinVolIndex != INDEX_NOT_SET && mVolIndex < mMinVolIndex) {
|
||||
throw new IllegalArgumentException("Volume index:" + mVolIndex
|
||||
+ " lower than min index:" + mMinVolIndex);
|
||||
}
|
||||
if (mMaxVolIndex != INDEX_NOT_SET && mVolIndex > mMaxVolIndex) {
|
||||
throw new IllegalArgumentException("Volume index:" + mVolIndex
|
||||
+ " greater than max index:" + mMaxVolIndex);
|
||||
}
|
||||
}
|
||||
if (mMinVolIndex != INDEX_NOT_SET && mMaxVolIndex != INDEX_NOT_SET
|
||||
&& mMinVolIndex > mMaxVolIndex) {
|
||||
throw new IllegalArgumentException("Min volume index:" + mMinVolIndex
|
||||
+ " greater than max index:" + mMaxVolIndex);
|
||||
}
|
||||
return new VolumeInfo(mUsesStreamType, mIsMuted,
|
||||
mVolIndex, mMinVolIndex, mMaxVolIndex,
|
||||
mVolGroupId, mStreamType);
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------
|
||||
// Parcelable
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mUsesStreamType, mStreamType, mIsMuted,
|
||||
mVolIndex, mMinVolIndex, mMaxVolIndex, mVolGroupId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
VolumeInfo that = (VolumeInfo) o;
|
||||
return ((mUsesStreamType == that.mUsesStreamType)
|
||||
&& (mStreamType == that.mStreamType)
|
||||
&& (mIsMuted == that.mIsMuted)
|
||||
&& (mVolIndex == that.mVolIndex)
|
||||
&& (mMinVolIndex == that.mMinVolIndex)
|
||||
&& (mMaxVolIndex == that.mMaxVolIndex)
|
||||
&& (mVolGroupId == that.mVolGroupId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new String("VolumeInfo:"
|
||||
+ (mUsesStreamType ? (" streamType:" + mStreamType)
|
||||
: (" volGroupId" + mVolGroupId))
|
||||
+ " muted:" + mIsMuted
|
||||
+ ((mVolIndex != INDEX_NOT_SET) ? (" volIndex:" + mVolIndex) : "")
|
||||
+ ((mMinVolIndex != INDEX_NOT_SET) ? (" min:" + mMinVolIndex) : "")
|
||||
+ ((mMaxVolIndex != INDEX_NOT_SET) ? (" max:" + mMaxVolIndex) : ""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeBoolean(mUsesStreamType);
|
||||
dest.writeInt(mStreamType);
|
||||
dest.writeBoolean(mIsMuted);
|
||||
dest.writeInt(mVolIndex);
|
||||
dest.writeInt(mMinVolIndex);
|
||||
dest.writeInt(mMaxVolIndex);
|
||||
dest.writeInt(mVolGroupId);
|
||||
}
|
||||
|
||||
private VolumeInfo(@NonNull Parcel in) {
|
||||
mUsesStreamType = in.readBoolean();
|
||||
mStreamType = in.readInt();
|
||||
mIsMuted = in.readBoolean();
|
||||
mVolIndex = in.readInt();
|
||||
mMinVolIndex = in.readInt();
|
||||
mMaxVolIndex = in.readInt();
|
||||
mVolGroupId = in.readInt();
|
||||
}
|
||||
|
||||
public static final @NonNull Parcelable.Creator<VolumeInfo> CREATOR =
|
||||
new Parcelable.Creator<VolumeInfo>() {
|
||||
/**
|
||||
* Rebuilds a VolumeInfo previously stored with writeToParcel().
|
||||
* @param p Parcel object to read the VolumeInfo from
|
||||
* @return a new VolumeInfo created from the data in the parcel
|
||||
*/
|
||||
public VolumeInfo createFromParcel(Parcel p) {
|
||||
return new VolumeInfo(p);
|
||||
}
|
||||
|
||||
public VolumeInfo[] newArray(int size) {
|
||||
return new VolumeInfo[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -36,6 +36,7 @@ import android.annotation.IntDef;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.ActivityManager;
|
||||
@@ -87,6 +88,7 @@ import android.media.AudioRecordingConfiguration;
|
||||
import android.media.AudioRoutesInfo;
|
||||
import android.media.AudioSystem;
|
||||
import android.media.BluetoothProfileConnectionInfo;
|
||||
import android.media.IAudioDeviceVolumeDispatcher;
|
||||
import android.media.IAudioFocusDispatcher;
|
||||
import android.media.IAudioModeDispatcher;
|
||||
import android.media.IAudioRoutesObserver;
|
||||
@@ -108,6 +110,7 @@ import android.media.MediaMetrics;
|
||||
import android.media.MediaRecorder.AudioSource;
|
||||
import android.media.PlayerBase;
|
||||
import android.media.Spatializer;
|
||||
import android.media.VolumeInfo;
|
||||
import android.media.VolumePolicy;
|
||||
import android.media.audiofx.AudioEffect;
|
||||
import android.media.audiopolicy.AudioMix;
|
||||
@@ -4246,6 +4249,24 @@ public class AudioService extends IAudioService.Stub
|
||||
return (mStreamStates[streamType].getIndex(device) + 5) / 10;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default VolumeInfo returned by {@link VolumeInfo#getDefaultVolumeInfo()}
|
||||
* Lazily initialized in {@link #getDefaultVolumeInfo()}
|
||||
*/
|
||||
static VolumeInfo sDefaultVolumeInfo;
|
||||
|
||||
/** @see VolumeInfo#getDefaultVolumeInfo() */
|
||||
public VolumeInfo getDefaultVolumeInfo() {
|
||||
if (sDefaultVolumeInfo == null) {
|
||||
sDefaultVolumeInfo = new VolumeInfo.Builder(AudioSystem.STREAM_MUSIC)
|
||||
.setMinVolumeIndex(getStreamMinVolume(AudioSystem.STREAM_MUSIC))
|
||||
.setMaxVolumeIndex(getStreamMaxVolume(AudioSystem.STREAM_MUSIC))
|
||||
.setMuted(false)
|
||||
.build();
|
||||
}
|
||||
return sDefaultVolumeInfo;
|
||||
}
|
||||
|
||||
/** @see AudioManager#getUiSoundsStreamType()
|
||||
* TODO(b/181140246): when using VolumeGroup alias, we are lacking configurability for
|
||||
* UI Sounds identification.
|
||||
@@ -6309,6 +6330,37 @@ public class AudioService extends IAudioService.Stub
|
||||
0 /*delay*/);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AudioDeviceVolumeManager#setDeviceAbsoluteMultiVolumeBehavior
|
||||
* @param cb
|
||||
* @param attr
|
||||
* @param volumes
|
||||
*/
|
||||
@RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED })
|
||||
public void registerDeviceVolumeDispatcherForAbsoluteVolume(boolean register,
|
||||
IAudioDeviceVolumeDispatcher cb, String packageName,
|
||||
AudioDeviceAttributes device, List<VolumeInfo> volumes) {
|
||||
// verify permissions
|
||||
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
|
||||
!= PackageManager.PERMISSION_GRANTED
|
||||
&& mContext.checkCallingOrSelfPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
throw new SecurityException(
|
||||
"Missing MODIFY_AUDIO_ROUTING or BLUETOOTH_PRIVILEGED permissions");
|
||||
}
|
||||
// verify arguments
|
||||
Objects.requireNonNull(device);
|
||||
Objects.requireNonNull(volumes);
|
||||
|
||||
// current implementation maps this call to existing abs volume API of AudioManager
|
||||
// TODO implement the volume/device listener through IAudioDeviceVolumeDispatcher
|
||||
final int volumeBehavior = volumes.size() == 1
|
||||
? AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE
|
||||
: AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE;
|
||||
setDeviceVolumeBehavior(device, volumeBehavior, packageName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AudioManager#setDeviceVolumeBehavior(AudioDeviceAttributes, int)
|
||||
* @param device the audio device to be affected
|
||||
|
||||
Reference in New Issue
Block a user