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

Commit f90b3a9a authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes from topic "mediasession2_stack"

* changes:
  MediaSessionService: Keep Media1 and Media2 session in the one place
  Introduce common interfaces for both session1 and session2
parents a9723b8c ffc63e88
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -141,6 +141,9 @@ public class MediaController2 implements AutoCloseable {
                // Note: unbindService() throws IllegalArgumentException when it's called twice.
                return;
            }
            if (DEBUG) {
                Log.d(TAG, "closing " + this);
            }
            mClosed = true;
            if (mServiceConnection != null) {
                // Note: This should be called even when the bindService() has returned false.
+179 −0
Original line number Diff line number Diff line
/*
 * Copyright 2019 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.server.media;

import android.media.MediaController2;
import android.media.Session2CommandGroup;
import android.media.Session2Token;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.ResultReceiver;
import android.os.UserHandle;
import android.util.Log;
import android.view.KeyEvent;

import com.android.internal.annotations.GuardedBy;

import java.io.PrintWriter;

/**
 * Keeps the record of {@link Session2Token} helps to send command to the corresponding session.
 */
// TODO(jaewan): Do not call service method directly -- introduce listener instead.
public class MediaSession2Record implements MediaSessionRecordImpl {
    private static final String TAG = "MediaSession2Record";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
    private final Object mLock = new Object();

    @GuardedBy("mLock")
    private final Session2Token mSessionToken;
    @GuardedBy("mLock")
    private final HandlerExecutor mHandlerExecutor;
    @GuardedBy("mLock")
    private final MediaController2 mController;
    @GuardedBy("mLock")
    private final MediaSessionService mService;
    @GuardedBy("mLock")
    private boolean mIsConnected;

    public MediaSession2Record(Session2Token sessionToken, MediaSessionService service,
            Looper handlerLooper) {
        mSessionToken = sessionToken;
        mService = service;
        mHandlerExecutor = new HandlerExecutor(new Handler(handlerLooper));
        mController = new MediaController2.Builder(service.getContext(), sessionToken)
                .setControllerCallback(mHandlerExecutor, new Controller2Callback())
                .build();
    }

    @Override
    public String getPackageName() {
        return mSessionToken.getPackageName();
    }

    public Session2Token getSession2Token() {
        return mSessionToken;
    }

    @Override
    public int getUid() {
        return mSessionToken.getUid();
    }

    @Override
    public int getUserId() {
        return UserHandle.getUserId(mSessionToken.getUid());
    }

    @Override
    public boolean isSystemPriority() {
        // System priority session is currently only allowed for telephony, and it's OK to stick to
        // the media1 API at this moment.
        return false;
    }

    @Override
    public void adjustVolume(String packageName, String opPackageName, int pid, int uid,
            boolean asSystemService, int direction, int flags, boolean useSuggested) {
        // TODO(jaewan): Add API to adjust volume.
    }

    @Override
    public boolean isActive() {
        synchronized (mLock) {
            return mIsConnected;
        }
    }

    @Override
    public boolean checkPlaybackActiveState(boolean expected) {
        synchronized (mLock) {
            return mIsConnected && mController.isPlaybackActive() == expected;
        }
    }

    @Override
    public boolean isPlaybackTypeLocal() {
        // TODO(jaewan): Implement -- need API to know whether the playback is remote or local.
        return true;
    }

    @Override
    public void close() {
        synchronized (mLock) {
            // Call close regardless of the mIsAvailable. This may be called when it's not yet
            // connected.
            mController.close();
        }
    }

    @Override
    public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
            KeyEvent ke, int sequenceId, ResultReceiver cb) {
        // TODO(jaewan): Implement.
        return false;
    }

    @Override
    public void dump(PrintWriter pw, String prefix) {
        pw.println(prefix + "token=" + mSessionToken);
        pw.println(prefix + "controller=" + mController);

        final String indent = prefix + "  ";
        pw.println(indent + "playbackActive=" + mController.isPlaybackActive());
    }

    @Override
    public String toString() {
        // TODO(jaewan): Also add getId().
        return getPackageName() + " (userId=" + getUserId() + ")";
    }

    private class Controller2Callback extends MediaController2.ControllerCallback {
        @Override
        public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
            if (DEBUG) {
                Log.d(TAG, "connected to " + mSessionToken + ", allowed=" + allowedCommands);
            }
            synchronized (mLock) {
                mIsConnected = true;
            }
            mService.onSessionActiveStateChanged(MediaSession2Record.this);
        }

        @Override
        public void onDisconnected(MediaController2 controller) {
            if (DEBUG) {
                Log.d(TAG, "disconnected from " + mSessionToken);
            }
            synchronized (mLock) {
                mIsConnected = false;
            }
            mService.onSessionDied(MediaSession2Record.this);
        }

        @Override
        public void onPlaybackActiveChanged(MediaController2 controller, boolean playbackActive) {
            if (DEBUG) {
                Log.d(TAG, "playback active changed, " + mSessionToken + ", active="
                        + playbackActive);
            }
            mService.onSessionPlaybackStateChanged(MediaSession2Record.this, playbackActive);
        }
    }
}
+42 −8
Original line number Diff line number Diff line
@@ -56,13 +56,15 @@ import com.android.server.LocalServices;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * This is the system implementation of a Session. Apps will interact with the
 * MediaSession wrapper class instead.
 */
public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable {
// TODO(jaewan): Do not call service method directly -- introduce listener instead.
public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionRecordImpl {
    private static final String TAG = "MediaSessionRecord";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

@@ -72,6 +74,24 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable
     */
    private static final int OPTIMISTIC_VOLUME_TIMEOUT = 1000;

    /**
     * These are states that usually indicate the user took an action and should
     * bump priority regardless of the old state.
     */
    private static final List<Integer> ALWAYS_PRIORITY_STATES = Arrays.asList(
            PlaybackState.STATE_FAST_FORWARDING,
            PlaybackState.STATE_REWINDING,
            PlaybackState.STATE_SKIPPING_TO_PREVIOUS,
            PlaybackState.STATE_SKIPPING_TO_NEXT);
    /**
     * These are states that usually indicate the user took an action if they
     * were entered from a non-priority state.
     */
    private static final List<Integer> TRANSITION_PRIORITY_STATES = Arrays.asList(
            PlaybackState.STATE_BUFFERING,
            PlaybackState.STATE_CONNECTING,
            PlaybackState.STATE_PLAYING);

    private final MessageHandler mHandler;

    private final int mOwnerPid;
@@ -170,6 +190,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable
     *
     * @return Info that identifies this session.
     */
    @Override
    public String getPackageName() {
        return mPackageName;
    }
@@ -188,6 +209,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable
     *
     * @return The UID for this session.
     */
    @Override
    public int getUid() {
        return mOwnerUid;
    }
@@ -197,6 +219,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable
     *
     * @return The user id for this session.
     */
    @Override
    public int getUserId() {
        return mUserId;
    }
@@ -207,6 +230,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable
     *
     * @return True if this is a system priority session, false otherwise
     */
    @Override
    public boolean isSystemPriority() {
        return (mFlags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0;
    }
@@ -220,7 +244,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable
     * @param opPackageName The op package that made the original volume request.
     * @param pid The pid that made the original volume request.
     * @param uid The uid that made the original volume request.
     * @param caller caller binder. can be {@code null} if it's from the volume key.
     * @param asSystemService {@code true} if the event sent to the session as if it was come from
     *          the system service instead of the app process. This helps sessions to distinguish
     *          between the key injection by the app and key events from the hardware devices.
@@ -318,9 +341,13 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable

    /**
     * Check if this session has been set to active by the app.
     * <p>
     * It's not used to prioritize sessions for dispatching media keys since API 26, but still used
     * to filter session list in MediaSessionManager#getActiveSessions().
     *
     * @return True if the session is active, false otherwise.
     */
    @Override
    public boolean isActive() {
        return mIsActive && !mDestroyed;
    }
@@ -333,6 +360,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable
     * @param expected True if playback is expected to be active. false otherwise.
     * @return True if the session's playback matches with the expectation. false otherwise.
     */
    @Override
    public boolean checkPlaybackActiveState(boolean expected) {
        if (mPlaybackState == null) {
            return false;
@@ -345,13 +373,14 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable
     *
     * @return {@code true} if the playback is local.
     */
    public boolean isPlaybackLocal() {
    @Override
    public boolean isPlaybackTypeLocal() {
        return mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL;
    }

    @Override
    public void binderDied() {
        mService.sessionDied(this);
        mService.onSessionDied(this);
    }

    /**
@@ -383,7 +412,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable
     * @param sequenceId (optional) sequence id. Use this only when a wake lock is needed.
     * @param cb (optional) result receiver to receive callback. Use this only when a wake lock is
     *           needed.
     * @return {@code true} if the attempt to send media button was successfuly.
     * @return {@code true} if the attempt to send media button was successfully.
     *         {@code false} otherwise.
     */
    public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
@@ -392,6 +421,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable
                cb);
    }

    @Override
    public void dump(PrintWriter pw, String prefix) {
        pw.println(prefix + mTag + " " + this);

@@ -712,7 +742,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable
        public void destroySession() throws RemoteException {
            final long token = Binder.clearCallingIdentity();
            try {
                mService.destroySession(MediaSessionRecord.this);
                mService.onSessionDied(MediaSessionRecord.this);
            } finally {
                Binder.restoreCallingIdentity(token);
            }
@@ -734,7 +764,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable
            mIsActive = active;
            final long token = Binder.clearCallingIdentity();
            try {
                mService.updateSession(MediaSessionRecord.this);
                mService.onSessionActiveStateChanged(MediaSessionRecord.this);
            } finally {
                Binder.restoreCallingIdentity(token);
            }
@@ -801,12 +831,16 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable
                    ? PlaybackState.STATE_NONE : mPlaybackState.getState();
            int newState = state == null
                    ? PlaybackState.STATE_NONE : state.getState();
            boolean shouldUpdatePriority = ALWAYS_PRIORITY_STATES.contains(newState)
                    || (!TRANSITION_PRIORITY_STATES.contains(oldState)
                    && TRANSITION_PRIORITY_STATES.contains(newState));
            synchronized (mLock) {
                mPlaybackState = state;
            }
            final long token = Binder.clearCallingIdentity();
            try {
                mService.onSessionPlaystateChanged(MediaSessionRecord.this, oldState, newState);
                mService.onSessionPlaybackStateChanged(
                        MediaSessionRecord.this, shouldUpdatePriority);
            } finally {
                Binder.restoreCallingIdentity(token);
            }
+143 −0
Original line number Diff line number Diff line
/*
 * Copyright 2019 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.server.media;

import android.media.AudioManager;
import android.os.ResultReceiver;
import android.view.KeyEvent;

import java.io.PrintWriter;

/**
 * Common interfaces between {@link MediaSessionRecord} and {@link MediaSession2Record}.
 */
public interface MediaSessionRecordImpl extends AutoCloseable {

    /**
     * Get the info for this session.
     *
     * @return Info that identifies this session.
     */
    String getPackageName();

    /**
     * Get the UID this session was created for.
     *
     * @return The UID for this session.
     */
    int getUid();

    /**
     * Get the user id this session was created for.
     *
     * @return The user id for this session.
     */
    int getUserId();

    /**
     * Check if this session has system priorty and should receive media buttons
     * before any other sessions.
     *
     * @return True if this is a system priority session, false otherwise
     */
    boolean isSystemPriority();

    /**
     * Send a volume adjustment to the session owner. Direction must be one of
     * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
     * {@link AudioManager#ADJUST_SAME}.
     *
     * @param packageName The package that made the original volume request.
     * @param opPackageName The op package that made the original volume request.
     * @param pid The pid that made the original volume request.
     * @param uid The uid that made the original volume request.
     * @param asSystemService {@code true} if the event sent to the session as if it was come from
     *          the system service instead of the app process. This helps sessions to distinguish
     *          between the key injection by the app and key events from the hardware devices.
     *          Should be used only when the volume key events aren't handled by foreground
     *          activity. {@code false} otherwise to tell session about the real caller.
     * @param direction The direction to adjust volume in.
     * @param flags Any of the flags from {@link AudioManager}.
     * @param useSuggested True to use adjustSuggestedStreamVolume instead of
     */
    void adjustVolume(String packageName, String opPackageName, int pid, int uid,
            boolean asSystemService, int direction, int flags, boolean useSuggested);

    /**
     * Check if this session has been set to active by the app. (i.e. ready to receive command and
     * getters are available).
     *
     * @return True if the session is active, false otherwise.
     */
    // TODO(jaewan): Find better naming, or remove this from the MediaSessionRecordImpl.
    boolean isActive();

    /**
     * Check if the session's playback active state matches with the expectation. This always return
     * {@code false} if the playback state is unknown (e.g. {@code null}), where we cannot know the
     * actual playback state associated with the session.
     *
     * @param expected True if playback is expected to be active. false otherwise.
     * @return True if the session's playback matches with the expectation. false otherwise.
     */
    boolean checkPlaybackActiveState(boolean expected);

    /**
     * Check whether the playback type is local or remote.
     * <p>
     * <ul>
     *   <li>Local: volume changes the stream volume because playback happens on this device.</li>
     *   <li>Remote: volume is sent to the apps callback because playback happens on the remote
     *     device and we cannot know how to control the volume of it.</li>
     * </ul>
     *
     * @return {@code true} if the playback is local. {@code false} if the playback is remote.
     */
    boolean isPlaybackTypeLocal();

    /**
     * Sends media button.
     *
     * @param packageName caller package name
     * @param pid caller pid
     * @param uid caller uid
     * @param asSystemService {@code true} if the event sent to the session as if it was come from
     *          the system service instead of the app process.
     * @param ke key events
     * @param sequenceId (optional) sequence id. Use this only when a wake lock is needed.
     * @param cb (optional) result receiver to receive callback. Use this only when a wake lock is
     *           needed.
     * @return {@code true} if the attempt to send media button was successfully.
     *         {@code false} otherwise.
     */
    boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
            KeyEvent ke, int sequenceId, ResultReceiver cb);

    /**
     * Dumps internal state
     *
     * @param pw print writer
     * @param prefix prefix
     */
    void dump(PrintWriter pw, String prefix);

    /**
     * Override {@link AutoCloseable#close} to tell not to throw exception.
     */
    @Override
    void close();
}
+123 −157

File changed.

Preview size limit exceeded, changes collapsed.

Loading