diff options
Diffstat (limited to 'media')
16 files changed, 1363 insertions, 152 deletions
diff --git a/media/java/android/media/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java index 0ab62c14ab9f..7caac899a603 100644 --- a/media/java/android/media/AudioDeviceAttributes.java +++ b/media/java/android/media/AudioDeviceAttributes.java @@ -72,6 +72,11 @@ public final class AudioDeviceAttributes implements Parcelable { private final @Role int mRole; /** + * The internal audio device type + */ + private final int mNativeType; + + /** * @hide * Constructor from a valid {@link AudioDeviceInfo} * @param deviceInfo the connected audio device from which to obtain the device-identifying @@ -83,6 +88,7 @@ public final class AudioDeviceAttributes implements Parcelable { mRole = deviceInfo.isSink() ? ROLE_OUTPUT : ROLE_INPUT; mType = deviceInfo.getType(); mAddress = deviceInfo.getAddress(); + mNativeType = deviceInfo.getInternalType(); } /** @@ -101,9 +107,12 @@ public final class AudioDeviceAttributes implements Parcelable { } if (role == ROLE_OUTPUT) { AudioDeviceInfo.enforceValidAudioDeviceTypeOut(type); - } - if (role == ROLE_INPUT) { + mNativeType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(type); + } else if (role == ROLE_INPUT) { AudioDeviceInfo.enforceValidAudioDeviceTypeIn(type); + mNativeType = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice(type); + } else { + mNativeType = AudioSystem.DEVICE_NONE; } mRole = role; @@ -111,10 +120,17 @@ public final class AudioDeviceAttributes implements Parcelable { mAddress = address; } - /*package*/ AudioDeviceAttributes(int nativeType, @NonNull String address) { + /** + * @hide + * Constructor from internal device type and address + * @param type the internal device type, as defined in {@link AudioSystem} + * @param address the address of the device, or an empty string for devices without one + */ + public AudioDeviceAttributes(int nativeType, @NonNull String address) { mRole = (nativeType & AudioSystem.DEVICE_BIT_IN) != 0 ? ROLE_INPUT : ROLE_OUTPUT; mType = AudioDeviceInfo.convertInternalDeviceToDeviceType(nativeType); mAddress = address; + mNativeType = nativeType; } /** @@ -147,6 +163,15 @@ public final class AudioDeviceAttributes implements Parcelable { return mAddress; } + /** + * @hide + * Returns the internal device type of a device + * @return the internal device type + */ + public int getInternalType() { + return mNativeType; + } + @Override public int hashCode() { return Objects.hash(mRole, mType, mAddress); @@ -172,10 +197,8 @@ public final class AudioDeviceAttributes implements Parcelable { public String toString() { return new String("AudioDeviceAttributes:" + " role:" + roleToString(mRole) - + " type:" + (mRole == ROLE_OUTPUT ? AudioSystem.getOutputDeviceName( - AudioDeviceInfo.convertDeviceTypeToInternalDevice(mType)) - : AudioSystem.getInputDeviceName( - AudioDeviceInfo.convertDeviceTypeToInternalDevice(mType))) + + " type:" + (mRole == ROLE_OUTPUT ? AudioSystem.getOutputDeviceName(mNativeType) + : AudioSystem.getInputDeviceName(mNativeType)) + " addr:" + mAddress); } @@ -189,12 +212,14 @@ public final class AudioDeviceAttributes implements Parcelable { dest.writeInt(mRole); dest.writeInt(mType); dest.writeString(mAddress); + dest.writeInt(mNativeType); } private AudioDeviceAttributes(@NonNull Parcel in) { mRole = in.readInt(); mType = in.readInt(); mAddress = in.readString(); + mNativeType = in.readInt(); } public static final @NonNull Parcelable.Creator<AudioDeviceAttributes> CREATOR = diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java index ff4a25622bca..f79fc92477f7 100644 --- a/media/java/android/media/AudioDeviceInfo.java +++ b/media/java/android/media/AudioDeviceInfo.java @@ -350,6 +350,14 @@ public final class AudioDeviceInfo { } /** + * @hide + * @return the internal device tyoe + */ + public int getInternalType() { + return mPort.type(); + } + + /** * @return The internal device ID. */ public int getId() { @@ -512,10 +520,21 @@ public final class AudioDeviceInfo { return INT_TO_EXT_DEVICE_MAPPING.get(intDevice, TYPE_UNKNOWN); } + /** @hide */ + public static int convertDeviceTypeToInternalInputDevice(int deviceType) { + return EXT_TO_INT_INPUT_DEVICE_MAPPING.get(deviceType, AudioSystem.DEVICE_NONE); + } + private static final SparseIntArray INT_TO_EXT_DEVICE_MAPPING; private static final SparseIntArray EXT_TO_INT_DEVICE_MAPPING; + /** + * EXT_TO_INT_INPUT_DEVICE_MAPPING aims at mapping external device type to internal input device + * type. + */ + private static final SparseIntArray EXT_TO_INT_INPUT_DEVICE_MAPPING; + static { INT_TO_EXT_DEVICE_MAPPING = new SparseIntArray(); INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_EARPIECE, TYPE_BUILTIN_EARPIECE); @@ -600,6 +619,32 @@ public final class AudioDeviceInfo { EXT_TO_INT_DEVICE_MAPPING.put(TYPE_REMOTE_SUBMIX, AudioSystem.DEVICE_OUT_REMOTE_SUBMIX); EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_HEADSET, AudioSystem.DEVICE_OUT_BLE_HEADSET); EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_SPEAKER, AudioSystem.DEVICE_OUT_BLE_SPEAKER); + + // privileges mapping to input device + EXT_TO_INT_INPUT_DEVICE_MAPPING = new SparseIntArray(); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_BUILTIN_MIC, AudioSystem.DEVICE_IN_BUILTIN_MIC); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put( + TYPE_BLUETOOTH_SCO, AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put( + TYPE_WIRED_HEADSET, AudioSystem.DEVICE_IN_WIRED_HEADSET); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_HDMI, AudioSystem.DEVICE_IN_HDMI); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_TELEPHONY, AudioSystem.DEVICE_IN_TELEPHONY_RX); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_DOCK, AudioSystem.DEVICE_IN_ANLG_DOCK_HEADSET); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put( + TYPE_USB_ACCESSORY, AudioSystem.DEVICE_IN_USB_ACCESSORY); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_USB_DEVICE, AudioSystem.DEVICE_IN_USB_DEVICE); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_USB_HEADSET, AudioSystem.DEVICE_IN_USB_HEADSET); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_FM_TUNER, AudioSystem.DEVICE_IN_FM_TUNER); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_TV_TUNER, AudioSystem.DEVICE_IN_TV_TUNER); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_LINE_ANALOG, AudioSystem.DEVICE_IN_LINE); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_LINE_DIGITAL, AudioSystem.DEVICE_IN_SPDIF); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put( + TYPE_BLUETOOTH_A2DP, AudioSystem.DEVICE_IN_BLUETOOTH_A2DP); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_IP, AudioSystem.DEVICE_IN_IP); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_BUS, AudioSystem.DEVICE_IN_BUS); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put( + TYPE_REMOTE_SUBMIX, AudioSystem.DEVICE_IN_REMOTE_SUBMIX); + EXT_TO_INT_INPUT_DEVICE_MAPPING.put(TYPE_BLE_HEADSET, AudioSystem.DEVICE_IN_BLE_HEADSET); } } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 107d6565a29e..7dff0c2b9380 100755 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -75,6 +75,7 @@ import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -1598,11 +1599,25 @@ public class AudioManager { @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForStrategy(@NonNull AudioProductStrategy strategy, @NonNull AudioDeviceAttributes device) { + return setPreferredDevicesForStrategy(strategy, Arrays.asList(device)); + } + + /** + * @hide + * Removes the preferred audio device(s) previously set with + * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)} or + * {@link #setPreferredDevicesForStrategy(AudioProductStrategy, List<AudioDeviceAttributes>)}. + * @param strategy the audio strategy whose routing will be affected + * @return true if the operation was successful, false otherwise (invalid strategy, or no + * device set for example) + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public boolean removePreferredDeviceForStrategy(@NonNull AudioProductStrategy strategy) { Objects.requireNonNull(strategy); - Objects.requireNonNull(device); try { final int status = - getService().setPreferredDeviceForStrategy(strategy.getId(), device); + getService().removePreferredDevicesForStrategy(strategy.getId()); return status == AudioSystem.SUCCESS; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1611,19 +1626,52 @@ public class AudioManager { /** * @hide - * Removes the preferred audio device previously set with - * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)}. + * Return the preferred device for an audio strategy, previously set with + * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)} or + * {@link #setPreferredDevicesForStrategy(AudioProductStrategy, List<AudioDeviceAttributes>)} + * @param strategy the strategy to query + * @return the preferred device for that strategy, if multiple devices are set as preferred + * devices, the first one in the list will be returned. Null will be returned if none was + * ever set or if the strategy is invalid + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @Nullable + public AudioDeviceAttributes getPreferredDeviceForStrategy( + @NonNull AudioProductStrategy strategy) { + List<AudioDeviceAttributes> devices = getPreferredDevicesForStrategy(strategy); + return devices.isEmpty() ? null : devices.get(0); + } + + /** + * @hide + * Set the preferred devices for a given strategy, i.e. the audio routing to be used by + * this audio strategy. Note that the devices may not be available at the time the preferred + * devices is set, but it will be used once made available. + * <p>Use {@link #removePreferredDeviceForStrategy(AudioProductStrategy)} to cancel setting + * this preference for this strategy.</p> + * Note that the list of devices is not a list ranked by preference, but a list of one or more + * devices used simultaneously to output the same audio signal. * @param strategy the audio strategy whose routing will be affected - * @return true if the operation was successful, false otherwise (invalid strategy, or no - * device set for example) + * @param devices a non-empty list of the audio devices to route to when available + * @return true if the operation was successful, false otherwise */ @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) - public boolean removePreferredDeviceForStrategy(@NonNull AudioProductStrategy strategy) { + public boolean setPreferredDevicesForStrategy(@NonNull AudioProductStrategy strategy, + @NonNull List<AudioDeviceAttributes> devices) { Objects.requireNonNull(strategy); + Objects.requireNonNull(devices); + if (devices.isEmpty()) { + throw new IllegalArgumentException( + "Tried to set preferred devices for strategy with a empty list"); + } + for (AudioDeviceAttributes device : devices) { + Objects.requireNonNull(device); + } try { final int status = - getService().removePreferredDeviceForStrategy(strategy.getId()); + getService().setPreferredDevicesForStrategy(strategy.getId(), devices); return status == AudioSystem.SUCCESS; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1632,19 +1680,21 @@ public class AudioManager { /** * @hide - * Return the preferred device for an audio strategy, previously set with + * Return the preferred devices for an audio strategy, previously set with * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)} + * {@link #setPreferredDevicesForStrategy(AudioProductStrategy, List<AudioDeviceAttributes>)} * @param strategy the strategy to query * @return the preferred device for that strategy, or null if none was ever set or if the * strategy is invalid */ @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) - public @Nullable AudioDeviceAttributes getPreferredDeviceForStrategy( + @NonNull + public List<AudioDeviceAttributes> getPreferredDevicesForStrategy( @NonNull AudioProductStrategy strategy) { Objects.requireNonNull(strategy); try { - return getService().getPreferredDeviceForStrategy(strategy.getId()); + return getService().getPreferredDevicesForStrategy(strategy.getId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1656,6 +1706,7 @@ public class AudioManager { * strategy. * <p>Note that this listener will only be invoked whenever * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)} or + * {@link #setPreferredDevicesForStrategy(AudioProductStrategy, List<AudioDeviceAttributes>)} * {@link #removePreferredDeviceForStrategy(AudioProductStrategy)} causes a change in * preferred device. It will not be invoked directly after registration with * {@link #addOnPreferredDeviceForStrategyChangedListener(Executor, OnPreferredDeviceForStrategyChangedListener)} @@ -1663,8 +1714,10 @@ public class AudioManager { * @see #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes) * @see #removePreferredDeviceForStrategy(AudioProductStrategy) * @see #getPreferredDeviceForStrategy(AudioProductStrategy) + * @deprecated use #OnPreferredDevicesForStrategyChangedListener */ @SystemApi + @Deprecated public interface OnPreferredDeviceForStrategyChangedListener { /** * Called on the listener to indicate that the preferred audio device for the given @@ -1679,23 +1732,87 @@ public class AudioManager { /** * @hide + * Interface to be notified of changes in the preferred audio devices set for a given audio + * strategy. + * <p>Note that this listener will only be invoked whenever + * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)} or + * {@link #setPreferredDevicesForStrategy(AudioProductStrategy, List<AudioDeviceAttributes>)} + * {@link #removePreferredDeviceForStrategy(AudioProductStrategy)} causes a change in + * preferred device(s). It will not be invoked directly after registration with + * {@link #addOnPreferredDevicesForStrategyChangedListener( + * Executor, OnPreferredDevicesForStrategyChangedListener)} + * to indicate which strategies had preferred devices at the time of registration.</p> + * @see #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes) + * @see #setPreferredDevicesForStrategy(AudioProductStrategy, List) + * @see #removePreferredDeviceForStrategy(AudioProductStrategy) + * @see #getPreferredDeviceForStrategy(AudioProductStrategy) + * @see #getPreferredDevicesForStrategy(AudioProductStrategy) + */ + @SystemApi + public interface OnPreferredDevicesForStrategyChangedListener { + /** + * Called on the listener to indicate that the preferred audio devices for the given + * strategy has changed. + * @param strategy the {@link AudioProductStrategy} whose preferred device changed + * @param devices a list of newly set preferred audio devices + */ + void onPreferredDevicesForStrategyChanged(@NonNull AudioProductStrategy strategy, + @NonNull List<AudioDeviceAttributes> devices); + } + + /** + * @hide * Adds a listener for being notified of changes to the strategy-preferred audio device. * @param executor * @param listener * @throws SecurityException if the caller doesn't hold the required permission + * @deprecated use {@link #addOnPreferredDevicesForStrategyChangedListener( + * Executor, AudioManager.OnPreferredDevicesForStrategyChangedListener)} instead */ @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @Deprecated public void addOnPreferredDeviceForStrategyChangedListener( @NonNull @CallbackExecutor Executor executor, @NonNull OnPreferredDeviceForStrategyChangedListener listener) throws SecurityException { + // No-op, the method is deprecated. + } + + /** + * @hide + * Removes a previously added listener of changes to the strategy-preferred audio device. + * @param listener + * @deprecated use {@link #removeOnPreferredDevicesForStrategyChangedListener( + * AudioManager.OnPreferredDevicesForStrategyChangedListener)} instead + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @Deprecated + public void removeOnPreferredDeviceForStrategyChangedListener( + @NonNull OnPreferredDeviceForStrategyChangedListener listener) { + // No-op, the method is deprecated. + } + + /** + * @hide + * Adds a listener for being notified of changes to the strategy-preferred audio device. + * @param executor + * @param listener + * @throws SecurityException if the caller doesn't hold the required permission + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void addOnPreferredDevicesForStrategyChangedListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull OnPreferredDevicesForStrategyChangedListener listener) + throws SecurityException { Objects.requireNonNull(executor); Objects.requireNonNull(listener); synchronized (mPrefDevListenerLock) { if (hasPrefDevListener(listener)) { throw new IllegalArgumentException( - "attempt to call addOnPreferredDeviceForStrategyChangedListener() " + "attempt to call addOnPreferredDevicesForStrategyChangedListener() " + "on a previously registered listener"); } // lazy initialization of the list of strategy-preferred device listener @@ -1707,10 +1824,10 @@ public class AudioManager { if (oldCbCount == 0 && mPrefDevListeners.size() > 0) { // register binder for callbacks if (mPrefDevDispatcherStub == null) { - mPrefDevDispatcherStub = new StrategyPreferredDeviceDispatcherStub(); + mPrefDevDispatcherStub = new StrategyPreferredDevicesDispatcherStub(); } try { - getService().registerStrategyPreferredDeviceDispatcher(mPrefDevDispatcherStub); + getService().registerStrategyPreferredDevicesDispatcher(mPrefDevDispatcherStub); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1725,8 +1842,8 @@ public class AudioManager { */ @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) - public void removeOnPreferredDeviceForStrategyChangedListener( - @NonNull OnPreferredDeviceForStrategyChangedListener listener) { + public void removeOnPreferredDevicesForStrategyChangedListener( + @NonNull OnPreferredDevicesForStrategyChangedListener listener) { Objects.requireNonNull(listener); synchronized (mPrefDevListenerLock) { if (!removePrefDevListener(listener)) { @@ -1737,7 +1854,7 @@ public class AudioManager { if (mPrefDevListeners.size() == 0) { // unregister binder for callbacks try { - getService().unregisterStrategyPreferredDeviceDispatcher( + getService().unregisterStrategyPreferredDevicesDispatcher( mPrefDevDispatcherStub); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1759,23 +1876,23 @@ public class AudioManager { private @Nullable ArrayList<PrefDevListenerInfo> mPrefDevListeners; private static class PrefDevListenerInfo { - final @NonNull OnPreferredDeviceForStrategyChangedListener mListener; + final @NonNull OnPreferredDevicesForStrategyChangedListener mListener; final @NonNull Executor mExecutor; - PrefDevListenerInfo(OnPreferredDeviceForStrategyChangedListener listener, Executor exe) { + PrefDevListenerInfo(OnPreferredDevicesForStrategyChangedListener listener, Executor exe) { mListener = listener; mExecutor = exe; } } @GuardedBy("mPrefDevListenerLock") - private StrategyPreferredDeviceDispatcherStub mPrefDevDispatcherStub; + private StrategyPreferredDevicesDispatcherStub mPrefDevDispatcherStub; - private final class StrategyPreferredDeviceDispatcherStub - extends IStrategyPreferredDeviceDispatcher.Stub { + private final class StrategyPreferredDevicesDispatcherStub + extends IStrategyPreferredDevicesDispatcher.Stub { @Override - public void dispatchPrefDeviceChanged(int strategyId, - @Nullable AudioDeviceAttributes device) { + public void dispatchPrefDevicesChanged(int strategyId, + @NonNull List<AudioDeviceAttributes> devices) { // make a shallow copy of listeners so callback is not executed under lock final ArrayList<PrefDevListenerInfo> prefDevListeners; synchronized (mPrefDevListenerLock) { @@ -1790,7 +1907,7 @@ public class AudioManager { try { for (PrefDevListenerInfo info : prefDevListeners) { info.mExecutor.execute(() -> - info.mListener.onPreferredDeviceForStrategyChanged(strategy, device)); + info.mListener.onPreferredDevicesForStrategyChanged(strategy, devices)); } } finally { Binder.restoreCallingIdentity(ident); @@ -1800,7 +1917,7 @@ public class AudioManager { @GuardedBy("mPrefDevListenerLock") private @Nullable PrefDevListenerInfo getPrefDevListenerInfo( - OnPreferredDeviceForStrategyChangedListener listener) { + OnPreferredDevicesForStrategyChangedListener listener) { if (mPrefDevListeners == null) { return null; } @@ -1813,7 +1930,7 @@ public class AudioManager { } @GuardedBy("mPrefDevListenerLock") - private boolean hasPrefDevListener(OnPreferredDeviceForStrategyChangedListener listener) { + private boolean hasPrefDevListener(OnPreferredDevicesForStrategyChangedListener listener) { return getPrefDevListenerInfo(listener) != null; } @@ -1821,7 +1938,7 @@ public class AudioManager { /** * @return true if the listener was removed from the list */ - private boolean removePrefDevListener(OnPreferredDeviceForStrategyChangedListener listener) { + private boolean removePrefDevListener(OnPreferredDevicesForStrategyChangedListener listener) { final PrefDevListenerInfo infoToRemove = getPrefDevListenerInfo(listener); if (infoToRemove != null) { mPrefDevListeners.remove(infoToRemove); @@ -1831,6 +1948,349 @@ public class AudioManager { } //==================================================================== + // Audio Capture Preset routing + + /** + * @hide + * Set the preferred device for a given capture preset, i.e. the audio routing to be used by + * this capture preset. Note that the device may not be available at the time the preferred + * device is set, but it will be used once made available. + * <p>Use {@link #clearPreferredDevicesForCapturePreset(int)} to cancel setting this preference + * for this capture preset.</p> + * @param capturePreset the audio capture preset whose routing will be affected + * @param device the audio device to route to when available + * @return true if the operation was successful, false otherwise + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public boolean setPreferredDeviceForCapturePreset(int capturePreset, + @NonNull AudioDeviceAttributes device) { + return setPreferredDevicesForCapturePreset(capturePreset, Arrays.asList(device)); + } + + /** + * @hide + * Remove all the preferred audio devices previously set + * @param capturePreset the audio capture preset whose routing will be affected + * @return true if the operation was successful, false otherwise (invalid capture preset, or no + * device set for example) + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public boolean clearPreferredDevicesForCapturePreset(int capturePreset) { + if (!MediaRecorder.isValidAudioSource(capturePreset)) { + return false; + } + try { + final int status = getService().clearPreferredDevicesForCapturePreset(capturePreset); + return status == AudioSystem.SUCCESS; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + * Return the preferred devices for an audio capture preset, previously set with + * {@link #setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes)} + * @param capturePreset the capture preset to query + * @return a list that contains preferred devices for that capture preset. + */ + @NonNull + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public List<AudioDeviceAttributes> getPreferredDevicesForCapturePreset(int capturePreset) { + if (!MediaRecorder.isValidAudioSource(capturePreset)) { + return new ArrayList<AudioDeviceAttributes>(); + } + try { + return getService().getPreferredDevicesForCapturePreset(capturePreset); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private boolean setPreferredDevicesForCapturePreset( + int capturePreset, @NonNull List<AudioDeviceAttributes> devices) { + Objects.requireNonNull(devices); + if (!MediaRecorder.isValidAudioSource(capturePreset)) { + return false; + } + if (devices.size() != 1) { + throw new IllegalArgumentException( + "Only support setting one preferred devices for capture preset"); + } + for (AudioDeviceAttributes device : devices) { + Objects.requireNonNull(device); + } + try { + final int status = + getService().setPreferredDevicesForCapturePreset(capturePreset, devices); + return status == AudioSystem.SUCCESS; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + * Interface to be notified of changes in the preferred audio devices set for a given capture + * preset. + * <p>Note that this listener will only be invoked whenever + * {@link #setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes)} or + * {@link #clearPreferredDevicesForCapturePreset(int)} causes a change in + * preferred device. It will not be invoked directly after registration with + * {@link #addOnPreferredDevicesForCapturePresetChangedListener( + * Executor, OnPreferredDevicesForCapturePresetChangedListener)} + * to indicate which strategies had preferred devices at the time of registration.</p> + * @see #setPreferredDeviceForCapturePreset(int, AudioDeviceAttributes) + * @see #clearPreferredDevicesForCapturePreset(int) + * @see #getPreferredDevicesForCapturePreset(int) + */ + @SystemApi + public interface OnPreferredDevicesForCapturePresetChangedListener { + /** + * Called on the listener to indicate that the preferred audio devices for the given + * capture preset has changed. + * @param capturePreset the capture preset whose preferred device changed + * @param devices a list of newly set preferred audio devices + */ + void onPreferredDevicesForCapturePresetChanged( + int capturePreset, @NonNull List<AudioDeviceAttributes> devices); + } + + /** + * @hide + * Adds a listener for being notified of changes to the capture-preset-preferred audio device. + * @param executor + * @param listener + * @throws SecurityException if the caller doesn't hold the required permission + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void addOnPreferredDevicesForCapturePresetChangedListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull OnPreferredDevicesForCapturePresetChangedListener listener) + throws SecurityException { + Objects.requireNonNull(executor); + Objects.requireNonNull(listener); + int status = addOnDevRoleForCapturePresetChangedListener( + executor, listener, AudioSystem.DEVICE_ROLE_PREFERRED); + if (status == AudioSystem.ERROR) { + // This must not happen + throw new RuntimeException("Unknown error happened"); + } + if (status == AudioSystem.BAD_VALUE) { + throw new IllegalArgumentException( + "attempt to call addOnPreferredDevicesForCapturePresetChangedListener() " + + "on a previously registered listener"); + } + } + + /** + * @hide + * Removes a previously added listener of changes to the capture-preset-preferred audio device. + * @param listener + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void removeOnPreferredDevicesForCapturePresetChangedListener( + @NonNull OnPreferredDevicesForCapturePresetChangedListener listener) { + Objects.requireNonNull(listener); + int status = removeOnDevRoleForCapturePresetChangedListener( + listener, AudioSystem.DEVICE_ROLE_PREFERRED); + if (status == AudioSystem.ERROR) { + // This must not happen + throw new RuntimeException("Unknown error happened"); + } + if (status == AudioSystem.BAD_VALUE) { + throw new IllegalArgumentException( + "attempt to call removeOnPreferredDevicesForCapturePresetChangedListener() " + + "on an unregistered listener"); + } + } + + private <T> int addOnDevRoleForCapturePresetChangedListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull T listener, int deviceRole) { + Objects.requireNonNull(executor); + Objects.requireNonNull(listener); + DevRoleListeners<T> devRoleListeners = + (DevRoleListeners<T>) mDevRoleForCapturePresetListeners.get(deviceRole); + if (devRoleListeners == null) { + return AudioSystem.ERROR; + } + synchronized (devRoleListeners.mDevRoleListenersLock) { + if (devRoleListeners.hasDevRoleListener(listener)) { + return AudioSystem.BAD_VALUE; + } + // lazy initialization of the list of device role listener + if (devRoleListeners.mListenerInfos == null) { + devRoleListeners.mListenerInfos = new ArrayList<>(); + } + final int oldCbCount = devRoleListeners.mListenerInfos.size(); + devRoleListeners.mListenerInfos.add(new DevRoleListenerInfo<T>(executor, listener)); + if (oldCbCount == 0 && devRoleListeners.mListenerInfos.size() > 0) { + // register binder for callbacks + synchronized (mDevRoleForCapturePresetListenersLock) { + int deviceRoleListenerStatus = mDeviceRoleListenersStatus; + mDeviceRoleListenersStatus |= (1 << deviceRole); + if (deviceRoleListenerStatus != 0) { + // There are already device role changed listeners active. + return AudioSystem.SUCCESS; + } + if (mDevicesRoleForCapturePresetDispatcherStub == null) { + mDevicesRoleForCapturePresetDispatcherStub = + new CapturePresetDevicesRoleDispatcherStub(); + } + try { + getService().registerCapturePresetDevicesRoleDispatcher( + mDevicesRoleForCapturePresetDispatcherStub); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + return AudioSystem.SUCCESS; + } + + private <T> int removeOnDevRoleForCapturePresetChangedListener( + @NonNull T listener, int deviceRole) { + Objects.requireNonNull(listener); + DevRoleListeners<T> devRoleListeners = + (DevRoleListeners<T>) mDevRoleForCapturePresetListeners.get(deviceRole); + if (devRoleListeners == null) { + return AudioSystem.ERROR; + } + synchronized (devRoleListeners.mDevRoleListenersLock) { + if (!devRoleListeners.removeDevRoleListener(listener)) { + return AudioSystem.BAD_VALUE; + } + if (devRoleListeners.mListenerInfos.size() == 0) { + // unregister binder for callbacks + synchronized (mDevRoleForCapturePresetListenersLock) { + mDeviceRoleListenersStatus ^= (1 << deviceRole); + if (mDeviceRoleListenersStatus != 0) { + // There are some other device role changed listeners active. + return AudioSystem.SUCCESS; + } + try { + getService().unregisterCapturePresetDevicesRoleDispatcher( + mDevicesRoleForCapturePresetDispatcherStub); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + return AudioSystem.SUCCESS; + } + + private final Map<Integer, Object> mDevRoleForCapturePresetListeners = new HashMap<>(){{ + put(AudioSystem.DEVICE_ROLE_PREFERRED, + new DevRoleListeners<OnPreferredDevicesForCapturePresetChangedListener>()); + }}; + + private class DevRoleListenerInfo<T> { + final @NonNull Executor mExecutor; + final @NonNull T mListener; + DevRoleListenerInfo(Executor executor, T listener) { + mExecutor = executor; + mListener = listener; + } + } + + private class DevRoleListeners<T> { + private final Object mDevRoleListenersLock = new Object(); + @GuardedBy("mDevRoleListenersLock") + private @Nullable ArrayList<DevRoleListenerInfo<T>> mListenerInfos; + + @GuardedBy("mDevRoleListenersLock") + private @Nullable DevRoleListenerInfo<T> getDevRoleListenerInfo(T listener) { + if (mListenerInfos == null) { + return null; + } + for (DevRoleListenerInfo<T> listenerInfo : mListenerInfos) { + if (listenerInfo.mListener == listener) { + return listenerInfo; + } + } + return null; + } + + @GuardedBy("mDevRoleListenersLock") + private boolean hasDevRoleListener(T listener) { + return getDevRoleListenerInfo(listener) != null; + } + + @GuardedBy("mDevRoleListenersLock") + private boolean removeDevRoleListener(T listener) { + final DevRoleListenerInfo<T> infoToRemove = getDevRoleListenerInfo(listener); + if (infoToRemove != null) { + mListenerInfos.remove(infoToRemove); + return true; + } + return false; + } + } + + private final Object mDevRoleForCapturePresetListenersLock = new Object(); + /** + * Record if there is a listener added for device role change. If there is a listener added for + * a specified device role change, the bit at position `1 << device_role` is set. + */ + @GuardedBy("mDevRoleForCapturePresetListenersLock") + private int mDeviceRoleListenersStatus = 0; + @GuardedBy("mDevRoleForCapturePresetListenersLock") + private CapturePresetDevicesRoleDispatcherStub mDevicesRoleForCapturePresetDispatcherStub; + + private final class CapturePresetDevicesRoleDispatcherStub + extends ICapturePresetDevicesRoleDispatcher.Stub { + + @Override + public void dispatchDevicesRoleChanged( + int capturePreset, int role, List<AudioDeviceAttributes> devices) { + final Object listenersObj = mDevRoleForCapturePresetListeners.get(role); + if (listenersObj == null) { + return; + } + switch (role) { + case AudioSystem.DEVICE_ROLE_PREFERRED: { + final DevRoleListeners<OnPreferredDevicesForCapturePresetChangedListener> + listeners = + (DevRoleListeners<OnPreferredDevicesForCapturePresetChangedListener>) + listenersObj; + final ArrayList<DevRoleListenerInfo< + OnPreferredDevicesForCapturePresetChangedListener>> prefDevListeners; + synchronized (listeners.mDevRoleListenersLock) { + if (listeners.mListenerInfos.isEmpty()) { + return; + } + prefDevListeners = (ArrayList<DevRoleListenerInfo< + OnPreferredDevicesForCapturePresetChangedListener>>) + listeners.mListenerInfos.clone(); + } + final long ident = Binder.clearCallingIdentity(); + try { + for (DevRoleListenerInfo< + OnPreferredDevicesForCapturePresetChangedListener> info : + prefDevListeners) { + info.mExecutor.execute(() -> + info.mListener.onPreferredDevicesForCapturePresetChanged( + capturePreset, devices)); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } break; + default: + break; + } + } + } + + //==================================================================== // Offload query /** * Returns whether offloaded playback of an audio format is supported on the device. @@ -5664,6 +6124,29 @@ public class AudioManager { } /** + * Returns an {@link AudioDeviceInfo} corresponding to the specified {@link AudioPort} ID. + * @param portId The audio port ID to look up for. + * @param flags A set of bitflags specifying the criteria to test. + * @see #GET_DEVICES_OUTPUTS + * @see #GET_DEVICES_INPUTS + * @see #GET_DEVICES_ALL + * @return An AudioDeviceInfo or null if no device with matching port ID is found. + * @hide + */ + public static AudioDeviceInfo getDeviceForPortId(int portId, int flags) { + if (portId == 0) { + return null; + } + AudioDeviceInfo[] devices = getDevicesStatic(flags); + for (AudioDeviceInfo device : devices) { + if (device.getId() == portId) { + return device; + } + } + return null; + } + + /** * Registers an {@link AudioDeviceCallback} object to receive notifications of changes * to the set of connected audio devices. * @param callback The {@link AudioDeviceCallback} object to receive connect/disconnect @@ -6206,6 +6689,297 @@ public class AudioManager { } } + /** + * Selects the audio device that should be used for communication use cases, for instance voice + * or video calls. This method can be used by voice or video chat applications to select a + * different audio device than the one selected by default by the platform. + * <p>The device selection is expressed as an {@link AudioDeviceInfo}, of role sink + * ({@link AudioDeviceInfo#isSink()} is <code>true</code>) and of one of the following types: + * <ul> + * <li> {@link AudioDeviceInfo#TYPE_BUILTIN_EARPIECE} + * <li> {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER} + * <li> {@link AudioDeviceInfo#TYPE_WIRED_HEADSET} + * <li> {@link AudioDeviceInfo#TYPE_BLUETOOTH_SCO} + * <li> {@link AudioDeviceInfo#TYPE_USB_HEADSET} + * <li> {@link AudioDeviceInfo#TYPE_BLE_HEADSET} + * </ul> + * The selection is active as long as the requesting application lives, until + * {@link #clearDeviceForCommunication} is called or until the device is disconnected. + * It is therefore important for applications to clear the request when a call ends or the + * application is paused. + * <p>In case of simultaneous requests by multiple applications the priority is given to the + * application currently controlling the audio mode (see {@link #setMode(int)}). This is the + * latest application having selected mode {@link #MODE_IN_COMMUNICATION} or mode + * {@link #MODE_IN_CALL}. Note that <code>MODE_IN_CALL</code> can only be selected by the main + * telephony application with permission + * {@link android.Manifest.permission#MODIFY_PHONE_STATE}. + * <p> If the requested devices is not currently available, the request will be rejected and + * the method will return false. + * <p>This API replaces the following deprecated APIs: + * <ul> + * <li> {@link #startBluetoothSco()} + * <li> {@link #stopBluetoothSco()} + * <li> {@link #setSpeakerphoneOn(boolean)} + * </ul> + * <h4>Example</h4> + * <p>The example below shows how to enable and disable speakerphone mode. + * <pre class="prettyprint"> + * // Get an AudioManager instance + * AudioManager audioManager = Context.getSystemService(AudioManager.class); + * try { + * AudioDeviceInfo speakerDevice = null; + * AudioDeviceInfo[] devices = audioManager.getDevices(GET_DEVICES_OUTPUTS); + * for (AudioDeviceInfo device : devices) { + * if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) { + * speakerDevice = device; + * break; + * } + * } + * if (speakerDevice != null) { + * // Turn speakerphone ON. + * boolean result = audioManager.setDeviceForCommunication(speakerDevice); + * if (!result) { + * // Handle error. + * } + * // Turn speakerphone OFF. + * audioManager.clearDeviceForCommunication(); + * } + * } catch (IllegalArgumentException e) { + * // Handle exception. + * } + * </pre> + * @param device the requested audio device. + * @return <code>true</code> if the request was accepted, <code>false</code> otherwise. + * @throws IllegalArgumentException If an invalid device is specified. + */ + public boolean setDeviceForCommunication(@NonNull AudioDeviceInfo device) { + Objects.requireNonNull(device); + try { + if (device.getId() == 0) { + throw new IllegalArgumentException("In valid device: " + device); + } + return getService().setDeviceForCommunication(mICallBack, device.getId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Cancels previous communication device selection made with + * {@link #setDeviceForCommunication(AudioDeviceInfo)}. + */ + public void clearDeviceForCommunication() { + try { + getService().setDeviceForCommunication(mICallBack, 0); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns currently selected audio device for communication. + * <p>This API replaces the following deprecated APIs: + * <ul> + * <li> {@link #isBluetoothScoOn()} + * <li> {@link #isSpeakerphoneOn()} + * </ul> + * @return an {@link AudioDeviceInfo} indicating which audio device is + * currently selected or communication use cases or null if default selection + * is used. + */ + @Nullable + public AudioDeviceInfo getDeviceForCommunication() { + try { + return getDeviceForPortId( + getService().getDeviceForCommunication(), GET_DEVICES_OUTPUTS); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + * Returns an {@link AudioDeviceInfo} corresponding to a connected device of the type provided. + * The type must be a valid output type defined in <code>AudioDeviceInfo</code> class, + * for instance {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER}. + * The method will return null if no device of the provided type is connected. + * If more than one device of the provided type is connected, an object corresponding to the + * first device encountered in the enumeration list will be returned. + * @param deviceType The device device for which an <code>AudioDeviceInfo</code> + * object is queried. + * @return An AudioDeviceInfo object or null if no device with the requested type is connected. + * @throws IllegalArgumentException If an invalid device type is specified. + */ + @TestApi + @Nullable + public static AudioDeviceInfo getDeviceInfoFromType( + @AudioDeviceInfo.AudioDeviceTypeOut int deviceType) { + AudioDeviceInfo[] devices = getDevicesStatic(GET_DEVICES_OUTPUTS); + for (AudioDeviceInfo device : devices) { + if (device.getType() == deviceType) { + return device; + } + } + return null; + } + + /** + * Listener registered by client to be notified upon communication audio device change. + * See {@link #setDeviceForCommunication(AudioDeviceInfo)}. + */ + public interface OnCommunicationDeviceChangedListener { + /** + * Callback method called upon communication audio device change. + * @param device the audio device selected for communication use cases + */ + void onCommunicationDeviceChanged(@Nullable AudioDeviceInfo device); + } + + /** + * Adds a listener for being notified of changes to the communication audio device. + * See {@link #setDeviceForCommunication(AudioDeviceInfo)}. + * @param executor + * @param listener + */ + public void addOnCommunicationDeviceChangedListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull OnCommunicationDeviceChangedListener listener) { + Objects.requireNonNull(executor); + Objects.requireNonNull(listener); + synchronized (mCommDevListenerLock) { + if (hasCommDevListener(listener)) { + throw new IllegalArgumentException( + "attempt to call addOnCommunicationDeviceChangedListener() " + + "on a previously registered listener"); + } + // lazy initialization of the list of strategy-preferred device listener + if (mCommDevListeners == null) { + mCommDevListeners = new ArrayList<>(); + } + final int oldCbCount = mCommDevListeners.size(); + mCommDevListeners.add(new CommDevListenerInfo(listener, executor)); + if (oldCbCount == 0 && mCommDevListeners.size() > 0) { + // register binder for callbacks + if (mCommDevDispatcherStub == null) { + mCommDevDispatcherStub = new CommunicationDeviceDispatcherStub(); + } + try { + getService().registerCommunicationDeviceDispatcher(mCommDevDispatcherStub); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + + /** + * Removes a previously added listener of changes to the communication audio device. + * See {@link #setDeviceForCommunication(AudioDeviceInfo)}. + * @param listener + */ + public void removeOnCommunicationDeviceChangedListener( + @NonNull OnCommunicationDeviceChangedListener listener) { + Objects.requireNonNull(listener); + synchronized (mCommDevListenerLock) { + if (!removeCommDevListener(listener)) { + throw new IllegalArgumentException( + "attempt to call removeOnCommunicationDeviceChangedListener() " + + "on an unregistered listener"); + } + if (mCommDevListeners.size() == 0) { + // unregister binder for callbacks + try { + getService().unregisterCommunicationDeviceDispatcher( + mCommDevDispatcherStub); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } finally { + mCommDevDispatcherStub = null; + mCommDevListeners = null; + } + } + } + } + + private final Object mCommDevListenerLock = new Object(); + /** + * List of listeners for preferred device for strategy and their associated Executor. + * List is lazy-initialized on first registration + */ + @GuardedBy("mCommDevListenerLock") + private @Nullable ArrayList<CommDevListenerInfo> mCommDevListeners; + + private static class CommDevListenerInfo { + final @NonNull OnCommunicationDeviceChangedListener mListener; + final @NonNull Executor mExecutor; + + CommDevListenerInfo(OnCommunicationDeviceChangedListener listener, Executor exe) { + mListener = listener; + mExecutor = exe; + } + } + + @GuardedBy("mCommDevListenerLock") + private CommunicationDeviceDispatcherStub mCommDevDispatcherStub; + + private final class CommunicationDeviceDispatcherStub + extends ICommunicationDeviceDispatcher.Stub { + + @Override + public void dispatchCommunicationDeviceChanged(int portId) { + // make a shallow copy of listeners so callback is not executed under lock + final ArrayList<CommDevListenerInfo> commDevListeners; + synchronized (mCommDevListenerLock) { + if (mCommDevListeners == null || mCommDevListeners.size() == 0) { + return; + } + commDevListeners = (ArrayList<CommDevListenerInfo>) mCommDevListeners.clone(); + } + AudioDeviceInfo device = getDeviceForPortId(portId, GET_DEVICES_OUTPUTS); + final long ident = Binder.clearCallingIdentity(); + try { + for (CommDevListenerInfo info : commDevListeners) { + info.mExecutor.execute(() -> + info.mListener.onCommunicationDeviceChanged(device)); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + @GuardedBy("mCommDevListenerLock") + private @Nullable CommDevListenerInfo getCommDevListenerInfo( + OnCommunicationDeviceChangedListener listener) { + if (mCommDevListeners == null) { + return null; + } + for (CommDevListenerInfo info : mCommDevListeners) { + if (info.mListener == listener) { + return info; + } + } + return null; + } + + @GuardedBy("mCommDevListenerLock") + private boolean hasCommDevListener(OnCommunicationDeviceChangedListener listener) { + return getCommDevListenerInfo(listener) != null; + } + + @GuardedBy("mCommDevListenerLock") + /** + * @return true if the listener was removed from the list + */ + private boolean removeCommDevListener(OnCommunicationDeviceChangedListener listener) { + final CommDevListenerInfo infoToRemove = getCommDevListenerInfo(listener); + if (infoToRemove != null) { + mCommDevListeners.remove(infoToRemove); + return true; + } + return false; + } + //--------------------------------------------------------- // Inner classes //-------------------- diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index f1eb53dda5b9..18c8a72b165b 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -28,11 +28,13 @@ import android.media.audiopolicy.AudioMix; import android.os.Build; import android.telephony.TelephonyManager; import android.util.Log; +import android.util.Pair; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -1371,6 +1373,11 @@ public class AudioSystem /** @hide */ public static final int FOR_VIBRATE_RINGING = 7; private static final int NUM_FORCE_USE = 8; + // Device role in audio policy + public static final int DEVICE_ROLE_NONE = 0; + public static final int DEVICE_ROLE_PREFERRED = 1; + public static final int DEVICE_ROLE_DISABLED = 2; + /** @hide */ public static String forceUseUsageToString(int usage) { switch (usage) { @@ -1691,47 +1698,186 @@ public class AudioSystem /** * @hide - * Sets the preferred device to use for a given audio strategy in the audio policy engine + * Set device as role for product strategy. * @param strategy the id of the strategy to configure - * @param device the device type and address to route to when available + * @param role the role of the devices + * @param devices the list of devices to be set as role for the given strategy * @return {@link #SUCCESS} if successfully set */ - public static int setPreferredDeviceForStrategy( - int strategy, @NonNull AudioDeviceAttributes device) { - return setPreferredDeviceForStrategy(strategy, - AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType()), - device.getAddress()); + public static int setDevicesRoleForStrategy( + int strategy, int role, @NonNull List<AudioDeviceAttributes> devices) { + if (devices.isEmpty()) { + return BAD_VALUE; + } + int[] types = new int[devices.size()]; + String[] addresses = new String[devices.size()]; + for (int i = 0; i < devices.size(); ++i) { + types[i] = devices.get(i).getInternalType(); + addresses[i] = devices.get(i).getAddress(); + } + return setDevicesRoleForStrategy(strategy, role, types, addresses); } + /** * @hide - * Set device routing per product strategy. + * Set device as role for product strategy. * @param strategy the id of the strategy to configure - * @param deviceType the native device type, NOT AudioDeviceInfo types - * @param deviceAddress the address of the device + * @param role the role of the devices + * @param types all device types + * @param addresses all device addresses * @return {@link #SUCCESS} if successfully set */ - private static native int setPreferredDeviceForStrategy( - int strategy, int deviceType, String deviceAddress); + private static native int setDevicesRoleForStrategy( + int strategy, int role, @NonNull int[] types, @NonNull String[] addresses); /** * @hide - * Remove preferred routing for the strategy + * Remove devices as role for the strategy * @param strategy the id of the strategy to configure + * @param role the role of the devices * @return {@link #SUCCESS} if successfully removed */ - public static native int removePreferredDeviceForStrategy(int strategy); + public static native int removeDevicesRoleForStrategy(int strategy, int role); /** * @hide - * Query previously set preferred device for a strategy + * Query previously set devices as role for a strategy * @param strategy the id of the strategy to query for - * @param device an array of size 1 that will contain the preferred device, or null if - * none was set + * @param role the role of the devices + * @param devices a list that will contain the devices of role + * @return {@link #SUCCESS} if there is a preferred device and it was successfully retrieved + * and written to the array + */ + public static native int getDevicesForRoleAndStrategy( + int strategy, int role, @NonNull List<AudioDeviceAttributes> devices); + + // use case routing by capture preset + + private static Pair<int[], String[]> populateInputDevicesTypeAndAddress( + @NonNull List<AudioDeviceAttributes> devices) { + int[] types = new int[devices.size()]; + String[] addresses = new String[devices.size()]; + for (int i = 0; i < devices.size(); ++i) { + types[i] = devices.get(i).getInternalType(); + if (types[i] == AudioSystem.DEVICE_NONE) { + types[i] = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice( + devices.get(i).getType()); + } + addresses[i] = devices.get(i).getAddress(); + } + return new Pair<int[], String[]>(types, addresses); + } + + /** + * @hide + * Set devices as role for capture preset. + * @param capturePreset the capture preset to configure + * @param role the role of the devices + * @param devices the list of devices to be set as role for the given capture preset + * @return {@link #SUCCESS} if successfully set + */ + public static int setDevicesRoleForCapturePreset( + int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices) { + if (devices.isEmpty()) { + return BAD_VALUE; + } + Pair<int[], String[]> typeAddresses = populateInputDevicesTypeAndAddress(devices); + return setDevicesRoleForCapturePreset( + capturePreset, role, typeAddresses.first, typeAddresses.second); + } + + /** + * @hide + * Set devices as role for capture preset. + * @param capturePreset the capture preset to configure + * @param role the role of the devices + * @param types all device types + * @param addresses all device addresses + * @return {@link #SUCCESS} if successfully set + */ + private static native int setDevicesRoleForCapturePreset( + int capturePreset, int role, @NonNull int[] types, @NonNull String[] addresses); + + /** + * @hide + * Add devices as role for capture preset. + * @param capturePreset the capture preset to configure + * @param role the role of the devices + * @param devices the list of devices to be added as role for the given capture preset + * @return {@link #SUCCESS} if successfully add + */ + public static int addDevicesRoleForCapturePreset( + int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices) { + if (devices.isEmpty()) { + return BAD_VALUE; + } + Pair<int[], String[]> typeAddresses = populateInputDevicesTypeAndAddress(devices); + return addDevicesRoleForCapturePreset( + capturePreset, role, typeAddresses.first, typeAddresses.second); + } + + /** + * @hide + * Add devices as role for capture preset. + * @param capturePreset the capture preset to configure + * @param role the role of the devices + * @param types all device types + * @param addresses all device addresses + * @return {@link #SUCCESS} if successfully set + */ + private static native int addDevicesRoleForCapturePreset( + int capturePreset, int role, @NonNull int[] types, @NonNull String[] addresses); + + /** + * @hide + * Remove devices as role for the capture preset + * @param capturePreset the capture preset to configure + * @param role the role of the devices + * @param devices the devices to be removed + * @return {@link #SUCCESS} if successfully removed + */ + public static int removeDevicesRoleForCapturePreset( + int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices) { + if (devices.isEmpty()) { + return BAD_VALUE; + } + Pair<int[], String[]> typeAddresses = populateInputDevicesTypeAndAddress(devices); + return removeDevicesRoleForCapturePreset( + capturePreset, role, typeAddresses.first, typeAddresses.second); + } + + /** + * @hide + * Remove devices as role for capture preset. + * @param capturePreset the capture preset to configure + * @param role the role of the devices + * @param types all device types + * @param addresses all device addresses + * @return {@link #SUCCESS} if successfully set + */ + private static native int removeDevicesRoleForCapturePreset( + int capturePreset, int role, @NonNull int[] types, @NonNull String[] addresses); + + /** + * @hide + * Remove all devices as role for the capture preset + * @param capturePreset the capture preset to configure + * @param role the role of the devices + * @return {@link #SUCCESS} if successfully removed + */ + public static native int clearDevicesRoleForCapturePreset(int capturePreset, int role); + + /** + * @hide + * Query previously set devices as role for a capture preset + * @param capturePreset the capture preset to query for + * @param role the role of the devices + * @param devices a list that will contain the devices of role * @return {@link #SUCCESS} if there is a preferred device and it was successfully retrieved * and written to the array */ - public static native int getPreferredDeviceForStrategy(int strategy, - AudioDeviceAttributes[] device); + public static native int getDevicesForRoleAndCapturePreset( + int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices); // Items shared with audio service diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 78806eb9e547..ebaa3162d0e4 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -26,10 +26,12 @@ import android.media.AudioRoutesInfo; import android.media.IAudioFocusDispatcher; import android.media.IAudioRoutesObserver; import android.media.IAudioServerStateDispatcher; +import android.media.ICapturePresetDevicesRoleDispatcher; +import android.media.ICommunicationDeviceDispatcher; import android.media.IPlaybackConfigDispatcher; import android.media.IRecordingConfigDispatcher; import android.media.IRingtonePlayer; -import android.media.IStrategyPreferredDeviceDispatcher; +import android.media.IStrategyPreferredDevicesDispatcher; import android.media.IVolumeController; import android.media.IVolumeController; import android.media.PlayerBase; @@ -279,11 +281,11 @@ interface IAudioService { boolean isCallScreeningModeSupported(); - int setPreferredDeviceForStrategy(in int strategy, in AudioDeviceAttributes device); + int setPreferredDevicesForStrategy(in int strategy, in List<AudioDeviceAttributes> device); - int removePreferredDeviceForStrategy(in int strategy); + int removePreferredDevicesForStrategy(in int strategy); - AudioDeviceAttributes getPreferredDeviceForStrategy(in int strategy); + List<AudioDeviceAttributes> getPreferredDevicesForStrategy(in int strategy); List<AudioDeviceAttributes> getDevicesForAttributes(in AudioAttributes attributes); @@ -291,10 +293,10 @@ interface IAudioService { int getAllowedCapturePolicy(); - void registerStrategyPreferredDeviceDispatcher(IStrategyPreferredDeviceDispatcher dispatcher); + void registerStrategyPreferredDevicesDispatcher(IStrategyPreferredDevicesDispatcher dispatcher); - oneway void unregisterStrategyPreferredDeviceDispatcher( - IStrategyPreferredDeviceDispatcher dispatcher); + oneway void unregisterStrategyPreferredDevicesDispatcher( + IStrategyPreferredDevicesDispatcher dispatcher); oneway void setRttEnabled(in boolean rttEnabled); @@ -307,4 +309,25 @@ interface IAudioService { // code via IAudioManager.h need to be added to the top section. oneway void setMultiAudioFocusEnabled(in boolean enabled); + + int setPreferredDevicesForCapturePreset( + in int capturePreset, in List<AudioDeviceAttributes> devices); + + int clearPreferredDevicesForCapturePreset(in int capturePreset); + + List<AudioDeviceAttributes> getPreferredDevicesForCapturePreset(in int capturePreset); + + void registerCapturePresetDevicesRoleDispatcher(ICapturePresetDevicesRoleDispatcher dispatcher); + + oneway void unregisterCapturePresetDevicesRoleDispatcher( + ICapturePresetDevicesRoleDispatcher dispatcher); + + boolean setDeviceForCommunication(IBinder cb, int portId); + + int getDeviceForCommunication(); + + void registerCommunicationDeviceDispatcher(ICommunicationDeviceDispatcher dispatcher); + + oneway void unregisterCommunicationDeviceDispatcher( + ICommunicationDeviceDispatcher dispatcher); } diff --git a/media/java/android/media/ICapturePresetDevicesRoleDispatcher.aidl b/media/java/android/media/ICapturePresetDevicesRoleDispatcher.aidl new file mode 100644 index 000000000000..5e03e632c4ff --- /dev/null +++ b/media/java/android/media/ICapturePresetDevicesRoleDispatcher.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 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 AudioService to signal devices role for capture preset updates. + * + * {@hide} + */ +oneway interface ICapturePresetDevicesRoleDispatcher { + + void dispatchDevicesRoleChanged( + int capturePreset, int role, in List<AudioDeviceAttributes> devices); + +} diff --git a/media/java/android/media/ICommunicationDeviceDispatcher.aidl b/media/java/android/media/ICommunicationDeviceDispatcher.aidl new file mode 100644 index 000000000000..429f934a77dc --- /dev/null +++ b/media/java/android/media/ICommunicationDeviceDispatcher.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020 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; + +/** + * AIDL for AudioService to signal audio communication device updates. + * + * {@hide} + */ +oneway interface ICommunicationDeviceDispatcher { + + void dispatchCommunicationDeviceChanged(int portId); + +} diff --git a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl b/media/java/android/media/IStrategyPreferredDevicesDispatcher.aidl index b1f99e6b729e..db674c36a5c9 100644 --- a/media/java/android/media/IStrategyPreferredDeviceDispatcher.aidl +++ b/media/java/android/media/IStrategyPreferredDevicesDispatcher.aidl @@ -19,12 +19,12 @@ package android.media; import android.media.AudioDeviceAttributes; /** - * AIDL for AudioService to signal audio strategy-preferred device updates. + * AIDL for AudioService to signal audio strategy-preferred devices updates. * * {@hide} */ -oneway interface IStrategyPreferredDeviceDispatcher { +oneway interface IStrategyPreferredDevicesDispatcher { - void dispatchPrefDeviceChanged(int strategyId, in AudioDeviceAttributes device); + void dispatchPrefDevicesChanged(int strategyId, in List<AudioDeviceAttributes> devices); } diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index e7e83ebb001f..9657b25e7c18 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -1865,10 +1865,6 @@ public final class MediaCodecInfo { && aligned.mMaxMacroBlockRate >= otherAligned.mMaxMacroBlockRate); } - /* package private */ boolean isEqualDimension(@NonNull PerformancePoint other) { - return mWidth == other.mWidth && mHeight == other.mHeight; - } - private @NonNull Size getCommonBlockSize(@NonNull PerformancePoint other) { return new Size( Math.max(mBlockSize.getWidth(), other.mBlockSize.getWidth()) * 16, @@ -2010,8 +2006,11 @@ public final class MediaCodecInfo { * codecs are active, should use that highest pixel count, and add the frame rates of * each individual codec. * <p class=note> - * Supported resolution could be further restricted for 32-bit processes due to - * the limited virtual memory space. + * 32-bit processes will not support resolutions larger than 4096x4096 due to + * the limited address space, but performance points will be presented as is. + * In other words, even though a component publishes a performance point for + * a resolution higher than 4096x4096, it does not mean that the resolution is supported + * for 32-bit processes. */ @Nullable public List<PerformancePoint> getSupportedPerformancePoints() { @@ -2215,28 +2214,6 @@ public final class MediaCodecInfo { (a.getMaxFrameRate() != b.getMaxFrameRate()) ? (a.getMaxFrameRate() < b.getMaxFrameRate() ? -1 : 1) : 0)); - // remove redundant points - for (int i = 1; i < ret.size(); ++i) { - PerformancePoint a = ret.get(i); - for (int j = 0; j < i; ++j) { - PerformancePoint b = ret.get(j); - if (b.isEqualDimension(a) && b.covers(a)) { - ret.set(i, null); - break; - } - } - } - int newSize = 0; - for (int i = 0; i < ret.size(); ++i) { - PerformancePoint a = ret.get(i); - if (a == null) { - continue; - } - ret.set(newSize, a); - ++newSize; - } - ret.setSize(newSize); - return Collections.unmodifiableList(ret); } diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 73ef31504b74..c61a2eb02921 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -404,6 +404,32 @@ public class MediaRecorder implements AudioRouting, } } + /** + * @hide + * @param source An audio source to test + * @return true if the source is a valid one + */ + public static boolean isValidAudioSource(int source) { + switch(source) { + case AudioSource.MIC: + case AudioSource.VOICE_UPLINK: + case AudioSource.VOICE_DOWNLINK: + case AudioSource.VOICE_CALL: + case AudioSource.CAMCORDER: + case AudioSource.VOICE_RECOGNITION: + case AudioSource.VOICE_COMMUNICATION: + case AudioSource.REMOTE_SUBMIX: + case AudioSource.UNPROCESSED: + case AudioSource.VOICE_PERFORMANCE: + case AudioSource.ECHO_REFERENCE: + case AudioSource.RADIO_TUNER: + case AudioSource.HOTWORD: + return true; + default: + return false; + } + } + /** @hide */ public static final String toLogFriendlyAudioSource(int source) { switch(source) { diff --git a/media/java/android/media/OWNERS b/media/java/android/media/OWNERS index cbc9ab7e4adf..cf06fad3e203 100644 --- a/media/java/android/media/OWNERS +++ b/media/java/android/media/OWNERS @@ -6,3 +6,4 @@ lajos@google.com olly@google.com andrewlewis@google.com sungsoo@google.com +jmtrivi@google.com diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java index 433c6227cd5f..30a14c84b72e 100644 --- a/media/java/android/media/tv/TvContract.java +++ b/media/java/android/media/tv/TvContract.java @@ -2450,6 +2450,71 @@ public final class TvContract { */ public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id"; + /** + * The remote control key preset number that is assigned to this channel. + * + * <p> This can be used for one-touch-tuning, tuning to the channel with + * pressing the preset button. + * + * <p> Type: INTEGER (remote control key preset number) + */ + public static final String COLUMN_REMOTE_CONTROL_KEY_PRESET_NUMBER = + "remote_control_key_preset_number"; + + /** + * The flag indicating whether this TV channel is scrambled or not. + * + * <p>Use the same coding for scrambled in the underlying broadcast standard + * if {@code free_ca_mode} in SDT is defined there (e.g. ETSI EN 300 468). + * + * <p>Type: INTEGER (boolean) + */ + public static final String COLUMN_SCRAMBLED = "scrambled"; + + /** + * The typical video resolution. + * + * <p>This is primarily used to filter out channels based on video resolution + * by applications. The value is from SDT if defined there. (e.g. ETSI EN 300 468) + * The value should match one of the followings: {@link #VIDEO_RESOLUTION_SD}, + * {@link #VIDEO_RESOLUTION_HD}, {@link #VIDEO_RESOLUTION_UHD}. + * + * <p>Type: TEXT + * + */ + public static final String COLUMN_VIDEO_RESOLUTION = "video_resolution"; + + /** + * The channel list ID of this TV channel. + * + * <p>It is used to identify the channel list constructed from broadcast SI based on the + * underlying broadcast standard or country/operator profile, if applicable. Otherwise, + * leave empty. + * + * <p>The ID can be defined by individual TV input services. For example, one may assign a + * service operator name for the service operator channel list constructed from broadcast + * SI or one may assign the {@code profile_name} of the operator_info() APDU defined in CI + * Plus 1.3 for the dedicated CICAM operator profile channel list constructed + * from CICAM NIT. + * + * <p>Type: TEXT + */ + public static final String COLUMN_CHANNEL_LIST_ID = "channel_list_id"; + + /** + * The comma-separated genre string of this TV channel. + * + * <p>Use the same language appeared in the underlying broadcast standard, if applicable. + * Otherwise, leave empty. Use + * {@link Genres#encode Genres.encode()} to create a text that can be stored in this column. + * Use {@link Genres#decode Genres.decode()} to get the broadcast genre strings from the + * text stored in the column. + * + * <p>Type: TEXT + * @see Programs#COLUMN_BROADCAST_GENRE + */ + public static final String COLUMN_BROADCAST_GENRE = Programs.COLUMN_BROADCAST_GENRE; + private Channels() {} /** diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 8bf688dba10b..b743a958828a 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -60,6 +60,7 @@ import com.android.internal.util.FrameworkStatsLog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -210,6 +211,7 @@ public class Tuner implements AutoCloseable { @Nullable private FrontendInfo mFrontendInfo; private Integer mFrontendHandle; + private Boolean mIsSharedFrontend = false; private int mFrontendType = FrontendSettings.TYPE_UNDEFINED; private int mUserId; private Lnb mLnb; @@ -228,8 +230,8 @@ public class Tuner implements AutoCloseable { private Executor mOnResourceLostListenerExecutor; private Integer mDemuxHandle; - private Map<Integer, Descrambler> mDescramblers = new HashMap<>(); - private List<Filter> mFilters = new ArrayList<>(); + private Map<Integer, WeakReference<Descrambler>> mDescramblers = new HashMap<>(); + private List<WeakReference<Filter>> mFilters = new ArrayList<WeakReference<Filter>>(); private final TunerResourceManager.ResourcesReclaimListener mResourceListener = new TunerResourceManager.ResourcesReclaimListener() { @@ -240,6 +242,7 @@ public class Tuner implements AutoCloseable { .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN); } + releaseAll(); mHandler.sendMessage(mHandler.obtainMessage(MSG_RESOURCE_LOST)); } }; @@ -336,8 +339,11 @@ public class Tuner implements AutoCloseable { */ public void shareFrontendFromTuner(@NonNull Tuner tuner) { mTunerResourceManager.shareFrontend(mClientId, tuner.mClientId); - mFrontendHandle = tuner.mFrontendHandle; - mFrontend = nativeOpenFrontendByHandle(mFrontendHandle); + synchronized (mIsSharedFrontend) { + mFrontendHandle = tuner.mFrontendHandle; + mFrontend = tuner.mFrontend; + mIsSharedFrontend = true; + } } /** @@ -362,33 +368,53 @@ public class Tuner implements AutoCloseable { */ @Override public void close() { + releaseAll(); + TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner"); + } + + private void releaseAll() { if (mFrontendHandle != null) { - int res = nativeCloseFrontend(mFrontendHandle); - if (res != Tuner.RESULT_SUCCESS) { - TunerUtils.throwExceptionForResult(res, "failed to close frontend"); + synchronized (mIsSharedFrontend) { + if (!mIsSharedFrontend) { + int res = nativeCloseFrontend(mFrontendHandle); + if (res != Tuner.RESULT_SUCCESS) { + TunerUtils.throwExceptionForResult(res, "failed to close frontend"); + } + } + mIsSharedFrontend = false; } mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId); FrameworkStatsLog .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId, - FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN); + FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN); mFrontendHandle = null; mFrontend = null; } if (mLnb != null) { mLnb.close(); } - if (!mDescramblers.isEmpty()) { - for (Map.Entry<Integer, Descrambler> d : mDescramblers.entrySet()) { - d.getValue().close(); - mTunerResourceManager.releaseDescrambler(d.getKey(), mClientId); + synchronized (mDescramblers) { + if (!mDescramblers.isEmpty()) { + for (Map.Entry<Integer, WeakReference<Descrambler>> d : mDescramblers.entrySet()) { + Descrambler descrambler = d.getValue().get(); + if (descrambler != null) { + descrambler.close(); + } + mTunerResourceManager.releaseDescrambler(d.getKey(), mClientId); + } + mDescramblers.clear(); } - mDescramblers.clear(); } - if (!mFilters.isEmpty()) { - for (Filter f : mFilters) { - f.close(); + synchronized (mFilters) { + if (!mFilters.isEmpty()) { + for (WeakReference<Filter> weakFilter : mFilters) { + Filter filter = weakFilter.get(); + if (filter != null) { + filter.close(); + } + } + mFilters.clear(); } - mFilters.clear(); } if (mDemuxHandle != null) { int res = nativeCloseDemux(mDemuxHandle); @@ -396,9 +422,9 @@ public class Tuner implements AutoCloseable { TunerUtils.throwExceptionForResult(res, "failed to close demux"); } mTunerResourceManager.releaseDemux(mDemuxHandle, mClientId); - mFrontendHandle = null; + mDemuxHandle = null; } - TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner"); + } /** @@ -610,10 +636,14 @@ public class Tuner implements AutoCloseable { @Result public int scan(@NonNull FrontendSettings settings, @ScanType int scanType, @NonNull @CallbackExecutor Executor executor, @NonNull ScanCallback scanCallback) { - if (mScanCallback != null || mScanCallbackExecutor != null) { + /** + * Scan can be called again for blink scan if scanCallback and executor are same as before. + */ + if (((mScanCallback != null) && (mScanCallback != scanCallback)) + || ((mScanCallbackExecutor != null) && (mScanCallbackExecutor != executor))) { throw new IllegalStateException( - "Scan already in progress. stopScan must be called before a new scan can be " - + "started."); + "Different Scan session already in progress. stopScan must be called " + + "before a new scan session can be " + "started."); } mFrontendType = settings.getType(); if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) { @@ -944,7 +974,10 @@ public class Tuner implements AutoCloseable { if (mHandler == null) { mHandler = createEventHandler(); } - mFilters.add(filter); + synchronized (mFilters) { + WeakReference<Filter> weakFilter = new WeakReference<Filter>(filter); + mFilters.add(weakFilter); + } } return filter; } @@ -1112,7 +1145,10 @@ public class Tuner implements AutoCloseable { int handle = descramblerHandle[0]; Descrambler descrambler = nativeOpenDescramblerByHandle(handle); if (descrambler != null) { - mDescramblers.put(handle, descrambler); + synchronized (mDescramblers) { + WeakReference weakDescrambler = new WeakReference<Descrambler>(descrambler); + mDescramblers.put(handle, weakDescrambler); + } } else { mTunerResourceManager.releaseDescrambler(handle, mClientId); } diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index 515d610109ab..5daf8b0f88f8 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -171,6 +171,12 @@ static int IP_V6_LENGTH = 16; void DestroyCallback(const C2Buffer * /* buf */, void *arg) { android::sp<android::MediaEvent> event = (android::MediaEvent *)arg; + if (event->mLinearBlockObj != NULL) { + JNIEnv *env = android::AndroidRuntime::getJNIEnv(); + env->DeleteWeakGlobalRef(event->mLinearBlockObj); + event->mLinearBlockObj = NULL; + } + event->mAvHandleRefCnt--; event->finalize(); } @@ -182,6 +188,12 @@ LnbCallback::LnbCallback(jobject lnbObj, LnbId id) : mId(id) { mLnb = env->NewWeakGlobalRef(lnbObj); } +LnbCallback::~LnbCallback() { + JNIEnv *env = AndroidRuntime::getJNIEnv(); + env->DeleteWeakGlobalRef(mLnb); + mLnb = NULL; +} + Return<void> LnbCallback::onEvent(LnbEventType lnbEventType) { ALOGD("LnbCallback::onEvent, type=%d", lnbEventType); JNIEnv *env = AndroidRuntime::getJNIEnv(); @@ -291,8 +303,9 @@ MQ& Dvr::getDvrMQ() { C2DataIdInfo::C2DataIdInfo(uint32_t index, uint64_t value) : C2Param(kParamSize, index) { CHECK(isGlobal()); CHECK_EQ(C2Param::INFO, kind()); - DummyInfo info{value}; - memcpy(this + 1, static_cast<C2Param *>(&info) + 1, kParamSize - sizeof(C2Param)); + mInfo = StubInfo(value); + memcpy(static_cast<C2Param *>(this) + 1, static_cast<C2Param *>(&mInfo) + 1, + kParamSize - sizeof(C2Param)); } /////////////// MediaEvent /////////////////////// @@ -304,6 +317,7 @@ MediaEvent::MediaEvent(sp<IFilter> iFilter, hidl_handle avHandle, JNIEnv *env = AndroidRuntime::getJNIEnv(); mMediaEventObj = env->NewWeakGlobalRef(obj); mAvHandle = native_handle_clone(avHandle.getNativeHandle()); + mLinearBlockObj = NULL; } MediaEvent::~MediaEvent() { @@ -337,33 +351,44 @@ jobject MediaEvent::getLinearBlock() { } mIonHandle = new C2HandleIon(dup(mAvHandle->data[0]), mDataLength); std::shared_ptr<C2LinearBlock> block = _C2BlockFactory::CreateLinearBlock(mIonHandle); - - JNIEnv *env = AndroidRuntime::getJNIEnv(); - std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock}; - context->mBlock = block; - std::shared_ptr<C2Buffer> pC2Buffer = context->toC2Buffer(0, mDataLength); - context->mBuffer = pC2Buffer; - mC2Buffer = pC2Buffer; - if (mAvHandle->numInts > 0) { - // use first int in the native_handle as the index - int index = mAvHandle->data[mAvHandle->numFds]; - std::shared_ptr<C2Param> c2param = std::make_shared<C2DataIdInfo>(index, mDataId); - std::shared_ptr<C2Info> info(std::static_pointer_cast<C2Info>(c2param)); - pC2Buffer->setInfo(info); - } - pC2Buffer->registerOnDestroyNotify(&DestroyCallback, this); - jobject linearBlock = - env->NewObject( - env->FindClass("android/media/MediaCodec$LinearBlock"), - gFields.linearBlockInitID); - env->CallVoidMethod( - linearBlock, - gFields.linearBlockSetInternalStateID, - (jlong)context.release(), - true); - mLinearBlockObj = env->NewWeakGlobalRef(linearBlock); - mAvHandleRefCnt++; - return mLinearBlockObj; + if (block != nullptr) { + // CreateLinearBlock delete mIonHandle after it create block successfully. + // ToDo: coordinate who is response to delete mIonHandle + mIonHandle = NULL; + JNIEnv *env = AndroidRuntime::getJNIEnv(); + std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock}; + context->mBlock = block; + std::shared_ptr<C2Buffer> pC2Buffer = context->toC2Buffer(0, mDataLength); + context->mBuffer = pC2Buffer; + mC2Buffer = pC2Buffer; + if (mAvHandle->numInts > 0) { + // use first int in the native_handle as the index + int index = mAvHandle->data[mAvHandle->numFds]; + std::shared_ptr<C2Param> c2param = std::make_shared<C2DataIdInfo>(index, mDataId); + std::shared_ptr<C2Info> info(std::static_pointer_cast<C2Info>(c2param)); + pC2Buffer->setInfo(info); + } + pC2Buffer->registerOnDestroyNotify(&DestroyCallback, this); + jobject linearBlock = + env->NewObject( + env->FindClass("android/media/MediaCodec$LinearBlock"), + gFields.linearBlockInitID); + env->CallVoidMethod( + linearBlock, + gFields.linearBlockSetInternalStateID, + (jlong)context.release(), + true); + mLinearBlockObj = env->NewWeakGlobalRef(linearBlock); + mAvHandleRefCnt++; + return linearBlock; + } else { + native_handle_close(const_cast<native_handle_t*>( + reinterpret_cast<const native_handle_t*>(mIonHandle))); + native_handle_delete(const_cast<native_handle_t*>( + reinterpret_cast<const native_handle_t*>(mIonHandle))); + mIonHandle = NULL; + return NULL; + } } uint64_t MediaEvent::getAudioHandle() { @@ -447,7 +472,7 @@ jobjectArray FilterCallback::getMediaEvent( if (mediaEvent.avMemory.getNativeHandle() != NULL || mediaEvent.avDataId != 0) { sp<MediaEvent> mediaEventSp = new MediaEvent(mIFilter, mediaEvent.avMemory, - mediaEvent.avDataId, dataLength, obj); + mediaEvent.avDataId, dataLength + offset, obj); mediaEventSp->mAvHandleRefCnt++; env->SetLongField(obj, eventContext, (jlong) mediaEventSp.get()); mediaEventSp->incStrong(obj); @@ -2048,7 +2073,7 @@ static FrontendSettings getDvbcFrontendSettings(JNIEnv *env, const jobject& sett env->GetIntField(settings, env->GetFieldID(clazz, "mModulation", "I"))); FrontendInnerFec innerFec = static_cast<FrontendInnerFec>( - env->GetLongField(settings, env->GetFieldID(clazz, "mFec", "J"))); + env->GetLongField(settings, env->GetFieldID(clazz, "mInnerFec", "J"))); uint32_t symbolRate = static_cast<uint32_t>( env->GetIntField(settings, env->GetFieldID(clazz, "mSymbolRate", "I"))); @@ -2057,7 +2082,7 @@ static FrontendSettings getDvbcFrontendSettings(JNIEnv *env, const jobject& sett env->GetIntField(settings, env->GetFieldID(clazz, "mOuterFec", "I"))); FrontendDvbcAnnex annex = static_cast<FrontendDvbcAnnex>( - env->GetByteField(settings, env->GetFieldID(clazz, "mAnnex", "B"))); + env->GetIntField(settings, env->GetFieldID(clazz, "mAnnex", "I"))); FrontendDvbcSpectralInversion spectralInversion = static_cast<FrontendDvbcSpectralInversion>( env->GetIntField( @@ -3497,6 +3522,10 @@ static jlong android_media_tv_Tuner_read_dvr(JNIEnv *env, jobject dvr, jlong siz } else { ALOGE("dvrMq.beginWrite failed"); } + + if (ret > 0) { + dvrSp->mDvrMQEventFlag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY)); + } return (jlong) ret; } @@ -3524,7 +3553,7 @@ static jlong android_media_tv_Tuner_read_dvr_from_array( if (dvrSp->mDvrMQ->write(reinterpret_cast<unsigned char*>(src) + offset, size)) { env->ReleaseByteArrayElements(buffer, src, 0); - dvrSp->mDvrMQEventFlag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_CONSUMED)); + dvrSp->mDvrMQEventFlag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY)); } else { ALOGD("Failed to write FMQ"); env->ReleaseByteArrayElements(buffer, src, 0); @@ -3585,6 +3614,9 @@ static jlong android_media_tv_Tuner_write_dvr(JNIEnv *env, jobject dvr, jlong si } else { ALOGE("dvrMq.beginRead failed"); } + if (ret > 0) { + dvrSp->mDvrMQEventFlag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_CONSUMED)); + } return (jlong) ret; } diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h index 83e9db796363..c4deeaf887bb 100644 --- a/media/jni/android_media_tv_Tuner.h +++ b/media/jni/android_media_tv_Tuner.h @@ -73,6 +73,7 @@ namespace android { struct LnbCallback : public ILnbCallback { LnbCallback(jweak tunerObj, LnbId id); + ~LnbCallback(); virtual Return<void> onEvent(LnbEventType lnbEventType); virtual Return<void> onDiseqcMessage(const hidl_vec<uint8_t>& diseqcMessage); jweak mLnb; @@ -250,8 +251,9 @@ class C2DataIdInfo : public C2Param { public: C2DataIdInfo(uint32_t index, uint64_t value); private: - typedef C2GlobalParam<C2Info, C2Int64Value, 0> DummyInfo; - static const size_t kParamSize = sizeof(DummyInfo); + typedef C2GlobalParam<C2Info, C2Int64Value, 0> StubInfo; + StubInfo mInfo; + static const size_t kParamSize = sizeof(StubInfo); }; } // namespace android diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp index d55e9d0efffd..0d53ab152129 100644 --- a/media/jni/audioeffect/android_media_AudioEffect.cpp +++ b/media/jni/audioeffect/android_media_AudioEffect.cpp @@ -333,7 +333,7 @@ android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_t if (deviceType != AUDIO_DEVICE_NONE) { device.mType = (audio_devices_t)deviceType; ScopedUtfChars address(env, deviceAddress); - device.mAddress = address.c_str(); + device.setAddress(address.c_str()); } // create the native AudioEffect object |