Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8553f77d authored by Eric Laurent's avatar Eric Laurent Committed by Gerrit Code Review
Browse files

Merge changes from topic "comm_routing"

* changes:
  Audio: use preferred device for strategy for communication route
  AudioManager: Add communication device management APIs
  AudioService: refactor communication route control
parents 617d1495 d88b1e45
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -19373,14 +19373,17 @@ package android.media {
  public class AudioManager {
    method @Deprecated public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener);
    method public int abandonAudioFocusRequest(@NonNull android.media.AudioFocusRequest);
    method public void addOnCommunicationDeviceChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnCommunicationDeviceChangedListener);
    method public void adjustStreamVolume(int, int, int);
    method public void adjustSuggestedStreamVolume(int, int, int);
    method public void adjustVolume(int, int);
    method public void clearDeviceForCommunication();
    method public void dispatchMediaKeyEvent(android.view.KeyEvent);
    method public int generateAudioSessionId();
    method @NonNull public java.util.List<android.media.AudioPlaybackConfiguration> getActivePlaybackConfigurations();
    method @NonNull public java.util.List<android.media.AudioRecordingConfiguration> getActiveRecordingConfigurations();
    method public int getAllowedCapturePolicy();
    method @Nullable public android.media.AudioDeviceInfo getDeviceForCommunication();
    method public android.media.AudioDeviceInfo[] getDevices(int);
    method public java.util.List<android.media.MicrophoneInfo> getMicrophones() throws java.io.IOException;
    method public int getMode();
@@ -19415,11 +19418,13 @@ package android.media {
    method @Deprecated public void registerMediaButtonEventReceiver(android.app.PendingIntent);
    method @Deprecated public void registerRemoteControlClient(android.media.RemoteControlClient);
    method @Deprecated public boolean registerRemoteController(android.media.RemoteController);
    method public void removeOnCommunicationDeviceChangedListener(@NonNull android.media.AudioManager.OnCommunicationDeviceChangedListener);
    method @Deprecated public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int);
    method public int requestAudioFocus(@NonNull android.media.AudioFocusRequest);
    method public void setAllowedCapturePolicy(int);
    method @Deprecated public void setBluetoothA2dpOn(boolean);
    method public void setBluetoothScoOn(boolean);
    method public boolean setDeviceForCommunication(@NonNull android.media.AudioDeviceInfo);
    method public void setMicrophoneMute(boolean);
    method public void setMode(int);
    method public void setParameters(String);
@@ -19554,6 +19559,10 @@ package android.media {
    method public void onAudioFocusChange(int);
  }
  public static interface AudioManager.OnCommunicationDeviceChangedListener {
    method public void onCommunicationDeviceChanged(@Nullable android.media.AudioDeviceInfo);
  }
  public final class AudioMetadata {
    method @NonNull public static android.media.AudioMetadataMap createMap();
  }
+1 −0
Original line number Diff line number Diff line
@@ -850,6 +850,7 @@ package android.media {
  }

  public class AudioManager {
    method @Nullable public static android.media.AudioDeviceInfo getDeviceInfoFromType(int);
    method public boolean hasRegisteredDynamicPolicy();
  }

+18 −10
Original line number Diff line number Diff line
@@ -294,20 +294,25 @@ static sp<JNIAudioPortCallback> setJniCallback(JNIEnv* env,
    return old;
}

#define check_AudioSystem_Command(status) _check_AudioSystem_Command(__func__, (status))
#define check_AudioSystem_Command(...) _check_AudioSystem_Command(__func__, __VA_ARGS__)

static int _check_AudioSystem_Command(const char* caller, status_t status)
{
    ALOGE_IF(status, "Command failed for %s: %d", caller, status);
static int _check_AudioSystem_Command(const char *caller, status_t status,
                                      std::vector<status_t> ignoredErrors = {}) {
    int jniStatus = kAudioStatusOk;
    switch (status) {
    case DEAD_OBJECT:
        return kAudioStatusMediaServerDied;
        jniStatus = kAudioStatusMediaServerDied;
        break;
    case NO_ERROR:
        return kAudioStatusOk;
        break;
    default:
        if (std::find(begin(ignoredErrors), end(ignoredErrors), status) == end(ignoredErrors)) {
            jniStatus = kAudioStatusError;
        }
        break;
    }
    return kAudioStatusError;
    ALOGE_IF(jniStatus != kAudioStatusOk, "Command failed for %s: %d", caller, status);
    return jniStatus;
}

static jint getVectorOfAudioDeviceTypeAddr(JNIEnv *env, jintArray deviceTypes,
@@ -2381,9 +2386,12 @@ static jint android_media_AudioSystem_setDevicesRoleForStrategy(JNIEnv *env, job

static jint android_media_AudioSystem_removeDevicesRoleForStrategy(JNIEnv *env, jobject thiz,
                                                                   jint strategy, jint role) {
    return (jint)check_AudioSystem_Command(
            AudioSystem::removeDevicesRoleForStrategy((product_strategy_t)strategy,
                                                      (device_role_t)role));
    return (jint)
            check_AudioSystem_Command(AudioSystem::removeDevicesRoleForStrategy((product_strategy_t)
                                                                                        strategy,
                                                                                (device_role_t)
                                                                                        role),
                                      {NAME_NOT_FOUND});
}

static jint android_media_AudioSystem_getDevicesForRoleAndStrategy(JNIEnv *env, jobject thiz,
+9 −5
Original line number Diff line number Diff line
@@ -120,7 +120,13 @@ 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;
@@ -191,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);
    }

+314 −0
Original line number Diff line number Diff line
@@ -6123,6 +6123,29 @@ public class AudioManager {
        return infoListFromPortList(ports, flags);
    }

    /**
     * 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.
@@ -6666,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
    //--------------------
Loading