diff options
| author | 2019-11-08 15:39:25 -0500 | |
|---|---|---|
| committer | 2019-11-11 17:28:11 -0500 | |
| commit | 69b1dfd940c5eff46547ea314a67f2e2dcf871bd (patch) | |
| tree | a9f46cee852783b7b57f90cc3f2896fd25e2be22 | |
| parent | 379f81b2e98a38c445b8a259b8ce059fc4c40ba9 (diff) | |
Dynamic output switcher chip and bug fixes
- Update the output switcher view to show the current device name and icon
- Show the requested buttons in mini player view
- Handle missing art or different number of buttons when changing tracks
- Catch NPEs for missing media metadata
Fixes: 143235162
Test: manual
Change-Id: I6fec4955a965a2011b6a02cb22f14a291afd1a18
6 files changed, 257 insertions, 59 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java index 694964075032..1a4c327b4405 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java @@ -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); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index cac90257cd43..5e98f9356403 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -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(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java index 3ec71ac30da1..d7b8b83f01fb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java @@ -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,32 +112,46 @@ 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++) { - ImageButton thisBtn = mMediaNotifView.findViewById(actionIds[i]); - ImageButton thatBtn = parentActionsLayout.findViewById(notifActionIds[i]); - if (thatBtn == null || thatBtn.getDrawable() == null) { - thisBtn.setVisibility(View.GONE); - continue; - } - Drawable thatIcon = thatBtn.getDrawable(); - thisBtn.setImageDrawable(thatIcon.mutate()); - thisBtn.setVisibility(View.VISIBLE); + 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]); + 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; + } + + Drawable thatIcon = thatBtn.getDrawable(); + thisBtn.setImageDrawable(thatIcon.mutate()); + thisBtn.setVisibility(View.VISIBLE); + thisBtn.setOnClickListener(v -> { + thatBtn.performClick(); + }); + } + } - 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); } } @@ -186,6 +202,7 @@ public class QuickQSMediaPlayer { mMediaNotifView.setBackground(roundedDrawable); } else { Log.e(TAG, "No album art available"); + mMediaNotifView.setBackground(null); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java index 926ae71194d9..b21c65ea85ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java @@ -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); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index b12c76c750a8..d1b9a87c1ddc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -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(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java index d0122c2b59b2..54c71418bd08 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java @@ -183,6 +183,8 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi .getParcelable(Notification.EXTRA_MEDIA_SESSION); if (Utils.useQsMediaPlayer(mContext)) { + final int[] compactActions = mRow.getEntry().getSbn().getNotification().extras + .getIntArray(Notification.EXTRA_COMPACT_ACTIONS); StatusBarWindowController ctrl = Dependency.get(StatusBarWindowController.class); QuickQSPanel panel = ctrl.getStatusBarView().findViewById( com.android.systemui.R.id.quick_qs_panel); @@ -190,7 +192,8 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi mRow.getStatusBarNotification().getNotification().getSmallIcon(), getNotificationHeader().getOriginalIconColor(), mRow.getCurrentBackgroundTint(), - mActions); + mActions, + compactActions); QSPanel bigPanel = ctrl.getStatusBarView().findViewById( com.android.systemui.R.id.quick_settings_panel); bigPanel.addMediaSession(token, |