Handle destroyed sessions and fix color issues
When a media session is destroyed, - Replace buttons with a single "restart" button that attempts to restart the app (using media key event if we found a receiver for it, or the session activity if not) - For the carousel, give user the option to remove the card on long-press Also fixes color issues when transitioning between tracks Fixes: 143235163 Fixes: 144033638 Test: manual Change-Id: Ie859aeb2fabfb1fc3eecf12fc1c19bb4cfa792d3
This commit is contained in:
41
packages/SystemUI/res/layout/qs_media_panel_options.xml
Normal file
41
packages/SystemUI/res/layout/qs_media_panel_options.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2019 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License
|
||||
-->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/qs_media_controls_options"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center"
|
||||
android:layout_gravity="center"
|
||||
android:padding="10dp"
|
||||
>
|
||||
<ImageButton
|
||||
android:id="@+id/remove"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@android:drawable/ic_menu_delete"
|
||||
android:padding="8dp"
|
||||
/>
|
||||
<ImageButton
|
||||
android:id="@+id/cancel"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@android:drawable/ic_menu_revert"
|
||||
android:padding="8dp"
|
||||
/>
|
||||
</LinearLayout>
|
||||
@@ -1179,4 +1179,5 @@
|
||||
<dimen name="qs_media_height">150dp</dimen>
|
||||
<dimen name="qs_media_width">350dp</dimen>
|
||||
<dimen name="qs_media_padding">8dp</dimen>
|
||||
<dimen name="qs_media_corner_radius">10dp</dimen>
|
||||
</resources>
|
||||
|
||||
@@ -18,8 +18,11 @@ package com.android.systemui.qs;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
@@ -35,6 +38,7 @@ import android.media.session.MediaSession;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -54,6 +58,8 @@ import com.android.systemui.Dependency;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.plugins.ActivityStarter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Single media player for carousel in QSPanel
|
||||
*/
|
||||
@@ -70,6 +76,83 @@ public class QSMediaPlayer {
|
||||
private int mHeight;
|
||||
private int mForegroundColor;
|
||||
private int mBackgroundColor;
|
||||
private ComponentName mRecvComponent;
|
||||
private QSPanel mParent;
|
||||
|
||||
private MediaController.Callback mSessionCallback = new MediaController.Callback() {
|
||||
@Override
|
||||
public void onSessionDestroyed() {
|
||||
Log.d(TAG, "session destroyed");
|
||||
mController.unregisterCallback(mSessionCallback);
|
||||
|
||||
// Hide all the old buttons
|
||||
final int[] actionIds = {
|
||||
R.id.action0,
|
||||
R.id.action1,
|
||||
R.id.action2,
|
||||
R.id.action3,
|
||||
R.id.action4
|
||||
};
|
||||
for (int i = 0; i < actionIds.length; i++) {
|
||||
ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
|
||||
if (thisBtn != null) {
|
||||
thisBtn.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a restart button
|
||||
ImageButton btn = mMediaNotifView.findViewById(actionIds[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 {
|
||||
Log.d(TAG, "No receiver to restart");
|
||||
// If we don't have a receiver, try relaunching the activity instead
|
||||
try {
|
||||
mController.getSessionActivity().send();
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Log.e(TAG, "Pending intent was canceled");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_replay));
|
||||
btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
|
||||
btn.setVisibility(View.VISIBLE);
|
||||
|
||||
// Add long-click option to remove the player
|
||||
ViewGroup mMediaCarousel = (ViewGroup) mMediaNotifView.getParent();
|
||||
mMediaNotifView.setOnLongClickListener(v -> {
|
||||
// Replace player view with delete/cancel view
|
||||
v.setVisibility(View.GONE);
|
||||
|
||||
View options = LayoutInflater.from(mContext).inflate(
|
||||
R.layout.qs_media_panel_options, null, false);
|
||||
ImageButton btnDelete = options.findViewById(R.id.remove);
|
||||
btnDelete.setOnClickListener(b -> {
|
||||
mMediaCarousel.removeView(options);
|
||||
mParent.removeMediaPlayer(QSMediaPlayer.this);
|
||||
});
|
||||
ImageButton btnCancel = options.findViewById(R.id.cancel);
|
||||
btnCancel.setOnClickListener(b -> {
|
||||
mMediaCarousel.removeView(options);
|
||||
v.setVisibility(View.VISIBLE);
|
||||
});
|
||||
|
||||
int pos = mMediaCarousel.indexOfChild(v);
|
||||
mMediaCarousel.addView(options, pos, v.getLayoutParams());
|
||||
return true; // consumed click
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -92,7 +175,8 @@ public class QSMediaPlayer {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Create or update the player view for the given media session
|
||||
* @param parent the parent QSPanel
|
||||
* @param token token for this media session
|
||||
* @param icon app notification icon
|
||||
* @param iconColor foreground color (for text, icons)
|
||||
@@ -101,13 +185,30 @@ public class QSMediaPlayer {
|
||||
* @param notif reference to original notification
|
||||
* @param device current playback device
|
||||
*/
|
||||
public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor,
|
||||
View actionsContainer, Notification notif, MediaDevice device) {
|
||||
Log.d(TAG, "got media session: " + token);
|
||||
public void setMediaSession(QSPanel parent, MediaSession.Token token, Icon icon, int iconColor,
|
||||
int bgColor, View actionsContainer, Notification notif, MediaDevice device) {
|
||||
mParent = parent;
|
||||
mToken = token;
|
||||
mForegroundColor = iconColor;
|
||||
mBackgroundColor = bgColor;
|
||||
mController = new MediaController(mContext, token);
|
||||
|
||||
// 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(notif.contentIntent.getCreatorPackage())) {
|
||||
Log.d(TAG, "Found receiver for package: " + inf);
|
||||
mRecvComponent = inf.getComponentInfo().getComponentName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reset in case we had previously restarted the stream
|
||||
mMediaNotifView.setOnLongClickListener(null);
|
||||
mController.registerCallback(mSessionCallback);
|
||||
MediaMetadata mMediaMetadata = mController.getMetadata();
|
||||
if (mMediaMetadata == null) {
|
||||
Log.e(TAG, "Media metadata was null");
|
||||
@@ -235,7 +336,6 @@ public class QSMediaPlayer {
|
||||
for (; i < actionIds.length; i++) {
|
||||
ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
|
||||
thisBtn.setVisibility(View.GONE);
|
||||
Log.d(TAG, "hid a button");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,8 +366,9 @@ public class QSMediaPlayer {
|
||||
|
||||
private void addAlbumArtBackground(MediaMetadata metadata, int bgColor, int width, int height) {
|
||||
Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
|
||||
float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
|
||||
if (albumArt != null) {
|
||||
|
||||
Log.d(TAG, "updating album art");
|
||||
Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true);
|
||||
Bitmap scaled = scaleBitmap(original, width, height);
|
||||
Canvas canvas = new Canvas(scaled);
|
||||
@@ -281,12 +382,15 @@ public class QSMediaPlayer {
|
||||
|
||||
RoundedBitmapDrawable roundedDrawable = RoundedBitmapDrawableFactory.create(
|
||||
mContext.getResources(), scaled);
|
||||
roundedDrawable.setCornerRadius(20);
|
||||
roundedDrawable.setCornerRadius(radius);
|
||||
|
||||
mMediaNotifView.setBackground(roundedDrawable);
|
||||
} else {
|
||||
Log.e(TAG, "No album art available");
|
||||
mMediaNotifView.setBackground(null);
|
||||
GradientDrawable rect = new GradientDrawable();
|
||||
rect.setCornerRadius(radius);
|
||||
rect.setColor(bgColor);
|
||||
mMediaNotifView.setBackground(rect);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -284,7 +284,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
|
||||
}
|
||||
|
||||
Log.d(TAG, "setting player session");
|
||||
player.setMediaSession(token, icon, iconColor, bgColor, actionsContainer,
|
||||
player.setMediaSession(this, token, icon, iconColor, bgColor, actionsContainer,
|
||||
notif.getNotification(), mDevice);
|
||||
|
||||
if (mMediaPlayers.size() > 0) {
|
||||
@@ -303,6 +303,27 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
|
||||
return mMediaCarousel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the media player from the carousel
|
||||
* @param player Player to remove
|
||||
* @return true if removed, false if player was not found
|
||||
*/
|
||||
protected boolean removeMediaPlayer(QSMediaPlayer player) {
|
||||
// Remove from list
|
||||
if (!mMediaPlayers.remove(player)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we need to collapse the carousel now
|
||||
mMediaCarousel.removeView(player.getView());
|
||||
if (mMediaPlayers.size() == 0) {
|
||||
((View) mMediaCarousel.getParent()).setVisibility(View.GONE);
|
||||
mLocalMediaManager.stopScan();
|
||||
mLocalMediaManager.unregisterCallback(mDeviceCallback);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void addDivider() {
|
||||
mDivider = LayoutInflater.from(mContext).inflate(R.layout.qs_divider, this, false);
|
||||
mDivider.setBackgroundColor(Utils.applyAlpha(mDivider.getAlpha(),
|
||||
|
||||
@@ -16,19 +16,27 @@
|
||||
|
||||
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.content.pm.ResolveInfo;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.media.MediaMetadata;
|
||||
import android.media.session.MediaController;
|
||||
import android.media.session.MediaSession;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -42,6 +50,8 @@ import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
|
||||
|
||||
import com.android.systemui.R;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* QQS mini media player
|
||||
*/
|
||||
@@ -53,6 +63,54 @@ public class QuickQSMediaPlayer {
|
||||
private LinearLayout mMediaNotifView;
|
||||
private MediaSession.Token mToken;
|
||||
private MediaController mController;
|
||||
private int mBackgroundColor;
|
||||
private int mForegroundColor;
|
||||
private ComponentName mRecvComponent;
|
||||
|
||||
private MediaController.Callback mSessionCallback = new MediaController.Callback() {
|
||||
@Override
|
||||
public void onSessionDestroyed() {
|
||||
Log.d(TAG, "session destroyed");
|
||||
mController.unregisterCallback(mSessionCallback);
|
||||
|
||||
// Hide all the old buttons
|
||||
final int[] actionIds = {R.id.action0, R.id.action1, R.id.action2};
|
||||
for (int i = 0; i < actionIds.length; i++) {
|
||||
ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
|
||||
if (thisBtn != null) {
|
||||
thisBtn.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a restart button
|
||||
ImageButton btn = mMediaNotifView.findViewById(actionIds[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 {
|
||||
Log.d(TAG, "No receiver to restart");
|
||||
// If we don't have a receiver, try relaunching the activity instead
|
||||
try {
|
||||
mController.getSessionActivity().send();
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Log.e(TAG, "Pending intent was canceled");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_replay));
|
||||
btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
|
||||
btn.setVisibility(View.VISIBLE);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -83,34 +141,50 @@ public class QuickQSMediaPlayer {
|
||||
View actionsContainer, int[] actionsToShow) {
|
||||
Log.d(TAG, "Setting media session: " + token);
|
||||
mToken = token;
|
||||
mForegroundColor = iconColor;
|
||||
mBackgroundColor = bgColor;
|
||||
mController = new MediaController(mContext, token);
|
||||
MediaMetadata mMediaMetadata = 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())) {
|
||||
Log.d(TAG, "Found receiver for package: " + inf);
|
||||
mRecvComponent = inf.getComponentInfo().getComponentName();
|
||||
}
|
||||
}
|
||||
}
|
||||
mController.registerCallback(mSessionCallback);
|
||||
|
||||
if (mMediaMetadata == null) {
|
||||
Log.e(TAG, "Media metadata was null");
|
||||
return;
|
||||
}
|
||||
|
||||
// Album art
|
||||
addAlbumArtBackground(mMediaMetadata, bgColor);
|
||||
addAlbumArtBackground(mMediaMetadata, mBackgroundColor);
|
||||
|
||||
// App icon
|
||||
ImageView appIcon = mMediaNotifView.findViewById(R.id.icon);
|
||||
Drawable iconDrawable = icon.loadDrawable(mContext);
|
||||
iconDrawable.setTint(iconColor);
|
||||
iconDrawable.setTint(mForegroundColor);
|
||||
appIcon.setImageDrawable(iconDrawable);
|
||||
|
||||
// Artist name
|
||||
TextView appText = mMediaNotifView.findViewById(R.id.header_title);
|
||||
String artistName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
|
||||
appText.setText(artistName);
|
||||
appText.setTextColor(iconColor);
|
||||
appText.setTextColor(mForegroundColor);
|
||||
|
||||
// Song name
|
||||
TextView titleText = mMediaNotifView.findViewById(R.id.header_text);
|
||||
String songName = mMediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
|
||||
titleText.setText(songName);
|
||||
titleText.setTextColor(iconColor);
|
||||
titleText.setTextColor(mForegroundColor);
|
||||
|
||||
// Buttons we can display
|
||||
final int[] actionIds = {R.id.action0, R.id.action1, R.id.action2};
|
||||
@@ -178,6 +252,7 @@ public class QuickQSMediaPlayer {
|
||||
|
||||
private void addAlbumArtBackground(MediaMetadata metadata, int bgColor) {
|
||||
Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
|
||||
float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
|
||||
if (albumArt != null) {
|
||||
Rect bounds = new Rect();
|
||||
mMediaNotifView.getBoundsOnScreen(bounds);
|
||||
@@ -197,12 +272,15 @@ public class QuickQSMediaPlayer {
|
||||
|
||||
RoundedBitmapDrawable roundedDrawable = RoundedBitmapDrawableFactory.create(
|
||||
mContext.getResources(), scaled);
|
||||
roundedDrawable.setCornerRadius(20);
|
||||
roundedDrawable.setCornerRadius(radius);
|
||||
|
||||
mMediaNotifView.setBackground(roundedDrawable);
|
||||
} else {
|
||||
Log.e(TAG, "No album art available");
|
||||
mMediaNotifView.setBackground(null);
|
||||
GradientDrawable rect = new GradientDrawable();
|
||||
rect.setCornerRadius(radius);
|
||||
rect.setColor(bgColor);
|
||||
mMediaNotifView.setBackground(rect);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -179,21 +179,22 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi
|
||||
if (Utils.useQsMediaPlayer(mContext)) {
|
||||
final int[] compactActions = mRow.getEntry().getSbn().getNotification().extras
|
||||
.getIntArray(Notification.EXTRA_COMPACT_ACTIONS);
|
||||
int tintColor = getNotificationHeader().getOriginalIconColor();
|
||||
StatusBarWindowController ctrl = Dependency.get(StatusBarWindowController.class);
|
||||
QuickQSPanel panel = ctrl.getStatusBarView().findViewById(
|
||||
com.android.systemui.R.id.quick_qs_panel);
|
||||
panel.getMediaPlayer().setMediaSession(token,
|
||||
mRow.getEntry().getSbn().getNotification().getSmallIcon(),
|
||||
getNotificationHeader().getOriginalIconColor(),
|
||||
mRow.getCurrentBackgroundTint(),
|
||||
tintColor,
|
||||
mBackgroundColor,
|
||||
mActions,
|
||||
compactActions);
|
||||
QSPanel bigPanel = ctrl.getStatusBarView().findViewById(
|
||||
com.android.systemui.R.id.quick_settings_panel);
|
||||
bigPanel.addMediaSession(token,
|
||||
mRow.getEntry().getSbn().getNotification().getSmallIcon(),
|
||||
getNotificationHeader().getOriginalIconColor(),
|
||||
mRow.getCurrentBackgroundTint(),
|
||||
tintColor,
|
||||
mBackgroundColor,
|
||||
mActions,
|
||||
mRow.getEntry().getSbn());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user