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:
Beth Thibodeau
2019-11-25 13:57:06 -05:00
parent eed139a407
commit fd51bb2d5c
6 changed files with 265 additions and 19 deletions

View 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>

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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(),

View File

@@ -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);
}
}

View File

@@ -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());
}