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

Commit ebcfcd0c authored by Ted Wang's avatar Ted Wang Committed by android-build-merger
Browse files

Merge "Monitor AudioManger Playback state to update the AVRCP playback state"...

Merge "Monitor AudioManger Playback state to update the AVRCP playback state" am: 55bab4f4 am: 84f816ec
am: eff36b81

Change-Id: I6847cdabf94fce218e8bade676f5e9248b0cb521
parents d523e56f eff36b81
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -23,6 +23,9 @@ import android.os.Looper;
import android.os.Message;
import android.util.Log;

import com.android.bluetooth.Utils;
import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -47,6 +50,7 @@ public class BrowsablePlayerConnector {
    private static final int MSG_CONNECT_CB = 1;
    private static final int MSG_TIMEOUT = 2;

    private static BrowsablePlayerConnector sInjectConnector;
    private Handler mHandler;
    private Context mContext;
    private PlayerListCallback mCallback;
@@ -58,11 +62,19 @@ public class BrowsablePlayerConnector {
        void run(List<BrowsedPlayerWrapper> result);
    }

    private static void setInstanceForTesting(BrowsablePlayerConnector connector) {
        Utils.enforceInstrumentationTestMode();
        sInjectConnector = connector;
    }

    static BrowsablePlayerConnector connectToPlayers(
            Context context,
            Looper looper,
            List<ResolveInfo> players,
            PlayerListCallback cb) {
        if (sInjectConnector != null) {
            return sInjectConnector;
        }
        if (cb == null) {
            Log.wtfStack(TAG, "Null callback passed");
            return null;
+5 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.bluetooth.avrcp;

import android.media.session.MediaSession;
import android.os.Looper;
import android.util.Log;

/**
@@ -28,6 +29,10 @@ class GPMWrapper extends MediaPlayerWrapper {
    private static final String TAG = "AvrcpGPMWrapper";
    private static final boolean DEBUG = true;

    GPMWrapper(MediaController controller, Looper looper) {
        super(controller, looper);
    }

    @Override
    boolean isMetadataSynced() {
        if (getQueue() == null) {
+121 −11
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
@@ -33,6 +35,7 @@ import android.util.Log;
import android.view.KeyEvent;

import com.android.bluetooth.Utils;
import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.Collections;
@@ -79,6 +82,8 @@ public class MediaPlayerList {
    private Looper mLooper; // Thread all media player callbacks and timeouts happen on
    private PackageManager mPackageManager;
    private MediaSessionManager mMediaSessionManager;
    private MediaData mCurrMediaData = null;
    private final AudioManager mAudioManager;

    private Map<Integer, MediaPlayerWrapper> mMediaPlayers =
            Collections.synchronizedMap(new HashMap<Integer, MediaPlayerWrapper>());
@@ -88,6 +93,9 @@ public class MediaPlayerList {
            Collections.synchronizedMap(new HashMap<Integer, BrowsedPlayerWrapper>());
    private int mActivePlayerId = NO_ACTIVE_PLAYER;

    @VisibleForTesting
    private boolean mAudioPlaybackIsActive = false;

    private AvrcpTargetService.ListCallback mCallback;
    private BrowsablePlayerConnector mBrowsablePlayerConnector;

@@ -122,6 +130,9 @@ public class MediaPlayerList {
        pkgFilter.addDataScheme(PACKAGE_SCHEME);
        context.registerReceiver(mPackageChangedBroadcastReceiver, pkgFilter);

        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        mAudioManager.registerAudioPlaybackCallback(mAudioPlaybackCallback, new Handler(mLooper));

        mMediaSessionManager =
                (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
        mMediaSessionManager.addOnActiveSessionsChangedListener(
@@ -190,6 +201,8 @@ public class MediaPlayerList {
        mMediaSessionManager.setCallback(null, null);
        mMediaSessionManager = null;

        mAudioManager.unregisterAudioPlaybackCallback(mAudioPlaybackCallback);

        mMediaPlayerIds.clear();

        for (MediaPlayerWrapper player : mMediaPlayers.values()) {
@@ -275,7 +288,16 @@ public class MediaPlayerList {
        final MediaPlayerWrapper player = getActivePlayer();
        if (player == null) return null;

        return player.getPlaybackState();
        PlaybackState state = player.getPlaybackState();
        if (mAudioPlaybackIsActive
                && (state == null || state.getState() != PlaybackState.STATE_PLAYING)) {
            return new PlaybackState.Builder()
                .setState(PlaybackState.STATE_PLAYING,
                          state == null ? 0 : state.getPosition(),
                          1.0f)
                .build();
        }
        return state;
    }

    @NonNull
@@ -406,12 +428,8 @@ public class MediaPlayerList {
        }
    }

    // Adds the controller to the MediaPlayerList or updates the controller if we already had
    // a controller for a package. Returns the new ID of the controller where its added or its
    // previous value if it already existed. Returns -1 if the controller passed in is invalid
    int addMediaPlayer(android.media.session.MediaController controller) {
        if (controller == null) return -1;

    @VisibleForTesting
    int addMediaPlayer(MediaController controller) {
        // Each new player has an ID of 1 plus the highest ID. The ID 0 is reserved to signify that
        // there is no active player. If we already have a browsable player for the package, reuse
        // that key.
@@ -427,7 +445,7 @@ public class MediaPlayerList {
        if (mMediaPlayers.containsKey(playerId)) {
            d("Already have a controller for the player: " + packageName + ", updating instead");
            MediaPlayerWrapper player = mMediaPlayers.get(playerId);
            player.updateMediaController(MediaControllerFactory.wrap(controller));
            player.updateMediaController(controller);

            // If the media controller we updated was the active player check if the media updated
            if (playerId == mActivePlayerId) {
@@ -437,8 +455,8 @@ public class MediaPlayerList {
            return playerId;
        }

        MediaPlayerWrapper newPlayer = MediaPlayerWrapper.wrap(
                MediaControllerFactory.wrap(controller),
        MediaPlayerWrapper newPlayer = MediaPlayerWrapperFactory.wrap(
                controller,
                mLooper);

        Log.i(TAG, "Adding wrapped media player: " + packageName + " at key: "
@@ -448,6 +466,18 @@ public class MediaPlayerList {
        return playerId;
    }

    // Adds the controller to the MediaPlayerList or updates the controller if we already had
    // a controller for a package. Returns the new ID of the controller where its added or its
    // previous value if it already existed. Returns -1 if the controller passed in is invalid
    int addMediaPlayer(android.media.session.MediaController controller) {
        if (controller == null) {
            e("Trying to add a null MediaController");
            return -1;
        }

        return addMediaPlayer(MediaControllerFactory.wrap(controller));
    }

    void removeMediaPlayer(int playerId) {
        if (!mMediaPlayers.containsKey(playerId)) {
            e("Trying to remove nonexistent media player: " + playerId);
@@ -504,7 +534,12 @@ public class MediaPlayerList {
            sendFolderUpdate(true, true, false);
        }

        sendMediaUpdate(getActivePlayer().getCurrentMediaData());
        MediaData data = getActivePlayer().getCurrentMediaData();
        if (mAudioPlaybackIsActive) {
            data.state = mCurrMediaData.state;
            Log.d(TAG, "setActivePlayer mAudioPlaybackIsActive=true, state=" + data.state);
        }
        sendMediaUpdate(data);
    }

    // TODO (apanicke): Add logging for media key events in dumpsys
@@ -537,6 +572,8 @@ public class MediaPlayerList {
            data.queue.add(data.metadata);
        }

        Log.d(TAG, "sendMediaUpdate state=" + data.state);
        mCurrMediaData = data;
        mCallback.run(data);
    }

@@ -600,6 +637,75 @@ public class MediaPlayerList {
        }
    };

    void updateMediaForAudioPlayback() {
        MediaData currMediaData = null;
        PlaybackState currState = null;
        if (getActivePlayer() == null) {
            Log.d(TAG, "updateMediaForAudioPlayback: no active player");
            PlaybackState.Builder builder = new PlaybackState.Builder()
                    .setState(PlaybackState.STATE_STOPPED, 0L, 0L);
            List<Metadata> queue = new ArrayList<Metadata>();
            queue.add(Util.empty_data());
            currMediaData = new MediaData(
                    Util.empty_data(),
                    builder.build(),
                    queue
                );
        } else {
            currMediaData = getActivePlayer().getCurrentMediaData();
            currState = currMediaData.state;
        }

        if (currState != null
                && currState.getState() == PlaybackState.STATE_PLAYING) {
            Log.i(TAG, "updateMediaForAudioPlayback: Active player is playing, drop it");
            return;
        }

        if (mAudioPlaybackIsActive) {
            PlaybackState.Builder builder = new PlaybackState.Builder()
                    .setState(PlaybackState.STATE_PLAYING,
                        currState == null ? 0 : currState.getPosition(),
                        1.0f);
            currMediaData.state = builder.build();
        }
        Log.i(TAG, "updateMediaForAudioPlayback: update state=" + currMediaData.state);
        sendMediaUpdate(currMediaData);
    }

    @VisibleForTesting
    void injectAudioPlaybacActive(boolean isActive) {
        mAudioPlaybackIsActive = isActive;
        updateMediaForAudioPlayback();
    }

    private final AudioManager.AudioPlaybackCallback mAudioPlaybackCallback =
            new AudioManager.AudioPlaybackCallback() {
        @Override
        public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
            if (configs == null) {
                return;
            }
            boolean isActive = false;
            Log.v(TAG, "onPlaybackConfigChanged(): Configs list size=" + configs.size());
            for (AudioPlaybackConfiguration config : configs) {
                if (config.isActive()) {
                    if (DEBUG) {
                        Log.d(TAG, "onPlaybackConfigChanged(): config="
                                 + AudioPlaybackConfiguration.toLogFriendlyString(config));
                    }
                    isActive = true;
                }
            }
            if (isActive != mAudioPlaybackIsActive) {
                Log.d(TAG, "onPlaybackConfigChanged isActive=" + isActive
                        + ", mAudioPlaybackIsActive=" + mAudioPlaybackIsActive);
                mAudioPlaybackIsActive = isActive;
                updateMediaForAudioPlayback();
            }
        }
    };

    private final MediaPlayerWrapper.Callback mMediaPlayerCallback =
            new MediaPlayerWrapper.Callback() {
        @Override
@@ -614,6 +720,10 @@ public class MediaPlayerList {
                return;
            }

            if (mAudioPlaybackIsActive && (data.state.getState() != PlaybackState.STATE_PLAYING)) {
                Log.d(TAG, "Some audio playbacks are still active, drop it");
                return;
            }
            sendMediaUpdate(data);
        }

+8 −27
Original line number Diff line number Diff line
@@ -54,10 +54,6 @@ class MediaPlayerWrapper {
    private final Object mCallbackLock = new Object();
    private Callback mRegisteredCallback = null;

    protected MediaPlayerWrapper() {
        mCurrentData = new MediaData(null, null, null);
    }

    public interface Callback {
        void mediaUpdatedCallback(MediaData data);
        void sessionUpdatedCallback(String packageName);
@@ -81,30 +77,15 @@ class MediaPlayerWrapper {
        return true;
    }

    // TODO (apanicke): Implement a factory to make testing and creating interop wrappers easier
    static MediaPlayerWrapper wrap(MediaController controller, Looper looper) {
        if (controller == null || looper == null) {
            e("MediaPlayerWrapper.wrap(): Null parameter - Controller: " + controller
                    + " | Looper: " + looper);
            return null;
        }
    MediaPlayerWrapper(MediaController controller, Looper looper) {
        mMediaController = controller;
        mPackageName = controller.getPackageName();
        mLooper = looper;

        MediaPlayerWrapper newWrapper;
        if (controller.getPackageName().equals("com.google.android.music")) {
            Log.v(TAG, "Creating compatibility wrapper for Google Play Music");
            newWrapper = new GPMWrapper();
        } else {
            newWrapper = new MediaPlayerWrapper();
        }

        newWrapper.mMediaController = controller;
        newWrapper.mPackageName = controller.getPackageName();
        newWrapper.mLooper = looper;

        newWrapper.mCurrentData.queue = Util.toMetadataList(newWrapper.getQueue());
        newWrapper.mCurrentData.metadata = Util.toMetadata(newWrapper.getMetadata());
        newWrapper.mCurrentData.state = newWrapper.getPlaybackState();
        return newWrapper;
        mCurrentData = new MediaData(null, null, null);
        mCurrentData.queue = Util.toMetadataList(getQueue());
        mCurrentData.metadata = Util.toMetadata(getMetadata());
        mCurrentData.state = getPlaybackState();
    }

    void cleanup() {
+52 −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.bluetooth.avrcp;

import android.os.Looper;

import com.android.internal.annotations.VisibleForTesting;

/**
 * Provide a method to inject custom MediaControllerWrapper objects for testing. By using the
 * factory methods instead of calling the wrap method of MediaControllerWrapper directly, we can
 * inject a custom MediaControllerWrapper that can be used with JUnit and Mockito to set
 * expectations and validate behaviour in tests.
 */
public final class MediaPlayerWrapperFactory {
    private static MediaPlayerWrapper sInjectedWrapper;

    static MediaPlayerWrapper wrap(MediaController controller, Looper looper) {
        if (sInjectedWrapper != null) return sInjectedWrapper;
        if (controller == null || looper == null) {
            return null;
        }

        MediaPlayerWrapper newWrapper;
        if (controller.getPackageName().equals("com.google.android.music")) {
            newWrapper = new GPMWrapper(controller, looper);
        } else {
            newWrapper = new MediaPlayerWrapper(controller, looper);
        }
        return newWrapper;
    }

    @VisibleForTesting
    static void inject(MediaPlayerWrapper wrapper) {
        sInjectedWrapper = wrapper;
    }
}
Loading