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

Commit 23a33aba authored by Beth Thibodeau's avatar Beth Thibodeau
Browse files

Add media browser for resumption

Doc: go/sysui-media-resumption-requirements

The three main pieces are

1. When the user plays media from an app, check if that app implements a
MediaBrowserService. If so, store that app's info for up to N=5 apps

2. When QSPanel is created, use a QSMediaBrowser to query the saved services
for a playable media item and load those in the media controls panel

3. If the user taps the play button on one of those controls, use
QSMediaBrowser to send a play command to the app's MediaBrowserService

Also, if a media player does not have a MediaBrowserService that allows us to
connect, auto-remove the controls when the media session has ended.
Will explore adding a media button receiver back as an alternative in b/154127084

Bug: 151103474
Bug: 151737807
Test: manual- play from app, reboot, see controls, can play
Change-Id: Ia1172316f1b0c301d794d93b77c7628a736fb153
parent 3bfec326
Loading
Loading
Loading
Loading
+168 −74
Original line number Original line Diff line number Diff line
@@ -21,20 +21,21 @@ import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.ComponentName;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ResolveInfo;
import android.content.res.ColorStateList;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
import android.graphics.drawable.RippleDrawable;
import android.graphics.drawable.RippleDrawable;
import android.media.MediaDescription;
import android.media.MediaMetadata;
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.media.session.PlaybackState;
import android.service.media.MediaBrowserService;
import android.util.Log;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.View.OnAttachStateChangeListener;
@@ -55,6 +56,7 @@ import com.android.settingslib.widget.AdaptiveIcon;
import com.android.systemui.Dependency;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSMediaBrowser;
import com.android.systemui.util.Assert;
import com.android.systemui.util.Assert;


import java.util.List;
import java.util.List;
@@ -67,7 +69,7 @@ public class MediaControlPanel {
    private static final String TAG = "MediaControlPanel";
    private static final String TAG = "MediaControlPanel";
    @Nullable private final LocalMediaManager mLocalMediaManager;
    @Nullable private final LocalMediaManager mLocalMediaManager;
    private final Executor mForegroundExecutor;
    private final Executor mForegroundExecutor;
    private final Executor mBackgroundExecutor;
    protected final Executor mBackgroundExecutor;


    private Context mContext;
    private Context mContext;
    protected LinearLayout mMediaNotifView;
    protected LinearLayout mMediaNotifView;
@@ -76,13 +78,18 @@ public class MediaControlPanel {
    private MediaController mController;
    private MediaController mController;
    private int mForegroundColor;
    private int mForegroundColor;
    private int mBackgroundColor;
    private int mBackgroundColor;
    protected ComponentName mRecvComponent;
    private MediaDevice mDevice;
    private MediaDevice mDevice;
    protected ComponentName mServiceComponent;
    private boolean mIsRegistered = false;
    private boolean mIsRegistered = false;
    private String mKey;
    private String mKey;


    private final int[] mActionIds;
    private final int[] mActionIds;


    public static final String MEDIA_PREFERENCES = "media_control_prefs";
    public static final String MEDIA_PREFERENCE_KEY = "browser_components";
    private SharedPreferences mSharedPrefs;
    private boolean mCheckedForResumption = false;

    // Button IDs used in notifications
    // Button IDs used in notifications
    protected static final int[] NOTIF_ACTION_IDS = {
    protected static final int[] NOTIF_ACTION_IDS = {
            com.android.internal.R.id.action0,
            com.android.internal.R.id.action0,
@@ -154,7 +161,6 @@ public class MediaControlPanel {
     * Initialize a new control panel
     * Initialize a new control panel
     * @param context
     * @param context
     * @param parent
     * @param parent
     * @param manager
     * @param routeManager Manager used to listen for device change events.
     * @param routeManager Manager used to listen for device change events.
     * @param layoutId layout resource to use for this control panel
     * @param layoutId layout resource to use for this control panel
     * @param actionIds resource IDs for action buttons in the layout
     * @param actionIds resource IDs for action buttons in the layout
@@ -198,47 +204,50 @@ public class MediaControlPanel {
    /**
    /**
     * Update the media panel view for the given media session
     * Update the media panel view for the given media session
     * @param token
     * @param token
     * @param icon
     * @param iconDrawable
     * @param iconColor
     * @param iconColor
     * @param bgColor
     * @param bgColor
     * @param contentIntent
     * @param contentIntent
     * @param appNameString
     * @param appNameString
     * @param key
     * @param key
     */
     */
    public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor,
    public void setMediaSession(MediaSession.Token token, Drawable iconDrawable, int iconColor,
            int bgColor, PendingIntent contentIntent, String appNameString, String key) {
            int bgColor, PendingIntent contentIntent, String appNameString, String key) {
        // Ensure that component names are updated if token has changed
        if (mToken == null || !mToken.equals(token)) {
            mToken = token;
            mToken = token;
            mServiceComponent = null;
            mCheckedForResumption = false;
        }

        mForegroundColor = iconColor;
        mForegroundColor = iconColor;
        mBackgroundColor = bgColor;
        mBackgroundColor = bgColor;
        mController = new MediaController(mContext, mToken);
        mController = new MediaController(mContext, mToken);
        mKey = key;
        mKey = key;


        MediaMetadata mediaMetadata = mController.getMetadata();
        // Try to find a browser service component for this app

        // TODO also check for a media button receiver intended for restarting (b/154127084)
        // Try to find a receiver for the media button that matches this app
        // Only check if we haven't tried yet or the session token changed
        String pkgName = mController.getPackageName();
        if (mServiceComponent == null && !mCheckedForResumption) {
            Log.d(TAG, "Checking for service component");
            PackageManager pm = mContext.getPackageManager();
            PackageManager pm = mContext.getPackageManager();
        Intent it = new Intent(Intent.ACTION_MEDIA_BUTTON);
            Intent resumeIntent = new Intent(MediaBrowserService.SERVICE_INTERFACE);
        List<ResolveInfo> info = pm.queryBroadcastReceiversAsUser(it, 0, mContext.getUser());
            List<ResolveInfo> resumeInfo = pm.queryIntentServices(resumeIntent, 0);
        if (info != null) {
            if (resumeInfo != null) {
            for (ResolveInfo inf : info) {
                for (ResolveInfo inf : resumeInfo) {
                if (inf.activityInfo.packageName.equals(mController.getPackageName())) {
                    if (inf.serviceInfo.packageName.equals(mController.getPackageName())) {
                    mRecvComponent = inf.getComponentInfo().getComponentName();
                        mBackgroundExecutor.execute(() ->
                                tryUpdateResumptionList(inf.getComponentInfo().getComponentName()));
                        break;
                    }
                }
                }
            }
            }
            mCheckedForResumption = true;
        }
        }


        mController.registerCallback(mSessionCallback);
        mController.registerCallback(mSessionCallback);


        if (mediaMetadata == null) {
            Log.e(TAG, "Media metadata was null");
            return;
        }

        ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
        if (albumView != null) {
            // Resize art in a background thread
            mBackgroundExecutor.execute(() -> processAlbumArt(mediaMetadata, albumView));
        }
        mMediaNotifView.setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor));
        mMediaNotifView.setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor));


        // Click action
        // Click action
@@ -256,32 +265,9 @@ public class MediaControlPanel {


        // App icon
        // App icon
        ImageView appIcon = mMediaNotifView.findViewById(R.id.icon);
        ImageView appIcon = mMediaNotifView.findViewById(R.id.icon);
        Drawable iconDrawable = icon.loadDrawable(mContext);
        iconDrawable.setTint(mForegroundColor);
        iconDrawable.setTint(mForegroundColor);
        appIcon.setImageDrawable(iconDrawable);
        appIcon.setImageDrawable(iconDrawable);


        // Song name
        TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
        String songName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
        titleText.setText(songName);
        titleText.setTextColor(mForegroundColor);

        // Not in mini player:
        // App title
        TextView appName = mMediaNotifView.findViewById(R.id.app_name);
        if (appName != null) {
            appName.setText(appNameString);
            appName.setTextColor(mForegroundColor);
        }

        // Artist name
        TextView artistText = mMediaNotifView.findViewById(R.id.header_artist);
        if (artistText != null) {
            String artistName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
            artistText.setText(artistName);
            artistText.setTextColor(mForegroundColor);
        }

        // Transfer chip
        // Transfer chip
        mSeamless = mMediaNotifView.findViewById(R.id.media_seamless);
        mSeamless = mMediaNotifView.findViewById(R.id.media_seamless);
        if (mSeamless != null && mLocalMediaManager != null) {
        if (mSeamless != null && mLocalMediaManager != null) {
@@ -300,6 +286,39 @@ public class MediaControlPanel {
        }
        }


        makeActive();
        makeActive();

        // App title (not in mini player)
        TextView appName = mMediaNotifView.findViewById(R.id.app_name);
        if (appName != null) {
            appName.setText(appNameString);
            appName.setTextColor(mForegroundColor);
        }

        MediaMetadata mediaMetadata = mController.getMetadata();
        if (mediaMetadata == null) {
            Log.e(TAG, "Media metadata was null");
            return;
        }

        ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
        if (albumView != null) {
            // Resize art in a background thread
            mBackgroundExecutor.execute(() -> processAlbumArt(mediaMetadata, albumView));
        }

        // Song name
        TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
        String songName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
        titleText.setText(songName);
        titleText.setTextColor(mForegroundColor);

        // Artist name (not in mini player)
        TextView artistText = mMediaNotifView.findViewById(R.id.header_artist);
        if (artistText != null) {
            String artistName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
            artistText.setText(artistName);
            artistText.setTextColor(mForegroundColor);
        }
    }
    }


    /**
    /**
@@ -320,9 +339,12 @@ public class MediaControlPanel {


    /**
    /**
     * Get the name of the package associated with the current media controller
     * Get the name of the package associated with the current media controller
     * @return the package name
     * @return the package name, or null if no controller
     */
     */
    public String getMediaPlayerPackage() {
    public String getMediaPlayerPackage() {
        if (mController == null) {
            return null;
        }
        return mController.getPackageName();
        return mController.getPackageName();
    }
    }


@@ -368,6 +390,17 @@ public class MediaControlPanel {
        return (state.getState() == PlaybackState.STATE_PLAYING);
        return (state.getState() == PlaybackState.STATE_PLAYING);
    }
    }


    /**
     * Process album art for layout
     * @param description media description
     * @param albumView view to hold the album art
     */
    protected void processAlbumArt(MediaDescription description, ImageView albumView) {
        Bitmap albumArt = description.getIconBitmap();
        //TODO check other fields (b/151054111, b/152067055)
        processAlbumArtInternal(albumArt, albumView);
    }

    /**
    /**
     * Process album art for layout
     * Process album art for layout
     * @param metadata media metadata
     * @param metadata media metadata
@@ -375,6 +408,11 @@ public class MediaControlPanel {
     */
     */
    private void processAlbumArt(MediaMetadata metadata, ImageView albumView) {
    private void processAlbumArt(MediaMetadata metadata, ImageView albumView) {
        Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
        Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
        //TODO check other fields (b/151054111, b/152067055)
        processAlbumArtInternal(albumArt, albumView);
    }

    private void processAlbumArtInternal(Bitmap albumArt, ImageView albumView) {
        float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
        float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
        RoundedBitmapDrawable roundedDrawable = null;
        RoundedBitmapDrawable roundedDrawable = null;
        if (albumArt != null) {
        if (albumArt != null) {
@@ -449,10 +487,24 @@ public class MediaControlPanel {
    }
    }


    /**
    /**
     * Put controls into a resumption state
     * Puts controls into a resumption state if possible, or calls removePlayer if no component was
     * found that could resume playback
     */
     */
    public void clearControls() {
    public void clearControls() {
        Log.d(TAG, "clearControls to resumption state package=" + getMediaPlayerPackage());
        Log.d(TAG, "clearControls to resumption state package=" + getMediaPlayerPackage());
        if (mServiceComponent == null) {
            // If we don't have a way to resume, just remove the player altogether
            Log.d(TAG, "Removing unresumable controls");
            removePlayer();
            return;
        }
        resetButtons();
    }

    /**
     * Hide the media buttons and show only a restart button
     */
    protected void resetButtons() {
        // Hide all the old buttons
        // Hide all the old buttons
        for (int i = 0; i < mActionIds.length; i++) {
        for (int i = 0; i < mActionIds.length; i++) {
            ImageButton thisBtn = mMediaNotifView.findViewById(mActionIds[i]);
            ImageButton thisBtn = mMediaNotifView.findViewById(mActionIds[i]);
@@ -465,27 +517,8 @@ public class MediaControlPanel {
        ImageButton btn = mMediaNotifView.findViewById(mActionIds[0]);
        ImageButton btn = mMediaNotifView.findViewById(mActionIds[0]);
        btn.setOnClickListener(v -> {
        btn.setOnClickListener(v -> {
            Log.d(TAG, "Attempting to restart session");
            Log.d(TAG, "Attempting to restart session");
            // Send a media button event to previously found receiver
            QSMediaBrowser browser = new QSMediaBrowser(mContext, null, mServiceComponent);
            if (mRecvComponent != null) {
            browser.restart();
                Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
                intent.setComponent(mRecvComponent);
                int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
                intent.putExtra(
                        Intent.EXTRA_KEY_EVENT,
                        new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
                mContext.sendBroadcast(intent);
            } else {
                // If we don't have a receiver, try relaunching the activity instead
                if (mController.getSessionActivity() != null) {
                    try {
                        mController.getSessionActivity().send();
                    } catch (PendingIntent.CanceledException e) {
                        Log.e(TAG, "Pending intent was canceled", e);
                    }
                } else {
                    Log.e(TAG, "No receiver or activity to restart");
                }
            }
        });
        });
        btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
        btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
        btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
        btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
@@ -514,4 +547,65 @@ public class MediaControlPanel {
        }
        }
    }
    }


    /**
     * Verify that we can connect to the given component with a MediaBrowser, and if so, add that
     * component to the list of resumption components
     */
    private void tryUpdateResumptionList(ComponentName componentName) {
        Log.d(TAG, "Testing if we can connect to " + componentName);
        QSMediaBrowser.testConnection(mContext,
                new QSMediaBrowser.Callback() {
                    @Override
                    public void onConnected() {
                        Log.d(TAG, "yes we can resume with " + componentName);
                        mServiceComponent = componentName;
                        updateResumptionList(componentName);
                    }

                    @Override
                    public void onError() {
                        Log.d(TAG, "Cannot resume with " + componentName);
                        mServiceComponent = null;
                        clearControls();
                        // remove
                    }
                },
                componentName);
    }

    /**
     * Add the component to the saved list of media browser services, checking for duplicates and
     * removing older components that exceed the maximum limit
     * @param componentName
     */
    private synchronized void updateResumptionList(ComponentName componentName) {
        // Add to front of saved list
        if (mSharedPrefs == null) {
            mSharedPrefs = mContext.getSharedPreferences(MEDIA_PREFERENCES, 0);
        }
        String componentString = componentName.flattenToString();
        String listString = mSharedPrefs.getString(MEDIA_PREFERENCE_KEY, null);
        if (listString == null) {
            listString = componentString;
        } else {
            String[] components = listString.split(QSMediaBrowser.DELIMITER);
            StringBuilder updated = new StringBuilder(componentString);
            int nBrowsers = 1;
            for (int i = 0; i < components.length
                    && nBrowsers < QSMediaBrowser.MAX_RESUMPTION_CONTROLS; i++) {
                if (componentString.equals(components[i])) {
                    continue;
                }
                updated.append(QSMediaBrowser.DELIMITER).append(components[i]);
                nBrowsers++;
            }
            listString = updated.toString();
        }
        mSharedPrefs.edit().putString(MEDIA_PREFERENCE_KEY, listString).apply();
    }

    /**
     * Called when a player can't be resumed to give it an opportunity to hide or remove itself
     */
    protected void removePlayer() { }
}
}
+259 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2020 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.systemui.qs;

import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.MediaDescription;
import android.media.browse.MediaBrowser;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.os.Bundle;
import android.service.media.MediaBrowserService;
import android.util.Log;

import java.util.List;

/**
 * Media browser for managing resumption in QS media controls
 */
public class QSMediaBrowser {

    /** Maximum number of controls to show on boot */
    public static final int MAX_RESUMPTION_CONTROLS = 5;

    /** Delimiter for saved component names */
    public static final String DELIMITER = ":";

    private static final String TAG = "QSMediaBrowser";
    private final Context mContext;
    private final Callback mCallback;
    private MediaBrowser mMediaBrowser;
    private ComponentName mComponentName;

    /**
     * Initialize a new media browser
     * @param context the context
     * @param callback used to report media items found
     * @param componentName Component name of the MediaBrowserService this browser will connect to
     */
    public QSMediaBrowser(Context context, Callback callback, ComponentName componentName) {
        mContext = context;
        mCallback = callback;
        mComponentName = componentName;

        Bundle rootHints = new Bundle();
        rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
        mMediaBrowser = new MediaBrowser(mContext,
                mComponentName,
                mConnectionCallback,
                rootHints);
    }

    /**
     * Connects to the MediaBrowserService and looks for valid media. If a media item is returned
     * by the service, QSMediaBrowser.Callback#addTrack will be called with its MediaDescription
     */
    public void findRecentMedia() {
        Log.d(TAG, "Connecting to " + mComponentName);
        mMediaBrowser.connect();
    }

    private final MediaBrowser.SubscriptionCallback mSubscriptionCallback =
            new MediaBrowser.SubscriptionCallback() {
        @Override
        public void onChildrenLoaded(String parentId,
                List<MediaBrowser.MediaItem> children) {
            if (children.size() == 0) {
                Log.e(TAG, "No children found");
                return;
            }
            // We ask apps to return a playable item as the first child when sending
            // a request with EXTRA_RECENT; if they don't, no resume controls
            MediaBrowser.MediaItem child = children.get(0);
            MediaDescription desc = child.getDescription();
            if (child.isPlayable()) {
                mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(), QSMediaBrowser.this);
            } else {
                Log.e(TAG, "Child found but not playable for " + mComponentName);
            }
            mMediaBrowser.disconnect();
        }

        @Override
        public void onError(String parentId) {
            Log.e(TAG, "Subscribe error for " + mComponentName + ": " + parentId);
            mMediaBrowser.disconnect();
        }

        @Override
        public void onError(String parentId, Bundle options) {
            Log.e(TAG, "Subscribe error for " + mComponentName + ": " + parentId
                    + ", options: " + options);
            mMediaBrowser.disconnect();
        }
    };

    private final MediaBrowser.ConnectionCallback mConnectionCallback =
            new MediaBrowser.ConnectionCallback() {
        /**
         * Invoked after {@link MediaBrowser#connect()} when the request has successfully completed.
         * For resumption controls, apps are expected to return a playable media item as the first
         * child. If there are no children or it isn't playable it will be ignored.
         */
        @Override
        public void onConnected() {
            if (mMediaBrowser.isConnected()) {
                mCallback.onConnected();
                Log.d(TAG, "Service connected for " + mComponentName);
                String root = mMediaBrowser.getRoot();
                mMediaBrowser.subscribe(root, mSubscriptionCallback);
            }
        }

        /**
         * Invoked when the client is disconnected from the media browser.
         */
        @Override
        public void onConnectionSuspended() {
            Log.d(TAG, "Connection suspended for " + mComponentName);
        }

        /**
         * Invoked when the connection to the media browser failed.
         */
        @Override
        public void onConnectionFailed() {
            Log.e(TAG, "Connection failed for " + mComponentName);
            mCallback.onError();
        }
    };

    /**
     * Connects to the MediaBrowserService and starts playback
     */
    public void restart() {
        if (mMediaBrowser.isConnected()) {
            mMediaBrowser.disconnect();
        }
        Bundle rootHints = new Bundle();
        rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
        mMediaBrowser = new MediaBrowser(mContext, mComponentName,
                new MediaBrowser.ConnectionCallback() {
                    @Override
                    public void onConnected() {
                        Log.d(TAG, "Connected for restart " + mMediaBrowser.isConnected());
                        MediaSession.Token token = mMediaBrowser.getSessionToken();
                        MediaController controller = new MediaController(mContext, token);
                        controller.getTransportControls();
                        controller.getTransportControls().prepare();
                        controller.getTransportControls().play();
                    }
                }, rootHints);
        mMediaBrowser.connect();
    }

    /**
     * Get the media session token
     * @return the token, or null if the MediaBrowser is null or disconnected
     */
    public MediaSession.Token getToken() {
        if (mMediaBrowser == null || !mMediaBrowser.isConnected()) {
            return null;
        }
        return mMediaBrowser.getSessionToken();
    }

    /**
     * Get an intent to launch the app associated with this browser service
     * @return
     */
    public PendingIntent getAppIntent() {
        PackageManager pm = mContext.getPackageManager();
        Intent launchIntent = pm.getLaunchIntentForPackage(mComponentName.getPackageName());
        return PendingIntent.getActivity(mContext, 0, launchIntent, 0);
    }

    /**
     * Used to test if SystemUI is allowed to connect to the given component as a MediaBrowser
     * @param mContext the context
     * @param callback methods onConnected or onError will be called to indicate whether the
     *                 connection was successful or not
     * @param mComponentName Component name of the MediaBrowserService this browser will connect to
     */
    public static MediaBrowser testConnection(Context mContext, Callback callback,
            ComponentName mComponentName) {
        final MediaBrowser.ConnectionCallback mConnectionCallback =
                new MediaBrowser.ConnectionCallback() {
                    @Override
                    public void onConnected() {
                        Log.d(TAG, "connected");
                        callback.onConnected();
                    }

                    @Override
                    public void onConnectionSuspended() {
                        Log.d(TAG, "suspended");
                        callback.onError();
                    }

                    @Override
                    public void onConnectionFailed() {
                        Log.d(TAG, "failed");
                        callback.onError();
                    }
                };
        Bundle rootHints = new Bundle();
        rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
        MediaBrowser browser = new MediaBrowser(mContext,
                mComponentName,
                mConnectionCallback,
                rootHints);
        browser.connect();
        return browser;
    }

    /**
     * Interface to handle results from QSMediaBrowser
     */
    public static class Callback {
        /**
         * Called when the browser has successfully connected to the service
         */
        public void onConnected() {
        }

        /**
         * Called when the browser encountered an error connecting to the service
         */
        public void onError() {
        }

        /**
         * Called when the browser finds a suitable track to add to the media carousel
         * @param track media info for the item
         * @param component component of the MediaBrowserService which returned this
         * @param browser reference to the browser
         */
        public void addTrack(MediaDescription track, ComponentName component,
                QSMediaBrowser browser) {
        }
    }
}
+110 −35

File changed.

Preview size limit exceeded, changes collapsed.

+124 −5

File changed.

Preview size limit exceeded, changes collapsed.

+1 −2
Original line number Original line Diff line number Diff line
@@ -19,7 +19,6 @@ package com.android.systemui.qs;
import android.app.PendingIntent;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.media.session.MediaController;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.MediaSession;
import android.view.View;
import android.view.View;
@@ -67,7 +66,7 @@ public class QuickQSMediaPlayer extends MediaControlPanel {
     * @param contentIntent Intent to send when user taps on the view
     * @param contentIntent Intent to send when user taps on the view
     * @param key original notification's key
     * @param key original notification's key
     */
     */
    public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor,
    public void setMediaSession(MediaSession.Token token, Drawable icon, int iconColor, int bgColor,
            View actionsContainer, int[] actionsToShow, PendingIntent contentIntent, String key) {
            View actionsContainer, int[] actionsToShow, PendingIntent contentIntent, String key) {
        // Only update if this is a different session and currently playing
        // Only update if this is a different session and currently playing
        String oldPackage = "";
        String oldPackage = "";
Loading