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

Commit fd51bb2d authored by Beth Thibodeau's avatar Beth Thibodeau
Browse files

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
parent eed139a4
Loading
Loading
Loading
Loading
+41 −0
Original line number Diff line number Diff line
<?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>
+1 −0
Original line number Diff line number Diff line
@@ -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>
+112 −8
Original line number Diff line number Diff line
@@ -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);
        }
    }

+22 −1
Original line number Diff line number Diff line
@@ -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(),
+84 −6
Original line number Diff line number Diff line
@@ -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);
        }
    }

Loading