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

Commit d13e721d authored by Zoey Chen's avatar Zoey Chen Committed by Android (Google) Code Review
Browse files

Merge "[Le Audio] Add a new BroadcastDialog in SystemUI from MediaPanel" into tm-qpr-dev

parents b1f4e4cf 7c86f9ef
Loading
Loading
Loading
Loading
+72 −24
Original line number Diff line number Diff line
@@ -70,6 +70,7 @@ import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.GhostedViewLaunchAnimatorController;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.bluetooth.BroadcastDialogController;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -188,6 +189,11 @@ public class MediaControlPanel {
    private final SeekBarViewModel.EnabledChangeListener mEnabledChangeListener =
            this::setIsSeekBarEnabled;

    private final BroadcastDialogController mBroadcastDialogController;
    private boolean mIsCurrentBroadcastedApp = false;
    private boolean mShowBroadcastDialogButton = false;
    private String mSwitchBroadcastApp;

    /**
     * Initialize a new control panel
     *
@@ -213,7 +219,8 @@ public class MediaControlPanel {
            MediaUiEventLogger logger,
            KeyguardStateController keyguardStateController,
            ActivityIntentHelper activityIntentHelper,
            NotificationLockscreenUserManager lockscreenUserManager) {
            NotificationLockscreenUserManager lockscreenUserManager,
            BroadcastDialogController broadcastDialogController) {
        mContext = context;
        mBackgroundExecutor = backgroundExecutor;
        mMainExecutor = mainExecutor;
@@ -230,6 +237,7 @@ public class MediaControlPanel {
        mKeyguardStateController = keyguardStateController;
        mActivityIntentHelper = activityIntentHelper;
        mLockscreenUserManager = lockscreenUserManager;
        mBroadcastDialogController = broadcastDialogController;

        mSeekBarViewModel.setLogSeek(() -> {
            if (mPackageName != null && mInstanceId != null) {
@@ -449,7 +457,10 @@ public class MediaControlPanel {
        final MediaController controller = getController();
        mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));

        bindOutputSwitcherChip(data);
        // Show the broadcast dialog button only when the le audio is enabled.
        mShowBroadcastDialogButton =
                data.getDevice() != null && data.getDevice().getShowBroadcastButton();
        bindOutputSwitcherAndBroadcastButton(mShowBroadcastDialogButton, data);
        bindGutsMenuForPlayer(data);
        bindPlayerContentDescription(data);
        bindScrubbingTime(data);
@@ -467,21 +478,40 @@ public class MediaControlPanel {
        Trace.endSection();
    }

    private void bindOutputSwitcherChip(MediaData data) {
        // Output switcher chip
    private void bindOutputSwitcherAndBroadcastButton(boolean showBroadcastButton, MediaData data) {
        ViewGroup seamlessView = mMediaViewHolder.getSeamless();
        seamlessView.setVisibility(View.VISIBLE);
        ImageView iconView = mMediaViewHolder.getSeamlessIcon();
        TextView deviceName = mMediaViewHolder.getSeamlessText();
        final MediaDeviceData device = data.getDevice();

        final boolean enabled;
        final boolean seamlessDisabled;
        final int iconResource;
        CharSequence deviceString;
        if (showBroadcastButton) {
            // TODO(b/233698402): Use the package name instead of app label to avoid the
            // unexpected result.
            mIsCurrentBroadcastedApp = device != null
                    && TextUtils.equals(device.getName(),
                    MediaDataUtils.getAppLabel(mContext, mPackageName, mContext.getString(
                            R.string.bt_le_audio_broadcast_dialog_unknown_name)));
            seamlessDisabled = !mIsCurrentBroadcastedApp;
            // Always be enabled if the broadcast button is shown
            enabled = true;
            deviceString = mContext.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name);
            iconResource = R.drawable.settings_input_antenna;
        } else {
            // Disable clicking on output switcher for invalid devices and resumption controls
        final boolean seamlessDisabled = (device != null && !device.getEnabled())
                || data.getResumption();
        final float seamlessAlpha = seamlessDisabled ? DISABLED_ALPHA : 1.0f;
        mMediaViewHolder.getSeamlessButton().setAlpha(seamlessAlpha);
        seamlessView.setEnabled(!seamlessDisabled);
        CharSequence deviceString = mContext.getString(R.string.media_seamless_other_device);
            seamlessDisabled = (device != null && !device.getEnabled()) || data.getResumption();
            enabled = !seamlessDisabled;
            deviceString = mContext.getString(R.string.media_seamless_other_device);
            iconResource = R.drawable.ic_media_home_devices;
        }

        mMediaViewHolder.getSeamlessButton().setAlpha(seamlessDisabled ? DISABLED_ALPHA : 1.0f);
        seamlessView.setEnabled(enabled);

        if (device != null) {
            Drawable icon = device.getIcon();
            if (icon instanceof AdaptiveIcon) {
@@ -494,7 +524,7 @@ public class MediaControlPanel {
            deviceString = device.getName();
        } else {
            // Set to default icon
            iconView.setImageResource(R.drawable.ic_media_home_devices);
            iconView.setImageResource(iconResource);
        }
        deviceName.setText(deviceString);
        seamlessView.setContentDescription(deviceString);
@@ -503,6 +533,23 @@ public class MediaControlPanel {
                    if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
                        return;
                    }

                    if (showBroadcastButton) {
                        // If the current media app is not broadcasted and users press the outputer
                        // button, we should pop up the broadcast dialog to check do they want to
                        // switch broadcast to the other media app, otherwise we still pop up the
                        // media output dialog.
                        if (!mIsCurrentBroadcastedApp) {
                            mLogger.logOpenBroadcastDialog(mUid, mPackageName, mInstanceId);
                            mSwitchBroadcastApp = device.getName().toString();
                            mBroadcastDialogController.createBroadcastDialog(mSwitchBroadcastApp,
                                    mPackageName, true, mMediaViewHolder.getSeamlessButton());
                        } else {
                            mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
                            mMediaOutputDialogFactory.create(mPackageName, true,
                                    mMediaViewHolder.getSeamlessButton());
                        }
                    } else {
                        mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
                        if (device.getIntent() != null) {
                            if (device.getIntent().isActivity()) {
@@ -519,6 +566,7 @@ public class MediaControlPanel {
                            mMediaOutputDialogFactory.create(mPackageName, true,
                                    mMediaViewHolder.getSeamlessButton());
                        }
                    }
                });
    }

+4 −1
Original line number Diff line number Diff line
@@ -215,5 +215,8 @@ data class MediaDeviceData
    val intent: PendingIntent? = null,

    /** Unique id for this device */
    val id: String? = null
    val id: String? = null,

    /** Whether or not to show the broadcast button */
    val showBroadcastButton: Boolean
)
+2 −1
Original line number Diff line number Diff line
@@ -686,7 +686,8 @@ class MediaDataManager(
                val enabled = deviceIntent != null && deviceIntent.isActivity
                val deviceDrawable = Icon.createWithResource(sbn.packageName, deviceIcon)
                        .loadDrawable(sbn.getPackageContext(context))
                device = MediaDeviceData(enabled, deviceDrawable, deviceName, deviceIntent)
                device = MediaDeviceData(enabled, deviceDrawable, deviceName, deviceIntent,
                        showBroadcastButton = false)
            }
        }

+134 −13
Original line number Diff line number Diff line
@@ -16,15 +16,23 @@

package com.android.systemui.media

import android.bluetooth.BluetoothLeBroadcast
import android.bluetooth.BluetoothLeBroadcastMetadata
import android.content.Context
import android.graphics.drawable.Drawable
import android.media.MediaRouter2Manager
import android.media.session.MediaController
import android.text.TextUtils
import android.util.Log
import androidx.annotation.AnyThread
import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
@@ -36,16 +44,20 @@ import java.util.concurrent.Executor
import javax.inject.Inject

private const val PLAYBACK_TYPE_UNKNOWN = 0
private const val TAG = "MediaDeviceManager"
private const val DEBUG = true

/**
 * Provides information about the route (ie. device) where playback is occurring.
 */
class MediaDeviceManager @Inject constructor(
    private val context: Context,
    private val controllerFactory: MediaControllerFactory,
    private val localMediaManagerFactory: LocalMediaManagerFactory,
    private val mr2manager: MediaRouter2Manager,
    private val muteAwaitConnectionManagerFactory: MediaMuteAwaitConnectionManagerFactory,
    private val configurationController: ConfigurationController,
    private val localBluetoothManager: LocalBluetoothManager?,
    @Main private val fgExecutor: Executor,
    @Background private val bgExecutor: Executor,
    dumpManager: DumpManager
@@ -147,7 +159,8 @@ class MediaDeviceManager @Inject constructor(
        val controller: MediaController?,
        val localMediaManager: LocalMediaManager,
        val muteAwaitConnectionManager: MediaMuteAwaitConnectionManager?
    ) : LocalMediaManager.DeviceCallback, MediaController.Callback() {
    ) : LocalMediaManager.DeviceCallback, MediaController.Callback(),
            BluetoothLeBroadcast.Callback {

        val token
            get() = controller?.sessionToken
@@ -166,7 +179,7 @@ class MediaDeviceManager @Inject constructor(
        // A device that is not yet connected but is expected to connect imminently. Because it's
        // expected to connect imminently, it should be displayed as the current device.
        private var aboutToConnectDeviceOverride: AboutToConnectDevice? = null

        private var broadcastDescription: String? = null
        private val configListener = object : ConfigurationController.ConfigurationListener {
            override fun onLocaleListChanged() {
                updateCurrent()
@@ -238,7 +251,11 @@ class MediaDeviceManager @Inject constructor(
        ) {
            aboutToConnectDeviceOverride = AboutToConnectDevice(
                fullMediaDevice = localMediaManager.getMediaDeviceById(deviceAddress),
                backupMediaDeviceData = MediaDeviceData(enabled = true, deviceIcon, deviceName)
                backupMediaDeviceData = MediaDeviceData(
                        /* enabled */ enabled = true,
                        /* icon */ deviceIcon,
                        /* name */ deviceName,
                        /* showBroadcastButton */ showBroadcastButton = false)
            )
            updateCurrent()
        }
@@ -248,8 +265,71 @@ class MediaDeviceManager @Inject constructor(
            updateCurrent()
        }


        override fun onBroadcastStarted(reason: Int, broadcastId: Int) {
            if (DEBUG) {
                Log.d(TAG, "onBroadcastStarted(), reason = $reason , broadcastId = $broadcastId")
            }
            updateCurrent()
        }

        override fun onBroadcastStartFailed(reason: Int) {
            if (DEBUG) {
                Log.d(TAG, "onBroadcastStartFailed(), reason = $reason")
            }
        }

        override fun onBroadcastMetadataChanged(broadcastId: Int,
                                                metadata: BluetoothLeBroadcastMetadata) {
            if (DEBUG) {
                Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = $broadcastId , " +
                        "metadata = $metadata")
            }
            updateCurrent()
        }

        override fun onBroadcastStopped(reason: Int, broadcastId: Int) {
            if (DEBUG) {
                Log.d(TAG, "onBroadcastStopped(), reason = $reason , broadcastId = $broadcastId")

            }
            updateCurrent()
        }

        override fun onBroadcastStopFailed(reason: Int) {
            if (DEBUG) {
                Log.d(TAG, "onBroadcastStopFailed(), reason = $reason")
            }
        }

        override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {
            if (DEBUG) {
                Log.d(TAG, "onBroadcastUpdated(), reason = $reason , broadcastId = $broadcastId")
            }
            updateCurrent()
        }

        override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {
            if (DEBUG) {
                Log.d(TAG, "onBroadcastUpdateFailed(), reason = $reason , " +
                        "broadcastId = $broadcastId")
            }
        }

        override fun onPlaybackStarted(reason: Int, broadcastId: Int) {}

        override fun onPlaybackStopped(reason: Int, broadcastId: Int) {}

        @WorkerThread
        private fun updateCurrent() {
            if (isLeAudioBroadcastEnabled()) {
                current = MediaDeviceData(
                        /* enabled */ true,
                        /* icon */ context.getDrawable(R.drawable.settings_input_antenna),
                        /* name */ broadcastDescription,
                        /* intent */ null,
                        /* showBroadcastButton */ showBroadcastButton = true)
            } else {
                val aboutToConnect = aboutToConnectDeviceOverride
                if (aboutToConnect != null &&
                        aboutToConnect.fullMediaDevice == null &&
@@ -258,13 +338,54 @@ class MediaDeviceManager @Inject constructor(
                    current = aboutToConnect.backupMediaDeviceData
                    return
                }
            val device = aboutToConnect?.fullMediaDevice ?: localMediaManager.currentConnectedDevice
                val device = aboutToConnect?.fullMediaDevice
                        ?: localMediaManager.currentConnectedDevice
                val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) }

                // If we have a controller but get a null route, then don't trust the device
                val enabled = device != null && (controller == null || route != null)
                val name = route?.name?.toString() ?: device?.name
            current = MediaDeviceData(enabled, device?.iconWithoutBackground, name, id = device?.id)
                current = MediaDeviceData(enabled, device?.iconWithoutBackground, name,
                        id = device?.id, showBroadcastButton = false)
            }
        }

        private fun isLeAudioBroadcastEnabled(): Boolean {
            if (localBluetoothManager != null) {
                val profileManager = localBluetoothManager.profileManager
                if (profileManager != null) {
                    val bluetoothLeBroadcast = profileManager.leAudioBroadcastProfile
                    if (bluetoothLeBroadcast != null && bluetoothLeBroadcast.isEnabled(null)) {
                        getBroadcastingInfo(bluetoothLeBroadcast)
                        return true
                    } else if (DEBUG) {
                        Log.d(TAG, "Can not get LocalBluetoothLeBroadcast")
                    }
                } else if (DEBUG) {
                    Log.d(TAG, "Can not get LocalBluetoothProfileManager")
                }
            } else if (DEBUG) {
                Log.d(TAG, "Can not get LocalBluetoothManager")
            }
            return false
        }

        private fun getBroadcastingInfo(bluetoothLeBroadcast: LocalBluetoothLeBroadcast) {
            var currentBroadcastedApp = bluetoothLeBroadcast.appSourceName
            // TODO(b/233698402): Use the package name instead of app label to avoid the
            // unexpected result.
            // Check the current media app's name is the same with current broadcast app's name
            // or not.
            var mediaApp = MediaDataUtils.getAppLabel(
                    context, localMediaManager.packageName,
                    context.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name))
            var isCurrentBroadcastedApp = TextUtils.equals(mediaApp, currentBroadcastedApp)
            if (isCurrentBroadcastedApp) {
                broadcastDescription = context.getString(
                        R.string.broadcasting_description_is_broadcasting)
            } else {
                broadcastDescription = currentBroadcastedApp
            }
        }
    }
}
+9 −1
Original line number Diff line number Diff line
@@ -176,6 +176,11 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger)
        logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_CARD_TAP, 0, packageName,
            instanceId)
    }

    fun logOpenBroadcastDialog(uid: Int, packageName: String, instanceId: InstanceId) {
        logger.logWithInstanceId(MediaUiEvent.MEDIA_OPEN_BROADCAST_DIALOG, uid, packageName,
            instanceId)
    }
}

enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
@@ -273,7 +278,10 @@ enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
    MEDIA_RECOMMENDATION_ITEM_TAP(1044),

    @UiEvent(doc = "User tapped on a media recommendation card")
    MEDIA_RECOMMENDATION_CARD_TAP(1045);
    MEDIA_RECOMMENDATION_CARD_TAP(1045),

    @UiEvent(doc = "User opened the broadcast dialog from a media control")
    MEDIA_OPEN_BROADCAST_DIALOG(1079);

    override fun getId() = metricId
}
 No newline at end of file
Loading