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

Commit 6e5a628a authored by jiabin's avatar jiabin
Browse files

Add AudioRouting interface in MediaPlayer

Bug: b/64038649
Test: Run cts in RoutingTest
      && switch output device when using MediaPlayer playing music/video

Change-Id: I48dcfd40c3a6ce01c818f6268cc86ed02b7eaa58
parent bb6468ba
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -23026,8 +23026,9 @@ package android.media {
    field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1
  }
  public class MediaPlayer implements android.media.VolumeAutomation {
  public class MediaPlayer implements android.media.AudioRouting android.media.VolumeAutomation {
    ctor public MediaPlayer();
    method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
    method public void addTimedTextSource(java.lang.String, java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
    method public void addTimedTextSource(android.content.Context, android.net.Uri, java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
    method public void addTimedTextSource(java.io.FileDescriptor, java.lang.String) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
@@ -23049,6 +23050,8 @@ package android.media {
    method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException;
    method public android.os.PersistableBundle getMetrics();
    method public android.media.PlaybackParams getPlaybackParams();
    method public android.media.AudioDeviceInfo getPreferredDevice();
    method public android.media.AudioDeviceInfo getRoutedDevice();
    method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
    method public android.media.SyncParams getSyncParams();
    method public android.media.MediaTimestamp getTimestamp();
@@ -23064,6 +23067,7 @@ package android.media {
    method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.MediaPlayer.NoDrmSchemeException;
    method public void release();
    method public void releaseDrm() throws android.media.MediaPlayer.NoDrmSchemeException;
    method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
    method public void reset();
    method public void restoreKeys(byte[]) throws android.media.MediaPlayer.NoDrmSchemeException;
    method public void seekTo(long, int);
@@ -23100,6 +23104,7 @@ package android.media {
    method public void setOnTimedTextListener(android.media.MediaPlayer.OnTimedTextListener);
    method public void setOnVideoSizeChangedListener(android.media.MediaPlayer.OnVideoSizeChangedListener);
    method public void setPlaybackParams(android.media.PlaybackParams);
    method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
    method public void setScreenOnWhilePlaying(boolean);
    method public void setSurface(android.view.Surface);
    method public void setSyncParams(android.media.SyncParams);
+6 −1
Original line number Diff line number Diff line
@@ -24916,8 +24916,9 @@ package android.media {
    field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1
  }
  public class MediaPlayer implements android.media.VolumeAutomation {
  public class MediaPlayer implements android.media.AudioRouting android.media.VolumeAutomation {
    ctor public MediaPlayer();
    method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
    method public void addTimedTextSource(java.lang.String, java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
    method public void addTimedTextSource(android.content.Context, android.net.Uri, java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
    method public void addTimedTextSource(java.io.FileDescriptor, java.lang.String) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
@@ -24939,6 +24940,8 @@ package android.media {
    method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException;
    method public android.os.PersistableBundle getMetrics();
    method public android.media.PlaybackParams getPlaybackParams();
    method public android.media.AudioDeviceInfo getPreferredDevice();
    method public android.media.AudioDeviceInfo getRoutedDevice();
    method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
    method public android.media.SyncParams getSyncParams();
    method public android.media.MediaTimestamp getTimestamp();
@@ -24954,6 +24957,7 @@ package android.media {
    method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.MediaPlayer.NoDrmSchemeException;
    method public void release();
    method public void releaseDrm() throws android.media.MediaPlayer.NoDrmSchemeException;
    method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
    method public void reset();
    method public void restoreKeys(byte[]) throws android.media.MediaPlayer.NoDrmSchemeException;
    method public void seekTo(long, int);
@@ -24990,6 +24994,7 @@ package android.media {
    method public void setOnTimedTextListener(android.media.MediaPlayer.OnTimedTextListener);
    method public void setOnVideoSizeChangedListener(android.media.MediaPlayer.OnVideoSizeChangedListener);
    method public void setPlaybackParams(android.media.PlaybackParams);
    method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
    method public void setScreenOnWhilePlaying(boolean);
    method public void setSurface(android.view.Surface);
    method public void setSyncParams(android.media.SyncParams);
+6 −1
Original line number Diff line number Diff line
@@ -23226,8 +23226,9 @@ package android.media {
    field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1
  }
  public class MediaPlayer implements android.media.VolumeAutomation {
  public class MediaPlayer implements android.media.AudioRouting android.media.VolumeAutomation {
    ctor public MediaPlayer();
    method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
    method public void addTimedTextSource(java.lang.String, java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
    method public void addTimedTextSource(android.content.Context, android.net.Uri, java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
    method public void addTimedTextSource(java.io.FileDescriptor, java.lang.String) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
@@ -23249,6 +23250,8 @@ package android.media {
    method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException;
    method public android.os.PersistableBundle getMetrics();
    method public android.media.PlaybackParams getPlaybackParams();
    method public android.media.AudioDeviceInfo getPreferredDevice();
    method public android.media.AudioDeviceInfo getRoutedDevice();
    method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
    method public android.media.SyncParams getSyncParams();
    method public android.media.MediaTimestamp getTimestamp();
@@ -23264,6 +23267,7 @@ package android.media {
    method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.MediaPlayer.NoDrmSchemeException;
    method public void release();
    method public void releaseDrm() throws android.media.MediaPlayer.NoDrmSchemeException;
    method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
    method public void reset();
    method public void restoreKeys(byte[]) throws android.media.MediaPlayer.NoDrmSchemeException;
    method public void seekTo(long, int);
@@ -23300,6 +23304,7 @@ package android.media {
    method public void setOnTimedTextListener(android.media.MediaPlayer.OnTimedTextListener);
    method public void setOnVideoSizeChangedListener(android.media.MediaPlayer.OnVideoSizeChangedListener);
    method public void setPlaybackParams(android.media.PlaybackParams);
    method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
    method public void setScreenOnWhilePlaying(boolean);
    method public void setSurface(android.view.Surface);
    method public void setSyncParams(android.media.SyncParams);
+163 −0
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import android.system.Os;
import android.system.OsConstants;
import android.util.Log;
import android.util.Pair;
import android.util.ArrayMap;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.widget.VideoView;
@@ -58,6 +59,7 @@ import android.media.SubtitleData;
import android.media.SubtitleTrack.RenderingWidget;
import android.media.SyncParams;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;

import libcore.io.IoBridge;
@@ -577,6 +579,7 @@ import java.util.Vector;
public class MediaPlayer extends PlayerBase
                         implements SubtitleController.Listener
                                  , VolumeAutomation
                                  , AudioRouting
{
    /**
       Constant to retrieve only the new metadata since the last
@@ -1417,6 +1420,155 @@ public class MediaPlayer extends PlayerBase

    private native @Nullable VolumeShaper.State native_getVolumeShaperState(int id);

    //--------------------------------------------------------------------------
    // Explicit Routing
    //--------------------
    private AudioDeviceInfo mPreferredDevice = null;

    /**
     * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route
     * the output from this MediaPlayer.
     * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio sink or source.
     *  If deviceInfo is null, default routing is restored.
     * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and
     * does not correspond to a valid audio device.
     */
    @Override
    public boolean setPreferredDevice(AudioDeviceInfo deviceInfo) {
        if (deviceInfo != null && !deviceInfo.isSink()) {
            return false;
        }
        int preferredDeviceId = deviceInfo != null ? deviceInfo.getId() : 0;
        boolean status = native_setOutputDevice(preferredDeviceId);
        if (status == true) {
            synchronized (this) {
                mPreferredDevice = deviceInfo;
            }
        }
        return status;
    }

    /**
     * Returns the selected output specified by {@link #setPreferredDevice}. Note that this
     * is not guaranteed to correspond to the actual device being used for playback.
     */
    @Override
    public AudioDeviceInfo getPreferredDevice() {
        synchronized (this) {
            return mPreferredDevice;
        }
    }

    /**
     * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaPlayer
     * Note: The query is only valid if the MediaPlayer is currently playing.
     * If the player is not playing, the returned device can be null or correspond to previously
     * selected device when the player was last active.
     */
    @Override
    public AudioDeviceInfo getRoutedDevice() {
        int deviceId = native_getRoutedDeviceId();
        if (deviceId == 0) {
            return null;
        }
        AudioDeviceInfo[] devices =
                AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_OUTPUTS);
        for (int i = 0; i < devices.length; i++) {
            if (devices[i].getId() == deviceId) {
                return devices[i];
            }
        }
        return null;
    }

    /*
     * Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler.
     */
    private void enableNativeRoutingCallbacksLocked(boolean enabled) {
        if (mRoutingChangeListeners.size() == 0) {
            native_enableDeviceCallback(enabled);
        }
    }

    /**
     * The list of AudioRouting.OnRoutingChangedListener interfaces added (with
     * {@link #addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, Handler)}
     * by an app to receive (re)routing notifications.
     */
    @GuardedBy("mRoutingChangeListeners")
    private ArrayMap<AudioRouting.OnRoutingChangedListener,
            NativeRoutingEventHandlerDelegate> mRoutingChangeListeners = new ArrayMap<>();

    /**
     * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
     * changes on this MediaPlayer.
     * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive
     * notifications of rerouting events.
     * @param handler  Specifies the {@link Handler} object for the thread on which to execute
     * the callback. If <code>null</code>, the handler on the main looper will be used.
     */
    @Override
    public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener,
            Handler handler) {
        synchronized (mRoutingChangeListeners) {
            if (listener != null && !mRoutingChangeListeners.containsKey(listener)) {
                enableNativeRoutingCallbacksLocked(true);
                mRoutingChangeListeners.put(
                        listener, new NativeRoutingEventHandlerDelegate(this, listener, handler));
            }
        }
    }

    /**
     * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added
     * to receive rerouting notifications.
     * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
     * to remove.
     */
    @Override
    public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener) {
        synchronized (mRoutingChangeListeners) {
            if (mRoutingChangeListeners.containsKey(listener)) {
                mRoutingChangeListeners.remove(listener);
                enableNativeRoutingCallbacksLocked(false);
            }
        }
    }

    /**
     * Helper class to handle the forwarding of native events to the appropriate listener
     * (potentially) handled in a different thread
     */
    private class NativeRoutingEventHandlerDelegate {
        private MediaPlayer mMediaPlayer;
        private AudioRouting.OnRoutingChangedListener mOnRoutingChangedListener;
        private Handler mHandler;

        NativeRoutingEventHandlerDelegate(final MediaPlayer mediaPlayer,
                final AudioRouting.OnRoutingChangedListener listener, Handler handler) {
            mMediaPlayer = mediaPlayer;
            mOnRoutingChangedListener = listener;
            mHandler = handler != null ? handler : mEventHandler;
        }

        void notifyClient() {
            if (mHandler != null) {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        if (mOnRoutingChangedListener != null) {
                            mOnRoutingChangedListener.onRoutingChanged(mMediaPlayer);
                        }
                    }
                });
            }
        }
    }

    private native final boolean native_setOutputDevice(int deviceId);
    private native final int native_getRoutedDeviceId();
    private native final void native_enableDeviceCallback(boolean enabled);

    /**
     * Set the low-level power management behavior for this MediaPlayer.  This
     * can be used when the MediaPlayer is not playing through a SurfaceHolder
@@ -3176,6 +3328,7 @@ public class MediaPlayer extends PlayerBase
    private static final int MEDIA_SUBTITLE_DATA = 201;
    private static final int MEDIA_META_DATA = 202;
    private static final int MEDIA_DRM_INFO = 210;
    private static final int MEDIA_AUDIO_ROUTING_CHANGED = 10000;

    private TimeProvider mTimeProvider;

@@ -3414,6 +3567,16 @@ public class MediaPlayer extends PlayerBase
            case MEDIA_NOP: // interface test message - ignore
                break;

            case MEDIA_AUDIO_ROUTING_CHANGED:
                AudioManager.resetAudioPortGeneration();
                synchronized (mRoutingChangeListeners) {
                    for (NativeRoutingEventHandlerDelegate delegate
                            : mRoutingChangeListeners.values()) {
                        delegate.notifyClient();
                    }
                }
                return;

            default:
                Log.e(TAG, "Unknown message type " + msg.what);
                return;
+43 −0
Original line number Diff line number Diff line
@@ -1387,6 +1387,44 @@ static void android_media_MediaPlayer_releaseDrm(JNIEnv *env, jobject thiz)
// Modular DRM end
// ----------------------------------------------------------------------------

/////////////////////////////////////////////////////////////////////////////////////
// AudioRouting begin
static jboolean android_media_MediaPlayer_setOutputDevice(JNIEnv *env, jobject thiz, jint device_id)
{
    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
    if (mp == NULL) {
        return false;
    }
    return mp->setOutputDevice(device_id) == NO_ERROR;
}

static jint android_media_MediaPlayer_getRoutedDeviceId(JNIEnv *env, jobject thiz)
{
    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
    if (mp == NULL) {
        return AUDIO_PORT_HANDLE_NONE;
    }
    return mp->getRoutedDeviceId();
}

static void android_media_MediaPlayer_enableDeviceCallback(
        JNIEnv* env, jobject thiz, jboolean enabled)
{
    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
    if (mp == NULL) {
        return;
    }

    status_t status = mp->enableAudioDeviceCallback(enabled);
    if (status != NO_ERROR) {
        jniThrowException(env, "java/lang/IllegalStateException", NULL);
        ALOGE("enable device callback failed: %d", status);
    }
}

// AudioRouting end
// ----------------------------------------------------------------------------

static const JNINativeMethod gMethods[] = {
    {
        "nativeSetDataSource",
@@ -1448,6 +1486,11 @@ static const JNINativeMethod gMethods[] = {
    // Modular DRM
    { "_prepareDrm", "([B[B)V",                                 (void *)android_media_MediaPlayer_prepareDrm },
    { "_releaseDrm", "()V",                                     (void *)android_media_MediaPlayer_releaseDrm },

    // AudioRouting
    {"native_setOutputDevice", "(I)Z",                          (void *)android_media_MediaPlayer_setOutputDevice},
    {"native_getRoutedDeviceId", "()I",                         (void *)android_media_MediaPlayer_getRoutedDeviceId},
    {"native_enableDeviceCallback", "(Z)V",                     (void *)android_media_MediaPlayer_enableDeviceCallback},
};

// This function only registers the native methods