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

Commit 979cf139 authored by Ajay Panicker's avatar Ajay Panicker
Browse files

Add the BrowsedPlayerWrapper

The BrowserPlayerWrapper provides a convienent interface for AVRCP to
use that handles all connections, and requests with a convienent API.
Also this patch adds a mockable MediaBrowser as well as adding the
MediaControllerFactory and MediaBrowserFactory classes in order to make
injecting test objects much easier.

Bug: 68854188
Test: runtest -c com.android.bluetooth.avrcp.BrowserPlayerWrapperTest
Change-Id: I34b5326eaf35d8db43f6a8d709f60ade4c642e88
(cherry picked from commit 390145f1fd2f83bbfb2576823a9c9d7831efa152)
Merged-In: I4f80c17e4290d36c9948eb46cce6745b810173a0
parent 05ed1bda
Loading
Loading
Loading
Loading
+2 −2
Original line number Original line Diff line number Diff line
@@ -1800,7 +1800,7 @@ public final class Avrcp {
                    for (android.media.session.MediaController controller : newControllers) {
                    for (android.media.session.MediaController controller : newControllers) {
                        String packageName = controller.getPackageName();
                        String packageName = controller.getPackageName();
                        if (DEBUG) {
                        if (DEBUG) {
                            Log.v(TAG, "ActiveSession: " + MediaController.wrap(controller));
                            Log.v(TAG, "ActiveSession: " + MediaControllerFactory.wrap(controller));
                        }
                        }
                        // Only use the first (highest priority) controller from each package
                        // Only use the first (highest priority) controller from each package
                        if (updatedPackages.contains(packageName)) {
                        if (updatedPackages.contains(packageName)) {
@@ -2000,7 +2000,7 @@ public final class Avrcp {
    /** Add (or update) a player to the media player list given an active controller */
    /** Add (or update) a player to the media player list given an active controller */
    private boolean addMediaPlayerController(android.media.session.MediaController controller) {
    private boolean addMediaPlayerController(android.media.session.MediaController controller) {
        String packageName = controller.getPackageName();
        String packageName = controller.getPackageName();
        MediaPlayerInfo info = new MediaPlayerInfo(MediaController.wrap(controller),
        MediaPlayerInfo info = new MediaPlayerInfo(MediaControllerFactory.wrap(controller),
                AvrcpConstants.PLAYER_TYPE_AUDIO, AvrcpConstants.PLAYER_SUBTYPE_NONE,
                AvrcpConstants.PLAYER_TYPE_AUDIO, AvrcpConstants.PLAYER_SUBTYPE_NONE,
                getBluetoothPlayState(controller.getPlaybackState()),
                getBluetoothPlayState(controller.getPlaybackState()),
                getFeatureBitMask(packageName), controller.getPackageName(),
                getFeatureBitMask(packageName), controller.getPackageName(),
+1 −2
Original line number Original line Diff line number Diff line
@@ -318,8 +318,7 @@ class BrowsedMediaPlayer {
                    mPathStack.push(mMediaId);
                    mPathStack.push(mMediaId);
                }
                }


                mMediaController = MediaController.wrap(
                mMediaController = MediaControllerFactory.make(mContext, token);
                        new android.media.session.MediaController(mContext, token));
                /* get root folder items */
                /* get root folder items */
                mMediaBrowser.subscribe(mRootFolderUid, mFolderItemsCb);
                mMediaBrowser.subscribe(mRootFolderUid, mFolderItemsCb);
                return;
                return;
+165 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright 2018 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.content.ComponentName;
import android.content.Context;
import android.media.session.MediaSession;
import android.os.Bundle;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;

/**
 * Provide a mockable interface in order to test classes that use MediaBrowser.
 * We need this class due to the fact that the MediaController class is marked as final and
 * there is no way to currently mock final classes in Android. Once this is possible this class
 * can be deleted.
 */
public class MediaBrowser {
    android.media.browse.MediaBrowser mDelegate;

    /**
     * Wrap a real MediaBrowser object
     */
    public MediaBrowser(android.media.browse.MediaBrowser delegate) {
        mDelegate = delegate;
    }

    /**
     * Create a real MediaBrowser object and wrap it
     */
    public MediaBrowser(Context context, ComponentName serviceComponent,
            ConnectionCallback callback, Bundle rootHints) {
        mDelegate = new android.media.browse.MediaBrowser(context, serviceComponent, callback,
                rootHints);
    }

    /**
     * Wrapper for MediaBrowser.ConnectionCallback
     */
    public abstract static class ConnectionCallback extends
            android.media.browse.MediaBrowser.ConnectionCallback {}

    /**
     * Wrapper for MediaBrowser.ItemCallback
     */
    public abstract static class ItemCallback extends
            android.media.browse.MediaBrowser.ItemCallback {}

    /**
     * Wrapper for MediaBrowser.SubscriptionCallback
     */
    public abstract static class SubscriptionCallback extends
            android.media.browse.MediaBrowser.SubscriptionCallback {}

    /**
     * Wrapper for MediaBrowser.connect()
     */
    public void connect() {
        mDelegate.connect();
    }

    /**
     * Wrapper for MediaBrowser.disconnect()
     */
    public void disconnect() {
        mDelegate.disconnect();
    }

    /**
     * Wrapper for MediaBrowser.getExtras()
     */
    public Bundle getExtras() {
        return mDelegate.getExtras();
    }

    /**
     * Wrapper for MediaBrowser.getItem(String mediaId, ItemCallback callback)
     */
    public void getItem(String mediaId, ItemCallback callback) {
        mDelegate.getItem(mediaId, callback);
    }

    /**
     * Wrapper for MediaBrowser.getRoot()
     */
    public String getRoot() {
        return mDelegate.getRoot();
    }

    /**
     * Wrapper for MediaBrowser.getServiceComponent()
     */
    public ComponentName getServiceComponent() {
        return mDelegate.getServiceComponent();
    }

    /**
     * Wrapper for MediaBrowser.getSessionToken()
     */
    public MediaSession.Token getSessionToken() {
        return mDelegate.getSessionToken();
    }
    /**
     * Wrapper for MediaBrowser.isConnected()
     */
    public boolean isConnected() {
        return mDelegate.isConnected();
    }

    /**
     * Wrapper for MediaBrowser.subscribe(String parentId, Bundle options,
     * SubscriptionCallback callback)
     */
    public void subscribe(String parentId, Bundle options, SubscriptionCallback callback) {
        mDelegate.subscribe(parentId, options, callback);
    }

    /**
     * Wrapper for MediaBrowser.subscribe(String parentId, SubscriptionCallback callback)
     */
    public void subscribe(String parentId, SubscriptionCallback callback) {
        mDelegate.subscribe(parentId, callback);
    }

    /**
     * Wrapper for MediaBrowser.unsubscribe(String parentId)
     */
    public void unsubscribe(String parentId) {
        mDelegate.unsubscribe(parentId);
    }


    /**
     * Wrapper for MediaBrowser.unsubscribe(String parentId, SubscriptionCallback callback)
     */
    public void unsubscribe(String parentId, SubscriptionCallback callback) {
        mDelegate.unsubscribe(parentId, callback);
    }

    /**
     * A function that allows Mockito to capture the constructor arguments when using
     * MediaBrowserFactory.make()
     */
    @VisibleForTesting
    public void testInit(Context context, ComponentName serviceComponent,
            ConnectionCallback callback, Bundle rootHints) {
        // This is only used by Mockito to capture the constructor arguments on creation
        Log.wtfStack("AvrcpMockMediaBrowser", "This function should never be called");
    }
}
+52 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright 2018 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.content.ComponentName;
import android.content.Context;
import android.os.Bundle;

import com.android.internal.annotations.VisibleForTesting;

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

    static MediaBrowser wrap(android.media.browse.MediaBrowser delegate) {
        if (sInjectedBrowser != null) return sInjectedBrowser;
        return (delegate != null) ? new MediaBrowser(delegate) : null;
    }

    static MediaBrowser make(Context context, ComponentName serviceComponent,
            MediaBrowser.ConnectionCallback callback, Bundle rootHints) {
        if (sInjectedBrowser != null) {
            sInjectedBrowser.testInit(context, serviceComponent, callback, rootHints);
            return sInjectedBrowser;
        }
        return new MediaBrowser(context, serviceComponent, callback, rootHints);
    }

    @VisibleForTesting
    static void inject(MediaBrowser browser) {
        sInjectedBrowser = browser;
    }
}
+29 −5
Original line number Original line Diff line number Diff line
/*
 * Copyright 2018 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;
package com.android.bluetooth.avrcp;


import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.app.PendingIntent;
import android.content.Context;
import android.media.MediaDescription;
import android.media.MediaDescription;
import android.media.MediaMetadata;
import android.media.MediaMetadata;
import android.media.Rating;
import android.media.Rating;
@@ -16,22 +33,29 @@ import android.view.KeyEvent;


import java.util.List;
import java.util.List;


/**
 * Provide a mockable interface in order to test classes that use MediaController.
 * We need this class due to the fact that the MediaController class is marked as final and
 * there is no way to currently mock final classes in Android. Once this is possible this class
 * can be deleted.
 */
public class MediaController {
public class MediaController {
    @NonNull public android.media.session.MediaController mDelegate;
    @NonNull public android.media.session.MediaController mDelegate;
    public android.media.session.MediaController.TransportControls mTransportDelegate;
    public android.media.session.MediaController.TransportControls mTransportDelegate;
    public TransportControls mTransportControls;
    public TransportControls mTransportControls;


    @Nullable
    public static MediaController wrap(@Nullable android.media.session.MediaController delegate) {
        return (delegate != null) ? new MediaController(delegate) : null;
    }

    public MediaController(@NonNull android.media.session.MediaController delegate) {
    public MediaController(@NonNull android.media.session.MediaController delegate) {
        mDelegate = delegate;
        mDelegate = delegate;
        mTransportDelegate = delegate.getTransportControls();
        mTransportDelegate = delegate.getTransportControls();
        mTransportControls = new TransportControls();
        mTransportControls = new TransportControls();
    }
    }


    public MediaController(Context context, MediaSession.Token token) {
        mDelegate = new android.media.session.MediaController(context, token);
        mTransportDelegate = mDelegate.getTransportControls();
        mTransportControls = new TransportControls();
    }

    public android.media.session.MediaController getWrappedInstance() {
    public android.media.session.MediaController getWrappedInstance() {
        return mDelegate;
        return mDelegate;
    }
    }
Loading