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

Commit 4bef731e authored by Beth Thibodeau's avatar Beth Thibodeau Committed by Android (Google) Code Review
Browse files

Merge "Dynamic output switcher chip and bug fixes"

parents eda96cdf 69b1dfd9
Loading
Loading
Loading
Loading
+64 −24
Original line number Diff line number Diff line
@@ -47,7 +47,9 @@ import android.widget.TextView;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;

import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.MediaOutputSliceConstants;
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
@@ -61,10 +63,13 @@ public class QSMediaPlayer {

    private Context mContext;
    private LinearLayout mMediaNotifView;
    private View mSeamless;
    private MediaSession.Token mToken;
    private MediaController mController;
    private int mWidth;
    private int mHeight;
    private int mForegroundColor;
    private int mBackgroundColor;

    /**
     *
@@ -93,15 +98,17 @@ public class QSMediaPlayer {
     * @param iconColor foreground color (for text, icons)
     * @param bgColor background color
     * @param actionsContainer a LinearLayout containing the media action buttons
     * @param notif
     * @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) {
            View actionsContainer, Notification notif, MediaDevice device) {
        Log.d(TAG, "got media session: " + token);
        mToken = token;
        mForegroundColor = iconColor;
        mBackgroundColor = bgColor;
        mController = new MediaController(mContext, token);
        MediaMetadata mMediaMetadata = mController.getMetadata();

        if (mMediaMetadata == null) {
            Log.e(TAG, "Media metadata was null");
            return;
@@ -123,9 +130,6 @@ public class QSMediaPlayer {
        headerView.removeAllViews();
        headerView.addView(result);

        View seamless = headerView.findViewById(com.android.internal.R.id.media_seamless);
        seamless.setVisibility(View.VISIBLE);

        // App icon
        ImageView appIcon = headerView.findViewById(com.android.internal.R.id.icon);
        Drawable iconDrawable = icon.loadDrawable(mContext);
@@ -168,23 +172,11 @@ public class QSMediaPlayer {
        }

        // Transfer chip
        View transferBackgroundView = headerView.findViewById(
                com.android.internal.R.id.media_seamless);
        LinearLayout viewLayout = (LinearLayout) transferBackgroundView;
        RippleDrawable bkgDrawable = (RippleDrawable) viewLayout.getBackground();
        GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0);
        rect.setStroke(2, iconColor);
        rect.setColor(bgColor);
        ImageView transferIcon = headerView.findViewById(
                com.android.internal.R.id.media_seamless_image);
        transferIcon.setBackgroundColor(bgColor);
        transferIcon.setImageTintList(ColorStateList.valueOf(iconColor));
        TextView transferText = headerView.findViewById(
                com.android.internal.R.id.media_seamless_text);
        transferText.setTextColor(iconColor);

        mSeamless = headerView.findViewById(com.android.internal.R.id.media_seamless);
        mSeamless.setVisibility(View.VISIBLE);
        updateChip(device);
        ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
        transferBackgroundView.setOnClickListener(v -> {
        mSeamless.setOnClickListener(v -> {
            final Intent intent = new Intent()
                    .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT);
            mActivityStarter.startActivity(intent, false, true /* dismissShade */,
@@ -219,10 +211,13 @@ public class QSMediaPlayer {
                com.android.internal.R.id.action3,
                com.android.internal.R.id.action4
        };
        for (int i = 0; i < parentActionsLayout.getChildCount() && i < actionIds.length; i++) {

        int i = 0;
        for (; i < parentActionsLayout.getChildCount() && i < actionIds.length; i++) {
            ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
            ImageButton thatBtn = parentActionsLayout.findViewById(notifActionIds[i]);
            if (thatBtn == null || thatBtn.getDrawable() == null) {
            if (thatBtn == null || thatBtn.getDrawable() == null
                    || thatBtn.getVisibility() != View.VISIBLE) {
                thisBtn.setVisibility(View.GONE);
                continue;
            }
@@ -235,6 +230,13 @@ public class QSMediaPlayer {
                thatBtn.performClick();
            });
        }

        // Hide any unused buttons
        for (; i < actionIds.length; i++) {
            ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
            thisBtn.setVisibility(View.GONE);
            Log.d(TAG, "hid a button");
        }
    }

    public MediaSession.Token getMediaSessionToken() {
@@ -284,6 +286,7 @@ public class QSMediaPlayer {
            mMediaNotifView.setBackground(roundedDrawable);
        } else {
            Log.e(TAG, "No album art available");
            mMediaNotifView.setBackground(null);
        }
    }

@@ -303,4 +306,41 @@ public class QSMediaPlayer {

        return cropped;
    }

    protected void updateChip(MediaDevice device) {
        if (mSeamless == null) {
            return;
        }
        ColorStateList fgTintList = ColorStateList.valueOf(mForegroundColor);

        // Update the outline color
        LinearLayout viewLayout = (LinearLayout) mSeamless;
        RippleDrawable bkgDrawable = (RippleDrawable) viewLayout.getBackground();
        GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0);
        rect.setStroke(2, mForegroundColor);
        rect.setColor(mBackgroundColor);

        ImageView iconView = mSeamless.findViewById(com.android.internal.R.id.media_seamless_image);
        TextView deviceName = mSeamless.findViewById(com.android.internal.R.id.media_seamless_text);
        deviceName.setTextColor(fgTintList);

        if (device != null) {
            Drawable icon = device.getIcon();
            iconView.setVisibility(View.VISIBLE);
            iconView.setImageTintList(fgTintList);

            if (icon instanceof AdaptiveIcon) {
                AdaptiveIcon aIcon = (AdaptiveIcon) icon;
                aIcon.setBackgroundColor(mBackgroundColor);
                iconView.setImageDrawable(aIcon);
            } else {
                iconView.setImageDrawable(icon);
            }
            deviceName.setText(device.getName());
        } else {
            // Reset to default
            iconView.setVisibility(View.GONE);
            deviceName.setText(com.android.internal.R.string.ext_media_seamless_action);
        }
    }
}
+47 −1
Original line number Diff line number Diff line
@@ -46,6 +46,8 @@ import android.widget.LinearLayout;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.Utils;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.systemui.Dependency;
import com.android.systemui.DumpController;
import com.android.systemui.Dumpable;
@@ -70,6 +72,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.inject.Inject;
import javax.inject.Named;
@@ -92,6 +95,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne

    private final LinearLayout mMediaCarousel;
    private final ArrayList<QSMediaPlayer> mMediaPlayers = new ArrayList<>();
    private LocalMediaManager mLocalMediaManager;
    private MediaDevice mDevice;

    protected boolean mExpanded;
    protected boolean mListening;
@@ -117,6 +122,31 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
    private final PluginManager mPluginManager;
    private NPVPluginManager mNPVPluginManager;

    private final LocalMediaManager.DeviceCallback mDeviceCallback =
            new LocalMediaManager.DeviceCallback() {
        @Override
        public void onDeviceListUpdate(List<MediaDevice> devices) {
            MediaDevice currentDevice = mLocalMediaManager.getCurrentConnectedDevice();
            // Check because this can be called several times while changing devices
            if (mDevice == null || !mDevice.equals(currentDevice)) {
                mDevice = currentDevice;
                for (QSMediaPlayer p : mMediaPlayers) {
                    p.updateChip(mDevice);
                }
            }
        }

        @Override
        public void onSelectedDeviceStateChanged(MediaDevice device, int state) {
            if (mDevice == null || !mDevice.equals(device)) {
                mDevice = device;
                for (QSMediaPlayer p : mMediaPlayers) {
                    p.updateChip(mDevice);
                }
            }
        }
    };

    public QSPanel(Context context) {
        this(context, null);
    }
@@ -208,6 +238,11 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
            Log.e(TAG, "Tried to add media session without player!");
            return;
        }
        if (token == null) {
            Log.e(TAG, "Media session token was null!");
            return;
        }

        QSMediaPlayer player = null;
        String packageName = notif.getPackageName();
        for (QSMediaPlayer p : mMediaPlayers) {
@@ -250,10 +285,17 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne

        Log.d(TAG, "setting player session");
        player.setMediaSession(token, icon, iconColor, bgColor, actionsContainer,
                notif.getNotification());
                notif.getNotification(), mDevice);

        if (mMediaPlayers.size() > 0) {
            ((View) mMediaCarousel.getParent()).setVisibility(View.VISIBLE);

            // Set up listener for device changes
            // TODO: integrate with MediaTransferManager?
            mLocalMediaManager = new LocalMediaManager(mContext, null, null);
            mLocalMediaManager.startScan();
            mDevice = mLocalMediaManager.getCurrentConnectedDevice();
            mLocalMediaManager.registerCallback(mDeviceCallback);
        }
    }

@@ -326,6 +368,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
            mBrightnessMirrorController.removeCallback(this);
        }
        if (mDumpController != null) mDumpController.unregisterDumpable(this);
        if (mLocalMediaManager != null) {
            mLocalMediaManager.stopScan();
            mLocalMediaManager.unregisterCallback(mDeviceCallback);
        }
        super.onDetachedFromWindow();
    }

+36 −19
Original line number Diff line number Diff line
@@ -76,9 +76,11 @@ public class QuickQSMediaPlayer {
     * @param iconColor foreground color (for text, icons)
     * @param bgColor background color
     * @param actionsContainer a LinearLayout containing the media action buttons
     * @param actionsToShow indices of which actions to display in the mini player
     *                      (max 3: Notification.MediaStyle.MAX_MEDIA_BUTTONS_IN_COMPACT)
     */
    public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor,
            View actionsContainer) {
            View actionsContainer, int[] actionsToShow) {
        Log.d(TAG, "Setting media session: " + token);
        mToken = token;
        mController = new MediaController(mContext, token);
@@ -110,20 +112,29 @@ public class QuickQSMediaPlayer {
        titleText.setText(songName);
        titleText.setTextColor(iconColor);

        // Action buttons
        LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
        // Buttons we can display
        final int[] actionIds = {R.id.action0, R.id.action1, R.id.action2};

        // TODO some apps choose different buttons to show in compact mode
        // Existing buttons in the notification
        LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
        final int[] notifActionIds = {
                com.android.internal.R.id.action0,
                com.android.internal.R.id.action1,
                com.android.internal.R.id.action2,
                com.android.internal.R.id.action3
                com.android.internal.R.id.action3,
                com.android.internal.R.id.action4
        };
        for (int i = 0; i < parentActionsLayout.getChildCount() && i < actionIds.length; i++) {

        int i = 0;
        if (actionsToShow != null) {
            int maxButtons = Math.min(actionsToShow.length, parentActionsLayout.getChildCount());
            maxButtons = Math.min(maxButtons, actionIds.length);
            for (; i < maxButtons; i++) {
                ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
            ImageButton thatBtn = parentActionsLayout.findViewById(notifActionIds[i]);
            if (thatBtn == null || thatBtn.getDrawable() == null) {
                int thatId = notifActionIds[actionsToShow[i]];
                ImageButton thatBtn = parentActionsLayout.findViewById(thatId);
                if (thatBtn == null || thatBtn.getDrawable() == null
                        || thatBtn.getVisibility() != View.VISIBLE) {
                    thisBtn.setVisibility(View.GONE);
                    continue;
                }
@@ -131,14 +142,19 @@ public class QuickQSMediaPlayer {
                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 < actionIds.length; i++) {
            ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]);
            thisBtn.setVisibility(View.GONE);
        }
    }

    public MediaSession.Token getMediaSessionToken() {
        return mToken;
    }
@@ -186,6 +202,7 @@ public class QuickQSMediaPlayer {
            mMediaNotifView.setBackground(roundedDrawable);
        } else {
            Log.e(TAG, "No album art available");
            mMediaNotifView.setBackground(null);
        }
    }

+104 −14
Original line number Diff line number Diff line
@@ -19,10 +19,12 @@ package com.android.systemui.statusbar;
import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.RippleDrawable;
import android.service.notification.StatusBarNotification;
import android.util.FeatureFlagUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
@@ -30,19 +32,29 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.android.internal.R;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.MediaOutputSliceConstants;
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.systemui.Dependency;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;

import java.util.ArrayList;
import java.util.List;

/**
 * Class for handling MediaTransfer state over a set of notifications.
 */
public class MediaTransferManager {
    private final Context mContext;
    private final ActivityStarter mActivityStarter;
    private MediaDevice mDevice;
    private List<View> mViews = new ArrayList<>();
    private LocalMediaManager mLocalMediaManager;

    private static final String TAG = "MediaTransferManager";

    private final View.OnClickListener mOnClickHandler = new View.OnClickListener() {
        @Override
@@ -70,9 +82,50 @@ public class MediaTransferManager {
        }
    };

    private final LocalMediaManager.DeviceCallback mMediaDeviceCallback =
            new LocalMediaManager.DeviceCallback() {
        @Override
        public void onDeviceListUpdate(List<MediaDevice> devices) {
            MediaDevice currentDevice = mLocalMediaManager.getCurrentConnectedDevice();
            // Check because this can be called several times while changing devices
            if (mDevice == null || !mDevice.equals(currentDevice)) {
                mDevice = currentDevice;
                updateAllChips();
            }
        }

        @Override
        public void onSelectedDeviceStateChanged(MediaDevice device, int state) {
            if (mDevice == null || !mDevice.equals(device)) {
                mDevice = device;
                updateAllChips();
            }
        }
    };

    public MediaTransferManager(Context context) {
        mContext = context;
        mActivityStarter = Dependency.get(ActivityStarter.class);
        mLocalMediaManager = new LocalMediaManager(mContext, null, null);
    }

    /**
     * Mark a view as removed. If no views remain the media device listener will be unregistered.
     * @param root
     */
    public void setRemoved(View root) {
        if (!FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SEAMLESS_TRANSFER)
                || mLocalMediaManager == null || root == null) {
            return;
        }
        View view = root.findViewById(com.android.internal.R.id.media_seamless);
        if (mViews.remove(view)) {
            if (mViews.size() == 0) {
                mLocalMediaManager.unregisterCallback(mMediaDeviceCallback);
            }
        } else {
            Log.e(TAG, "Tried to remove unknown view " + view);
        }
    }

    private ExpandableNotificationRow getRowForParent(ViewParent parent) {
@@ -92,7 +145,8 @@ public class MediaTransferManager {
     * @param entry The entry of MediaTransfer action button.
     */
    public void applyMediaTransferView(ViewGroup root, NotificationEntry entry) {
        if (!FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SEAMLESS_TRANSFER)) {
        if (!FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SEAMLESS_TRANSFER)
                || mLocalMediaManager == null || root == null) {
            return;
        }

@@ -103,23 +157,59 @@ public class MediaTransferManager {

        view.setVisibility(View.VISIBLE);
        view.setOnClickListener(mOnClickHandler);
        if (!mViews.contains(view)) {
            mViews.add(view);
            if (mViews.size() == 1) {
                mLocalMediaManager.registerCallback(mMediaDeviceCallback);
            }
        }

        // Initial update
        mLocalMediaManager.startScan();
        mDevice = mLocalMediaManager.getCurrentConnectedDevice();
        updateChip(view);
    }

    private void updateAllChips() {
        for (View view : mViews) {
            updateChip(view);
        }
    }

    private void updateChip(View view) {
        ExpandableNotificationRow enr = getRowForParent(view.getParent());
        int color = enr.getNotificationHeader().getOriginalIconColor();
        ColorStateList tintList = ColorStateList.valueOf(color);
        int fgColor = enr.getNotificationHeader().getOriginalIconColor();
        ColorStateList fgTintList = ColorStateList.valueOf(fgColor);
        int bgColor = enr.getCurrentBackgroundTint();

        // Update the outline color
        // Update outline color
        LinearLayout viewLayout = (LinearLayout) view;
        RippleDrawable bkgDrawable = (RippleDrawable) viewLayout.getBackground();
        GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0);
        rect.setStroke(2, color);

        // Update the image color
        ImageView image = view.findViewById(R.id.media_seamless_image);
        image.setImageTintList(tintList);

        // Update the text color
        TextView text = view.findViewById(R.id.media_seamless_text);
        text.setTextColor(tintList);
        rect.setStroke(2, fgColor);
        rect.setColor(bgColor);

        ImageView iconView = view.findViewById(com.android.internal.R.id.media_seamless_image);
        TextView deviceName = view.findViewById(com.android.internal.R.id.media_seamless_text);
        deviceName.setTextColor(fgTintList);

        if (mDevice != null) {
            Drawable icon = mDevice.getIcon();
            iconView.setVisibility(View.VISIBLE);
            iconView.setImageTintList(fgTintList);

            if (icon instanceof AdaptiveIcon) {
                AdaptiveIcon aIcon = (AdaptiveIcon) icon;
                aIcon.setBackgroundColor(bgColor);
                iconView.setImageDrawable(aIcon);
            } else {
                iconView.setImageDrawable(icon);
            }
            deviceName.setText(mDevice.getName());
        } else {
            // Reset to default
            iconView.setVisibility(View.GONE);
            deviceName.setText(com.android.internal.R.string.ext_media_seamless_action);
        }
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -1546,9 +1546,11 @@ public class NotificationContentView extends FrameLayout {
        }
        if (mExpandedWrapper != null) {
            mExpandedWrapper.setRemoved();
            mMediaTransferManager.setRemoved(mExpandedChild);
        }
        if (mContractedWrapper != null) {
            mContractedWrapper.setRemoved();
            mMediaTransferManager.setRemoved(mContractedChild);
        }
        if (mHeadsUpWrapper != null) {
            mHeadsUpWrapper.setRemoved();
Loading