/* * Copyright (C) 2024 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.bluetooth; import static android.Manifest.permission.BLUETOOTH_CONNECT; import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; import static android.bluetooth.BluetoothUtils.callService; import static android.bluetooth.BluetoothUtils.logRemoteException; import static java.util.Objects.requireNonNull; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.RequiresNoPermission; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.bluetooth.annotations.RequiresBluetoothConnectPermission; import android.content.AttributionSource; import android.os.RemoteException; import com.android.bluetooth.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; import java.util.concurrent.Executor; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * This class provides APIs to control a remote AICS (Audio Input Control Service) * *
Each {@link AudioInputControl} object represents an instance of the Audio Input Control * Service (AICS) on the remote device. A device may have multiple instances of the AICS, as * described in the Audio Input * Control Service Specification (AICS 1.0). * * @see BluetoothVolumeControl#getAudioInputControlServices * @hide */ @FlaggedApi(Flags.FLAG_AICS_API) @SystemApi public class AudioInputControl { private static final String TAG = AudioInputControl.class.getSimpleName(); /** Unspecified Input */ public static final int AUDIO_INPUT_TYPE_UNSPECIFIED = bluetooth.constants.AudioInputType.UNSPECIFIED; /** Bluetooth Audio Stream */ public static final int AUDIO_INPUT_TYPE_BLUETOOTH = bluetooth.constants.AudioInputType.BLUETOOTH; /** Microphone */ public static final int AUDIO_INPUT_TYPE_MICROPHONE = bluetooth.constants.AudioInputType.MICROPHONE; /** Analog Interface */ public static final int AUDIO_INPUT_TYPE_ANALOG = bluetooth.constants.AudioInputType.ANALOG; /** Digital Interface */ public static final int AUDIO_INPUT_TYPE_DIGITAL = bluetooth.constants.AudioInputType.DIGITAL; /** AM/FM/XM/etc. */ public static final int AUDIO_INPUT_TYPE_RADIO = bluetooth.constants.AudioInputType.RADIO; /** Streaming Audio Source */ public static final int AUDIO_INPUT_TYPE_STREAMING = bluetooth.constants.AudioInputType.STREAMING; /** Transparency/Pass-through */ public static final int AUDIO_INPUT_TYPE_AMBIENT = bluetooth.constants.AudioInputType.AMBIENT; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef( prefix = {"AUDIO_INPUT_TYPE_"}, value = { AUDIO_INPUT_TYPE_UNSPECIFIED, AUDIO_INPUT_TYPE_BLUETOOTH, AUDIO_INPUT_TYPE_MICROPHONE, AUDIO_INPUT_TYPE_ANALOG, AUDIO_INPUT_TYPE_DIGITAL, AUDIO_INPUT_TYPE_RADIO, AUDIO_INPUT_TYPE_STREAMING, AUDIO_INPUT_TYPE_AMBIENT, }) public @interface AudioInputType {} /** Inactive */ public static final int AUDIO_INPUT_STATUS_INACTIVE = bluetooth.constants.aics.AudioInputStatus.INACTIVE; /** Active */ public static final int AUDIO_INPUT_STATUS_ACTIVE = bluetooth.constants.aics.AudioInputStatus.ACTIVE; /** * Status is none of {@link #AUDIO_INPUT_STATUS_ACTIVE}, {@link #AUDIO_INPUT_STATUS_INACTIVE}. * This fallback value will be used for forward compatibility, if the 3.4. Audio Input Status * field extend its definition. */ public static final int AUDIO_INPUT_STATUS_UNKNOWN = -1; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef( prefix = {"AUDIO_INPUT_STATUS_"}, value = { AUDIO_INPUT_STATUS_INACTIVE, AUDIO_INPUT_STATUS_ACTIVE, AUDIO_INPUT_STATUS_UNKNOWN, }) public @interface AudioInputStatus {} /** Not Muted */ public static final int MUTE_NOT_MUTED = bluetooth.constants.aics.Mute.NOT_MUTED; /** Muted */ public static final int MUTE_MUTED = bluetooth.constants.aics.Mute.MUTED; /** * Disabled * *
Mute command are disabled by the server. For example with a local privacy switch. */ public static final int MUTE_DISABLED = bluetooth.constants.aics.Mute.DISABLED; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef( prefix = {"MUTE_"}, value = { MUTE_NOT_MUTED, MUTE_MUTED, MUTE_DISABLED, }) public @interface Mute {} /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef( prefix = {"MUTE_"}, value = { MUTE_NOT_MUTED, MUTE_MUTED, }) public @interface MuteSettable {} /** * Manual Only * *
Gain adjustments are made manually through {@link #setGainSetting}. * *
The server cannot be switched to automatic gain. */ public static final int GAIN_MODE_MANUAL_ONLY = bluetooth.constants.aics.GainMode.MANUAL_ONLY; /** * Automatic Only * *
Gain adjustments are automatic and calls to {@link #setGainSetting} are ignored. * *
The server cannot be switched to manual gain. */ public static final int GAIN_MODE_AUTOMATIC_ONLY = bluetooth.constants.aics.GainMode.AUTOMATIC_ONLY; /** * Manual * *
Gain adjustments are made manually through {@link #setGainSetting}. * *
The server supports switching between manual and automatic gain mode. */ public static final int GAIN_MODE_MANUAL = bluetooth.constants.aics.GainMode.MANUAL; /** * Automatic * *
Gain adjustments are automatic and calls to {@link #setGainSetting} are ignored. * *
The server supports switching between manual and automatic gain mode.
*/
public static final int GAIN_MODE_AUTOMATIC = bluetooth.constants.aics.GainMode.AUTOMATIC;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(
prefix = {"GAIN_MODE_"},
value = {
GAIN_MODE_MANUAL_ONLY,
GAIN_MODE_AUTOMATIC_ONLY,
GAIN_MODE_MANUAL,
GAIN_MODE_AUTOMATIC,
})
public @interface GainMode {}
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(
prefix = {"GAIN_MODE_"},
value = {
GAIN_MODE_MANUAL,
GAIN_MODE_AUTOMATIC,
})
public @interface GainModeSettable {}
/** Local identifier of the AICS */
private final int mInstanceId;
private final IBluetoothVolumeControl mService;
private final BluetoothDevice mDevice;
private final AttributionSource mAttributionSource;
private final CallbackWrapper Repeated registration of the same callback object will have no effect after the first call
* to this method, even when the executor is different. API caller must call {@link
* #unregisterCallback(AudioInputCallback)} with the same callback object before registering it
* again.
*
* Callbacks are automatically unregistered when the application process goes away.
*
* @param executor an {@link Executor} to execute given callback
* @param callback user implementation of the {@link AudioInputCallback}
* @throws IllegalArgumentException if callback is already registered
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
public void registerCallback(
@NonNull @CallbackExecutor Executor executor, @NonNull AudioInputCallback callback) {
mCallbackWrapper.registerCallback(mService, callback, executor);
}
/**
* Unregister the {@link AudioInputCallback}.
*
* The same {@link AudioInputCallback} object used when calling {@link
* #registerCallback(Executor, AudioInputCallback)} must be used.
*
* Callbacks are automatically unregistered when the application process goes away.
*
* @param callback user implementation of the {@link AudioInputCallback}
* @throws IllegalArgumentException when no callback is registered
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
public void unregisterCallback(@NonNull AudioInputCallback callback) {
mCallbackWrapper.unregisterCallback(mService, callback);
}
/**
* Gets the Audio Input Type.
*
* This reflects the source of audio for the audio input described by this AICS. The
* description may optionally further describe the Audio Input Type with values such as
* Microphone, HDMI, etc…
*
* @return The Audio Input Type as defined in AICS 1.0 - 3.3.
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
public @AudioInputType int getAudioInputType() {
return callService(
mService,
s -> s.getAudioInputType(mAttributionSource, mDevice, mInstanceId),
bluetooth.constants.AudioInputType.UNSPECIFIED);
}
/**
* Gets the unit of the gain setting.
*
* It reflects the size of a single increment or decrement of {@link #setGainSetting} in 0.1
* decibel units.
*
* @return The Gain Setting Units as defined in AICS 1.0 - 3.2.1.
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
public @IntRange(from = 0, to = 0xFF) int getGainSettingUnit() {
return callService(
mService,
s -> s.getAudioInputGainSettingUnit(mAttributionSource, mDevice, mInstanceId),
0);
}
/**
* Gets the minimum value for the gain setting.
*
* @return The minimum Gain Setting as defined in AICS 1.0 - 3.2.2.
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
public @IntRange(from = -128, to = 127) int getGainSettingMin() {
return callService(
mService,
s -> s.getAudioInputGainSettingMin(mAttributionSource, mDevice, mInstanceId),
0);
}
/**
* Gets the maximum value for the gain setting.
*
* @return The maximum Gain Setting as defined in AICS 1.0 - 3.2.3.
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
public @IntRange(from = -128, to = 127) int getGainSettingMax() {
return callService(
mService,
s -> s.getAudioInputGainSettingMax(mAttributionSource, mDevice, mInstanceId),
0);
}
/**
* Gets the description.
*
* Register an {@link AudioInputCallback} to be notified via {@link
* AudioInputCallback#onDescriptionChanged} when the description changes.
*
* This describes the AICS. For example, if a device instantiated a service for both
* “Bluetooth” and “Line In” audio inputs, then the description value would be set to
* “Bluetooth” on one service and “Line In” on the other service. If multiple Bluetooth audio
* inputs are represented, the server may set the Audio Input Description to the remote source’s
* name, a string representing the content type, the content control server name, etc. The value
* is a UTF-8 string of zero or more characters.
*
* @return The description as defined in AICS 1.0 - 3.6.
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
public @NonNull String getDescription() {
return callService(
mService,
s -> s.getAudioInputDescription(mAttributionSource, mDevice, mInstanceId),
"");
}
/**
* Checks whether the description is writable as defined in AICS 1.0 - 3.6.
*
* @return true if the description can be written to, false otherwise.
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
public boolean isDescriptionWritable() {
return callService(
mService,
s -> s.isAudioInputDescriptionWritable(mAttributionSource, mDevice, mInstanceId),
false);
}
/**
* Sets the description as defined in AICS 1.0 - 3.6.
*
* The operation will fail if the description is not writable. This can be verified with
* {@link #isDescriptionWritable}
*
* Register an {@link AudioInputCallback} to be notified via {@link
* AudioInputCallback#onDescriptionChanged} when the description change is applied on remote
* device.
*
* @param description The description of the AICS.
* @return true if the operation is successfully initiated, false otherwise.
* @throws IllegalStateException if the description is not writable
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
public boolean setDescription(@NonNull String description) {
requireNonNull(description);
return callService(
mService,
s ->
s.setAudioInputDescription(
mAttributionSource, mDevice, mInstanceId, description),
false);
}
/**
* Gets the Audio Input Status.
*
* Register an {@link AudioInputCallback} to be notified via {@link
* AudioInputCallback#onAudioInputStatusChanged} when the status changes.
*
* @return The Audio Input Status as defined in AICS 1.0 - 3.4.
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
public @AudioInputStatus int getAudioInputStatus() {
return callService(
mService,
s -> s.getAudioInputStatus(mAttributionSource, mDevice, mInstanceId),
(int) bluetooth.constants.aics.AudioInputStatus.INACTIVE);
}
/**
* Gets the gain setting.
*
* Register an {@link AudioInputCallback} to be notified via {@link
* AudioInputCallback#onGainSettingChanged} when the gain setting changes.
*
* @return The current gain setting as defined in AICS 1.0 - 2.2.1.1.
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
public @IntRange(from = -128, to = 127) int getGainSetting() {
return callService(
mService,
s -> s.getAudioInputGainSetting(mAttributionSource, mDevice, mInstanceId),
0);
}
/**
* Sets the gain setting as defined in AICS 1.0 - 3.5.2.1.
*
* The operation will fail if the current gain mode is {@link #AUTOMATIC} or {@link
* #AUTOMATIC_ONLY}.
*
* Register an {@link AudioInputControl.AudioInputCallback} to be notified via
*
* Register an {@link AudioInputCallback} to be notified via {@link
* AudioInputCallback#onGainModeChanged} when the gain mode changes.
*
* @return The current gain mode as defined in AICS 1.0 - 2.2.1.3.
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
public @GainMode int getGainMode() {
return callService(
mService,
s -> s.getAudioInputGainMode(mAttributionSource, mDevice, mInstanceId),
(int) bluetooth.constants.aics.GainMode.AUTOMATIC_ONLY);
}
/**
* Sets the gain mode as defined in AICS 1.0 - 3.5.2.4/5.
*
* The operation will fail if the current gain mode is {@link #MANUAL_ONLY} or {@link
* #AUTOMATIC_ONLY}.
*
* Register an {@link AudioInputControl.AudioInputCallback} to be notified via
*
* Register an {@link AudioInputCallback} to be notified via {@link
* AudioInputCallback#onMuteChanged} when the mute state changes.
*
* @return The current mute state as defined in AICS 1.0 - 2.2.1.2.
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
public @Mute int getMute() {
return callService(
mService,
s -> s.getAudioInputMute(mAttributionSource, mDevice, mInstanceId),
(int) bluetooth.constants.aics.Mute.DISABLED);
}
/**
* Sets the mute state as defined in AICS 1.0 - 3.5.2.2/3.
*
* The operation will fail if the current mute state is {@link #MUTE_DISABLED}.
*
* Register an {@link AudioInputControl.AudioInputCallback} to be notified via
*
*
*
*
* The gain setting is a signed value for which a single increment or decrement should result in
* a corresponding increase or decrease of the input amplitude by the value of the gain setting
* unit (see {@link #getGainSettingUnit()}). A gain setting value of 0 should result in no
* change to the input’s original amplitude.
*
* @param gainSetting The desired gain setting value. Refer to {@link #getGainSettingMin()} and
* {@link #getGainSettingMax()} for the allowed range. Refer to {@link
* #getGainSettingUnit()} to knows how much decibel this represents.
* @return true if the operation is successfully initiated, false otherwise. The callback {@link
* AudioInputCallback#onSetGainSettingFailed()} will not be call if false is returned
* @throws IllegalStateException if the gain mode is {@link #AUTOMATIC} or {@link
* #AUTOMATIC_ONLY}
* @throws IllegalArgumentException if the gain setting is not in range
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
public boolean setGainSetting(@IntRange(from = -128, to = 127) int gainSetting) {
return callService(
mService,
s ->
s.setAudioInputGainSetting(
mAttributionSource, mDevice, mInstanceId, gainSetting),
false);
}
/**
* Gets the gain mode.
*
*
*
*
* @param gainMode The desired gain mode
* @return true if the operation is successfully initiated, false otherwise. The callback {@link
* AudioInputCallback#onSetGainModeFailed()} will not be call if false is returned
* @throws IllegalStateException if the gain mode is {@link #MANUAL_ONLY} or {@link
* #AUTOMATIC_ONLY}
* @throws IllegalArgumentException if the gain mode value is invalid.
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
public boolean setGainMode(@GainModeSettable int gainMode) {
if (gainMode != GAIN_MODE_MANUAL && gainMode != GAIN_MODE_AUTOMATIC) {
throw new IllegalArgumentException("Illegal GainMode value: " + gainMode);
}
return callService(
mService,
s -> s.setAudioInputGainMode(mAttributionSource, mDevice, mInstanceId, gainMode),
false);
}
/**
* Gets the mute state.
*
*
*
*
* @param mute the new mute state.
* @return true on success, false otherwise.
* @throws IllegalStateException if the mute state is {@link #MUTE_DISABLED}
* @throws IllegalArgumentException if the provided {@code mute} is not valid
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
public boolean setMute(@MuteSettable int mute) {
if (mute != MUTE_NOT_MUTED && mute != MUTE_MUTED) {
throw new IllegalArgumentException("Illegal mute value: " + mute);
}
return callService(
mService,
s -> s.setAudioInputMute(mAttributionSource, mDevice, mInstanceId, mute),
false);
}
}