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

Commit feeb4bf1 authored by Ján Sebechlebský's avatar Ján Sebechlebský Committed by Android (Google) Code Review
Browse files

Merge "Configure audio session ids of MediaPlayer for device-specific context."

parents c9d18d6b bebe2cf0
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -23135,6 +23135,7 @@ package android.media {
  public class MediaPlayer implements android.media.AudioRouting android.media.VolumeAutomation {
    ctor public MediaPlayer();
    ctor public MediaPlayer(@NonNull android.content.Context);
    method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
    method public void addTimedTextSource(String, String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
    method public void addTimedTextSource(android.content.Context, android.net.Uri, String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
+1 −36
Original line number Diff line number Diff line
@@ -16,9 +16,6 @@

package android.media;

import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;

import android.annotation.CallbackExecutor;
@@ -30,7 +27,6 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.media.audiopolicy.AudioMix;
@@ -824,12 +820,7 @@ public class AudioTrack extends PlayerBase

        int[] sampleRate = new int[] {mSampleRate};
        int[] session = new int[1];
        if (sessionId == AUDIO_SESSION_ID_GENERATE) {
            // If there's no specific session id requested, try to get one from context.
            session[0] = getSessionIdForContext(context);
        } else {
            session[0] = sessionId;
        }
        session[0] = resolvePlaybackSessionId(context, sessionId);

        // native initialization
        int initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,
@@ -1415,32 +1406,6 @@ public class AudioTrack extends PlayerBase
        }
    }

    /**
     * Helper method to extract device specific audio session id from Context.
     *
     * @param context {@link Context} to use for extraction of device specific session id.
     * @return device specific session id. If context is null or doesn't have specific audio
     *   session id associated, this method returns {@link AUDIO_SESSION_ID_GENERATE}.
     */
    private static int getSessionIdForContext(@Nullable Context context) {
        if (context == null) {
            return AUDIO_SESSION_ID_GENERATE;
        }

        int deviceId = context.getDeviceId();
        if (deviceId == DEVICE_ID_DEFAULT) {
            return AUDIO_SESSION_ID_GENERATE;
        }

        VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
        if (vdm == null || vdm.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)
                == DEVICE_POLICY_DEFAULT) {
            return AUDIO_SESSION_ID_GENERATE;
        }

        return vdm.getAudioPlaybackSessionId(deviceId);
    }

    /**
     * Sets an {@link AudioPolicy} to automatically unregister when the track is released.
     *
+29 −7
Original line number Diff line number Diff line
@@ -661,10 +661,26 @@ public class MediaPlayer extends PlayerBase
     * Doing so frees any resources you have previously acquired.
     */
    public MediaPlayer() {
        this(AudioSystem.AUDIO_SESSION_ALLOCATE);
        this(/*context=*/null, AudioSystem.AUDIO_SESSION_ALLOCATE);
    }

    private MediaPlayer(int sessionId) {

    /**
     * Default constructor with context.
     *
     *  <p>Consider using one of the create() methods for synchronously instantiating a
     *  MediaPlayer from a Uri or resource.
     *
     * @param context non-null context. This context will be used to pull information,
     *  such as {@link android.content.AttributionSource} and device specific session ids, which
     *  will be associated with the {@link MediaPlayer}.
     *  However, the context itself will not be retained by the MediaPlayer.
     */
    public MediaPlayer(@NonNull Context context) {
        this(Objects.requireNonNull(context), AudioSystem.AUDIO_SESSION_ALLOCATE);
    }

    private MediaPlayer(Context context, int sessionId) {
        super(new AudioAttributes.Builder().build(),
                AudioPlaybackConfiguration.PLAYER_TYPE_JAM_MEDIAPLAYER);

@@ -680,7 +696,9 @@ public class MediaPlayer extends PlayerBase
        mTimeProvider = new TimeProvider(this);
        mOpenSubtitleSources = new Vector<InputStream>();

        AttributionSource attributionSource = AttributionSource.myAttributionSource();
        AttributionSource attributionSource =
                context == null ? AttributionSource.myAttributionSource()
                        : context.getAttributionSource();
        // set the package name to empty if it was null
        if (attributionSource.getPackageName() == null) {
            attributionSource = attributionSource.withPackageName("");
@@ -693,7 +711,9 @@ public class MediaPlayer extends PlayerBase
            native_setup(new WeakReference<MediaPlayer>(this), attributionSourceState.getParcel());
        }

        baseRegisterPlayer(sessionId);
        int effectiveSessionId = resolvePlaybackSessionId(context, sessionId);
        baseRegisterPlayer(effectiveSessionId);
        native_setAudioSessionId(effectiveSessionId);
    }

    private Parcel createPlayerIIdParcel() {
@@ -932,11 +952,10 @@ public class MediaPlayer extends PlayerBase
            AudioAttributes audioAttributes, int audioSessionId) {

        try {
            MediaPlayer mp = new MediaPlayer(audioSessionId);
            MediaPlayer mp = new MediaPlayer(context, audioSessionId);
            final AudioAttributes aa = audioAttributes != null ? audioAttributes :
                new AudioAttributes.Builder().build();
            mp.setAudioAttributes(aa);
            mp.native_setAudioSessionId(audioSessionId);
            mp.setDataSource(context, uri);
            if (holder != null) {
                mp.setDisplay(holder);
@@ -998,7 +1017,7 @@ public class MediaPlayer extends PlayerBase
            AssetFileDescriptor afd = context.getResources().openRawResourceFd(resid);
            if (afd == null) return null;

            MediaPlayer mp = new MediaPlayer(audioSessionId);
            MediaPlayer mp = new MediaPlayer(context, audioSessionId);

            final AudioAttributes aa = audioAttributes != null ? audioAttributes :
                new AudioAttributes.Builder().build();
@@ -2402,6 +2421,9 @@ public class MediaPlayer extends PlayerBase
     * However, it is possible to force this player to be part of an already existing audio session
     * by calling this method.
     * This method must be called before one of the overloaded <code> setDataSource </code> methods.
     * Note that session id set using this method will override device-specific audio session id,
     * if the {@link MediaPlayer} was instantiated using device-specific {@link Context} -
     * see {@link MediaPlayer#MediaPlayer(Context)}.
     * @throws IllegalStateException if it is called in an invalid state
     */
    public void setAudioSessionId(int sessionId)
+46 −0
Original line number Diff line number Diff line
@@ -16,9 +16,15 @@

package android.media;

import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
import android.companion.virtual.VirtualDeviceManager;
import android.content.Context;
import android.os.IBinder;
import android.os.Parcel;
@@ -535,4 +541,44 @@ public abstract class PlayerBase {
    protected String getCurrentOpPackageName() {
        return TextUtils.emptyIfNull(ActivityThread.currentOpPackageName());
    }

    /**
     * Helper method to resolve which session id should be used for player initialization.
     *
     * This method will assign session id in following way:
     * 1. Explicitly requested session id has the highest priority, if there is one,
     *    it will be used.
     * 2. If there's device-specific session id associated with the provided context,
     *    it will be used.
     * 3. Otherwise {@link AUDIO_SESSION_ID_GENERATE} is returned.
     *
     * @param context {@link Context} to use for extraction of device specific session id.
     * @param requestedSessionId explicitly requested session id or AUDIO_SESSION_ID_GENERATE.
     * @return session id to be passed to AudioService for the player initialization given
     *   provided {@link Context} instance and explicitly requested session id.
     */
    protected static int resolvePlaybackSessionId(@Nullable Context context,
            int requestedSessionId) {
        if (requestedSessionId != AUDIO_SESSION_ID_GENERATE) {
            // Use explicitly requested session id.
            return requestedSessionId;
        }

        if (context == null) {
            return AUDIO_SESSION_ID_GENERATE;
        }

        int deviceId = context.getDeviceId();
        if (deviceId == DEVICE_ID_DEFAULT) {
            return AUDIO_SESSION_ID_GENERATE;
        }

        VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
        if (vdm == null || vdm.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)
                == DEVICE_POLICY_DEFAULT) {
            return AUDIO_SESSION_ID_GENERATE;
        }

        return vdm.getAudioPlaybackSessionId(deviceId);
    }
}
+111 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 com.android.mediaframeworktest.unit;

import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import android.companion.virtual.VirtualDeviceManager;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.test.mock.MockContext;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;

import org.junit.Test;
import org.junit.runner.RunWith;


@RunWith(AndroidJUnit4.class)
public class MediaPlayerUnitTest {

    private static final int TEST_VIRTUAL_DEVICE_ID = 42;

    @Test
    public void testConstructionWithContext_virtualDeviceDefaultAudioPolicy() {
        int vdmPlaybackSessionId = getContext().getSystemService(
                AudioManager.class).generateAudioSessionId();
        VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
                vdmPlaybackSessionId, DEVICE_POLICY_DEFAULT);
        Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);

        MediaPlayer mediaPlayer = new MediaPlayer(virtualDeviceContext);

        assertNotEquals(vdmPlaybackSessionId, mediaPlayer.getAudioSessionId());
    }

    @Test
    public void testConstructionWithContext_virtualDeviceCustomAudioPolicy() {
        int vdmPlaybackSessionId = getContext().getSystemService(
                AudioManager.class).generateAudioSessionId();
        VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
                vdmPlaybackSessionId, DEVICE_POLICY_CUSTOM);
        Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);

        MediaPlayer mediaPlayer = new MediaPlayer(virtualDeviceContext);

        assertEquals(vdmPlaybackSessionId, mediaPlayer.getAudioSessionId());
    }

    @Test
    public void testConstructionWithContext_virtualSetSessionIdOverridesContext() {
        int vdmPlaybackSessionId = getContext().getSystemService(
                AudioManager.class).generateAudioSessionId();
        int anotherSessionId = getContext().getSystemService(
                AudioManager.class).generateAudioSessionId();
        VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
                vdmPlaybackSessionId, DEVICE_POLICY_CUSTOM);
        Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);

        MediaPlayer mediaPlayer = new MediaPlayer(virtualDeviceContext);
        mediaPlayer.setAudioSessionId(anotherSessionId);

        assertEquals(anotherSessionId, mediaPlayer.getAudioSessionId());
    }

    private Context getContext() {
        return InstrumentationRegistry.getInstrumentation().getContext();
    }

    private Context getVirtualDeviceMockContext(int deviceId, VirtualDeviceManager vdm) {
        MockContext mockContext = mock(MockContext.class);
        when(mockContext.getDeviceId()).thenReturn(deviceId);
        when(mockContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
        when(mockContext.getAttributionSource()).thenReturn(getContext().getAttributionSource());
        return mockContext;
    }

    private static VirtualDeviceManager getMockVirtualDeviceManager(int deviceId,
            int playbackSessionId, int audioDevicePolicy) {
        VirtualDeviceManager vdmMock = mock(VirtualDeviceManager.class);
        when(vdmMock.getAudioPlaybackSessionId(anyInt())).thenReturn(AUDIO_SESSION_ID_GENERATE);
        when(vdmMock.getAudioPlaybackSessionId(deviceId)).thenReturn(playbackSessionId);
        when(vdmMock.getDevicePolicy(anyInt(), anyInt())).thenReturn(DEVICE_POLICY_DEFAULT);
        when(vdmMock.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)).thenReturn(audioDevicePolicy);
        return vdmMock;
    }
}