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

Commit b46969e3 authored by Santiago Seifert's avatar Santiago Seifert Committed by Android (Google) Code Review
Browse files

Merge "Implement baseline system media session management" into main

parents 85772154 c9617d35
Loading
Loading
Loading
Loading
+16 −9
Original line number Diff line number Diff line
@@ -358,7 +358,9 @@ public abstract class MediaRoute2ProviderService extends Service {
     * @return a {@link MediaStreams} instance that holds the media streams to route as part of the
     *     newly created routing session. May be null if system media capture failed, in which case
     *     you can ignore the return value, as you will receive a call to {@link #onReleaseSession}
     *     where you can clean up this session
     *     where you can clean up this session. {@link AudioRecord#startRecording()} must be called
     *     immediately on {@link MediaStreams#getAudioRecord()} after calling this method, in order
     *     to start streaming audio to the receiver.
     * @hide
     */
    // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
@@ -458,7 +460,6 @@ public abstract class MediaRoute2ProviderService extends Service {
        if (uid != Process.INVALID_UID) {
            audioMixingRuleBuilder.addMixRule(AudioMixingRule.RULE_MATCH_UID, uid);
        }

        AudioMix mix =
                new AudioMix.Builder(audioMixingRuleBuilder.build())
                        .setFormat(audioFormat)
@@ -471,7 +472,11 @@ public abstract class MediaRoute2ProviderService extends Service {
            Log.e(TAG, "Couldn't fetch the audio manager.");
            return;
        }
        audioManager.registerAudioPolicy(audioPolicy);
        int audioPolicyResult = audioManager.registerAudioPolicy(audioPolicy);
        if (audioPolicyResult != AudioManager.SUCCESS) {
            Log.e(TAG, "Failed to register the audio policy.");
            return;
        }
        var audioRecord = audioPolicy.createAudioRecordSink(mix);
        if (audioRecord == null) {
            Log.e(TAG, "Audio record creation failed.");
@@ -540,17 +545,19 @@ public abstract class MediaRoute2ProviderService extends Service {
    }

    /** Releases any system media routing resources associated with the given {@code sessionId}. */
    private void maybeReleaseMediaStreams(String sessionId) {
    private boolean maybeReleaseMediaStreams(String sessionId) {
        if (!Flags.enableMirroringInMediaRouter2()) {
            return;
            return false;
        }
        synchronized (mSessionLock) {
            var streams = mOngoingMediaStreams.remove(sessionId);
            if (streams != null) {
                releaseAudioStream(streams.mAudioPolicy, streams.mAudioRecord);
                // TODO: b/380431086: Release the video stream once implemented.
                return true;
            }
        }
        return false;
    }

    // We cannot reach the code that requires MODIFY_AUDIO_ROUTING without holding it.
@@ -1019,12 +1026,12 @@ public abstract class MediaRoute2ProviderService extends Service {
            if (!checkCallerIsSystem()) {
                return;
            }
            if (!checkSessionIdIsValid(sessionId, "releaseSession")) {
                return;
            }
            // We proactively release the system media routing once the system requests it, to
            // ensure it happens immediately.
            maybeReleaseMediaStreams(sessionId);
            if (!maybeReleaseMediaStreams(sessionId)
                    && !checkSessionIdIsValid(sessionId, "releaseSession")) {
                return;
            }

            addRequestId(requestId);
            mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onReleaseSession,
+11 −1
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.content.ComponentName;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService.Reason;
import android.media.MediaRouter2;
import android.media.MediaRouter2Utils;
import android.media.RouteDiscoveryPreference;
@@ -123,6 +124,13 @@ abstract class MediaRoute2Provider {
        }
    }

    /** Calls {@link Callback#onRequestFailed} with the given id and reason. */
    protected void notifyRequestFailed(long requestId, @Reason int reason) {
        if (mCallback != null) {
            mCallback.onRequestFailed(/* provider= */ this, requestId, reason);
        }
    }

    void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo) {
        setProviderState(providerInfo);
        notifyProviderState();
@@ -175,7 +183,9 @@ abstract class MediaRoute2Provider {
                @NonNull RoutingSessionInfo sessionInfo);
        void onSessionReleased(@NonNull MediaRoute2Provider provider,
                @NonNull RoutingSessionInfo sessionInfo);
        void onRequestFailed(@NonNull MediaRoute2Provider provider, long requestId, int reason);

        void onRequestFailed(
                @NonNull MediaRoute2Provider provider, long requestId, @Reason int reason);
    }

    /**
+114 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import android.media.IMediaRoute2ProviderServiceCallback;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService;
import android.media.MediaRoute2ProviderService.Reason;
import android.media.RouteDiscoveryPreference;
import android.media.RoutingSessionInfo;
import android.os.Bundle;
@@ -41,6 +42,7 @@ import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.Slog;
@@ -88,6 +90,12 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
    private final LongSparseArray<SessionCreationOrTransferRequest>
            mRequestIdToSessionCreationRequest;

    @GuardedBy("mLock")
    private final Map<String, SystemMediaSessionCallback> mSystemSessionCallbacks;

    @GuardedBy("mLock")
    private final LongSparseArray<SystemMediaSessionCallback> mRequestIdToSystemSessionRequest;

    @GuardedBy("mLock")
    private final Map<String, SessionCreationOrTransferRequest> mSessionOriginalIdToTransferRequest;

@@ -102,6 +110,8 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
        mContext = Objects.requireNonNull(context, "Context must not be null.");
        mRequestIdToSessionCreationRequest = new LongSparseArray<>();
        mSessionOriginalIdToTransferRequest = new HashMap<>();
        mRequestIdToSystemSessionRequest = new LongSparseArray<>();
        mSystemSessionCallbacks = new ArrayMap<>();
        mIsSelfScanOnlyProvider = isSelfScanOnlyProvider;
        mSupportsSystemMediaRouting = supportsSystemMediaRouting;
        mUserId = userId;
@@ -236,6 +246,48 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
        }
    }

    /**
     * Requests the creation of a system media routing session.
     *
     * @param requestId The id of the request.
     * @param uid The uid of the package whose media to route, or {@link
     *     android.os.Process#INVALID_UID} if not applicable (for example, if all the system's media
     *     must be routed).
     * @param packageName The package name to populate {@link
     *     RoutingSessionInfo#getClientPackageName()}.
     * @param routeId The id of the route to be initially {@link
     *     RoutingSessionInfo#getSelectedRoutes()}.
     * @param sessionHints An optional bundle with paramets.
     * @param callback A {@link SystemMediaSessionCallback} to notify of session events.
     * @see MediaRoute2ProviderService#onCreateSystemRoutingSession
     */
    public void requestCreateSystemMediaSession(
            long requestId,
            int uid,
            String packageName,
            String routeId,
            @Nullable Bundle sessionHints,
            @NonNull SystemMediaSessionCallback callback) {
        if (!Flags.enableMirroringInMediaRouter2()) {
            throw new IllegalStateException(
                    "Unexpected call to requestCreateSystemMediaSession. Governing flag is"
                            + " disabled.");
        }
        if (mConnectionReady) {
            boolean binderRequestSucceeded =
                    mActiveConnection.requestCreateSystemMediaSession(
                            requestId, uid, packageName, routeId, sessionHints);
            if (!binderRequestSucceeded) {
                // notify failure.
                return;
            }
            updateBinding();
            synchronized (mLock) {
                mRequestIdToSystemSessionRequest.put(requestId, callback);
            }
        }
    }

    public boolean hasComponentName(String packageName, String className) {
        return mComponentName.getPackageName().equals(packageName)
                && mComponentName.getClassName().equals(className);
@@ -292,7 +344,14 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
                mLastDiscoveryPreference != null
                        && mLastDiscoveryPreference.shouldPerformActiveScan()
                        && mSupportsSystemMediaRouting;
        boolean bindDueToOngoingSystemMediaRoutingSessions = false;
        if (Flags.enableMirroringInMediaRouter2()) {
            synchronized (mLock) {
                bindDueToOngoingSystemMediaRoutingSessions = !mSystemSessionCallbacks.isEmpty();
            }
        }
        if (!getSessionInfos().isEmpty()
                || bindDueToOngoingSystemMediaRoutingSessions
                || bindDueToManagerScan
                || bindDueToSystemMediaRoutingSupport) {
            return true;
@@ -438,6 +497,13 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
        String newSessionId = newSession.getId();

        synchronized (mLock) {
            var systemMediaSessionCallback = mRequestIdToSystemSessionRequest.get(requestId);
            if (systemMediaSessionCallback != null) {
                mSystemSessionCallbacks.put(newSession.getOriginalId(), systemMediaSessionCallback);
                systemMediaSessionCallback.onSessionUpdate(newSession);
                return;
            }

            if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
                newSession =
                        createSessionWithPopulatedTransferInitiationDataLocked(
@@ -569,6 +635,12 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {

        boolean found = false;
        synchronized (mLock) {
            var sessionCallback = mSystemSessionCallbacks.get(releasedSession.getOriginalId());
            if (sessionCallback != null) {
                sessionCallback.onSessionReleased();
                return;
            }

            mSessionOriginalIdToTransferRequest.remove(releasedSession.getId());
            for (RoutingSessionInfo session : mSessionInfos) {
                if (TextUtils.equals(session.getId(), releasedSession.getId())) {
@@ -673,6 +745,26 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
                pendingTransferCount);
    }

    /**
     * Callback for events related to system media sessions.
     *
     * @see MediaRoute2ProviderService#onCreateSystemRoutingSession
     */
    public interface SystemMediaSessionCallback {

        /**
         * Called when the corresponding session's {@link RoutingSessionInfo}, or upon the creation
         * of the given session info.
         */
        void onSessionUpdate(@NonNull RoutingSessionInfo sessionInfo);

        /** Called when the request with the given id fails for the given reason. */
        void onRequestFailed(long requestId, @Reason int reason);

        /** Called when the corresponding session is released. */
        void onSessionReleased();
    }

    // All methods in this class are called on the main thread.
    private final class ServiceConnectionImpl implements ServiceConnection {

@@ -739,6 +831,28 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
            }
        }

        /**
         * Sends a system media session creation request to the provider service, and returns
         * whether the request transaction succeeded.
         *
         * <p>The transaction might fail, for example, if the recipient process has died.
         */
        public boolean requestCreateSystemMediaSession(
                long requestId,
                int uid,
                String packageName,
                String routeId,
                @Nullable Bundle sessionHints) {
            try {
                mService.requestCreateSystemMediaSession(
                        requestId, uid, packageName, routeId, sessionHints);
                return true;
            } catch (RemoteException ex) {
                Slog.e(TAG, "requestCreateSystemMediaSession: Failed to deliver request.");
            }
            return false;
        }

        public void releaseSession(long requestId, String sessionId) {
            try {
                mService.releaseSession(requestId, sessionId);
+10 −14
Original line number Diff line number Diff line
@@ -846,33 +846,29 @@ class MediaRouter2ServiceImpl {
        try {
            synchronized (mLock) {
                UserRecord userRecord = getOrCreateUserRecordLocked(userId);
                List<RoutingSessionInfo> sessionInfos;
                SystemMediaRoute2Provider systemProvider = userRecord.mHandler.getSystemProvider();
                if (hasSystemRoutingPermissions) {
                    if (setDeviceRouteSelected && !Flags.enableMirroringInMediaRouter2()) {
                    if (!Flags.enableMirroringInMediaRouter2() && setDeviceRouteSelected) {
                        // Return a fake system session that shows the device route as selected and
                        // available bluetooth routes as transferable.
                        return userRecord.mHandler.getSystemProvider()
                                .generateDeviceRouteSelectedSessionInfo(targetPackageName);
                        return systemProvider.generateDeviceRouteSelectedSessionInfo(
                                targetPackageName);
                    } else {
                        sessionInfos = userRecord.mHandler.getSystemProvider().getSessionInfos();
                        if (!sessionInfos.isEmpty()) {
                            // Return a copy of the current system session with no modification,
                            // except setting the client package name.
                            return new RoutingSessionInfo.Builder(sessionInfos.get(0))
                                    .setClientPackageName(targetPackageName)
                                    .build();
                        RoutingSessionInfo session =
                                systemProvider.getSessionForPackage(targetPackageName);
                        if (session != null) {
                            return session;
                        } else {
                            Slog.w(TAG, "System provider does not have any session info.");
                            return null;
                        }
                    }
                } else {
                    return new RoutingSessionInfo.Builder(
                                    userRecord.mHandler.getSystemProvider().getDefaultSessionInfo())
                    return new RoutingSessionInfo.Builder(systemProvider.getDefaultSessionInfo())
                            .setClientPackageName(targetPackageName)
                            .build();
                }
            }
            return null;
        } finally {
            Binder.restoreCallingIdentity(token);
        }
+19 −2
Original line number Diff line number Diff line
@@ -326,6 +326,23 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
        return mDefaultSessionInfo;
    }

    /**
     * Returns the {@link RoutingSessionInfo} that corresponds to the package with the given name.
     */
    public RoutingSessionInfo getSessionForPackage(String targetPackageName) {
        synchronized (mLock) {
            if (!mSessionInfos.isEmpty()) {
                // Return a copy of the current system session with no modification,
                // except setting the client package name.
                return new RoutingSessionInfo.Builder(mSessionInfos.get(0))
                        .setClientPackageName(targetPackageName)
                        .build();
            } else {
                return null;
            }
        }
    }

    /**
     * Builds a system {@link RoutingSessionInfo} with the selected route set to the currently
     * selected <b>device</b> route (wired or built-in, but not bluetooth) and transferable routes
@@ -633,10 +650,10 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {

        RoutingSessionInfo sessionInfo;
        synchronized (mLock) {
            sessionInfo = mSessionInfos.get(0);
            if (sessionInfo == null) {
            if (mSessionInfos.isEmpty()) {
                return;
            }
            sessionInfo = mSessionInfos.get(0);
        }

        mCallback.onSessionUpdated(this, sessionInfo);
Loading