summaryrefslogtreecommitdiff
path: root/android/media/AudioManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'android/media/AudioManager.java')
-rw-r--r--android/media/AudioManager.java426
1 files changed, 391 insertions, 35 deletions
diff --git a/android/media/AudioManager.java b/android/media/AudioManager.java
index 2ac4063d..aeef2158 100644
--- a/android/media/AudioManager.java
+++ b/android/media/AudioManager.java
@@ -32,6 +32,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.media.audiopolicy.AudioPolicy;
+import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.MediaSessionLegacyHelper;
@@ -48,17 +49,25 @@ import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.view.KeyEvent;
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+
/**
* AudioManager provides access to volume and ringer mode control.
@@ -400,6 +409,18 @@ public class AudioManager {
public static final int ADJUST_TOGGLE_MUTE = 101;
/** @hide */
+ @IntDef(flag = false, prefix = "ADJUST", value = {
+ ADJUST_RAISE,
+ ADJUST_LOWER,
+ ADJUST_SAME,
+ ADJUST_MUTE,
+ ADJUST_UNMUTE,
+ ADJUST_TOGGLE_MUTE }
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface VolumeAdjustment {}
+
+ /** @hide */
public static final String adjustToString(int adj) {
switch (adj) {
case ADJUST_RAISE: return "ADJUST_RAISE";
@@ -1331,6 +1352,7 @@ public class AudioManager {
//====================================================================
// Offload query
/**
+ * @hide
* Returns whether offloaded playback of an audio format is supported on the device.
* Offloaded playback is where the decoding of an audio stream is not competing with other
* software resources. In general, it is supported by dedicated hardware, such as audio DSPs.
@@ -2077,27 +2099,7 @@ public class AudioManager {
*/
private boolean querySoundEffectsEnabled(int user) {
return Settings.System.getIntForUser(getContext().getContentResolver(),
- Settings.System.SOUND_EFFECTS_ENABLED, 0, user) != 0
- && !areSystemSoundsZenModeBlocked(getContext());
- }
-
- private boolean areSystemSoundsZenModeBlocked(Context context) {
- int zenMode = Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.ZEN_MODE, 0);
-
- switch (zenMode) {
- case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS:
- case Settings.Global.ZEN_MODE_ALARMS:
- return true;
- case Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
- final NotificationManager noMan = (NotificationManager) context
- .getSystemService(Context.NOTIFICATION_SERVICE);
- return (noMan.getNotificationPolicy().priorityCategories
- & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) == 0;
- case Settings.Global.ZEN_MODE_OFF:
- default:
- return false;
- }
+ Settings.System.SOUND_EFFECTS_ENABLED, 0, user) != 0;
}
/**
@@ -2324,6 +2326,20 @@ public class AudioManager {
}
}
}
+
+ @Override
+ public void dispatchFocusResultFromExtPolicy(int requestResult, String clientId) {
+ synchronized (mFocusRequestsLock) {
+ // TODO use generation counter as the key instead
+ final BlockingFocusResultReceiver focusReceiver =
+ mFocusRequestsAwaitingResult.remove(clientId);
+ if (focusReceiver != null) {
+ focusReceiver.notifyResult(requestResult);
+ } else {
+ Log.e(TAG, "dispatchFocusResultFromExtPolicy found no result receiver");
+ }
+ }
+ }
};
private String getIdForAudioFocusListener(OnAudioFocusChangeListener l) {
@@ -2376,6 +2392,40 @@ public class AudioManager {
*/
public static final int AUDIOFOCUS_REQUEST_DELAYED = 2;
+ /** @hide */
+ @IntDef(flag = false, prefix = "AUDIOFOCUS_REQUEST", value = {
+ AUDIOFOCUS_REQUEST_FAILED,
+ AUDIOFOCUS_REQUEST_GRANTED,
+ AUDIOFOCUS_REQUEST_DELAYED }
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FocusRequestResult {}
+
+ /**
+ * @hide
+ * code returned when a synchronous focus request on the client-side is to be blocked
+ * until the external audio focus policy decides on the response for the client
+ */
+ public static final int AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY = 100;
+
+ /**
+ * Timeout duration in ms when waiting on an external focus policy for the result for a
+ * focus request
+ */
+ private static final int EXT_FOCUS_POLICY_TIMEOUT_MS = 200;
+
+ private static final String FOCUS_CLIENT_ID_STRING = "android_audio_focus_client_id";
+
+ private final Object mFocusRequestsLock = new Object();
+ /**
+ * Map of all receivers of focus request results, one per unresolved focus request.
+ * Receivers are added before sending the request to the external focus policy,
+ * and are removed either after receiving the result, or after the timeout.
+ * This variable is lazily initialized.
+ */
+ @GuardedBy("mFocusRequestsLock")
+ private HashMap<String, BlockingFocusResultReceiver> mFocusRequestsAwaitingResult;
+
/**
* Request audio focus.
@@ -2642,18 +2692,100 @@ public class AudioManager {
// some tests don't have a Context
sdk = Build.VERSION.SDK_INT;
}
- try {
- status = service.requestAudioFocus(afr.getAudioAttributes(),
- afr.getFocusGain(), mICallBack,
- mAudioFocusDispatcher,
- getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener()),
- getContext().getOpPackageName() /* package name */, afr.getFlags(),
- ap != null ? ap.cb() : null,
- sdk);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+
+ final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());
+ final BlockingFocusResultReceiver focusReceiver;
+ synchronized (mFocusRequestsLock) {
+ try {
+ // TODO status contains result and generation counter for ext policy
+ status = service.requestAudioFocus(afr.getAudioAttributes(),
+ afr.getFocusGain(), mICallBack,
+ mAudioFocusDispatcher,
+ clientId,
+ getContext().getOpPackageName() /* package name */, afr.getFlags(),
+ ap != null ? ap.cb() : null,
+ sdk);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {
+ // default path with no external focus policy
+ return status;
+ }
+ if (mFocusRequestsAwaitingResult == null) {
+ mFocusRequestsAwaitingResult =
+ new HashMap<String, BlockingFocusResultReceiver>(1);
+ }
+ focusReceiver = new BlockingFocusResultReceiver(clientId);
+ mFocusRequestsAwaitingResult.put(clientId, focusReceiver);
+ }
+ focusReceiver.waitForResult(EXT_FOCUS_POLICY_TIMEOUT_MS);
+ if (DEBUG && !focusReceiver.receivedResult()) {
+ Log.e(TAG, "requestAudio response from ext policy timed out, denying request");
+ }
+ synchronized (mFocusRequestsLock) {
+ mFocusRequestsAwaitingResult.remove(clientId);
+ }
+ return focusReceiver.requestResult();
+ }
+
+ // helper class that abstracts out the handling of spurious wakeups in Object.wait()
+ private static final class SafeWaitObject {
+ private boolean mQuit = false;
+
+ public void safeNotify() {
+ synchronized (this) {
+ mQuit = true;
+ this.notify();
+ }
+ }
+
+ public void safeWait(long millis) throws InterruptedException {
+ final long timeOutTime = java.lang.System.currentTimeMillis() + millis;
+ synchronized (this) {
+ while (!mQuit) {
+ final long timeToWait = timeOutTime - java.lang.System.currentTimeMillis();
+ if (timeToWait < 0) { break; }
+ this.wait(timeToWait);
+ }
+ }
+ }
+ }
+
+ private static final class BlockingFocusResultReceiver {
+ private final SafeWaitObject mLock = new SafeWaitObject();
+ @GuardedBy("mLock")
+ private boolean mResultReceived = false;
+ // request denied by default (e.g. timeout)
+ private int mFocusRequestResult = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+ private final String mFocusClientId;
+
+ BlockingFocusResultReceiver(String clientId) {
+ mFocusClientId = clientId;
+ }
+
+ boolean receivedResult() { return mResultReceived; }
+ int requestResult() { return mFocusRequestResult; }
+
+ void notifyResult(int requestResult) {
+ synchronized (mLock) {
+ mResultReceived = true;
+ mFocusRequestResult = requestResult;
+ mLock.safeNotify();
+ }
+ }
+
+ public void waitForResult(long timeOutMs) {
+ synchronized (mLock) {
+ if (mResultReceived) {
+ // the result was received before waiting
+ return;
+ }
+ try {
+ mLock.safeWait(timeOutMs);
+ } catch (InterruptedException e) { }
+ }
}
- return status;
}
/**
@@ -2700,6 +2832,32 @@ public class AudioManager {
/**
* @hide
+ * Set the result to the audio focus request received through
+ * {@link AudioPolicyFocusListener#onAudioFocusRequest(AudioFocusInfo, int)}.
+ * @param afi the information about the focus requester
+ * @param requestResult the result to the focus request to be passed to the requester
+ * @param ap a valid registered {@link AudioPolicy} configured as a focus policy.
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public void setFocusRequestResult(@NonNull AudioFocusInfo afi,
+ @FocusRequestResult int requestResult, @NonNull AudioPolicy ap) {
+ if (afi == null) {
+ throw new IllegalArgumentException("Illegal null AudioFocusInfo");
+ }
+ if (ap == null) {
+ throw new IllegalArgumentException("Illegal null AudioPolicy");
+ }
+ final IAudioService service = getService();
+ try {
+ service.setFocusRequestResultFromExtPolicy(afi, requestResult, ap.cb());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
* Notifies an application with a focus listener of gain or loss of audio focus.
* This method can only be used by owners of an {@link AudioPolicy} configured with
* {@link AudioPolicy.Builder#setIsAudioFocusPolicy(boolean)} set to true.
@@ -2989,7 +3147,7 @@ public class AudioManager {
final IAudioService service = getService();
try {
String regId = service.registerAudioPolicy(policy.getConfig(), policy.cb(),
- policy.hasFocusListener(), policy.isFocusPolicy());
+ policy.hasFocusListener(), policy.isFocusPolicy(), policy.isVolumeController());
if (regId == null) {
return ERROR;
} else {
@@ -3736,6 +3894,21 @@ public class AudioManager {
}
/**
+ * Indicate Hearing Aid connection state change.
+ * @param device Bluetooth device connected/disconnected
+ * @param state new connection state (BluetoothProfile.STATE_xxx)
+ * {@hide}
+ */
+ public void setHearingAidDeviceConnectionState(BluetoothDevice device, int state) {
+ final IAudioService service = getService();
+ try {
+ service.setHearingAidDeviceConnectionState(device, state);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Indicate A2DP source or sink connection state change.
* @param device Bluetooth device connected/disconnected
* @param state new connection state (BluetoothProfile.STATE_xxx)
@@ -4423,8 +4596,7 @@ public class AudioManager {
private static boolean checkTypes(AudioDevicePort port) {
return AudioDeviceInfo.convertInternalDeviceToDeviceType(port.type()) !=
- AudioDeviceInfo.TYPE_UNKNOWN &&
- port.type() != AudioSystem.DEVICE_IN_BACK_MIC;
+ AudioDeviceInfo.TYPE_UNKNOWN;
}
/**
@@ -4566,6 +4738,82 @@ public class AudioManager {
}
}
+ /**
+ * Set port id for microphones by matching device type and address.
+ * @hide
+ */
+ public static void setPortIdForMicrophones(ArrayList<MicrophoneInfo> microphones) {
+ AudioDeviceInfo[] devices = getDevicesStatic(AudioManager.GET_DEVICES_INPUTS);
+ for (int i = microphones.size() - 1; i >= 0; i--) {
+ boolean foundPortId = false;
+ for (AudioDeviceInfo device : devices) {
+ if (device.getPort().type() == microphones.get(i).getInternalDeviceType()
+ && TextUtils.equals(device.getAddress(), microphones.get(i).getAddress())) {
+ microphones.get(i).setId(device.getId());
+ foundPortId = true;
+ break;
+ }
+ }
+ if (!foundPortId) {
+ Log.i(TAG, "Failed to find port id for device with type:"
+ + microphones.get(i).getType() + " address:"
+ + microphones.get(i).getAddress());
+ microphones.remove(i);
+ }
+ }
+ }
+
+ /**
+ * Convert {@link AudioDeviceInfo} to {@link MicrophoneInfo}.
+ * @hide
+ */
+ public static MicrophoneInfo microphoneInfoFromAudioDeviceInfo(AudioDeviceInfo deviceInfo) {
+ int deviceType = deviceInfo.getType();
+ int micLocation = (deviceType == AudioDeviceInfo.TYPE_BUILTIN_MIC
+ || deviceType == AudioDeviceInfo.TYPE_TELEPHONY) ? MicrophoneInfo.LOCATION_MAINBODY
+ : deviceType == AudioDeviceInfo.TYPE_UNKNOWN ? MicrophoneInfo.LOCATION_UNKNOWN
+ : MicrophoneInfo.LOCATION_PERIPHERAL;
+ MicrophoneInfo microphone = new MicrophoneInfo(
+ deviceInfo.getPort().name() + deviceInfo.getId(),
+ deviceInfo.getPort().type(), deviceInfo.getAddress(), micLocation,
+ MicrophoneInfo.GROUP_UNKNOWN, MicrophoneInfo.INDEX_IN_THE_GROUP_UNKNOWN,
+ MicrophoneInfo.POSITION_UNKNOWN, MicrophoneInfo.ORIENTATION_UNKNOWN,
+ new ArrayList<Pair<Float, Float>>(), new ArrayList<Pair<Integer, Integer>>(),
+ MicrophoneInfo.SENSITIVITY_UNKNOWN, MicrophoneInfo.SPL_UNKNOWN,
+ MicrophoneInfo.SPL_UNKNOWN, MicrophoneInfo.DIRECTIONALITY_UNKNOWN);
+ microphone.setId(deviceInfo.getId());
+ return microphone;
+ }
+
+ /**
+ * Returns a list of {@link MicrophoneInfo} that corresponds to the characteristics
+ * of all available microphones. The list is empty when no microphones are available
+ * on the device. An error during the query will result in an IOException being thrown.
+ *
+ * @return a list that contains all microphones' characteristics
+ * @throws IOException if an error occurs.
+ */
+ public List<MicrophoneInfo> getMicrophones() throws IOException {
+ ArrayList<MicrophoneInfo> microphones = new ArrayList<MicrophoneInfo>();
+ int status = AudioSystem.getMicrophones(microphones);
+ if (status != AudioManager.SUCCESS) {
+ // fail and bail!
+ Log.e(TAG, "getMicrophones failed:" + status);
+ return new ArrayList<MicrophoneInfo>(); // Always return a list.
+ }
+ setPortIdForMicrophones(microphones);
+ AudioDeviceInfo[] devices = getDevicesStatic(GET_DEVICES_INPUTS);
+ for (AudioDeviceInfo device : devices) {
+ if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_MIC ||
+ device.getType() == AudioDeviceInfo.TYPE_TELEPHONY) {
+ continue;
+ }
+ MicrophoneInfo microphone = microphoneInfoFromAudioDeviceInfo(device);
+ microphones.add(microphone);
+ }
+ return microphones;
+ }
+
// Since we need to calculate the changes since THE LAST NOTIFICATION, and not since the
// (unpredictable) last time updateAudioPortCache() was called by someone, keep a list
// of the ports that exist at the time of the last notification.
@@ -4645,6 +4893,114 @@ public class AudioManager {
}
}
+
+ /**
+ * @hide
+ * Abstract class to receive event notification about audioserver process state.
+ */
+ @SystemApi
+ public abstract static class AudioServerStateCallback {
+ public void onAudioServerDown() { }
+ public void onAudioServerUp() { }
+ }
+
+ private Executor mAudioServerStateExec;
+ private AudioServerStateCallback mAudioServerStateCb;
+ private final Object mAudioServerStateCbLock = new Object();
+
+ private final IAudioServerStateDispatcher mAudioServerStateDispatcher =
+ new IAudioServerStateDispatcher.Stub() {
+ @Override
+ public void dispatchAudioServerStateChange(boolean state) {
+ Executor exec;
+ AudioServerStateCallback cb;
+
+ synchronized (mAudioServerStateCbLock) {
+ exec = mAudioServerStateExec;
+ cb = mAudioServerStateCb;
+ }
+
+ if ((exec == null) || (cb == null)) {
+ return;
+ }
+ if (state) {
+ exec.execute(() -> cb.onAudioServerUp());
+ } else {
+ exec.execute(() -> cb.onAudioServerDown());
+ }
+ }
+ };
+
+ /**
+ * @hide
+ * Registers a callback for notification of audio server state changes.
+ * @param executor {@link Executor} to handle the callbacks
+ * @param stateCallback the callback to receive the audio server state changes
+ * To remove the callabck, pass a null reference for both executor and stateCallback.
+ */
+ @SystemApi
+ public void setAudioServerStateCallback(@NonNull Executor executor,
+ @NonNull AudioServerStateCallback stateCallback) {
+ if (stateCallback == null) {
+ throw new IllegalArgumentException("Illegal null AudioServerStateCallback");
+ }
+ if (executor == null) {
+ throw new IllegalArgumentException(
+ "Illegal null Executor for the AudioServerStateCallback");
+ }
+
+ synchronized (mAudioServerStateCbLock) {
+ if (mAudioServerStateCb != null) {
+ throw new IllegalStateException(
+ "setAudioServerStateCallback called with already registered callabck");
+ }
+ final IAudioService service = getService();
+ try {
+ service.registerAudioServerStateDispatcher(mAudioServerStateDispatcher);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mAudioServerStateExec = executor;
+ mAudioServerStateCb = stateCallback;
+ }
+ }
+
+ /**
+ * @hide
+ * Unregisters the callback for notification of audio server state changes.
+ */
+ @SystemApi
+ public void clearAudioServerStateCallback() {
+ synchronized (mAudioServerStateCbLock) {
+ if (mAudioServerStateCb != null) {
+ final IAudioService service = getService();
+ try {
+ service.unregisterAudioServerStateDispatcher(
+ mAudioServerStateDispatcher);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ mAudioServerStateExec = null;
+ mAudioServerStateCb = null;
+ }
+ }
+
+ /**
+ * @hide
+ * Checks if native audioservice is running or not.
+ * @return true if native audioservice runs, false otherwise.
+ */
+ @SystemApi
+ public boolean isAudioServerRunning() {
+ final IAudioService service = getService();
+ try {
+ return service.isAudioServerRunning();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
//---------------------------------------------------------
// Inner classes
//--------------------