Loading packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java +64 −24 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; /** * Loading Loading @@ -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; Loading @@ -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); Loading Loading @@ -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 */, Loading Loading @@ -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; } Loading @@ -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() { Loading Loading @@ -284,6 +286,7 @@ public class QSMediaPlayer { mMediaNotifView.setBackground(roundedDrawable); } else { Log.e(TAG, "No album art available"); mMediaNotifView.setBackground(null); } } Loading @@ -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); } } } packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +47 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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); } Loading Loading @@ -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) { Loading Loading @@ -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); } } Loading Loading @@ -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(); } Loading packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java +36 −19 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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; } Loading @@ -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; } Loading Loading @@ -186,6 +202,7 @@ public class QuickQSMediaPlayer { mMediaNotifView.setBackground(roundedDrawable); } else { Log.e(TAG, "No album art available"); mMediaNotifView.setBackground(null); } } Loading packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java +104 −14 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading Loading @@ -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) { Loading @@ -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; } Loading @@ -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); } } } packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +2 −0 Original line number Diff line number Diff line Loading @@ -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 Loading
packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java +64 −24 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; /** * Loading Loading @@ -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; Loading @@ -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); Loading Loading @@ -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 */, Loading Loading @@ -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; } Loading @@ -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() { Loading Loading @@ -284,6 +286,7 @@ public class QSMediaPlayer { mMediaNotifView.setBackground(roundedDrawable); } else { Log.e(TAG, "No album art available"); mMediaNotifView.setBackground(null); } } Loading @@ -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); } } }
packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +47 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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); } Loading Loading @@ -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) { Loading Loading @@ -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); } } Loading Loading @@ -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(); } Loading
packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java +36 −19 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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; } Loading @@ -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; } Loading Loading @@ -186,6 +202,7 @@ public class QuickQSMediaPlayer { mMediaNotifView.setBackground(roundedDrawable); } else { Log.e(TAG, "No album art available"); mMediaNotifView.setBackground(null); } } Loading
packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java +104 −14 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading Loading @@ -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) { Loading @@ -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; } Loading @@ -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); } } }
packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +2 −0 Original line number Diff line number Diff line Loading @@ -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