diff options
Diffstat (limited to 'android/media/AudioManager.java')
-rw-r--r-- | android/media/AudioManager.java | 426 |
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 //-------------------- |