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

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

Add SystemAPI to override default media output switcher

To support RCN use case, allow system apps with the
MEDIA_CONTENT_CONTROL permission to declare that their MediaStyle notification
is for remote playback, and pass in their own device name, icon, and intent to
use in place of the default output switcher that appears on the media controls

Bug: 204910409
Test: atest
Test: manual with test app
Change-Id: Icbfa288c48da9c2ab2fdfdd6b0bfe2b545afaac0
parent 4353b0b0
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -848,6 +848,10 @@ package android.app {
    field public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; // 0xb
  }
  public static class Notification.MediaStyle extends android.app.Notification.Style {
    method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public android.app.Notification.MediaStyle setRemotePlaybackInfo(@NonNull CharSequence, @DrawableRes int, @Nullable android.app.PendingIntent);
  }
  public static final class Notification.TvExtender implements android.app.Notification.Extender {
    ctor public Notification.TvExtender();
    ctor public Notification.TvExtender(android.app.Notification);
+3 −0
Original line number Diff line number Diff line
@@ -299,6 +299,9 @@ package android.app {

  public class Notification implements android.os.Parcelable {
    method public boolean shouldShowForegroundImmediately();
    field public static final String EXTRA_MEDIA_REMOTE_DEVICE = "android.mediaRemoteDevice";
    field public static final String EXTRA_MEDIA_REMOTE_ICON = "android.mediaRemoteIcon";
    field public static final String EXTRA_MEDIA_REMOTE_INTENT = "android.mediaRemoteIntent";
  }

  public final class NotificationChannel implements android.os.Parcelable {
+73 −0
Original line number Diff line number Diff line
@@ -1329,6 +1329,32 @@ public class Notification implements Parcelable
     */
    public static final String EXTRA_MEDIA_SESSION = "android.mediaSession";

    /**
     * {@link #extras} key: A {@code CharSequence} name of a remote device used for a media session
     * associated with a {@link Notification.MediaStyle} notification. This will show in the media
     * controls output switcher instead of the local device name.
     * @hide
     */
    @TestApi
    public static final String EXTRA_MEDIA_REMOTE_DEVICE = "android.mediaRemoteDevice";

    /**
     * {@link #extras} key: A {@code int} resource ID for an icon that should show in the output
     * switcher of the media controls for a {@link Notification.MediaStyle} notification.
     * @hide
     */
    @TestApi
    public static final String EXTRA_MEDIA_REMOTE_ICON = "android.mediaRemoteIcon";

    /**
     * {@link #extras} key: A {@code PendingIntent} that will replace the default action for the
     * media controls output switcher chip, associated with a {@link Notification.MediaStyle}
     * notification. This should launch an activity.
     * @hide
     */
    @TestApi
    public static final String EXTRA_MEDIA_REMOTE_INTENT = "android.mediaRemoteIntent";

    /**
     * {@link #extras} key: the indices of actions to be shown in the compact view,
     * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}.
@@ -8943,6 +8969,9 @@ public class Notification implements Parcelable

        private int[] mActionsToShowInCompact = null;
        private MediaSession.Token mToken;
        private CharSequence mDeviceName;
        private int mDeviceIcon;
        private PendingIntent mDeviceIntent;

        public MediaStyle() {
        }
@@ -8975,6 +9004,32 @@ public class Notification implements Parcelable
            return this;
        }

        /**
         * For media notifications associated with playback on a remote device, provide device
         * information that will replace the default values for the output switcher chip on the
         * media control, as well as an intent to use when the output switcher chip is tapped,
         * on devices where this is supported.
         *
         * @param deviceName The name of the remote device to display
         * @param iconResource Icon resource representing the device
         * @param chipIntent PendingIntent to send when the output switcher is tapped. May be
         *                   {@code null}, in which case the output switcher will be disabled.
         *                   This intent should open an Activity or it will be ignored.
         * @return MediaStyle
         *
         * @hide
         */
        @SystemApi
        @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)
        @NonNull
        public MediaStyle setRemotePlaybackInfo(@NonNull CharSequence deviceName,
                @DrawableRes int iconResource, @Nullable PendingIntent chipIntent) {
            mDeviceName = deviceName;
            mDeviceIcon = iconResource;
            mDeviceIntent = chipIntent;
            return this;
        }

        /**
         * @hide
         */
@@ -9023,6 +9078,15 @@ public class Notification implements Parcelable
            if (mActionsToShowInCompact != null) {
                extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact);
            }
            if (mDeviceName != null) {
                extras.putCharSequence(EXTRA_MEDIA_REMOTE_DEVICE, mDeviceName);
            }
            if (mDeviceIcon > 0) {
                extras.putInt(EXTRA_MEDIA_REMOTE_ICON, mDeviceIcon);
            }
            if (mDeviceIntent != null) {
                extras.putParcelable(EXTRA_MEDIA_REMOTE_INTENT, mDeviceIntent);
            }
        }

        /**
@@ -9038,6 +9102,15 @@ public class Notification implements Parcelable
            if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) {
                mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS);
            }
            if (extras.containsKey(EXTRA_MEDIA_REMOTE_DEVICE)) {
                mDeviceName = extras.getCharSequence(EXTRA_MEDIA_REMOTE_DEVICE);
            }
            if (extras.containsKey(EXTRA_MEDIA_REMOTE_ICON)) {
                mDeviceIcon = extras.getInt(EXTRA_MEDIA_REMOTE_ICON);
            }
            if (extras.containsKey(EXTRA_MEDIA_REMOTE_INTENT)) {
                mDeviceIntent = extras.getParcelable(EXTRA_MEDIA_REMOTE_INTENT);
            }
        }

        /**
+24 −12
Original line number Diff line number Diff line
@@ -371,13 +371,6 @@ public class MediaControlPanel {
        // Output switcher chip
        ViewGroup seamlessView = mMediaViewHolder.getSeamless();
        seamlessView.setVisibility(View.VISIBLE);
        seamlessView.setOnClickListener(
                v -> {
                    if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
                        mMediaOutputDialogFactory.create(data.getPackageName(), true,
                                mMediaViewHolder.getSeamlessButton());
                    }
                });
        ImageView iconView = mMediaViewHolder.getSeamlessIcon();
        TextView deviceName = mMediaViewHolder.getSeamlessText();
        final MediaDeviceData device = data.getDevice();
@@ -387,8 +380,8 @@ public class MediaControlPanel {
        final float seamlessAlpha = seamlessDisabled ? DISABLED_ALPHA : 1.0f;
        mMediaViewHolder.getSeamlessButton().setAlpha(seamlessAlpha);
        seamlessView.setEnabled(!seamlessDisabled);
        String deviceString = null;
        if (device != null && device.getEnabled()) {
        CharSequence deviceString = mContext.getString(R.string.media_seamless_other_device);
        if (device != null) {
            Drawable icon = device.getIcon();
            if (icon instanceof AdaptiveIcon) {
                AdaptiveIcon aIcon = (AdaptiveIcon) icon;
@@ -399,13 +392,32 @@ public class MediaControlPanel {
            }
            deviceString = device.getName();
        } else {
            // Reset to default
            Log.w(TAG, "Device is null or not enabled: " + device + ", not binding output chip.");
            // Set to default icon
            iconView.setImageResource(R.drawable.ic_media_home_devices);
            deviceString =  mContext.getString(R.string.media_seamless_other_device);
        }
        deviceName.setText(deviceString);
        seamlessView.setContentDescription(deviceString);
        seamlessView.setOnClickListener(
                v -> {
                    if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
                        return;
                    }
                    if (device.getIntent() != null) {
                        if (device.getIntent().isActivity()) {
                            mActivityStarter.startActivity(
                                    device.getIntent().getIntent(), true);
                        } else {
                            try {
                                device.getIntent().send();
                            } catch (PendingIntent.CanceledException e) {
                                Log.e(TAG, "Device pending intent was canceled");
                            }
                        }
                    } else {
                        mMediaOutputDialogFactory.create(data.getPackageName(), true,
                                mMediaViewHolder.getSeamlessButton());
                    }
            });

        // Dismiss
        mMediaViewHolder.getDismissText().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
+11 −2
Original line number Diff line number Diff line
@@ -164,8 +164,17 @@ data class MediaAction(
)

/** State of the media device. */
data class MediaDeviceData(
data class MediaDeviceData
@JvmOverloads constructor(
    /** Whether or not to enable the chip */
    val enabled: Boolean,

    /** Device icon to show in the chip */
    val icon: Drawable?,
    val name: String?

    /** Device display name */
    val name: CharSequence?,

    /** Optional intent to override the default output switcher for this control */
    val intent: PendingIntent? = null
)
Loading