Merge "Add media browser for resumption" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
529773f6f0
@@ -21,20 +21,21 @@ import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.graphics.drawable.RippleDrawable;
|
||||
import android.media.MediaDescription;
|
||||
import android.media.MediaMetadata;
|
||||
import android.media.session.MediaController;
|
||||
import android.media.session.MediaSession;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.service.media.MediaBrowserService;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnAttachStateChangeListener;
|
||||
@@ -55,6 +56,7 @@ import com.android.settingslib.widget.AdaptiveIcon;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
import com.android.systemui.qs.QSMediaBrowser;
|
||||
import com.android.systemui.util.Assert;
|
||||
|
||||
import java.util.List;
|
||||
@@ -67,7 +69,7 @@ public class MediaControlPanel {
|
||||
private static final String TAG = "MediaControlPanel";
|
||||
@Nullable private final LocalMediaManager mLocalMediaManager;
|
||||
private final Executor mForegroundExecutor;
|
||||
private final Executor mBackgroundExecutor;
|
||||
protected final Executor mBackgroundExecutor;
|
||||
|
||||
private Context mContext;
|
||||
protected LinearLayout mMediaNotifView;
|
||||
@@ -76,13 +78,18 @@ public class MediaControlPanel {
|
||||
private MediaController mController;
|
||||
private int mForegroundColor;
|
||||
private int mBackgroundColor;
|
||||
protected ComponentName mRecvComponent;
|
||||
private MediaDevice mDevice;
|
||||
protected ComponentName mServiceComponent;
|
||||
private boolean mIsRegistered = false;
|
||||
private String mKey;
|
||||
|
||||
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
|
||||
protected static final int[] NOTIF_ACTION_IDS = {
|
||||
com.android.internal.R.id.action0,
|
||||
@@ -154,7 +161,6 @@ public class MediaControlPanel {
|
||||
* Initialize a new control panel
|
||||
* @param context
|
||||
* @param parent
|
||||
* @param manager
|
||||
* @param routeManager Manager used to listen for device change events.
|
||||
* @param layoutId layout resource to use for this control panel
|
||||
* @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
|
||||
* @param token
|
||||
* @param icon
|
||||
* @param iconDrawable
|
||||
* @param iconColor
|
||||
* @param bgColor
|
||||
* @param contentIntent
|
||||
* @param appNameString
|
||||
* @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) {
|
||||
mToken = token;
|
||||
// Ensure that component names are updated if token has changed
|
||||
if (mToken == null || !mToken.equals(token)) {
|
||||
mToken = token;
|
||||
mServiceComponent = null;
|
||||
mCheckedForResumption = false;
|
||||
}
|
||||
|
||||
mForegroundColor = iconColor;
|
||||
mBackgroundColor = bgColor;
|
||||
mController = new MediaController(mContext, mToken);
|
||||
mKey = key;
|
||||
|
||||
MediaMetadata mediaMetadata = mController.getMetadata();
|
||||
|
||||
// Try to find a receiver for the media button that matches this app
|
||||
PackageManager pm = mContext.getPackageManager();
|
||||
Intent it = new Intent(Intent.ACTION_MEDIA_BUTTON);
|
||||
List<ResolveInfo> info = pm.queryBroadcastReceiversAsUser(it, 0, mContext.getUser());
|
||||
if (info != null) {
|
||||
for (ResolveInfo inf : info) {
|
||||
if (inf.activityInfo.packageName.equals(mController.getPackageName())) {
|
||||
mRecvComponent = inf.getComponentInfo().getComponentName();
|
||||
// Try to find a browser service component for this app
|
||||
// TODO also check for a media button receiver intended for restarting (b/154127084)
|
||||
// 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();
|
||||
Intent resumeIntent = new Intent(MediaBrowserService.SERVICE_INTERFACE);
|
||||
List<ResolveInfo> resumeInfo = pm.queryIntentServices(resumeIntent, 0);
|
||||
if (resumeInfo != null) {
|
||||
for (ResolveInfo inf : resumeInfo) {
|
||||
if (inf.serviceInfo.packageName.equals(mController.getPackageName())) {
|
||||
mBackgroundExecutor.execute(() ->
|
||||
tryUpdateResumptionList(inf.getComponentInfo().getComponentName()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
mCheckedForResumption = true;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
// Click action
|
||||
@@ -256,32 +265,9 @@ public class MediaControlPanel {
|
||||
|
||||
// App icon
|
||||
ImageView appIcon = mMediaNotifView.findViewById(R.id.icon);
|
||||
Drawable iconDrawable = icon.loadDrawable(mContext);
|
||||
iconDrawable.setTint(mForegroundColor);
|
||||
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
|
||||
mSeamless = mMediaNotifView.findViewById(R.id.media_seamless);
|
||||
if (mSeamless != null && mLocalMediaManager != null) {
|
||||
@@ -300,6 +286,39 @@ public class MediaControlPanel {
|
||||
}
|
||||
|
||||
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
|
||||
* @return the package name
|
||||
* @return the package name, or null if no controller
|
||||
*/
|
||||
public String getMediaPlayerPackage() {
|
||||
if (mController == null) {
|
||||
return null;
|
||||
}
|
||||
return mController.getPackageName();
|
||||
}
|
||||
|
||||
@@ -368,6 +390,17 @@ public class MediaControlPanel {
|
||||
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
|
||||
* @param metadata media metadata
|
||||
@@ -375,6 +408,11 @@ public class MediaControlPanel {
|
||||
*/
|
||||
private void processAlbumArt(MediaMetadata metadata, ImageView albumView) {
|
||||
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);
|
||||
RoundedBitmapDrawable roundedDrawable = 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() {
|
||||
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
|
||||
for (int i = 0; i < mActionIds.length; i++) {
|
||||
ImageButton thisBtn = mMediaNotifView.findViewById(mActionIds[i]);
|
||||
@@ -465,27 +517,8 @@ public class MediaControlPanel {
|
||||
ImageButton btn = mMediaNotifView.findViewById(mActionIds[0]);
|
||||
btn.setOnClickListener(v -> {
|
||||
Log.d(TAG, "Attempting to restart session");
|
||||
// Send a media button event to previously found receiver
|
||||
if (mRecvComponent != null) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
QSMediaBrowser browser = new QSMediaBrowser(mContext, null, mServiceComponent);
|
||||
browser.restart();
|
||||
});
|
||||
btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
|
||||
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() { }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* 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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,12 @@ package com.android.systemui.qs;
|
||||
|
||||
import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.media.MediaDescription;
|
||||
import android.media.session.MediaController;
|
||||
import android.media.session.MediaSession;
|
||||
import android.util.Log;
|
||||
@@ -60,9 +61,11 @@ public class QSMediaPlayer extends MediaControlPanel {
|
||||
};
|
||||
|
||||
private final QSPanel mParent;
|
||||
private final Executor mForegroundExecutor;
|
||||
private final DelayableExecutor mBackgroundExecutor;
|
||||
private final SeekBarViewModel mSeekBarViewModel;
|
||||
private final SeekBarObserver mSeekBarObserver;
|
||||
private String mPackageName;
|
||||
|
||||
/**
|
||||
* Initialize quick shade version of player
|
||||
@@ -77,6 +80,7 @@ public class QSMediaPlayer extends MediaControlPanel {
|
||||
super(context, parent, routeManager, R.layout.qs_media_panel, QS_ACTION_IDS,
|
||||
foregroundExecutor, backgroundExecutor);
|
||||
mParent = (QSPanel) parent;
|
||||
mForegroundExecutor = foregroundExecutor;
|
||||
mBackgroundExecutor = backgroundExecutor;
|
||||
mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor);
|
||||
mSeekBarObserver = new SeekBarObserver(getView());
|
||||
@@ -89,6 +93,58 @@ public class QSMediaPlayer extends MediaControlPanel {
|
||||
bar.setOnTouchListener(mSeekBarViewModel.getSeekBarTouchListener());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a media panel view based on a media description. Used for resumption
|
||||
* @param description
|
||||
* @param iconColor
|
||||
* @param bgColor
|
||||
* @param contentIntent
|
||||
* @param pkgName
|
||||
*/
|
||||
public void setMediaSession(MediaSession.Token token, MediaDescription description,
|
||||
int iconColor, int bgColor, PendingIntent contentIntent, String pkgName) {
|
||||
mPackageName = pkgName;
|
||||
PackageManager pm = getContext().getPackageManager();
|
||||
Drawable icon = null;
|
||||
CharSequence appName = pkgName.substring(pkgName.lastIndexOf("."));
|
||||
try {
|
||||
icon = pm.getApplicationIcon(pkgName);
|
||||
appName = pm.getApplicationLabel(pm.getApplicationInfo(pkgName, 0));
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e(TAG, "Error getting package information", e);
|
||||
}
|
||||
|
||||
// Set what we can normally
|
||||
super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, appName.toString(),
|
||||
null);
|
||||
|
||||
// Then add info from MediaDescription
|
||||
ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
|
||||
if (albumView != null) {
|
||||
// Resize art in a background thread
|
||||
mBackgroundExecutor.execute(() -> processAlbumArt(description, albumView));
|
||||
}
|
||||
|
||||
// Song name
|
||||
TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
|
||||
CharSequence songName = description.getTitle();
|
||||
titleText.setText(songName);
|
||||
titleText.setTextColor(iconColor);
|
||||
|
||||
// Artist name (not in mini player)
|
||||
TextView artistText = mMediaNotifView.findViewById(R.id.header_artist);
|
||||
if (artistText != null) {
|
||||
CharSequence artistName = description.getSubtitle();
|
||||
artistText.setText(artistName);
|
||||
artistText.setTextColor(iconColor);
|
||||
}
|
||||
|
||||
initLongPressMenu(iconColor);
|
||||
|
||||
// Set buttons to resume state
|
||||
resetButtons();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update media panel view for the given media session
|
||||
* @param token token for this media session
|
||||
@@ -96,41 +152,43 @@ public class QSMediaPlayer extends MediaControlPanel {
|
||||
* @param iconColor foreground color (for text, icons)
|
||||
* @param bgColor background color
|
||||
* @param actionsContainer a LinearLayout containing the media action buttons
|
||||
* @param notif reference to original notification
|
||||
* @param contentIntent Intent to send when user taps on player
|
||||
* @param appName Application title
|
||||
* @param key original notification's key
|
||||
*/
|
||||
public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor,
|
||||
int bgColor, View actionsContainer, Notification notif, String key) {
|
||||
public void setMediaSession(MediaSession.Token token, Drawable icon, int iconColor,
|
||||
int bgColor, View actionsContainer, PendingIntent contentIntent, String appName,
|
||||
String key) {
|
||||
|
||||
String appName = Notification.Builder.recoverBuilder(getContext(), notif)
|
||||
.loadHeaderAppName();
|
||||
super.setMediaSession(token, icon, iconColor, bgColor, notif.contentIntent, appName, key);
|
||||
super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, appName, key);
|
||||
|
||||
// Media controls
|
||||
LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
|
||||
int i = 0;
|
||||
for (; i < parentActionsLayout.getChildCount() && i < QS_ACTION_IDS.length; i++) {
|
||||
ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
|
||||
ImageButton thatBtn = parentActionsLayout.findViewById(NOTIF_ACTION_IDS[i]);
|
||||
if (thatBtn == null || thatBtn.getDrawable() == null
|
||||
|| thatBtn.getVisibility() != View.VISIBLE) {
|
||||
thisBtn.setVisibility(View.GONE);
|
||||
continue;
|
||||
if (actionsContainer != null) {
|
||||
LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
|
||||
int i = 0;
|
||||
for (; i < parentActionsLayout.getChildCount() && i < QS_ACTION_IDS.length; i++) {
|
||||
ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
|
||||
ImageButton thatBtn = parentActionsLayout.findViewById(NOTIF_ACTION_IDS[i]);
|
||||
if (thatBtn == null || thatBtn.getDrawable() == null
|
||||
|| thatBtn.getVisibility() != View.VISIBLE) {
|
||||
thisBtn.setVisibility(View.GONE);
|
||||
continue;
|
||||
}
|
||||
|
||||
Drawable thatIcon = thatBtn.getDrawable();
|
||||
thisBtn.setImageDrawable(thatIcon.mutate());
|
||||
thisBtn.setVisibility(View.VISIBLE);
|
||||
thisBtn.setOnClickListener(v -> {
|
||||
Log.d(TAG, "clicking on other button");
|
||||
thatBtn.performClick();
|
||||
});
|
||||
}
|
||||
|
||||
Drawable thatIcon = thatBtn.getDrawable();
|
||||
thisBtn.setImageDrawable(thatIcon.mutate());
|
||||
thisBtn.setVisibility(View.VISIBLE);
|
||||
thisBtn.setOnClickListener(v -> {
|
||||
Log.d(TAG, "clicking on other button");
|
||||
thatBtn.performClick();
|
||||
});
|
||||
}
|
||||
|
||||
// Hide any unused buttons
|
||||
for (; i < QS_ACTION_IDS.length; i++) {
|
||||
ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
|
||||
thisBtn.setVisibility(View.GONE);
|
||||
// Hide any unused buttons
|
||||
for (; i < QS_ACTION_IDS.length; i++) {
|
||||
ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
|
||||
thisBtn.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
// Seek Bar
|
||||
@@ -138,6 +196,10 @@ public class QSMediaPlayer extends MediaControlPanel {
|
||||
mBackgroundExecutor.execute(
|
||||
() -> mSeekBarViewModel.updateController(controller, iconColor));
|
||||
|
||||
initLongPressMenu(iconColor);
|
||||
}
|
||||
|
||||
private void initLongPressMenu(int iconColor) {
|
||||
// Set up long press menu
|
||||
View guts = mMediaNotifView.findViewById(R.id.media_guts);
|
||||
View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options);
|
||||
@@ -145,7 +207,7 @@ public class QSMediaPlayer extends MediaControlPanel {
|
||||
|
||||
View clearView = options.findViewById(R.id.remove);
|
||||
clearView.setOnClickListener(b -> {
|
||||
mParent.removeMediaPlayer(QSMediaPlayer.this);
|
||||
removePlayer();
|
||||
});
|
||||
ImageView removeIcon = options.findViewById(R.id.remove_icon);
|
||||
removeIcon.setImageTintList(ColorStateList.valueOf(iconColor));
|
||||
@@ -165,11 +227,9 @@ public class QSMediaPlayer extends MediaControlPanel {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearControls() {
|
||||
super.clearControls();
|
||||
|
||||
protected void resetButtons() {
|
||||
super.resetButtons();
|
||||
mSeekBarViewModel.clearController();
|
||||
|
||||
View guts = mMediaNotifView.findViewById(R.id.media_guts);
|
||||
View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options);
|
||||
|
||||
@@ -192,4 +252,19 @@ public class QSMediaPlayer extends MediaControlPanel {
|
||||
public void setListening(boolean listening) {
|
||||
mSeekBarViewModel.setListening(listening);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePlayer() {
|
||||
Log.d(TAG, "removing player from parent: " + mParent);
|
||||
// Ensure this happens on the main thread (could happen in QSMediaBrowser callback)
|
||||
mForegroundExecutor.execute(() -> mParent.removeMediaPlayer(QSMediaPlayer.this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMediaPlayerPackage() {
|
||||
if (getController() == null) {
|
||||
return mPackageName;
|
||||
}
|
||||
return super.getMediaPlayerPackage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,16 +21,25 @@ import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEX
|
||||
import static com.android.systemui.util.Utils.useQsMediaPlayer;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.app.Notification;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.media.MediaDescription;
|
||||
import android.media.session.MediaSession;
|
||||
import android.metrics.LogMaker;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.service.quicksettings.Tile;
|
||||
import android.util.AttributeSet;
|
||||
@@ -54,6 +63,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
|
||||
import com.android.systemui.dagger.qualifiers.Background;
|
||||
import com.android.systemui.dagger.qualifiers.Main;
|
||||
import com.android.systemui.dump.DumpManager;
|
||||
import com.android.systemui.media.MediaControlPanel;
|
||||
import com.android.systemui.plugins.qs.DetailAdapter;
|
||||
import com.android.systemui.plugins.qs.QSTile;
|
||||
import com.android.systemui.plugins.qs.QSTileView;
|
||||
@@ -90,6 +100,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
|
||||
|
||||
protected final Context mContext;
|
||||
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
|
||||
private final BroadcastDispatcher mBroadcastDispatcher;
|
||||
private String mCachedSpecs = "";
|
||||
protected final View mBrightnessView;
|
||||
private final H mHandler = new H();
|
||||
@@ -123,6 +134,19 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
|
||||
|
||||
private BrightnessMirrorController mBrightnessMirrorController;
|
||||
private View mDivider;
|
||||
private boolean mHasLoadedMediaControls;
|
||||
|
||||
private final BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final String action = intent.getAction();
|
||||
if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
|
||||
if (!mHasLoadedMediaControls) {
|
||||
loadMediaResumptionControls();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Inject
|
||||
public QSPanel(
|
||||
@@ -142,6 +166,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
|
||||
mForegroundExecutor = foregroundExecutor;
|
||||
mBackgroundExecutor = backgroundExecutor;
|
||||
mLocalBluetoothManager = localBluetoothManager;
|
||||
mBroadcastDispatcher = broadcastDispatcher;
|
||||
|
||||
setOrientation(VERTICAL);
|
||||
|
||||
@@ -176,7 +201,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
|
||||
updateResources();
|
||||
|
||||
mBrightnessController = new BrightnessController(getContext(),
|
||||
findViewById(R.id.brightness_slider), broadcastDispatcher);
|
||||
findViewById(R.id.brightness_slider), mBroadcastDispatcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -206,7 +231,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
|
||||
* @param notif
|
||||
* @param key
|
||||
*/
|
||||
public void addMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor,
|
||||
public void addMediaSession(MediaSession.Token token, Drawable icon, int iconColor, int bgColor,
|
||||
View actionsContainer, StatusBarNotification notif, String key) {
|
||||
if (!useQsMediaPlayer(mContext)) {
|
||||
// Shouldn't happen, but just in case
|
||||
@@ -221,7 +246,14 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
|
||||
QSMediaPlayer player = null;
|
||||
String packageName = notif.getPackageName();
|
||||
for (QSMediaPlayer p : mMediaPlayers) {
|
||||
if (p.getMediaSessionToken().equals(token)) {
|
||||
if (p.getKey() == null) {
|
||||
// No notification key = loaded via mediabrowser, so just match on package
|
||||
if (packageName.equals(p.getMediaPlayerPackage())) {
|
||||
Log.d(TAG, "Found matching resume player by package: " + packageName);
|
||||
player = p;
|
||||
break;
|
||||
}
|
||||
} else if (p.getMediaSessionToken().equals(token)) {
|
||||
Log.d(TAG, "Found matching player by token " + packageName);
|
||||
player = p;
|
||||
break;
|
||||
@@ -262,8 +294,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
|
||||
}
|
||||
|
||||
Log.d(TAG, "setting player session");
|
||||
String appName = Notification.Builder.recoverBuilder(getContext(), notif.getNotification())
|
||||
.loadHeaderAppName();
|
||||
player.setMediaSession(token, icon, iconColor, bgColor, actionsContainer,
|
||||
notif.getNotification(), key);
|
||||
notif.getNotification().contentIntent, appName, key);
|
||||
|
||||
if (mMediaPlayers.size() > 0) {
|
||||
((View) mMediaCarousel.getParent()).setVisibility(View.VISIBLE);
|
||||
@@ -293,6 +327,74 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
|
||||
return true;
|
||||
}
|
||||
|
||||
private final QSMediaBrowser.Callback mMediaBrowserCallback = new QSMediaBrowser.Callback() {
|
||||
@Override
|
||||
public void addTrack(MediaDescription desc, ComponentName component,
|
||||
QSMediaBrowser browser) {
|
||||
if (component == null) {
|
||||
Log.e(TAG, "Component cannot be null");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "adding track from browser: " + desc + ", " + component);
|
||||
QSMediaPlayer player = new QSMediaPlayer(mContext, QSPanel.this,
|
||||
null, mForegroundExecutor, mBackgroundExecutor);
|
||||
|
||||
String pkgName = component.getPackageName();
|
||||
|
||||
// Add controls to carousel
|
||||
int playerWidth = (int) getResources().getDimension(R.dimen.qs_media_width);
|
||||
int padding = (int) getResources().getDimension(R.dimen.qs_media_padding);
|
||||
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(playerWidth,
|
||||
LayoutParams.MATCH_PARENT);
|
||||
lp.setMarginStart(padding);
|
||||
lp.setMarginEnd(padding);
|
||||
mMediaCarousel.addView(player.getView(), lp);
|
||||
((View) mMediaCarousel.getParent()).setVisibility(View.VISIBLE);
|
||||
mMediaPlayers.add(player);
|
||||
|
||||
int iconColor = Color.DKGRAY;
|
||||
int bgColor = Color.LTGRAY;
|
||||
|
||||
MediaSession.Token token = browser.getToken();
|
||||
player.setMediaSession(token, desc, iconColor, bgColor, browser.getAppIntent(),
|
||||
pkgName);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Load controls for resuming media, if available
|
||||
*/
|
||||
private void loadMediaResumptionControls() {
|
||||
if (!useQsMediaPlayer(mContext)) {
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "Loading resumption controls");
|
||||
|
||||
// Look up saved components to resume
|
||||
Context userContext = mContext.createContextAsUser(mContext.getUser(), 0);
|
||||
SharedPreferences prefs = userContext.getSharedPreferences(
|
||||
MediaControlPanel.MEDIA_PREFERENCES, Context.MODE_PRIVATE);
|
||||
String listString = prefs.getString(MediaControlPanel.MEDIA_PREFERENCE_KEY, null);
|
||||
if (listString == null) {
|
||||
Log.d(TAG, "No saved media components");
|
||||
return;
|
||||
}
|
||||
|
||||
String[] components = listString.split(QSMediaBrowser.DELIMITER);
|
||||
Log.d(TAG, "components are: " + listString + " count " + components.length);
|
||||
for (int i = 0; i < components.length && i < QSMediaBrowser.MAX_RESUMPTION_CONTROLS; i++) {
|
||||
String[] info = components[i].split("/");
|
||||
String packageName = info[0];
|
||||
String className = info[1];
|
||||
ComponentName component = new ComponentName(packageName, className);
|
||||
QSMediaBrowser browser = new QSMediaBrowser(mContext, mMediaBrowserCallback,
|
||||
component);
|
||||
browser.findRecentMedia();
|
||||
}
|
||||
mHasLoadedMediaControls = true;
|
||||
}
|
||||
|
||||
protected void addDivider() {
|
||||
mDivider = LayoutInflater.from(mContext).inflate(R.layout.qs_divider, this, false);
|
||||
mDivider.setBackgroundColor(Utils.applyAlpha(mDivider.getAlpha(),
|
||||
@@ -343,6 +445,22 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
|
||||
mBrightnessMirrorController.addCallback(this);
|
||||
}
|
||||
mDumpManager.registerDumpable(getDumpableTag(), this);
|
||||
|
||||
if (getClass() == QSPanel.class) {
|
||||
//TODO(ethibodeau) remove class check after media refactor in ag/11059751
|
||||
// Only run this in QSPanel proper, not QQS
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_USER_UNLOCKED);
|
||||
mBroadcastDispatcher.registerReceiver(mUserChangeReceiver, filter, null,
|
||||
UserHandle.ALL);
|
||||
mHasLoadedMediaControls = false;
|
||||
|
||||
UserManager userManager = mContext.getSystemService(UserManager.class);
|
||||
if (userManager.isUserUnlocked(mContext.getUserId())) {
|
||||
// If it's already unlocked (like if dark theme was toggled), we can load now
|
||||
loadMediaResumptionControls();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -358,6 +476,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
|
||||
mBrightnessMirrorController.removeCallback(this);
|
||||
}
|
||||
mDumpManager.unregisterDumpable(getDumpableTag());
|
||||
mBroadcastDispatcher.unregisterReceiver(mUserChangeReceiver);
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ package com.android.systemui.qs;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.media.session.MediaController;
|
||||
import android.media.session.MediaSession;
|
||||
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 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) {
|
||||
// Only update if this is a different session and currently playing
|
||||
String oldPackage = "";
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.annotation.Nullable;
|
||||
import android.app.Notification;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.media.MediaMetadata;
|
||||
import android.media.session.MediaController;
|
||||
import android.media.session.MediaSession;
|
||||
@@ -187,8 +188,9 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi
|
||||
com.android.systemui.R.id.quick_qs_panel);
|
||||
StatusBarNotification sbn = mRow.getEntry().getSbn();
|
||||
Notification notif = sbn.getNotification();
|
||||
Drawable iconDrawable = notif.getSmallIcon().loadDrawable(mContext);
|
||||
panel.getMediaPlayer().setMediaSession(token,
|
||||
notif.getSmallIcon(),
|
||||
iconDrawable,
|
||||
tintColor,
|
||||
mBackgroundColor,
|
||||
mActions,
|
||||
@@ -198,7 +200,7 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi
|
||||
QSPanel bigPanel = ctrl.getNotificationShadeView().findViewById(
|
||||
com.android.systemui.R.id.quick_settings_panel);
|
||||
bigPanel.addMediaSession(token,
|
||||
notif.getSmallIcon(),
|
||||
iconDrawable,
|
||||
tintColor,
|
||||
mBackgroundColor,
|
||||
mActions,
|
||||
|
||||
Reference in New Issue
Block a user