diff options
59 files changed, 5498 insertions, 4702 deletions
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt index 31ab24748c93..0f037e40b997 100644 --- a/packages/SystemUI/ktfmt_includes.txt +++ b/packages/SystemUI/ktfmt_includes.txt @@ -189,45 +189,8 @@ -packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt -packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt -packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.kt --packages/SystemUI/src/com/android/systemui/media/AnimationBindHandler.kt --packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt --packages/SystemUI/src/com/android/systemui/media/GutsViewHolder.kt --packages/SystemUI/src/com/android/systemui/media/IlluminationDrawable.kt --packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt --packages/SystemUI/src/com/android/systemui/media/LightSourceDrawable.kt --packages/SystemUI/src/com/android/systemui/media/LocalMediaManagerFactory.kt --packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt --packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt --packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt --packages/SystemUI/src/com/android/systemui/media/MediaData.kt --packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt --packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt --packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt --packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt --packages/SystemUI/src/com/android/systemui/media/MediaFeatureFlag.kt --packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt --packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt --packages/SystemUI/src/com/android/systemui/media/MediaHost.kt --packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt -packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt -packages/SystemUI/src/com/android/systemui/media/MediaProjectionCaptureTarget.kt --packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt --packages/SystemUI/src/com/android/systemui/media/MediaScrollView.kt --packages/SystemUI/src/com/android/systemui/media/MediaSessionBasedFilter.kt --packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt --packages/SystemUI/src/com/android/systemui/media/MediaTimeoutLogger.kt --packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt --packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt --packages/SystemUI/src/com/android/systemui/media/MediaViewHolder.kt --packages/SystemUI/src/com/android/systemui/media/MediaViewLogger.kt --packages/SystemUI/src/com/android/systemui/media/MetadataAnimationHandler.kt --packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt --packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserLogger.kt --packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt --packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt --packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt --packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt --packages/SystemUI/src/com/android/systemui/media/SquigglyProgress.kt -packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt -packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt -packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt @@ -653,26 +616,6 @@ -packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt -packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt -packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/AnimationBindHandlerTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/KeyguardMediaControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/MediaSessionBasedFilterTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt --packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/MetadataAnimationHandlerTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/SmartspaceMediaDataTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/SquigglyProgressTest.kt -packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt -packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt -packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/GutsViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/GutsViewHolder.kt index dc0ff7758f4f..531506771459 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/models/GutsViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/GutsViewHolder.kt @@ -98,11 +98,6 @@ class GutsViewHolder constructor(itemView: View) { } companion object { - val ids = setOf( - R.id.remove_text, - R.id.cancel, - R.id.dismiss, - R.id.settings - ) + val ids = setOf(R.id.remove_text, R.id.cancel, R.id.dismiss, R.id.settings) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt index abe26f12fad2..ed649b1c0265 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt @@ -27,69 +27,42 @@ import com.android.systemui.R data class MediaData( val userId: Int, val initialized: Boolean = false, - /** - * App name that will be displayed on the player. - */ + /** App name that will be displayed on the player. */ val app: String?, - /** - * App icon shown on player. - */ + /** App icon shown on player. */ val appIcon: Icon?, - /** - * Artist name. - */ + /** Artist name. */ val artist: CharSequence?, - /** - * Song name. - */ + /** Song name. */ val song: CharSequence?, - /** - * Album artwork. - */ + /** Album artwork. */ val artwork: Icon?, - /** - * List of generic action buttons for the media player, based on notification actions - */ + /** List of generic action buttons for the media player, based on notification actions */ val actions: List<MediaAction>, - /** - * Same as above, but shown on smaller versions of the player, like in QQS or keyguard. - */ + /** Same as above, but shown on smaller versions of the player, like in QQS or keyguard. */ val actionsToShowInCompact: List<Int>, /** - * Semantic actions buttons, based on the PlaybackState of the media session. - * If present, these actions will be preferred in the UI over [actions] + * Semantic actions buttons, based on the PlaybackState of the media session. If present, these + * actions will be preferred in the UI over [actions] */ val semanticActions: MediaButton? = null, - /** - * Package name of the app that's posting the media. - */ + /** Package name of the app that's posting the media. */ val packageName: String, - /** - * Unique media session identifier. - */ + /** Unique media session identifier. */ val token: MediaSession.Token?, - /** - * Action to perform when the player is tapped. - * This is unrelated to {@link #actions}. - */ + /** Action to perform when the player is tapped. This is unrelated to {@link #actions}. */ val clickIntent: PendingIntent?, - /** - * Where the media is playing: phone, headphones, ear buds, remote session. - */ + /** Where the media is playing: phone, headphones, ear buds, remote session. */ val device: MediaDeviceData?, /** - * When active, a player will be displayed on keyguard and quick-quick settings. - * This is unrelated to the stream being playing or not, a player will not be active if - * timed out, or in resumption mode. + * When active, a player will be displayed on keyguard and quick-quick settings. This is + * unrelated to the stream being playing or not, a player will not be active if timed out, or in + * resumption mode. */ var active: Boolean, - /** - * Action that should be performed to restart a non active session. - */ + /** Action that should be performed to restart a non active session. */ var resumeAction: Runnable?, - /** - * Playback location: one of PLAYBACK_LOCAL, PLAYBACK_CAST_LOCAL, or PLAYBACK_CAST_REMOTE - */ + /** Playback location: one of PLAYBACK_LOCAL, PLAYBACK_CAST_LOCAL, or PLAYBACK_CAST_REMOTE */ var playbackLocation: Int = PLAYBACK_LOCAL, /** * Indicates that this player is a resumption player (ie. It only shows a play actions which @@ -102,29 +75,19 @@ data class MediaData( val notificationKey: String? = null, var hasCheckedForResume: Boolean = false, - /** - * If apps do not report PlaybackState, set as null to imply 'undetermined' - */ + /** If apps do not report PlaybackState, set as null to imply 'undetermined' */ val isPlaying: Boolean? = null, - /** - * Set from the notification and used as fallback when PlaybackState cannot be determined - */ + /** Set from the notification and used as fallback when PlaybackState cannot be determined */ val isClearable: Boolean = true, - /** - * Timestamp when this player was last active. - */ + /** Timestamp when this player was last active. */ var lastActive: Long = 0L, - /** - * Instance ID for logging purposes - */ + /** Instance ID for logging purposes */ val instanceId: InstanceId, - /** - * The UID of the app, used for logging - */ + /** The UID of the app, used for logging */ val appUid: Int ) { companion object { @@ -141,37 +104,21 @@ data class MediaData( } } -/** - * Contains [MediaAction] objects which represent specific buttons in the UI - */ +/** Contains [MediaAction] objects which represent specific buttons in the UI */ data class MediaButton( - /** - * Play/pause button - */ + /** Play/pause button */ val playOrPause: MediaAction? = null, - /** - * Next button, or custom action - */ + /** Next button, or custom action */ val nextOrCustom: MediaAction? = null, - /** - * Previous button, or custom action - */ + /** Previous button, or custom action */ val prevOrCustom: MediaAction? = null, - /** - * First custom action space - */ + /** First custom action space */ val custom0: MediaAction? = null, - /** - * Second custom action space - */ + /** Second custom action space */ val custom1: MediaAction? = null, - /** - * Whether to reserve the empty space when the nextOrCustom is null - */ + /** Whether to reserve the empty space when the nextOrCustom is null */ val reserveNext: Boolean = false, - /** - * Whether to reserve the empty space when the prevOrCustom is null - */ + /** Whether to reserve the empty space when the prevOrCustom is null */ val reservePrev: Boolean = false ) { fun getActionById(id: Int): MediaAction? { @@ -201,7 +148,8 @@ data class MediaAction( /** State of the media device. */ data class MediaDeviceData -@JvmOverloads constructor( +@JvmOverloads +constructor( /** Whether or not to enable the chip */ val enabled: Boolean, @@ -221,8 +169,8 @@ data class MediaDeviceData val showBroadcastButton: Boolean ) { /** - * Check whether [MediaDeviceData] objects are equal in all fields except the icon. The icon - * is ignored because it can change by reference frequently depending on the device type's + * Check whether [MediaDeviceData] objects are equal in all fields except the icon. The icon is + * ignored because it can change by reference frequently depending on the device type's * implementation, but this is not usually relevant unless other info has changed */ fun equalsWithoutIcon(other: MediaDeviceData?): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt index d6e40ae8eacf..2511324a943e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt @@ -30,9 +30,7 @@ import com.android.systemui.util.animation.TransitionLayout private const val TAG = "MediaViewHolder" -/** - * Holder class for media player view - */ +/** Holder class for media player view */ class MediaViewHolder constructor(itemView: View) { val player = itemView as TransitionLayout @@ -53,8 +51,7 @@ class MediaViewHolder constructor(itemView: View) { // These views are only shown while the user is actively scrubbing val scrubbingElapsedTimeView: TextView = itemView.requireViewById(R.id.media_scrubbing_elapsed_time) - val scrubbingTotalTimeView: TextView = - itemView.requireViewById(R.id.media_scrubbing_total_time) + val scrubbingTotalTimeView: TextView = itemView.requireViewById(R.id.media_scrubbing_total_time) val gutsViewHolder = GutsViewHolder(itemView) @@ -87,15 +84,7 @@ class MediaViewHolder constructor(itemView: View) { } fun getTransparentActionButtons(): List<ImageButton> { - return listOf( - actionNext, - actionPrev, - action0, - action1, - action2, - action3, - action4 - ) + return listOf(actionNext, actionPrev, action0, action1, action2, action3, action4) } fun marquee(start: Boolean, delay: Long) { @@ -109,10 +98,8 @@ class MediaViewHolder constructor(itemView: View) { * @param inflater LayoutInflater to use to inflate the layout. * @param parent Parent of inflated view. */ - @JvmStatic fun create( - inflater: LayoutInflater, - parent: ViewGroup - ): MediaViewHolder { + @JvmStatic + fun create(inflater: LayoutInflater, parent: ViewGroup): MediaViewHolder { val mediaView = inflater.inflate(R.layout.media_session_view, parent, false) mediaView.setLayerType(View.LAYER_TYPE_HARDWARE, null) // Because this media view (a TransitionLayout) is used to measure and layout the views @@ -125,7 +112,8 @@ class MediaViewHolder constructor(itemView: View) { } } - val controlsIds = setOf( + val controlsIds = + setOf( R.id.icon, R.id.app_name, R.id.header_title, @@ -143,27 +131,23 @@ class MediaViewHolder constructor(itemView: View) { R.id.icon, R.id.media_scrubbing_elapsed_time, R.id.media_scrubbing_total_time - ) + ) // Buttons used for notification-based actions - val genericButtonIds = setOf( - R.id.action0, - R.id.action1, - R.id.action2, - R.id.action3, - R.id.action4 - ) - - val expandedBottomActionIds = setOf( - R.id.actionPrev, - R.id.actionNext, - R.id.action0, - R.id.action1, - R.id.action2, - R.id.action3, - R.id.action4, - R.id.media_scrubbing_elapsed_time, - R.id.media_scrubbing_total_time - ) + val genericButtonIds = + setOf(R.id.action0, R.id.action1, R.id.action2, R.id.action3, R.id.action4) + + val expandedBottomActionIds = + setOf( + R.id.actionPrev, + R.id.actionNext, + R.id.action0, + R.id.action1, + R.id.action2, + R.id.action3, + R.id.action4, + R.id.media_scrubbing_elapsed_time, + R.id.media_scrubbing_total_time + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt index e5d774a111e0..37d956bd09eb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt @@ -31,34 +31,49 @@ import com.android.systemui.media.controls.ui.SquigglyProgress * * <p>Updates the seek bar views in response to changes to the model. */ -open class SeekBarObserver( - private val holder: MediaViewHolder -) : Observer<SeekBarViewModel.Progress> { +open class SeekBarObserver(private val holder: MediaViewHolder) : + Observer<SeekBarViewModel.Progress> { companion object { @JvmStatic val RESET_ANIMATION_DURATION_MS: Int = 750 @JvmStatic val RESET_ANIMATION_THRESHOLD_MS: Int = 250 } - val seekBarEnabledMaxHeight = holder.seekBar.context.resources - .getDimensionPixelSize(R.dimen.qs_media_enabled_seekbar_height) - val seekBarDisabledHeight = holder.seekBar.context.resources - .getDimensionPixelSize(R.dimen.qs_media_disabled_seekbar_height) - val seekBarEnabledVerticalPadding = holder.seekBar.context.resources - .getDimensionPixelSize(R.dimen.qs_media_session_enabled_seekbar_vertical_padding) - val seekBarDisabledVerticalPadding = holder.seekBar.context.resources - .getDimensionPixelSize(R.dimen.qs_media_session_disabled_seekbar_vertical_padding) + val seekBarEnabledMaxHeight = + holder.seekBar.context.resources.getDimensionPixelSize( + R.dimen.qs_media_enabled_seekbar_height + ) + val seekBarDisabledHeight = + holder.seekBar.context.resources.getDimensionPixelSize( + R.dimen.qs_media_disabled_seekbar_height + ) + val seekBarEnabledVerticalPadding = + holder.seekBar.context.resources.getDimensionPixelSize( + R.dimen.qs_media_session_enabled_seekbar_vertical_padding + ) + val seekBarDisabledVerticalPadding = + holder.seekBar.context.resources.getDimensionPixelSize( + R.dimen.qs_media_session_disabled_seekbar_vertical_padding + ) var seekBarResetAnimator: Animator? = null init { - val seekBarProgressWavelength = holder.seekBar.context.resources - .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_wavelength).toFloat() - val seekBarProgressAmplitude = holder.seekBar.context.resources - .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_amplitude).toFloat() - val seekBarProgressPhase = holder.seekBar.context.resources - .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_phase).toFloat() - val seekBarProgressStrokeWidth = holder.seekBar.context.resources - .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_stroke_width).toFloat() + val seekBarProgressWavelength = + holder.seekBar.context.resources + .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_wavelength) + .toFloat() + val seekBarProgressAmplitude = + holder.seekBar.context.resources + .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_amplitude) + .toFloat() + val seekBarProgressPhase = + holder.seekBar.context.resources + .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_phase) + .toFloat() + val seekBarProgressStrokeWidth = + holder.seekBar.context.resources + .getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_stroke_width) + .toFloat() val progressDrawable = holder.seekBar.progressDrawable as? SquigglyProgress progressDrawable?.let { it.waveLength = seekBarProgressWavelength @@ -98,16 +113,18 @@ open class SeekBarObserver( } holder.seekBar.setMax(data.duration) - val totalTimeString = DateUtils.formatElapsedTime( - data.duration / DateUtils.SECOND_IN_MILLIS) + val totalTimeString = + DateUtils.formatElapsedTime(data.duration / DateUtils.SECOND_IN_MILLIS) if (data.scrubbing) { holder.scrubbingTotalTimeView.text = totalTimeString } data.elapsedTime?.let { if (!data.scrubbing && !(seekBarResetAnimator?.isRunning ?: false)) { - if (it <= RESET_ANIMATION_THRESHOLD_MS && - holder.seekBar.progress > RESET_ANIMATION_THRESHOLD_MS) { + if ( + it <= RESET_ANIMATION_THRESHOLD_MS && + holder.seekBar.progress > RESET_ANIMATION_THRESHOLD_MS + ) { // This animation resets for every additional update to zero. val animator = buildResetAnimator(it) animator.start() @@ -117,24 +134,29 @@ open class SeekBarObserver( } } - val elapsedTimeString = DateUtils.formatElapsedTime( - it / DateUtils.SECOND_IN_MILLIS) + val elapsedTimeString = DateUtils.formatElapsedTime(it / DateUtils.SECOND_IN_MILLIS) if (data.scrubbing) { holder.scrubbingElapsedTimeView.text = elapsedTimeString } - holder.seekBar.contentDescription = holder.seekBar.context.getString( - R.string.controls_media_seekbar_description, - elapsedTimeString, - totalTimeString - ) + holder.seekBar.contentDescription = + holder.seekBar.context.getString( + R.string.controls_media_seekbar_description, + elapsedTimeString, + totalTimeString + ) } } @VisibleForTesting open fun buildResetAnimator(targetTime: Int): Animator { - val animator = ObjectAnimator.ofInt(holder.seekBar, "progress", - holder.seekBar.progress, targetTime + RESET_ANIMATION_DURATION_MS) + val animator = + ObjectAnimator.ofInt( + holder.seekBar, + "progress", + holder.seekBar.progress, + targetTime + RESET_ANIMATION_DURATION_MS + ) animator.setAutoCancel(true) animator.duration = RESET_ANIMATION_DURATION_MS.toLong() animator.interpolator = Interpolators.EMPHASIZED diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt index e4e5cd3526a5..bba5f350dd16 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt @@ -42,8 +42,8 @@ private const val MIN_FLING_VELOCITY_SCALE_FACTOR = 10 private fun PlaybackState.isInMotion(): Boolean { return this.state == PlaybackState.STATE_PLAYING || - this.state == PlaybackState.STATE_FAST_FORWARDING || - this.state == PlaybackState.STATE_REWINDING + this.state == PlaybackState.STATE_FAST_FORWARDING || + this.state == PlaybackState.STATE_REWINDING } /** @@ -59,8 +59,8 @@ private fun PlaybackState.computePosition(duration: Long): Long { val updateTime = this.getLastPositionUpdateTime() val currentTime = SystemClock.elapsedRealtime() if (updateTime > 0) { - var position = (this.playbackSpeed * (currentTime - updateTime)).toLong() + - this.getPosition() + var position = + (this.playbackSpeed * (currentTime - updateTime)).toLong() + this.getPosition() if (duration >= 0 && position > duration) { position = duration.toLong() } else if (position < 0) { @@ -73,7 +73,9 @@ private fun PlaybackState.computePosition(duration: Long): Long { } /** ViewModel for seek bar in QS media player. */ -class SeekBarViewModel @Inject constructor( +class SeekBarViewModel +@Inject +constructor( @Background private val bgExecutor: RepeatableExecutor, private val falsingManager: FalsingManager, ) { @@ -86,9 +88,7 @@ class SeekBarViewModel @Inject constructor( } _progress.postValue(value) } - private val _progress = MutableLiveData<Progress>().apply { - postValue(_data) - } + private val _progress = MutableLiveData<Progress>().apply { postValue(_data) } val progress: LiveData<Progress> get() = _progress private var controller: MediaController? = null @@ -100,20 +100,21 @@ class SeekBarViewModel @Inject constructor( } } private var playbackState: PlaybackState? = null - private var callback = object : MediaController.Callback() { - override fun onPlaybackStateChanged(state: PlaybackState?) { - playbackState = state - if (playbackState == null || PlaybackState.STATE_NONE.equals(playbackState)) { - clearController() - } else { - checkIfPollingNeeded() + private var callback = + object : MediaController.Callback() { + override fun onPlaybackStateChanged(state: PlaybackState?) { + playbackState = state + if (playbackState == null || PlaybackState.STATE_NONE.equals(playbackState)) { + clearController() + } else { + checkIfPollingNeeded() + } } - } - override fun onSessionDestroyed() { - clearController() + override fun onSessionDestroyed() { + clearController() + } } - } private var cancel: Runnable? = null /** Indicates if the seek interaction is considered a false guesture. */ @@ -121,12 +122,13 @@ class SeekBarViewModel @Inject constructor( /** Listening state (QS open or closed) is used to control polling of progress. */ var listening = true - set(value) = bgExecutor.execute { - if (field != value) { - field = value - checkIfPollingNeeded() + set(value) = + bgExecutor.execute { + if (field != value) { + field = value + checkIfPollingNeeded() + } } - } private var scrubbingChangeListener: ScrubbingChangeListener? = null private var enabledChangeListener: EnabledChangeListener? = null @@ -144,14 +146,13 @@ class SeekBarViewModel @Inject constructor( lateinit var logSeek: () -> Unit - /** - * Event indicating that the user has started interacting with the seek bar. - */ + /** Event indicating that the user has started interacting with the seek bar. */ @AnyThread - fun onSeekStarting() = bgExecutor.execute { - scrubbing = true - isFalseSeek = false - } + fun onSeekStarting() = + bgExecutor.execute { + scrubbing = true + isFalseSeek = false + } /** * Event indicating that the user has moved the seek bar. @@ -159,47 +160,51 @@ class SeekBarViewModel @Inject constructor( * @param position Current location in the track. */ @AnyThread - fun onSeekProgress(position: Long) = bgExecutor.execute { - if (scrubbing) { - // The user hasn't yet finished their touch gesture, so only update the data for visual - // feedback and don't update [controller] yet. - _data = _data.copy(elapsedTime = position.toInt()) - } else { - // The seek progress came from an a11y action and we should immediately update to the - // new position. (a11y actions to change the seekbar position don't trigger - // SeekBar.OnSeekBarChangeListener.onStartTrackingTouch or onStopTrackingTouch.) - onSeek(position) + fun onSeekProgress(position: Long) = + bgExecutor.execute { + if (scrubbing) { + // The user hasn't yet finished their touch gesture, so only update the data for + // visual + // feedback and don't update [controller] yet. + _data = _data.copy(elapsedTime = position.toInt()) + } else { + // The seek progress came from an a11y action and we should immediately update to + // the + // new position. (a11y actions to change the seekbar position don't trigger + // SeekBar.OnSeekBarChangeListener.onStartTrackingTouch or onStopTrackingTouch.) + onSeek(position) + } } - } - /** - * Event indicating that the seek interaction is a false gesture and it should be ignored. - */ + /** Event indicating that the seek interaction is a false gesture and it should be ignored. */ @AnyThread - fun onSeekFalse() = bgExecutor.execute { - if (scrubbing) { - isFalseSeek = true + fun onSeekFalse() = + bgExecutor.execute { + if (scrubbing) { + isFalseSeek = true + } } - } /** * Handle request to change the current position in the media track. * @param position Place to seek to in the track. */ @AnyThread - fun onSeek(position: Long) = bgExecutor.execute { - if (isFalseSeek) { - scrubbing = false - checkPlaybackPosition() - } else { - logSeek() - controller?.transportControls?.seekTo(position) - // Invalidate the cached playbackState to avoid the thumb jumping back to the previous - // position. - playbackState = null - scrubbing = false + fun onSeek(position: Long) = + bgExecutor.execute { + if (isFalseSeek) { + scrubbing = false + checkPlaybackPosition() + } else { + logSeek() + controller?.transportControls?.seekTo(position) + // Invalidate the cached playbackState to avoid the thumb jumping back to the + // previous + // position. + playbackState = null + scrubbing = false + } } - } /** * Updates media information. @@ -216,11 +221,18 @@ class SeekBarViewModel @Inject constructor( val seekAvailable = ((playbackState?.actions ?: 0L) and PlaybackState.ACTION_SEEK_TO) != 0L val position = playbackState?.position?.toInt() val duration = mediaMetadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt() ?: 0 - val playing = NotificationMediaManager - .isPlayingState(playbackState?.state ?: PlaybackState.STATE_NONE) - val enabled = if (playbackState == null || - playbackState?.getState() == PlaybackState.STATE_NONE || - (duration <= 0)) false else true + val playing = + NotificationMediaManager.isPlayingState( + playbackState?.state ?: PlaybackState.STATE_NONE + ) + val enabled = + if ( + playbackState == null || + playbackState?.getState() == PlaybackState.STATE_NONE || + (duration <= 0) + ) + false + else true _data = Progress(enabled, seekAvailable, playing, scrubbing, position, duration) checkIfPollingNeeded() } @@ -231,26 +243,26 @@ class SeekBarViewModel @Inject constructor( * This should be called when the media session behind the controller has been destroyed. */ @AnyThread - fun clearController() = bgExecutor.execute { - controller = null - playbackState = null - cancel?.run() - cancel = null - _data = _data.copy(enabled = false) - } + fun clearController() = + bgExecutor.execute { + controller = null + playbackState = null + cancel?.run() + cancel = null + _data = _data.copy(enabled = false) + } - /** - * Call to clean up any resources. - */ + /** Call to clean up any resources. */ @AnyThread - fun onDestroy() = bgExecutor.execute { - controller = null - playbackState = null - cancel?.run() - cancel = null - scrubbingChangeListener = null - enabledChangeListener = null - } + fun onDestroy() = + bgExecutor.execute { + controller = null + playbackState = null + cancel?.run() + cancel = null + scrubbingChangeListener = null + enabledChangeListener = null + } @WorkerThread private fun checkPlaybackPosition() { @@ -266,8 +278,12 @@ class SeekBarViewModel @Inject constructor( val needed = listening && !scrubbing && playbackState?.isInMotion() ?: false if (needed) { if (cancel == null) { - cancel = bgExecutor.executeRepeatedly(this::checkPlaybackPosition, 0L, - POSITION_UPDATE_INTERVAL_MILLIS) + cancel = + bgExecutor.executeRepeatedly( + this::checkPlaybackPosition, + 0L, + POSITION_UPDATE_INTERVAL_MILLIS + ) } } else { cancel?.run() @@ -353,9 +369,10 @@ class SeekBarViewModel @Inject constructor( // Gesture detector helps decide which touch events to intercept. private val detector = GestureDetectorCompat(bar.context, this) // Velocity threshold used to decide when a fling is considered a false gesture. - private val flingVelocity: Int = ViewConfiguration.get(bar.context).run { - getScaledMinimumFlingVelocity() * MIN_FLING_VELOCITY_SCALE_FACTOR - } + private val flingVelocity: Int = + ViewConfiguration.get(bar.context).run { + getScaledMinimumFlingVelocity() * MIN_FLING_VELOCITY_SCALE_FACTOR + } // Indicates if the gesture should go to the seek bar or if it should be intercepted. private var shouldGoToSeekBar = false @@ -385,9 +402,9 @@ class SeekBarViewModel @Inject constructor( /** * Handle down events that press down on the thumb. * - * On the down action, determine a target box around the thumb to know when a scroll - * gesture starts by clicking on the thumb. The target box will be used by subsequent - * onScroll events. + * On the down action, determine a target box around the thumb to know when a scroll gesture + * starts by clicking on the thumb. The target box will be used by subsequent onScroll + * events. * * Returns true when the down event hits within the target box of the thumb. */ @@ -398,17 +415,19 @@ class SeekBarViewModel @Inject constructor( // TODO: account for thumb offset val progress = bar.getProgress() val range = bar.max - bar.min - val widthFraction = if (range > 0) { - (progress - bar.min).toDouble() / range - } else { - 0.0 - } + val widthFraction = + if (range > 0) { + (progress - bar.min).toDouble() / range + } else { + 0.0 + } val availableWidth = bar.width - padL - padR - val thumbX = if (bar.isLayoutRtl()) { - padL + availableWidth * (1 - widthFraction) - } else { - padL + availableWidth * widthFraction - } + val thumbX = + if (bar.isLayoutRtl()) { + padL + availableWidth * (1 - widthFraction) + } else { + padL + availableWidth * widthFraction + } // Set the min, max boundaries of the thumb box. // I'm cheating by using the height of the seek bar as the width of the box. val halfHeight: Int = bar.height / 2 diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt index c34c1992919f..1a10b18a5a69 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt @@ -35,26 +35,30 @@ class RecommendationViewHolder private constructor(itemView: View) { // Recommendation screen val cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon) - val mediaCoverItems = listOf<ImageView>( - itemView.requireViewById(R.id.media_cover1), - itemView.requireViewById(R.id.media_cover2), - itemView.requireViewById(R.id.media_cover3) - ) - val mediaCoverContainers = listOf<ViewGroup>( - itemView.requireViewById(R.id.media_cover1_container), - itemView.requireViewById(R.id.media_cover2_container), - itemView.requireViewById(R.id.media_cover3_container) - ) - val mediaTitles: List<TextView> = listOf( - itemView.requireViewById(R.id.media_title1), - itemView.requireViewById(R.id.media_title2), - itemView.requireViewById(R.id.media_title3) - ) - val mediaSubtitles: List<TextView> = listOf( - itemView.requireViewById(R.id.media_subtitle1), - itemView.requireViewById(R.id.media_subtitle2), - itemView.requireViewById(R.id.media_subtitle3) - ) + val mediaCoverItems = + listOf<ImageView>( + itemView.requireViewById(R.id.media_cover1), + itemView.requireViewById(R.id.media_cover2), + itemView.requireViewById(R.id.media_cover3) + ) + val mediaCoverContainers = + listOf<ViewGroup>( + itemView.requireViewById(R.id.media_cover1_container), + itemView.requireViewById(R.id.media_cover2_container), + itemView.requireViewById(R.id.media_cover3_container) + ) + val mediaTitles: List<TextView> = + listOf( + itemView.requireViewById(R.id.media_title1), + itemView.requireViewById(R.id.media_title2), + itemView.requireViewById(R.id.media_title3) + ) + val mediaSubtitles: List<TextView> = + listOf( + itemView.requireViewById(R.id.media_subtitle1), + itemView.requireViewById(R.id.media_subtitle2), + itemView.requireViewById(R.id.media_subtitle3) + ) val gutsViewHolder = GutsViewHolder(itemView) @@ -78,13 +82,14 @@ class RecommendationViewHolder private constructor(itemView: View) { * @param inflater LayoutInflater to use to inflate the layout. * @param parent Parent of inflated view. */ - @JvmStatic fun create(inflater: LayoutInflater, parent: ViewGroup): - RecommendationViewHolder { + @JvmStatic + fun create(inflater: LayoutInflater, parent: ViewGroup): RecommendationViewHolder { val itemView = inflater.inflate( R.layout.media_smartspace_recommendations, parent, - false /* attachToRoot */) + false /* attachToRoot */ + ) // Because this media view (a TransitionLayout) is used to measure and layout the views // in various states before being attached to its parent, we can't depend on the default // LAYOUT_DIRECTION_INHERIT to correctly resolve the ltr direction. @@ -93,35 +98,38 @@ class RecommendationViewHolder private constructor(itemView: View) { } // Res Ids for the control components on the recommendation view. - val controlsIds = setOf( - R.id.recommendation_card_icon, - R.id.media_cover1, - R.id.media_cover2, - R.id.media_cover3, - R.id.media_cover1_container, - R.id.media_cover2_container, - R.id.media_cover3_container, - R.id.media_title1, - R.id.media_title2, - R.id.media_title3, - R.id.media_subtitle1, - R.id.media_subtitle2, - R.id.media_subtitle3 - ) + val controlsIds = + setOf( + R.id.recommendation_card_icon, + R.id.media_cover1, + R.id.media_cover2, + R.id.media_cover3, + R.id.media_cover1_container, + R.id.media_cover2_container, + R.id.media_cover3_container, + R.id.media_title1, + R.id.media_title2, + R.id.media_title3, + R.id.media_subtitle1, + R.id.media_subtitle2, + R.id.media_subtitle3 + ) - val mediaTitlesAndSubtitlesIds = setOf( - R.id.media_title1, - R.id.media_title2, - R.id.media_title3, - R.id.media_subtitle1, - R.id.media_subtitle2, - R.id.media_subtitle3 - ) + val mediaTitlesAndSubtitlesIds = + setOf( + R.id.media_title1, + R.id.media_title2, + R.id.media_title3, + R.id.media_subtitle1, + R.id.media_subtitle2, + R.id.media_subtitle3 + ) - val mediaContainersIds = setOf( - R.id.media_cover1_container, - R.id.media_cover2_container, - R.id.media_cover3_container - ) + val mediaContainersIds = + setOf( + R.id.media_cover1_container, + R.id.media_cover2_container, + R.id.media_cover3_container + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt index eed725876349..1df42c641df6 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt @@ -25,55 +25,38 @@ import android.util.Log import androidx.annotation.VisibleForTesting import com.android.internal.logging.InstanceId -@VisibleForTesting -const val KEY_SMARTSPACE_APP_NAME = "KEY_SMARTSPACE_APP_NAME" +@VisibleForTesting const val KEY_SMARTSPACE_APP_NAME = "KEY_SMARTSPACE_APP_NAME" /** State of a Smartspace media recommendations view. */ data class SmartspaceMediaData( - /** - * Unique id of a Smartspace media target. - */ + /** Unique id of a Smartspace media target. */ val targetId: String, - /** - * Indicates if the status is active. - */ + /** Indicates if the status is active. */ val isActive: Boolean, - /** - * Package name of the media recommendations' provider-app. - */ + /** Package name of the media recommendations' provider-app. */ val packageName: String, - /** - * Action to perform when the card is tapped. Also contains the target's extra info. - */ + /** Action to perform when the card is tapped. Also contains the target's extra info. */ val cardAction: SmartspaceAction?, - /** - * List of media recommendations. - */ + /** List of media recommendations. */ val recommendations: List<SmartspaceAction>, - /** - * Intent for the user's initiated dismissal. - */ + /** Intent for the user's initiated dismissal. */ val dismissIntent: Intent?, - /** - * The timestamp in milliseconds that headphone is connected. - */ + /** The timestamp in milliseconds that headphone is connected. */ val headphoneConnectionTimeMillis: Long, - /** - * Instance ID for [MediaUiEventLogger] - */ + /** Instance ID for [MediaUiEventLogger] */ val instanceId: InstanceId ) { /** * Indicates if all the data is valid. * * TODO(b/230333302): Make MediaControlPanel more flexible so that we can display fewer than + * ``` * [NUM_REQUIRED_RECOMMENDATIONS]. + * ``` */ fun isValid() = getValidRecommendations().size >= NUM_REQUIRED_RECOMMENDATIONS - /** - * Returns the list of [recommendations] that have valid data. - */ + /** Returns the list of [recommendations] that have valid data. */ fun getValidRecommendations() = recommendations.filter { it.icon != null } /** Returns the upstream app name if available. */ @@ -92,7 +75,8 @@ data class SmartspaceMediaData( Log.w( TAG, "Package $packageName does not have a main launcher activity. " + - "Fallback to full app name") + "Fallback to full app name" + ) return try { val applicationInfo = packageManager.getApplicationInfo(packageName, /* flags= */ 0) packageManager.getApplicationLabel(applicationInfo) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt index b30555b20547..a7ed69a9ab73 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.media.controls.models.recommendation import android.app.smartspace.SmartspaceTarget @@ -23,7 +39,7 @@ class SmartspaceMediaDataProvider @Inject constructor() : BcSmartspaceDataPlugin smartspaceMediaTargetListeners.remove(smartspaceTargetListener) } - /** Updates Smartspace data and propagates it to any listeners. */ + /** Updates Smartspace data and propagates it to any listeners. */ override fun onTargetsAvailable(targets: List<SmartspaceTarget>) { // Filter out non-media targets. val mediaTargets = mutableListOf<SmartspaceTarget>() diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt index 9d3a1bd2c8c8..ff763d81f950 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt @@ -17,17 +17,15 @@ package com.android.systemui.media.controls.pipeline import android.content.Context - import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.media.InfoMediaManager import com.android.settingslib.media.LocalMediaManager - import javax.inject.Inject -/** - * Factory to create [LocalMediaManager] objects. - */ -class LocalMediaManagerFactory @Inject constructor( +/** Factory to create [LocalMediaManager] objects. */ +class LocalMediaManagerFactory +@Inject +constructor( private val context: Context, private val localBluetoothManager: LocalBluetoothManager? ) { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatest.kt index 1f81d8d20b67..789ef407ea9d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatest.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatest.kt @@ -21,11 +21,9 @@ import com.android.systemui.media.controls.models.player.MediaDeviceData import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData import javax.inject.Inject -/** - * Combines [MediaDataManager.Listener] events with [MediaDeviceManager.Listener] events. - */ -class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener, - MediaDeviceManager.Listener { +/** Combines [MediaDataManager.Listener] events with [MediaDeviceManager.Listener] events. */ +class MediaDataCombineLatest @Inject constructor() : + MediaDataManager.Listener, MediaDeviceManager.Listener { private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf() private val entries: MutableMap<String, Pair<MediaData?, MediaDeviceData?>> = mutableMapOf() @@ -63,11 +61,7 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener, listeners.toSet().forEach { it.onSmartspaceMediaDataRemoved(key, immediately) } } - override fun onMediaDeviceChanged( - key: String, - oldKey: String?, - data: MediaDeviceData? - ) { + override fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?) { if (oldKey != null && oldKey != key && entries.contains(oldKey)) { entries[key] = entries.remove(oldKey)?.first to data update(key, oldKey) @@ -86,9 +80,7 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener, */ fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener) - /** - * Remove a listener registered with addListener. - */ + /** Remove a listener registered with addListener. */ fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener) private fun update(key: String, oldKey: String?) { @@ -96,18 +88,14 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener, if (entry != null && device != null) { val data = entry.copy(device = device) val listenersCopy = listeners.toSet() - listenersCopy.forEach { - it.onMediaDataLoaded(key, oldKey, data) - } + listenersCopy.forEach { it.onMediaDataLoaded(key, oldKey, data) } } } private fun remove(key: String) { entries.remove(key)?.let { val listenersCopy = listeners.toSet() - listenersCopy.forEach { - it.onMediaDataRemoved(key) - } + listenersCopy.forEach { it.onMediaDataRemoved(key) } } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt index b019086a2c6b..45b319b274b2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt @@ -23,9 +23,9 @@ import com.android.internal.annotations.VisibleForTesting import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData +import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.settings.CurrentUserTracker import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.util.time.SystemClock @@ -37,7 +37,8 @@ import kotlin.collections.LinkedHashMap private const val TAG = "MediaDataFilter" private const val DEBUG = true -private const val EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME = ("com.google" + +private const val EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME = + ("com.google" + ".android.apps.gsa.staticplugins.opa.smartspace.ExportedSmartspaceTrampolineActivity") private const val RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY = "resumable_media_max_age_seconds" @@ -46,8 +47,8 @@ private const val RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY = "resumable_media_max_age * available within this time window, smartspace recommendations will be shown instead. */ @VisibleForTesting -internal val SMARTSPACE_MAX_AGE = SystemProperties - .getLong("debug.sysui.smartspace_max_age", TimeUnit.MINUTES.toMillis(30)) +internal val SMARTSPACE_MAX_AGE = + SystemProperties.getLong("debug.sysui.smartspace_max_age", TimeUnit.MINUTES.toMillis(30)) /** * Filters data updates from [MediaDataCombineLatest] based on the current user ID, and handles user @@ -57,7 +58,9 @@ internal val SMARTSPACE_MAX_AGE = SystemProperties * This is added at the end of the pipeline since we may still need to handle callbacks from * background users (e.g. timeouts). */ -class MediaDataFilter @Inject constructor( +class MediaDataFilter +@Inject +constructor( private val context: Context, private val broadcastDispatcher: BroadcastDispatcher, private val broadcastSender: BroadcastSender, @@ -79,12 +82,13 @@ class MediaDataFilter @Inject constructor( private var reactivatedKey: String? = null init { - userTracker = object : CurrentUserTracker(broadcastDispatcher) { - override fun onUserSwitched(newUserId: Int) { - // Post this so we can be sure lockscreenUserManager already got the broadcast - executor.execute { handleUserSwitched(newUserId) } + userTracker = + object : CurrentUserTracker(broadcastDispatcher) { + override fun onUserSwitched(newUserId: Int) { + // Post this so we can be sure lockscreenUserManager already got the broadcast + executor.execute { handleUserSwitched(newUserId) } + } } - } userTracker.startTracking() } @@ -111,9 +115,7 @@ class MediaDataFilter @Inject constructor( userEntries.put(key, data) // Notify listeners - listeners.forEach { - it.onMediaDataLoaded(key, oldKey, data) - } + listeners.forEach { it.onMediaDataLoaded(key, oldKey, data) } } override fun onSmartspaceMediaDataLoaded( @@ -131,14 +133,11 @@ class MediaDataFilter @Inject constructor( smartspaceMediaData = data // Before forwarding the smartspace target, first check if we have recently inactive media - val sorted = userEntries.toSortedMap(compareBy { - userEntries.get(it)?.lastActive ?: -1 - }) + val sorted = userEntries.toSortedMap(compareBy { userEntries.get(it)?.lastActive ?: -1 }) val timeSinceActive = timeSinceActiveForMostRecentMedia(sorted) var smartspaceMaxAgeMillis = SMARTSPACE_MAX_AGE data.cardAction?.let { - val smartspaceMaxAgeSeconds = - it.extras.getLong(RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY, 0) + val smartspaceMaxAgeSeconds = it.extras.getLong(RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY, 0) if (smartspaceMaxAgeSeconds > 0) { smartspaceMaxAgeMillis = TimeUnit.SECONDS.toMillis(smartspaceMaxAgeSeconds) } @@ -155,13 +154,21 @@ class MediaDataFilter @Inject constructor( Log.d(TAG, "reactivating $lastActiveKey instead of smartspace") reactivatedKey = lastActiveKey val mediaData = sorted.get(lastActiveKey)!!.copy(active = true) - logger.logRecommendationActivated(mediaData.appUid, mediaData.packageName, - mediaData.instanceId) + logger.logRecommendationActivated( + mediaData.appUid, + mediaData.packageName, + mediaData.instanceId + ) listeners.forEach { - it.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData, - receivedSmartspaceCardLatency = + it.onMediaDataLoaded( + lastActiveKey, + lastActiveKey, + mediaData, + receivedSmartspaceCardLatency = (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis) - .toInt(), isSsReactivated = true) + .toInt(), + isSsReactivated = true + ) } } } else { @@ -173,8 +180,10 @@ class MediaDataFilter @Inject constructor( Log.d(TAG, "Invalid recommendation data. Skip showing the rec card") return } - logger.logRecommendationAdded(smartspaceMediaData.packageName, - smartspaceMediaData.instanceId) + logger.logRecommendationAdded( + smartspaceMediaData.packageName, + smartspaceMediaData.instanceId + ) listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) } } @@ -182,9 +191,7 @@ class MediaDataFilter @Inject constructor( allEntries.remove(key) userEntries.remove(key)?.let { // Only notify listeners if something actually changed - listeners.forEach { - it.onMediaDataRemoved(key) - } + listeners.forEach { it.onMediaDataRemoved(key) } } } @@ -197,16 +204,17 @@ class MediaDataFilter @Inject constructor( // Notify listeners to update with actual active value userEntries.get(lastActiveKey)?.let { mediaData -> listeners.forEach { - it.onMediaDataLoaded( - lastActiveKey, lastActiveKey, mediaData, immediately) + it.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData, immediately) } } } if (smartspaceMediaData.isActive) { - smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy( - targetId = smartspaceMediaData.targetId, - instanceId = smartspaceMediaData.instanceId) + smartspaceMediaData = + EMPTY_SMARTSPACE_MEDIA_DATA.copy( + targetId = smartspaceMediaData.targetId, + instanceId = smartspaceMediaData.instanceId + ) } listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) } } @@ -221,25 +229,19 @@ class MediaDataFilter @Inject constructor( userEntries.clear() keyCopy.forEach { if (DEBUG) Log.d(TAG, "Removing $it after user change") - listenersCopy.forEach { listener -> - listener.onMediaDataRemoved(it) - } + listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) } } allEntries.forEach { (key, data) -> if (lockscreenUserManager.isCurrentProfile(data.userId)) { if (DEBUG) Log.d(TAG, "Re-adding $key after user change") userEntries.put(key, data) - listenersCopy.forEach { listener -> - listener.onMediaDataLoaded(key, null, data) - } + listenersCopy.forEach { listener -> listener.onMediaDataLoaded(key, null, data) } } } } - /** - * Invoked when the user has dismissed the media carousel - */ + /** Invoked when the user has dismissed the media carousel */ fun onSwipeToDismiss() { if (DEBUG) Log.d(TAG, "Media carousel swiped away") val mediaKeys = userEntries.keys.toSet() @@ -250,55 +252,52 @@ class MediaDataFilter @Inject constructor( if (smartspaceMediaData.isActive) { val dismissIntent = smartspaceMediaData.dismissIntent if (dismissIntent == null) { - Log.w(TAG, "Cannot create dismiss action click action: " + - "extras missing dismiss_intent.") - } else if (dismissIntent.getComponent() != null && - dismissIntent.getComponent().getClassName() - == EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME) { + Log.w( + TAG, + "Cannot create dismiss action click action: " + "extras missing dismiss_intent." + ) + } else if ( + dismissIntent.getComponent() != null && + dismissIntent.getComponent().getClassName() == + EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME + ) { // Dismiss the card Smartspace data through Smartspace trampoline activity. context.startActivity(dismissIntent) } else { broadcastSender.sendBroadcast(dismissIntent) } - smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy( - targetId = smartspaceMediaData.targetId, - instanceId = smartspaceMediaData.instanceId) - mediaDataManager.dismissSmartspaceRecommendation(smartspaceMediaData.targetId, - delay = 0L) + smartspaceMediaData = + EMPTY_SMARTSPACE_MEDIA_DATA.copy( + targetId = smartspaceMediaData.targetId, + instanceId = smartspaceMediaData.instanceId + ) + mediaDataManager.dismissSmartspaceRecommendation( + smartspaceMediaData.targetId, + delay = 0L + ) } } - /** - * Are there any active media entries, including the recommendation? - */ - fun hasActiveMediaOrRecommendation() = userEntries.any { it.value.active } || + /** Are there any active media entries, including the recommendation? */ + fun hasActiveMediaOrRecommendation() = + userEntries.any { it.value.active } || (smartspaceMediaData.isActive && (smartspaceMediaData.isValid() || reactivatedKey != null)) - /** - * Are there any media entries we should display? - */ - fun hasAnyMediaOrRecommendation() = userEntries.isNotEmpty() || - (smartspaceMediaData.isActive && smartspaceMediaData.isValid()) + /** Are there any media entries we should display? */ + fun hasAnyMediaOrRecommendation() = + userEntries.isNotEmpty() || (smartspaceMediaData.isActive && smartspaceMediaData.isValid()) - /** - * Are there any media notifications active (excluding the recommendation)? - */ + /** Are there any media notifications active (excluding the recommendation)? */ fun hasActiveMedia() = userEntries.any { it.value.active } - /** - * Are there any media entries we should display (excluding the recommendation)? - */ + /** Are there any media entries we should display (excluding the recommendation)? */ fun hasAnyMedia() = userEntries.isNotEmpty() - /** - * Add a listener for filtered [MediaData] changes - */ + /** Add a listener for filtered [MediaData] changes */ fun addListener(listener: MediaDataManager.Listener) = _listeners.add(listener) - /** - * Remove a listener that was registered with addListener - */ + /** Remove a listener that was registered with addListener */ fun removeListener(listener: MediaDataManager.Listener) = _listeners.remove(listener) /** @@ -318,8 +317,6 @@ class MediaDataFilter @Inject constructor( val now = systemClock.elapsedRealtime() val lastActiveKey = sortedEntries.lastKey() // most recently active - return sortedEntries.get(lastActiveKey)?.let { - now - it.lastActive - } ?: Long.MAX_VALUE + return sortedEntries.get(lastActiveKey)?.let { now - it.lastActive } ?: Long.MAX_VALUE } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt index 0d1699275d2e..14dd99023b92 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt @@ -57,10 +57,6 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager -import com.android.systemui.media.controls.util.MediaControllerFactory -import com.android.systemui.media.controls.util.MediaFlags -import com.android.systemui.media.controls.resume.MediaResumeListener -import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.media.controls.models.player.MediaAction import com.android.systemui.media.controls.models.player.MediaButton import com.android.systemui.media.controls.models.player.MediaData @@ -68,6 +64,10 @@ import com.android.systemui.media.controls.models.player.MediaDeviceData import com.android.systemui.media.controls.models.player.MediaViewHolder import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider +import com.android.systemui.media.controls.resume.MediaResumeListener +import com.android.systemui.media.controls.util.MediaControllerFactory +import com.android.systemui.media.controls.util.MediaFlags +import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState @@ -86,17 +86,19 @@ import java.util.concurrent.Executors import javax.inject.Inject // URI fields to try loading album art from -private val ART_URIS = arrayOf( +private val ART_URIS = + arrayOf( MediaMetadata.METADATA_KEY_ALBUM_ART_URI, MediaMetadata.METADATA_KEY_ART_URI, MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI -) + ) private const val TAG = "MediaDataManager" private const val DEBUG = true private const val EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY = "dismiss_intent" -private val LOADING = MediaData( +private val LOADING = + MediaData( userId = -1, initialized = false, app = null, @@ -113,37 +115,41 @@ private val LOADING = MediaData( active = true, resumeAction = null, instanceId = InstanceId.fakeInstanceId(-1), - appUid = Process.INVALID_UID) + appUid = Process.INVALID_UID + ) @VisibleForTesting -internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData( - targetId = "INVALID", - isActive = false, - packageName = "INVALID", - cardAction = null, - recommendations = emptyList(), - dismissIntent = null, - headphoneConnectionTimeMillis = 0, - instanceId = InstanceId.fakeInstanceId(-1)) +internal val EMPTY_SMARTSPACE_MEDIA_DATA = + SmartspaceMediaData( + targetId = "INVALID", + isActive = false, + packageName = "INVALID", + cardAction = null, + recommendations = emptyList(), + dismissIntent = null, + headphoneConnectionTimeMillis = 0, + instanceId = InstanceId.fakeInstanceId(-1) + ) fun isMediaNotification(sbn: StatusBarNotification): Boolean { return sbn.notification.isMediaNotification() } /** - * Allow recommendations from smartspace to show in media controls. - * Requires [Utils.useQsMediaPlayer] to be enabled. - * On by default, but can be disabled by setting to 0 + * Allow recommendations from smartspace to show in media controls. Requires + * [Utils.useQsMediaPlayer] to be enabled. On by default, but can be disabled by setting to 0 */ private fun allowMediaRecommendations(context: Context): Boolean { - val flag = Settings.Secure.getInt(context.contentResolver, - Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 1) + val flag = + Settings.Secure.getInt( + context.contentResolver, + Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, + 1 + ) return Utils.useQsMediaPlayer(context) && flag > 0 } -/** - * A class that facilitates management and loading of Media Data, ready for binding. - */ +/** A class that facilitates management and loading of Media Data, ready for binding. */ @SysUISingleton class MediaDataManager( private val context: Context, @@ -170,24 +176,24 @@ class MediaDataManager( companion object { // UI surface label for subscribing Smartspace updates. - @JvmField - val SMARTSPACE_UI_SURFACE_LABEL = "media_data_manager" + @JvmField val SMARTSPACE_UI_SURFACE_LABEL = "media_data_manager" // Smartspace package name's extra key. - @JvmField - val EXTRAS_MEDIA_SOURCE_PACKAGE_NAME = "package_name" + @JvmField val EXTRAS_MEDIA_SOURCE_PACKAGE_NAME = "package_name" // Maximum number of actions allowed in compact view - @JvmField - val MAX_COMPACT_ACTIONS = 3 + @JvmField val MAX_COMPACT_ACTIONS = 3 // Maximum number of actions allowed in expanded view - @JvmField - val MAX_NOTIFICATION_ACTIONS = MediaViewHolder.genericButtonIds.size + @JvmField val MAX_NOTIFICATION_ACTIONS = MediaViewHolder.genericButtonIds.size } - private val themeText = com.android.settingslib.Utils.getColorAttr(context, - com.android.internal.R.attr.textColorPrimary).defaultColor + private val themeText = + com.android.settingslib.Utils.getColorAttr( + context, + com.android.internal.R.attr.textColorPrimary + ) + .defaultColor // Internal listeners are part of the internal pipeline. External listeners (those registered // with [MediaDeviceManager.addListener]) receive events after they have propagated through @@ -203,9 +209,7 @@ class MediaDataManager( private var smartspaceSession: SmartspaceSession? = null private var allowMediaRecommendations = allowMediaRecommendations(context) - /** - * Check whether this notification is an RCN - */ + /** Check whether this notification is an RCN */ private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean { return sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE) } @@ -230,29 +234,44 @@ class MediaDataManager( tunerService: TunerService, mediaFlags: MediaFlags, logger: MediaUiEventLogger - ) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory, - broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener, - mediaSessionBasedFilter, mediaDeviceManager, mediaDataCombineLatest, mediaDataFilter, - activityStarter, smartspaceMediaDataProvider, Utils.useMediaResumption(context), - Utils.useQsMediaPlayer(context), clock, tunerService, mediaFlags, logger) - - private val appChangeReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - when (intent.action) { - Intent.ACTION_PACKAGES_SUSPENDED -> { - val packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST) - packages?.forEach { - removeAllForPackage(it) + ) : this( + context, + backgroundExecutor, + foregroundExecutor, + mediaControllerFactory, + broadcastDispatcher, + dumpManager, + mediaTimeoutListener, + mediaResumeListener, + mediaSessionBasedFilter, + mediaDeviceManager, + mediaDataCombineLatest, + mediaDataFilter, + activityStarter, + smartspaceMediaDataProvider, + Utils.useMediaResumption(context), + Utils.useQsMediaPlayer(context), + clock, + tunerService, + mediaFlags, + logger + ) + + private val appChangeReceiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + when (intent.action) { + Intent.ACTION_PACKAGES_SUSPENDED -> { + val packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST) + packages?.forEach { removeAllForPackage(it) } } - } - Intent.ACTION_PACKAGE_REMOVED, Intent.ACTION_PACKAGE_RESTARTED -> { - intent.data?.encodedSchemeSpecificPart?.let { - removeAllForPackage(it) + Intent.ACTION_PACKAGE_REMOVED, + Intent.ACTION_PACKAGE_RESTARTED -> { + intent.data?.encodedSchemeSpecificPart?.let { removeAllForPackage(it) } } } } } - } init { dumpManager.registerDumpable(TAG, this) @@ -273,20 +292,23 @@ class MediaDataManager( // Set up links back into the pipeline for listeners that need to send events upstream. mediaTimeoutListener.timeoutCallback = { key: String, timedOut: Boolean -> - setTimedOut(key, timedOut) } + setTimedOut(key, timedOut) + } mediaTimeoutListener.stateCallback = { key: String, state: PlaybackState -> - updateState(key, state) } + updateState(key, state) + } mediaResumeListener.setManager(this) mediaDataFilter.mediaDataManager = this val suspendFilter = IntentFilter(Intent.ACTION_PACKAGES_SUSPENDED) broadcastDispatcher.registerReceiver(appChangeReceiver, suspendFilter, null, UserHandle.ALL) - val uninstallFilter = IntentFilter().apply { - addAction(Intent.ACTION_PACKAGE_REMOVED) - addAction(Intent.ACTION_PACKAGE_RESTARTED) - addDataScheme("package") - } + val uninstallFilter = + IntentFilter().apply { + addAction(Intent.ACTION_PACKAGE_REMOVED) + addAction(Intent.ACTION_PACKAGE_RESTARTED) + addDataScheme("package") + } // BroadcastDispatcher does not allow filters with data schemes context.registerReceiver(appChangeReceiver, uninstallFilter) @@ -294,8 +316,10 @@ class MediaDataManager( smartspaceMediaDataProvider.registerListener(this) val smartspaceManager: SmartspaceManager = context.getSystemService(SmartspaceManager::class.java) - smartspaceSession = smartspaceManager.createSmartspaceSession( - SmartspaceConfig.Builder(context, SMARTSPACE_UI_SURFACE_LABEL).build()) + smartspaceSession = + smartspaceManager.createSmartspaceSession( + SmartspaceConfig.Builder(context, SMARTSPACE_UI_SURFACE_LABEL).build() + ) smartspaceSession?.let { it.addOnTargetsAvailableListener( // Use a new thread listening to Smartspace updates instead of using the existing @@ -307,17 +331,24 @@ class MediaDataManager( Executors.newCachedThreadPool(), SmartspaceSession.OnTargetsAvailableListener { targets -> smartspaceMediaDataProvider.onTargetsAvailable(targets) - }) + } + ) } smartspaceSession?.let { it.requestSmartspaceUpdate() } - tunerService.addTunable(object : TunerService.Tunable { - override fun onTuningChanged(key: String?, newValue: String?) { - allowMediaRecommendations = allowMediaRecommendations(context) - if (!allowMediaRecommendations) { - dismissSmartspaceRecommendation(key = smartspaceMediaData.targetId, delay = 0L) + tunerService.addTunable( + object : TunerService.Tunable { + override fun onTuningChanged(key: String?, newValue: String?) { + allowMediaRecommendations = allowMediaRecommendations(context) + if (!allowMediaRecommendations) { + dismissSmartspaceRecommendation( + key = smartspaceMediaData.targetId, + delay = 0L + ) + } } - } - }, Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION) + }, + Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION + ) } fun destroy() { @@ -332,10 +363,7 @@ class MediaDataManager( val oldKey = findExistingEntry(key, sbn.packageName) if (oldKey == null) { val instanceId = logger.getNewInstanceId() - val temp = LOADING.copy( - packageName = sbn.packageName, - instanceId = instanceId - ) + val temp = LOADING.copy(packageName = sbn.packageName, instanceId = instanceId) mediaEntries.put(key, temp) logEvent = true } else if (oldKey != key) { @@ -353,9 +381,7 @@ class MediaDataManager( private fun removeAllForPackage(packageName: String) { Assert.isMainThread() val toRemove = mediaEntries.filter { it.value.packageName == packageName } - toRemove.forEach { - removeEntry(it.key) - } + toRemove.forEach { removeEntry(it.key) } } fun setResumeAction(key: String, action: Runnable?) { @@ -377,32 +403,41 @@ class MediaDataManager( // Resume controls don't have a notification key, so store by package name instead if (!mediaEntries.containsKey(packageName)) { val instanceId = logger.getNewInstanceId() - val appUid = try { - context.packageManager.getApplicationInfo(packageName, 0)?.uid!! - } catch (e: PackageManager.NameNotFoundException) { - Log.w(TAG, "Could not get app UID for $packageName", e) - Process.INVALID_UID - } + val appUid = + try { + context.packageManager.getApplicationInfo(packageName, 0)?.uid!! + } catch (e: PackageManager.NameNotFoundException) { + Log.w(TAG, "Could not get app UID for $packageName", e) + Process.INVALID_UID + } - val resumeData = LOADING.copy( - packageName = packageName, - resumeAction = action, - hasCheckedForResume = true, - instanceId = instanceId, - appUid = appUid - ) + val resumeData = + LOADING.copy( + packageName = packageName, + resumeAction = action, + hasCheckedForResume = true, + instanceId = instanceId, + appUid = appUid + ) mediaEntries.put(packageName, resumeData) logger.logResumeMediaAdded(appUid, packageName, instanceId) } backgroundExecutor.execute { - loadMediaDataInBgForResumption(userId, desc, action, token, appName, appIntent, - packageName) + loadMediaDataInBgForResumption( + userId, + desc, + action, + token, + appName, + appIntent, + packageName + ) } } /** - * Check if there is an existing entry that matches the key or package name. - * Returns the key that matches, or null if not found. + * Check if there is an existing entry that matches the key or package name. Returns the key + * that matches, or null if not found. */ private fun findExistingEntry(key: String, packageName: String): String? { if (mediaEntries.containsKey(key)) { @@ -421,32 +456,24 @@ class MediaDataManager( oldKey: String?, logEvent: Boolean = false ) { - backgroundExecutor.execute { - loadMediaDataInBg(key, sbn, oldKey, logEvent) - } + backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, logEvent) } } - /** - * Add a listener for changes in this class - */ + /** Add a listener for changes in this class */ fun addListener(listener: Listener) { // mediaDataFilter is the current end of the internal pipeline. Register external // listeners as listeners to it. mediaDataFilter.addListener(listener) } - /** - * Remove a listener for changes in this class - */ + /** Remove a listener for changes in this class */ fun removeListener(listener: Listener) { // Since mediaDataFilter is the current end of the internal pipelie, external listeners // have been registered to it. So, they need to be removed from it too. mediaDataFilter.removeListener(listener) } - /** - * Add a listener for internal events. - */ + /** Add a listener for internal events. */ private fun addInternalListener(listener: Listener) = internalListeners.add(listener) /** @@ -494,8 +521,8 @@ class MediaDataManager( } /** - * Called whenever the player has been paused or stopped for a while, or swiped from QQS. - * This will make the player not active anymore, hiding it from QQS and Keyguard. + * Called whenever the player has been paused or stopped for a while, or swiped from QQS. This + * will make the player not active anymore, hiding it from QQS and Keyguard. * @see MediaData.active */ internal fun setTimedOut(key: String, timedOut: Boolean, forceUpdate: Boolean = false) { @@ -517,9 +544,7 @@ class MediaDataManager( } } - /** - * Called when the player's [PlaybackState] has been updated with new actions and/or state - */ + /** Called when the player's [PlaybackState] has been updated with new actions and/or state */ private fun updateState(key: String, state: PlaybackState) { mediaEntries.get(key)?.let { val token = it.token @@ -527,22 +552,23 @@ class MediaDataManager( if (DEBUG) Log.d(TAG, "State updated, but token was null") return } - val actions = createActionsFromState(it.packageName, - mediaControllerFactory.create(it.token), UserHandle(it.userId)) + val actions = + createActionsFromState( + it.packageName, + mediaControllerFactory.create(it.token), + UserHandle(it.userId) + ) // Control buttons // If flag is enabled and controller has a PlaybackState, // create actions from session info // otherwise, no need to update semantic actions. - val data = if (actions != null) { - it.copy( - semanticActions = actions, - isPlaying = isPlayingState(state.state)) - } else { - it.copy( - isPlaying = isPlayingState(state.state) - ) - } + val data = + if (actions != null) { + it.copy(semanticActions = actions, isPlaying = isPlayingState(state.state)) + } else { + it.copy(isPlaying = isPlayingState(state.state)) + } if (DEBUG) Log.d(TAG, "State updated outside of notification") onMediaDataLoaded(key, key, data) } @@ -555,9 +581,7 @@ class MediaDataManager( notifyMediaDataRemoved(key) } - /** - * Dismiss a media entry. Returns false if the key was not found. - */ + /** Dismiss a media entry. Returns false if the key was not found. */ fun dismissMediaData(key: String, delay: Long): Boolean { val existed = mediaEntries[key] != null backgroundExecutor.execute { @@ -575,9 +599,8 @@ class MediaDataManager( } /** - * Called whenever the recommendation has been expired, or swiped from QQS. - * This will make the recommendation view to not be shown anymore during this headphone - * connection session. + * Called whenever the recommendation has been expired, or swiped from QQS. This will make the + * recommendation view to not be shown anymore during this headphone connection session. */ fun dismissSmartspaceRecommendation(key: String, delay: Long) { if (smartspaceMediaData.targetId != key || !smartspaceMediaData.isValid()) { @@ -587,13 +610,16 @@ class MediaDataManager( if (DEBUG) Log.d(TAG, "Dismissing Smartspace media target") if (smartspaceMediaData.isActive) { - smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy( - targetId = smartspaceMediaData.targetId, - instanceId = smartspaceMediaData.instanceId) + smartspaceMediaData = + EMPTY_SMARTSPACE_MEDIA_DATA.copy( + targetId = smartspaceMediaData.targetId, + instanceId = smartspaceMediaData.instanceId + ) } foregroundExecutor.executeDelayed( - { notifySmartspaceMediaDataRemoved( - smartspaceMediaData.targetId, immediately = true) }, delay) + { notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = true) }, + delay + ) } private fun loadMediaDataInBgForResumption( @@ -621,11 +647,12 @@ class MediaDataManager( if (artworkBitmap == null && desc.iconUri != null) { artworkBitmap = loadBitmapFromUri(desc.iconUri!!) } - val artworkIcon = if (artworkBitmap != null) { - Icon.createWithBitmap(artworkBitmap) - } else { - null - } + val artworkIcon = + if (artworkBitmap != null) { + Icon.createWithBitmap(artworkBitmap) + } else { + null + } val currentEntry = mediaEntries.get(packageName) val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId() @@ -634,13 +661,34 @@ class MediaDataManager( val mediaAction = getResumeMediaAction(resumeAction) val lastActive = systemClock.elapsedRealtime() foregroundExecutor.execute { - onMediaDataLoaded(packageName, null, MediaData(userId, true, appName, - null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0), - MediaButton(playOrPause = mediaAction), packageName, token, appIntent, - device = null, active = false, - resumeAction = resumeAction, resumption = true, notificationKey = packageName, - hasCheckedForResume = true, lastActive = lastActive, instanceId = instanceId, - appUid = appUid)) + onMediaDataLoaded( + packageName, + null, + MediaData( + userId, + true, + appName, + null, + desc.subtitle, + desc.title, + artworkIcon, + listOf(mediaAction), + listOf(0), + MediaButton(playOrPause = mediaAction), + packageName, + token, + appIntent, + device = null, + active = false, + resumeAction = resumeAction, + resumption = true, + notificationKey = packageName, + hasCheckedForResume = true, + lastActive = lastActive, + instanceId = instanceId, + appUid = appUid + ) + ) } } @@ -650,8 +698,11 @@ class MediaDataManager( oldKey: String?, logEvent: Boolean = false ) { - val token = sbn.notification.extras.getParcelable( - Notification.EXTRA_MEDIA_SESSION, MediaSession.Token::class.java) + val token = + sbn.notification.extras.getParcelable( + Notification.EXTRA_MEDIA_SESSION, + MediaSession.Token::class.java + ) if (token == null) { return } @@ -659,10 +710,12 @@ class MediaDataManager( val metadata = mediaController.metadata val notif: Notification = sbn.notification - val appInfo = notif.extras.getParcelable( - Notification.EXTRA_BUILDER_APPLICATION_INFO, - ApplicationInfo::class.java - ) ?: getAppInfoFromPackage(sbn.packageName) + val appInfo = + notif.extras.getParcelable( + Notification.EXTRA_BUILDER_APPLICATION_INFO, + ApplicationInfo::class.java + ) + ?: getAppInfoFromPackage(sbn.packageName) // Album art var artworkBitmap = metadata?.let { loadBitmapFromUri(it) } @@ -672,11 +725,12 @@ class MediaDataManager( if (artworkBitmap == null) { artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART) } - val artWorkIcon = if (artworkBitmap == null) { - notif.getLargeIcon() - } else { - Icon.createWithBitmap(artworkBitmap) - } + val artWorkIcon = + if (artworkBitmap == null) { + notif.getLargeIcon() + } else { + Icon.createWithBitmap(artworkBitmap) + } // App name val appName = getAppName(sbn, appInfo) @@ -705,17 +759,27 @@ class MediaDataManager( val extras = sbn.notification.extras val deviceName = extras.getCharSequence(Notification.EXTRA_MEDIA_REMOTE_DEVICE, null) val deviceIcon = extras.getInt(Notification.EXTRA_MEDIA_REMOTE_ICON, -1) - val deviceIntent = extras.getParcelable( - Notification.EXTRA_MEDIA_REMOTE_INTENT, PendingIntent::class.java) + val deviceIntent = + extras.getParcelable( + Notification.EXTRA_MEDIA_REMOTE_INTENT, + PendingIntent::class.java + ) Log.d(TAG, "$key is RCN for $deviceName") if (deviceName != null && deviceIcon > -1) { // Name and icon must be present, but intent may be null val enabled = deviceIntent != null && deviceIntent.isActivity - val deviceDrawable = Icon.createWithResource(sbn.packageName, deviceIcon) + val deviceDrawable = + Icon.createWithResource(sbn.packageName, deviceIcon) .loadDrawable(sbn.getPackageContext(context)) - device = MediaDeviceData(enabled, deviceDrawable, deviceName, deviceIntent, - showBroadcastButton = false) + device = + MediaDeviceData( + enabled, + deviceDrawable, + deviceName, + deviceIntent, + showBroadcastButton = false + ) } } @@ -732,10 +796,13 @@ class MediaDataManager( } val playbackLocation = - if (isRemoteCastNotification(sbn)) MediaData.PLAYBACK_CAST_REMOTE - else if (mediaController.playbackInfo?.playbackType == - MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) MediaData.PLAYBACK_LOCAL - else MediaData.PLAYBACK_CAST_LOCAL + if (isRemoteCastNotification(sbn)) MediaData.PLAYBACK_CAST_REMOTE + else if ( + mediaController.playbackInfo?.playbackType == + MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL + ) + MediaData.PLAYBACK_LOCAL + else MediaData.PLAYBACK_CAST_LOCAL val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) } ?: null val currentEntry = mediaEntries.get(key) @@ -753,13 +820,36 @@ class MediaDataManager( val resumeAction: Runnable? = mediaEntries[key]?.resumeAction val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true val active = mediaEntries[key]?.active ?: true - onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, appName, - smallIcon, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed, - semanticActions, sbn.packageName, token, notif.contentIntent, device, - active, resumeAction = resumeAction, playbackLocation = playbackLocation, - notificationKey = key, hasCheckedForResume = hasCheckedForResume, - isPlaying = isPlaying, isClearable = sbn.isClearable(), - lastActive = lastActive, instanceId = instanceId, appUid = appUid)) + onMediaDataLoaded( + key, + oldKey, + MediaData( + sbn.normalizedUserId, + true, + appName, + smallIcon, + artist, + song, + artWorkIcon, + actionIcons, + actionsToShowCollapsed, + semanticActions, + sbn.packageName, + token, + notif.contentIntent, + device, + active, + resumeAction = resumeAction, + playbackLocation = playbackLocation, + notificationKey = key, + hasCheckedForResume = hasCheckedForResume, + isPlaying = isPlaying, + isClearable = sbn.isClearable(), + lastActive = lastActive, + instanceId = instanceId, + appUid = appUid + ) + ) } } @@ -785,27 +875,33 @@ class MediaDataManager( } } - /** - * Generate action buttons based on notification actions - */ - private fun createActionsFromNotification(sbn: StatusBarNotification): - Pair<List<MediaAction>, List<Int>> { + /** Generate action buttons based on notification actions */ + private fun createActionsFromNotification( + sbn: StatusBarNotification + ): Pair<List<MediaAction>, List<Int>> { val notif = sbn.notification val actionIcons: MutableList<MediaAction> = ArrayList() val actions = notif.actions - var actionsToShowCollapsed = notif.extras.getIntArray( - Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList() ?: mutableListOf() + var actionsToShowCollapsed = + notif.extras.getIntArray(Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList() + ?: mutableListOf() if (actionsToShowCollapsed.size > MAX_COMPACT_ACTIONS) { - Log.e(TAG, "Too many compact actions for ${sbn.key}," + - "limiting to first $MAX_COMPACT_ACTIONS") + Log.e( + TAG, + "Too many compact actions for ${sbn.key}," + + "limiting to first $MAX_COMPACT_ACTIONS" + ) actionsToShowCollapsed = actionsToShowCollapsed.subList(0, MAX_COMPACT_ACTIONS) } if (actions != null) { for ((index, action) in actions.withIndex()) { if (index == MAX_NOTIFICATION_ACTIONS) { - Log.w(TAG, "Too many notification actions for ${sbn.key}," + - " limiting to first $MAX_NOTIFICATION_ACTIONS") + Log.w( + TAG, + "Too many notification actions for ${sbn.key}," + + " limiting to first $MAX_NOTIFICATION_ACTIONS" + ) break } if (action.getIcon() == null) { @@ -813,33 +909,38 @@ class MediaDataManager( actionsToShowCollapsed.remove(index) continue } - val runnable = if (action.actionIntent != null) { - Runnable { - if (action.actionIntent.isActivity) { - activityStarter.startPendingIntentDismissingKeyguard( - action.actionIntent) - } else if (action.isAuthenticationRequired()) { - activityStarter.dismissKeyguardThenExecute({ - var result = sendPendingIntent(action.actionIntent) - result - }, {}, true) - } else { - sendPendingIntent(action.actionIntent) + val runnable = + if (action.actionIntent != null) { + Runnable { + if (action.actionIntent.isActivity) { + activityStarter.startPendingIntentDismissingKeyguard( + action.actionIntent + ) + } else if (action.isAuthenticationRequired()) { + activityStarter.dismissKeyguardThenExecute( + { + var result = sendPendingIntent(action.actionIntent) + result + }, + {}, + true + ) + } else { + sendPendingIntent(action.actionIntent) + } } + } else { + null } - } else { - null - } - val mediaActionIcon = if (action.getIcon()?.getType() == Icon.TYPE_RESOURCE) { - Icon.createWithResource(sbn.packageName, action.getIcon()!!.getResId()) - } else { - action.getIcon() - }.setTint(themeText).loadDrawable(context) - val mediaAction = MediaAction( - mediaActionIcon, - runnable, - action.title, - null) + val mediaActionIcon = + if (action.getIcon()?.getType() == Icon.TYPE_RESOURCE) { + Icon.createWithResource(sbn.packageName, action.getIcon()!!.getResId()) + } else { + action.getIcon() + } + .setTint(themeText) + .loadDrawable(context) + val mediaAction = MediaAction(mediaActionIcon, runnable, action.title, null) actionIcons.add(mediaAction) } } @@ -852,7 +953,9 @@ class MediaDataManager( * @param packageName Package name for the media app * @param controller MediaController for the current session * @return a Pair consisting of a list of media actions, and a list of ints representing which + * ``` * of those actions should be shown in the compact player + * ``` */ private fun createActionsFromState( packageName: String, @@ -865,59 +968,69 @@ class MediaDataManager( } // First, check for standard actions - val playOrPause = if (isConnectingState(state.state)) { - // Spinner needs to be animating to render anything. Start it here. - val drawable = context.getDrawable( - com.android.internal.R.drawable.progress_small_material) - (drawable as Animatable).start() - MediaAction( - drawable, - null, // no action to perform when clicked - context.getString(R.string.controls_media_button_connecting), - context.getDrawable(R.drawable.ic_media_connecting_container), - // Specify a rebind id to prevent the spinner from restarting on later binds. - com.android.internal.R.drawable.progress_small_material - ) - } else if (isPlayingState(state.state)) { - getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE) - } else { - getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY) - } - val prevButton = getStandardAction(controller, state.actions, - PlaybackState.ACTION_SKIP_TO_PREVIOUS) - val nextButton = getStandardAction(controller, state.actions, - PlaybackState.ACTION_SKIP_TO_NEXT) + val playOrPause = + if (isConnectingState(state.state)) { + // Spinner needs to be animating to render anything. Start it here. + val drawable = + context.getDrawable(com.android.internal.R.drawable.progress_small_material) + (drawable as Animatable).start() + MediaAction( + drawable, + null, // no action to perform when clicked + context.getString(R.string.controls_media_button_connecting), + context.getDrawable(R.drawable.ic_media_connecting_container), + // Specify a rebind id to prevent the spinner from restarting on later binds. + com.android.internal.R.drawable.progress_small_material + ) + } else if (isPlayingState(state.state)) { + getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE) + } else { + getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY) + } + val prevButton = + getStandardAction(controller, state.actions, PlaybackState.ACTION_SKIP_TO_PREVIOUS) + val nextButton = + getStandardAction(controller, state.actions, PlaybackState.ACTION_SKIP_TO_NEXT) // Then, create a way to build any custom actions that will be needed - val customActions = state.customActions.asSequence().filterNotNull().map { - getCustomAction(state, packageName, controller, it) - }.iterator() + val customActions = + state.customActions + .asSequence() + .filterNotNull() + .map { getCustomAction(state, packageName, controller, it) } + .iterator() fun nextCustomAction() = if (customActions.hasNext()) customActions.next() else null // Finally, assign the remaining button slots: play/pause A B C D // A = previous, else custom action (if not reserved) // B = next, else custom action (if not reserved) // C and D are always custom actions - val reservePrev = controller.extras?.getBoolean( - MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV) == true - val reserveNext = controller.extras?.getBoolean( - MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT) == true - - val prevOrCustom = if (prevButton != null) { - prevButton - } else if (!reservePrev) { - nextCustomAction() - } else { - null - } + val reservePrev = + controller.extras?.getBoolean( + MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV + ) == true + val reserveNext = + controller.extras?.getBoolean( + MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT + ) == true + + val prevOrCustom = + if (prevButton != null) { + prevButton + } else if (!reservePrev) { + nextCustomAction() + } else { + null + } - val nextOrCustom = if (nextButton != null) { - nextButton - } else if (!reserveNext) { - nextCustomAction() - } else { - null - } + val nextOrCustom = + if (nextButton != null) { + nextButton + } else if (!reserveNext) { + nextCustomAction() + } else { + null + } return MediaButton( playOrPause, @@ -936,11 +1049,14 @@ class MediaDataManager( * @param controller MediaController for the session * @param stateActions The actions included with the session's [PlaybackState] * @param action A [PlaybackState.Actions] value representing what action to generate. One of: + * ``` * [PlaybackState.ACTION_PLAY] * [PlaybackState.ACTION_PAUSE] * [PlaybackState.ACTION_SKIP_TO_PREVIOUS] * [PlaybackState.ACTION_SKIP_TO_NEXT] - * @return A [MediaAction] with correct values set, or null if the state doesn't support it + * @return + * ``` + * A [MediaAction] with correct values set, or null if the state doesn't support it */ private fun getStandardAction( controller: MediaController, @@ -988,20 +1104,18 @@ class MediaDataManager( } } - /** - * Check whether the actions from a [PlaybackState] include a specific action - */ + /** Check whether the actions from a [PlaybackState] include a specific action */ private fun includesAction(stateActions: Long, @PlaybackState.Actions action: Long): Boolean { - if ((action == PlaybackState.ACTION_PLAY || action == PlaybackState.ACTION_PAUSE) && - (stateActions and PlaybackState.ACTION_PLAY_PAUSE > 0L)) { + if ( + (action == PlaybackState.ACTION_PLAY || action == PlaybackState.ACTION_PAUSE) && + (stateActions and PlaybackState.ACTION_PLAY_PAUSE > 0L) + ) { return true } return (stateActions and action != 0L) } - /** - * Get a [MediaAction] representing a [PlaybackState.CustomAction] - */ + /** Get a [MediaAction] representing a [PlaybackState.CustomAction] */ private fun getCustomAction( state: PlaybackState, packageName: String, @@ -1016,9 +1130,7 @@ class MediaDataManager( ) } - /** - * Load a bitmap from the various Art metadata URIs - */ + /** Load a bitmap from the various Art metadata URIs */ private fun loadBitmapFromUri(metadata: MediaMetadata): Bitmap? { for (uri in ART_URIS) { val uriString = metadata.getString(uri) @@ -1053,16 +1165,18 @@ class MediaDataManager( return null } - if (!uri.scheme.equals(ContentResolver.SCHEME_CONTENT) && + if ( + !uri.scheme.equals(ContentResolver.SCHEME_CONTENT) && !uri.scheme.equals(ContentResolver.SCHEME_ANDROID_RESOURCE) && - !uri.scheme.equals(ContentResolver.SCHEME_FILE)) { + !uri.scheme.equals(ContentResolver.SCHEME_FILE) + ) { return null } val source = ImageDecoder.createSource(context.getContentResolver(), uri) return try { - ImageDecoder.decodeBitmap(source) { - decoder, info, source -> decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE + ImageDecoder.decodeBitmap(source) { decoder, _, _ -> + decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE } } catch (e: IOException) { Log.e(TAG, "Unable to load bitmap", e) @@ -1076,25 +1190,23 @@ class MediaDataManager( private fun getResumeMediaAction(action: Runnable): MediaAction { return MediaAction( Icon.createWithResource(context, R.drawable.ic_media_play) - .setTint(themeText).loadDrawable(context), + .setTint(themeText) + .loadDrawable(context), action, context.getString(R.string.controls_media_resume), context.getDrawable(R.drawable.ic_media_play_container) ) } - fun onMediaDataLoaded( - key: String, - oldKey: String?, - data: MediaData - ) = traceSection("MediaDataManager#onMediaDataLoaded") { - Assert.isMainThread() - if (mediaEntries.containsKey(key)) { - // Otherwise this was removed already - mediaEntries.put(key, data) - notifyMediaDataLoaded(key, oldKey, data) + fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) = + traceSection("MediaDataManager#onMediaDataLoaded") { + Assert.isMainThread() + if (mediaEntries.containsKey(key)) { + // Otherwise this was removed already + mediaEntries.put(key, data) + notifyMediaDataLoaded(key, oldKey, data) + } } - } override fun onSmartspaceTargetsUpdated(targets: List<Parcelable>) { if (!allowMediaRecommendations) { @@ -1111,9 +1223,11 @@ class MediaDataManager( if (DEBUG) { Log.d(TAG, "Set Smartspace media to be inactive for the data update") } - smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy( - targetId = smartspaceMediaData.targetId, - instanceId = smartspaceMediaData.instanceId) + smartspaceMediaData = + EMPTY_SMARTSPACE_MEDIA_DATA.copy( + targetId = smartspaceMediaData.targetId, + instanceId = smartspaceMediaData.instanceId + ) notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = false) } 1 -> { @@ -1124,15 +1238,16 @@ class MediaDataManager( } if (DEBUG) Log.d(TAG, "Forwarding Smartspace media update.") smartspaceMediaData = toSmartspaceMediaData(newMediaTarget, isActive = true) - notifySmartspaceMediaDataLoaded( - smartspaceMediaData.targetId, smartspaceMediaData) + notifySmartspaceMediaDataLoaded(smartspaceMediaData.targetId, smartspaceMediaData) } else -> { // There should NOT be more than 1 Smartspace media update. When it happens, it // indicates a bad state or an error. Reset the status accordingly. Log.wtf(TAG, "More than 1 Smartspace Media Update. Resetting the status...") notifySmartspaceMediaDataRemoved( - smartspaceMediaData.targetId, false /* immediately */) + smartspaceMediaData.targetId, + false /* immediately */ + ) smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA } } @@ -1145,10 +1260,17 @@ class MediaDataManager( Log.d(TAG, "Not removing $key because resumable") // Move to resume key (aka package name) if that key doesn't already exist. val resumeAction = getResumeMediaAction(removed.resumeAction!!) - val updated = removed.copy(token = null, actions = listOf(resumeAction), + val updated = + removed.copy( + token = null, + actions = listOf(resumeAction), semanticActions = MediaButton(playOrPause = resumeAction), - actionsToShowInCompact = listOf(0), active = false, resumption = true, - isPlaying = false, isClearable = true) + actionsToShowInCompact = listOf(0), + active = false, + resumption = true, + isPlaying = false, + isClearable = true + ) val pkg = removed.packageName val migrate = mediaEntries.put(pkg, updated) == null // Notify listeners of "new" controls when migrating or removed and update when not @@ -1190,33 +1312,27 @@ class MediaDataManager( } } - /** - * Invoked when the user has dismissed the media carousel - */ + /** Invoked when the user has dismissed the media carousel */ fun onSwipeToDismiss() = mediaDataFilter.onSwipeToDismiss() - /** - * Are there any media notifications active, including the recommendations? - */ + /** Are there any media notifications active, including the recommendations? */ fun hasActiveMediaOrRecommendation() = mediaDataFilter.hasActiveMediaOrRecommendation() /** * Are there any media entries we should display, including the recommendations? - * If resumption is enabled, this will include inactive players - * If resumption is disabled, we only want to show active players + * - If resumption is enabled, this will include inactive players + * - If resumption is disabled, we only want to show active players */ fun hasAnyMediaOrRecommendation() = mediaDataFilter.hasAnyMediaOrRecommendation() - /** - * Are there any resume media notifications active, excluding the recommendations? - */ + /** Are there any resume media notifications active, excluding the recommendations? */ fun hasActiveMedia() = mediaDataFilter.hasActiveMedia() /** - * Are there any resume media notifications active, excluding the recommendations? - * If resumption is enabled, this will include inactive players - * If resumption is disabled, we only want to show active players - */ + * Are there any resume media notifications active, excluding the recommendations? + * - If resumption is enabled, this will include inactive players + * - If resumption is disabled, we only want to show active players + */ fun hasAnyMedia() = mediaDataFilter.hasAnyMedia() interface Listener { @@ -1286,10 +1402,9 @@ class MediaDataManager( ): SmartspaceMediaData { var dismissIntent: Intent? = null if (target.baseAction != null && target.baseAction.extras != null) { - dismissIntent = target - .baseAction - .extras - .getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY) as Intent? + dismissIntent = + target.baseAction.extras.getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY) + as Intent? } packageName(target)?.let { return SmartspaceMediaData( @@ -1300,14 +1415,16 @@ class MediaDataManager( recommendations = target.iconGrid, dismissIntent = dismissIntent, headphoneConnectionTimeMillis = target.creationTimeMillis, - instanceId = logger.getNewInstanceId()) + instanceId = logger.getNewInstanceId() + ) } - return EMPTY_SMARTSPACE_MEDIA_DATA - .copy(targetId = target.smartspaceTargetId, - isActive = isActive, - dismissIntent = dismissIntent, - headphoneConnectionTimeMillis = target.creationTimeMillis, - instanceId = logger.getNewInstanceId()) + return EMPTY_SMARTSPACE_MEDIA_DATA.copy( + targetId = target.smartspaceTargetId, + isActive = isActive, + dismissIntent = dismissIntent, + headphoneConnectionTimeMillis = target.creationTimeMillis, + instanceId = logger.getNewInstanceId() + ) } private fun packageName(target: SmartspaceTarget): String? { @@ -1319,8 +1436,9 @@ class MediaDataManager( for (recommendation in recommendationList) { val extras = recommendation.extras extras?.let { - it.getString(EXTRAS_MEDIA_SOURCE_PACKAGE_NAME)?.let { - packageName -> return packageName } + it.getString(EXTRAS_MEDIA_SOURCE_PACKAGE_NAME)?.let { packageName -> + return packageName + } } } Log.w(TAG, "No valid package name is provided.") diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt index af1d6cf340b7..6a512be091e1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt @@ -36,10 +36,10 @@ 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 -import com.android.systemui.media.controls.util.MediaControllerFactory -import com.android.systemui.media.controls.util.MediaDataUtils import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.player.MediaDeviceData +import com.android.systemui.media.controls.util.MediaControllerFactory +import com.android.systemui.media.controls.util.MediaDataUtils import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory import com.android.systemui.statusbar.policy.ConfigurationController @@ -51,10 +51,10 @@ 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( +/** 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, @@ -74,14 +74,10 @@ class MediaDeviceManager @Inject constructor( dumpManager.registerDumpable(javaClass.name, this) } - /** - * Add a listener for changes to the media route (ie. device). - */ + /** Add a listener for changes to the media route (ie. device). */ fun addListener(listener: Listener) = listeners.add(listener) - /** - * Remove a listener that has been registered with addListener. - */ + /** Remove a listener that has been registered with addListener. */ fun removeListener(listener: Listener) = listeners.remove(listener) override fun onMediaDataLoaded( @@ -105,19 +101,11 @@ class MediaDeviceManager @Inject constructor( processDevice(key, oldKey, data.device) return } - val controller = data.token?.let { - controllerFactory.create(it) - } + val controller = data.token?.let { controllerFactory.create(it) } val localMediaManager = localMediaManagerFactory.create(data.packageName) val muteAwaitConnectionManager = - muteAwaitConnectionManagerFactory.create(localMediaManager) - entry = Entry( - key, - oldKey, - controller, - localMediaManager, - muteAwaitConnectionManager - ) + muteAwaitConnectionManagerFactory.create(localMediaManager) + entry = Entry(key, oldKey, controller, localMediaManager, muteAwaitConnectionManager) entries[key] = entry entry.start() } @@ -126,11 +114,7 @@ class MediaDeviceManager @Inject constructor( override fun onMediaDataRemoved(key: String) { val token = entries.remove(key) token?.stop() - token?.let { - listeners.forEach { - it.onKeyRemoved(key) - } - } + token?.let { listeners.forEach { it.onKeyRemoved(key) } } } override fun dump(pw: PrintWriter, args: Array<String>) { @@ -145,9 +129,7 @@ class MediaDeviceManager @Inject constructor( @MainThread private fun processDevice(key: String, oldKey: String?, device: MediaDeviceData?) { - listeners.forEach { - it.onMediaDeviceChanged(key, oldKey, device) - } + listeners.forEach { it.onMediaDeviceChanged(key, oldKey, device) } } interface Listener { @@ -163,8 +145,10 @@ class MediaDeviceManager @Inject constructor( val controller: MediaController?, val localMediaManager: LocalMediaManager, val muteAwaitConnectionManager: MediaMuteAwaitConnectionManager? - ) : LocalMediaManager.DeviceCallback, MediaController.Callback(), - BluetoothLeBroadcast.Callback { + ) : + LocalMediaManager.DeviceCallback, + MediaController.Callback(), + BluetoothLeBroadcast.Callback { val token get() = controller?.sessionToken @@ -175,54 +159,52 @@ class MediaDeviceManager @Inject constructor( val sameWithoutIcon = value != null && value.equalsWithoutIcon(field) if (!started || !sameWithoutIcon) { field = value - fgExecutor.execute { - processDevice(key, oldKey, value) - } + fgExecutor.execute { processDevice(key, oldKey, value) } } } // 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() + private val configListener = + object : ConfigurationController.ConfigurationListener { + override fun onLocaleListChanged() { + updateCurrent() + } } - } @AnyThread - fun start() = bgExecutor.execute { - if (!started) { - localMediaManager.registerCallback(this) - localMediaManager.startScan() - muteAwaitConnectionManager?.startListening() - playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN - controller?.registerCallback(this) - updateCurrent() - started = true - configurationController.addCallback(configListener) + fun start() = + bgExecutor.execute { + if (!started) { + localMediaManager.registerCallback(this) + localMediaManager.startScan() + muteAwaitConnectionManager?.startListening() + playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN + controller?.registerCallback(this) + updateCurrent() + started = true + configurationController.addCallback(configListener) + } } - } @AnyThread - fun stop() = bgExecutor.execute { - if (started) { - started = false - controller?.unregisterCallback(this) - localMediaManager.stopScan() - localMediaManager.unregisterCallback(this) - muteAwaitConnectionManager?.stopListening() - configurationController.removeCallback(configListener) + fun stop() = + bgExecutor.execute { + if (started) { + started = false + controller?.unregisterCallback(this) + localMediaManager.stopScan() + localMediaManager.unregisterCallback(this) + muteAwaitConnectionManager?.stopListening() + configurationController.removeCallback(configListener) + } } - } fun dump(pw: PrintWriter) { - val routingSession = controller?.let { - mr2manager.getRoutingSessionForMediaController(it) - } - val selectedRoutes = routingSession?.let { - mr2manager.getSelectedRoutes(it) - } + val routingSession = + controller?.let { mr2manager.getRoutingSessionForMediaController(it) } + val selectedRoutes = routingSession?.let { mr2manager.getSelectedRoutes(it) } with(pw) { println(" current device is ${current?.name}") val type = controller?.playbackInfo?.playbackType @@ -242,14 +224,11 @@ class MediaDeviceManager @Inject constructor( updateCurrent() } - override fun onDeviceListUpdate(devices: List<MediaDevice>?) = bgExecutor.execute { - updateCurrent() - } + override fun onDeviceListUpdate(devices: List<MediaDevice>?) = + bgExecutor.execute { updateCurrent() } override fun onSelectedDeviceStateChanged(device: MediaDevice, state: Int) { - bgExecutor.execute { - updateCurrent() - } + bgExecutor.execute { updateCurrent() } } override fun onAboutToConnectDeviceAdded( @@ -257,14 +236,17 @@ class MediaDeviceManager @Inject constructor( deviceName: String, deviceIcon: Drawable? ) { - aboutToConnectDeviceOverride = AboutToConnectDevice( - fullMediaDevice = localMediaManager.getMediaDeviceById(deviceAddress), - backupMediaDeviceData = MediaDeviceData( - /* enabled */ enabled = true, - /* icon */ deviceIcon, - /* name */ deviceName, - /* showBroadcastButton */ showBroadcastButton = false) - ) + aboutToConnectDeviceOverride = + AboutToConnectDevice( + fullMediaDevice = localMediaManager.getMediaDeviceById(deviceAddress), + backupMediaDeviceData = + MediaDeviceData( + /* enabled */ enabled = true, + /* icon */ deviceIcon, + /* name */ deviceName, + /* showBroadcastButton */ showBroadcastButton = false + ) + ) updateCurrent() } @@ -291,8 +273,11 @@ class MediaDeviceManager @Inject constructor( metadata: BluetoothLeBroadcastMetadata ) { if (DEBUG) { - Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = $broadcastId , " + - "metadata = $metadata") + Log.d( + TAG, + "onBroadcastMetadataChanged(), broadcastId = $broadcastId , " + + "metadata = $metadata" + ) } updateCurrent() } @@ -319,8 +304,10 @@ class MediaDeviceManager @Inject constructor( override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) { if (DEBUG) { - Log.d(TAG, "onBroadcastUpdateFailed(), reason = $reason , " + - "broadcastId = $broadcastId") + Log.d( + TAG, + "onBroadcastUpdateFailed(), reason = $reason , " + "broadcastId = $broadcastId" + ) } } @@ -331,34 +318,45 @@ class MediaDeviceManager @Inject constructor( @WorkerThread private fun updateCurrent() { if (isLeAudioBroadcastEnabled()) { - current = MediaDeviceData( + current = + MediaDeviceData( /* enabled */ true, /* icon */ context.getDrawable(R.drawable.settings_input_antenna), /* name */ broadcastDescription, /* intent */ null, - /* showBroadcastButton */ showBroadcastButton = true) + /* showBroadcastButton */ showBroadcastButton = true + ) } else { val aboutToConnect = aboutToConnectDeviceOverride - if (aboutToConnect != null && + if ( + aboutToConnect != null && aboutToConnect.fullMediaDevice == null && - aboutToConnect.backupMediaDeviceData != null) { + aboutToConnect.backupMediaDeviceData != null + ) { // Only use [backupMediaDeviceData] when we don't have [fullMediaDevice]. 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 = if (controller == null || route != null) { - route?.name?.toString() ?: device?.name - } else { - null - } - current = MediaDeviceData(enabled, device?.iconWithoutBackground, name, - id = device?.id, showBroadcastButton = false) + val name = + if (controller == null || route != null) { + route?.name?.toString() ?: device?.name + } else { + null + } + current = + MediaDeviceData( + enabled, + device?.iconWithoutBackground, + name, + id = device?.id, + showBroadcastButton = false + ) } } @@ -388,13 +386,16 @@ class MediaDeviceManager @Inject constructor( // 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 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) + broadcastDescription = + context.getString(R.string.broadcasting_description_is_broadcasting) } else { broadcastDescription = currentBroadcastedApp } @@ -407,9 +408,9 @@ class MediaDeviceManager @Inject constructor( * [LocalMediaManager.DeviceCallback.onAboutToConnectDeviceAdded] for more information. * * @property fullMediaDevice a full-fledged [MediaDevice] object representing the device. If - * non-null, prefer using [fullMediaDevice] over [backupMediaDeviceData]. + * non-null, prefer using [fullMediaDevice] over [backupMediaDeviceData]. * @property backupMediaDeviceData a backup [MediaDeviceData] object containing the minimum - * information required to display the device. Only use if [fullMediaDevice] is null. + * information required to display the device. Only use if [fullMediaDevice] is null. */ private data class AboutToConnectDevice( val fullMediaDevice: MediaDevice? = null, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt index ebf7be8af679..ab93b292308e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt @@ -40,7 +40,9 @@ private const val TAG = "MediaSessionBasedFilter" * sessions. In this situation, there should only be a media object for the remote session. To * achieve this, update events for the local session need to be filtered. */ -class MediaSessionBasedFilter @Inject constructor( +class MediaSessionBasedFilter +@Inject +constructor( context: Context, private val sessionManager: MediaSessionManager, @Main private val foregroundExecutor: Executor, @@ -52,7 +54,7 @@ class MediaSessionBasedFilter @Inject constructor( // Keep track of MediaControllers for a given package to check if an app is casting and it // filter loaded events for local sessions. private val packageControllers: LinkedHashMap<String, MutableList<MediaController>> = - LinkedHashMap() + LinkedHashMap() // Keep track of the key used for the session tokens. This information is used to know when to // dispatch a removed event so that a media object for a local session will be removed. @@ -61,11 +63,12 @@ class MediaSessionBasedFilter @Inject constructor( // Keep track of which media session tokens have associated notifications. private val tokensWithNotifications: MutableSet<MediaSession.Token> = mutableSetOf() - private val sessionListener = object : MediaSessionManager.OnActiveSessionsChangedListener { - override fun onActiveSessionsChanged(controllers: List<MediaController>) { - handleControllersChanged(controllers) + private val sessionListener = + object : MediaSessionManager.OnActiveSessionsChangedListener { + override fun onActiveSessionsChanged(controllers: List<MediaController>) { + handleControllersChanged(controllers) + } } - } init { backgroundExecutor.execute { @@ -75,14 +78,10 @@ class MediaSessionBasedFilter @Inject constructor( } } - /** - * Add a listener for filtered [MediaData] changes - */ + /** Add a listener for filtered [MediaData] changes */ fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener) - /** - * Remove a listener that was registered with addListener - */ + /** Remove a listener that was registered with addListener */ fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener) /** @@ -102,31 +101,32 @@ class MediaSessionBasedFilter @Inject constructor( isSsReactivated: Boolean ) { backgroundExecutor.execute { - data.token?.let { - tokensWithNotifications.add(it) - } + data.token?.let { tokensWithNotifications.add(it) } val isMigration = oldKey != null && key != oldKey if (isMigration) { keyedTokens.remove(oldKey)?.let { removed -> keyedTokens.put(key, removed) } } if (data.token != null) { - keyedTokens.get(key)?.let { - tokens -> - tokens.add(data.token) - } ?: run { - val tokens = mutableSetOf(data.token) - keyedTokens.put(key, tokens) - } + keyedTokens.get(key)?.let { tokens -> tokens.add(data.token) } + ?: run { + val tokens = mutableSetOf(data.token) + keyedTokens.put(key, tokens) + } } // Determine if an app is casting by checking if it has a session with playback type // PLAYBACK_TYPE_REMOTE. - val remoteControllers = packageControllers.get(data.packageName)?.filter { - it.playbackInfo?.playbackType == PlaybackInfo.PLAYBACK_TYPE_REMOTE - } + val remoteControllers = + packageControllers.get(data.packageName)?.filter { + it.playbackInfo?.playbackType == PlaybackInfo.PLAYBACK_TYPE_REMOTE + } // Limiting search to only apps with a single remote session. val remote = if (remoteControllers?.size == 1) remoteControllers.firstOrNull() else null - if (isMigration || remote == null || remote.sessionToken == data.token || - !tokensWithNotifications.contains(remote.sessionToken)) { + if ( + isMigration || + remote == null || + remote.sessionToken == data.token || + !tokensWithNotifications.contains(remote.sessionToken) + ) { // Not filtering in this case. Passing the event along to listeners. dispatchMediaDataLoaded(key, oldKey, data, immediately) } else { @@ -148,9 +148,7 @@ class MediaSessionBasedFilter @Inject constructor( data: SmartspaceMediaData, shouldPrioritize: Boolean ) { - backgroundExecutor.execute { - dispatchSmartspaceMediaDataLoaded(key, data) - } + backgroundExecutor.execute { dispatchSmartspaceMediaDataLoaded(key, data) } } override fun onMediaDataRemoved(key: String) { @@ -162,9 +160,7 @@ class MediaSessionBasedFilter @Inject constructor( } override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) { - backgroundExecutor.execute { - dispatchSmartspaceMediaDataRemoved(key, immediately) - } + backgroundExecutor.execute { dispatchSmartspaceMediaDataRemoved(key, immediately) } } private fun dispatchMediaDataLoaded( @@ -179,9 +175,7 @@ class MediaSessionBasedFilter @Inject constructor( } private fun dispatchMediaDataRemoved(key: String) { - foregroundExecutor.execute { - listeners.toSet().forEach { it.onMediaDataRemoved(key) } - } + foregroundExecutor.execute { listeners.toSet().forEach { it.onMediaDataRemoved(key) } } } private fun dispatchSmartspaceMediaDataLoaded(key: String, info: SmartspaceMediaData) { @@ -198,15 +192,12 @@ class MediaSessionBasedFilter @Inject constructor( private fun handleControllersChanged(controllers: List<MediaController>) { packageControllers.clear() - controllers.forEach { - controller -> - packageControllers.get(controller.packageName)?.let { - tokens -> - tokens.add(controller) - } ?: run { - val tokens = mutableListOf(controller) - packageControllers.put(controller.packageName, tokens) - } + controllers.forEach { controller -> + packageControllers.get(controller.packageName)?.let { tokens -> tokens.add(controller) } + ?: run { + val tokens = mutableListOf(controller) + packageControllers.put(controller.packageName, tokens) + } } tokensWithNotifications.retainAll(controllers.map { it.sessionToken }) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt index e29ab6d43519..7f5c82fb5eee 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt @@ -22,8 +22,8 @@ import android.os.SystemProperties import com.android.internal.annotations.VisibleForTesting import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.controls.models.player.MediaData +import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState import com.android.systemui.statusbar.SysuiStatusBarStateController @@ -33,18 +33,18 @@ import java.util.concurrent.TimeUnit import javax.inject.Inject @VisibleForTesting -val PAUSED_MEDIA_TIMEOUT = SystemProperties - .getLong("debug.sysui.media_timeout", TimeUnit.MINUTES.toMillis(10)) +val PAUSED_MEDIA_TIMEOUT = + SystemProperties.getLong("debug.sysui.media_timeout", TimeUnit.MINUTES.toMillis(10)) @VisibleForTesting -val RESUME_MEDIA_TIMEOUT = SystemProperties - .getLong("debug.sysui.media_timeout_resume", TimeUnit.DAYS.toMillis(3)) +val RESUME_MEDIA_TIMEOUT = + SystemProperties.getLong("debug.sysui.media_timeout_resume", TimeUnit.DAYS.toMillis(3)) -/** - * Controller responsible for keeping track of playback states and expiring inactive streams. - */ +/** Controller responsible for keeping track of playback states and expiring inactive streams. */ @SysUISingleton -class MediaTimeoutListener @Inject constructor( +class MediaTimeoutListener +@Inject +constructor( private val mediaControllerFactory: MediaControllerFactory, @Main private val mainExecutor: DelayableExecutor, private val logger: MediaTimeoutLogger, @@ -58,7 +58,9 @@ class MediaTimeoutListener @Inject constructor( * Callback representing that a media object is now expired: * @param key Media control unique identifier * @param timedOut True when expired for {@code PAUSED_MEDIA_TIMEOUT} for active media, + * ``` * or {@code RESUME_MEDIA_TIMEOUT} for resume media + * ``` */ lateinit var timeoutCallback: (String, Boolean) -> Unit @@ -70,21 +72,25 @@ class MediaTimeoutListener @Inject constructor( lateinit var stateCallback: (String, PlaybackState) -> Unit init { - statusBarStateController.addCallback(object : StatusBarStateController.StateListener { - override fun onDozingChanged(isDozing: Boolean) { - if (!isDozing) { - // Check whether any timeouts should have expired - mediaListeners.forEach { (key, listener) -> - if (listener.cancellation != null && - listener.expiration <= systemClock.elapsedRealtime()) { - // We dozed too long - timeout now, and cancel the pending one - listener.expireMediaTimeout(key, "timeout happened while dozing") - listener.doTimeout() + statusBarStateController.addCallback( + object : StatusBarStateController.StateListener { + override fun onDozingChanged(isDozing: Boolean) { + if (!isDozing) { + // Check whether any timeouts should have expired + mediaListeners.forEach { (key, listener) -> + if ( + listener.cancellation != null && + listener.expiration <= systemClock.elapsedRealtime() + ) { + // We dozed too long - timeout now, and cancel the pending one + listener.expireMediaTimeout(key, "timeout happened while dozing") + listener.doTimeout() + } } } } } - }) + ) } override fun onMediaDataLoaded( @@ -147,10 +153,8 @@ class MediaTimeoutListener @Inject constructor( return mediaListeners[key]?.timedOut ?: false } - private inner class PlaybackStateListener( - var key: String, - data: MediaData - ) : MediaController.Callback() { + private inner class PlaybackStateListener(var key: String, data: MediaData) : + MediaController.Callback() { var timedOut = false var lastState: PlaybackState? = null @@ -164,11 +168,12 @@ class MediaTimeoutListener @Inject constructor( mediaController?.unregisterCallback(this) field = value val token = field.token - mediaController = if (token != null) { - mediaControllerFactory.create(token) - } else { - null - } + mediaController = + if (token != null) { + mediaControllerFactory.create(token) + } else { + null + } mediaController?.registerCallback(this) // Let's register the cancellations, but not dispatch events now. // Timeouts didn't happen yet and reentrant events are troublesome. @@ -214,7 +219,8 @@ class MediaTimeoutListener @Inject constructor( logger.logPlaybackState(key, state) val playingStateSame = (state?.state?.isPlaying() == isPlaying()) - val actionsSame = (lastState?.actions == state?.actions) && + val actionsSame = + (lastState?.actions == state?.actions) && areCustomActionListsEqual(lastState?.customActions, state?.customActions) val resumptionChanged = resumption != mediaData.resumption @@ -239,15 +245,14 @@ class MediaTimeoutListener @Inject constructor( return } expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state, $resumption") - val timeout = if (mediaData.resumption) { - RESUME_MEDIA_TIMEOUT - } else { - PAUSED_MEDIA_TIMEOUT - } + val timeout = + if (mediaData.resumption) { + RESUME_MEDIA_TIMEOUT + } else { + PAUSED_MEDIA_TIMEOUT + } expiration = systemClock.elapsedRealtime() + timeout - cancellation = mainExecutor.executeDelayed({ - doTimeout() - }, timeout) + cancellation = mainExecutor.executeDelayed({ doTimeout() }, timeout) } else { expireMediaTimeout(key, "playback started - $state, $key") timedOut = false @@ -303,9 +308,11 @@ class MediaTimeoutListener @Inject constructor( firstAction: PlaybackState.CustomAction, secondAction: PlaybackState.CustomAction ): Boolean { - if (firstAction.action != secondAction.action || + if ( + firstAction.action != secondAction.action || firstAction.name != secondAction.name || - firstAction.icon != secondAction.icon) { + firstAction.icon != secondAction.icon + ) { return false } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt index 3abeef3ae124..8f3f0548230f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt @@ -22,140 +22,91 @@ import com.android.systemui.log.dagger.MediaTimeoutListenerLog import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.plugins.log.LogLevel import javax.inject.Inject + private const val TAG = "MediaTimeout" -/** - * A buffered log for [MediaTimeoutListener] events - */ +/** A buffered log for [MediaTimeoutListener] events */ @SysUISingleton -class MediaTimeoutLogger @Inject constructor( - @MediaTimeoutListenerLog private val buffer: LogBuffer -) { - fun logReuseListener(key: String) = buffer.log( - TAG, - LogLevel.DEBUG, - { - str1 = key - }, - { - "reuse listener: $str1" - } - ) - - fun logMigrateListener(oldKey: String?, newKey: String?, hadListener: Boolean) = buffer.log( - TAG, - LogLevel.DEBUG, - { - str1 = oldKey - str2 = newKey - bool1 = hadListener - }, - { - "migrate from $str1 to $str2, had listener? $bool1" - } - ) +class MediaTimeoutLogger +@Inject +constructor(@MediaTimeoutListenerLog private val buffer: LogBuffer) { + fun logReuseListener(key: String) = + buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "reuse listener: $str1" }) - fun logUpdateListener(key: String, wasPlaying: Boolean) = buffer.log( - TAG, - LogLevel.DEBUG, - { - str1 = key - bool1 = wasPlaying - }, - { - "updating $str1, was playing? $bool1" - } - ) + fun logMigrateListener(oldKey: String?, newKey: String?, hadListener: Boolean) = + buffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = oldKey + str2 = newKey + bool1 = hadListener + }, + { "migrate from $str1 to $str2, had listener? $bool1" } + ) - fun logDelayedUpdate(key: String) = buffer.log( - TAG, - LogLevel.DEBUG, - { - str1 = key - }, - { - "deliver delayed playback state for $str1" - } - ) + fun logUpdateListener(key: String, wasPlaying: Boolean) = + buffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = key + bool1 = wasPlaying + }, + { "updating $str1, was playing? $bool1" } + ) - fun logSessionDestroyed(key: String) = buffer.log( - TAG, - LogLevel.DEBUG, - { - str1 = key - }, - { - "session destroyed $str1" - } - ) + fun logDelayedUpdate(key: String) = + buffer.log( + TAG, + LogLevel.DEBUG, + { str1 = key }, + { "deliver delayed playback state for $str1" } + ) - fun logPlaybackState(key: String, state: PlaybackState?) = buffer.log( - TAG, - LogLevel.VERBOSE, - { - str1 = key - str2 = state?.toString() - }, - { - "state update: key=$str1 state=$str2" - } - ) + fun logSessionDestroyed(key: String) = + buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "session destroyed $str1" }) - fun logStateCallback(key: String) = buffer.log( + fun logPlaybackState(key: String, state: PlaybackState?) = + buffer.log( TAG, LogLevel.VERBOSE, { str1 = key + str2 = state?.toString() }, - { - "dispatching state update for $key" - } - ) + { "state update: key=$str1 state=$str2" } + ) + + fun logStateCallback(key: String) = + buffer.log(TAG, LogLevel.VERBOSE, { str1 = key }, { "dispatching state update for $key" }) - fun logScheduleTimeout(key: String, playing: Boolean, resumption: Boolean) = buffer.log( - TAG, - LogLevel.DEBUG, - { - str1 = key - bool1 = playing - bool2 = resumption - }, - { - "schedule timeout $str1, playing=$bool1 resumption=$bool2" - } - ) + fun logScheduleTimeout(key: String, playing: Boolean, resumption: Boolean) = + buffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = key + bool1 = playing + bool2 = resumption + }, + { "schedule timeout $str1, playing=$bool1 resumption=$bool2" } + ) - fun logCancelIgnored(key: String) = buffer.log( - TAG, - LogLevel.DEBUG, - { - str1 = key - }, - { - "cancellation already exists for $str1" - } - ) + fun logCancelIgnored(key: String) = + buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "cancellation already exists for $str1" }) - fun logTimeout(key: String) = buffer.log( - TAG, - LogLevel.DEBUG, - { - str1 = key - }, - { - "execute timeout for $str1" - } - ) + fun logTimeout(key: String) = + buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "execute timeout for $str1" }) - fun logTimeoutCancelled(key: String, reason: String) = buffer.log( - TAG, - LogLevel.VERBOSE, - { - str1 = key - str2 = reason - }, - { - "media timeout cancelled for $str1, reason: $str2" - } - ) -}
\ No newline at end of file + fun logTimeoutCancelled(key: String, reason: String) = + buffer.log( + TAG, + LogLevel.VERBOSE, + { + str1 = key + str2 = reason + }, + { "media timeout cancelled for $str1, reason: $str2" } + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt index e5e10c2ca59a..4891297dbcf9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt @@ -50,7 +50,9 @@ private const val MEDIA_PREFERENCES = "media_control_prefs" private const val MEDIA_PREFERENCE_KEY = "browser_components_" @SysUISingleton -class MediaResumeListener @Inject constructor( +class MediaResumeListener +@Inject +constructor( private val context: Context, private val broadcastDispatcher: BroadcastDispatcher, @Background private val backgroundExecutor: Executor, @@ -62,7 +64,7 @@ class MediaResumeListener @Inject constructor( private var useMediaResumption: Boolean = Utils.useMediaResumption(context) private val resumeComponents: ConcurrentLinkedQueue<Pair<ComponentName, Long>> = - ConcurrentLinkedQueue() + ConcurrentLinkedQueue() private lateinit var mediaDataManager: MediaDataManager @@ -75,40 +77,49 @@ class MediaResumeListener @Inject constructor( private var currentUserId: Int = context.userId @VisibleForTesting - val userChangeReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - if (Intent.ACTION_USER_UNLOCKED == intent.action) { - loadMediaResumptionControls() - } else if (Intent.ACTION_USER_SWITCHED == intent.action) { - currentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1) - loadSavedComponents() + val userChangeReceiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (Intent.ACTION_USER_UNLOCKED == intent.action) { + loadMediaResumptionControls() + } else if (Intent.ACTION_USER_SWITCHED == intent.action) { + currentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1) + loadSavedComponents() + } } } - } - private val mediaBrowserCallback = object : ResumeMediaBrowser.Callback() { - override fun addTrack( - desc: MediaDescription, - component: ComponentName, - browser: ResumeMediaBrowser - ) { - val token = browser.token - val appIntent = browser.appIntent - val pm = context.getPackageManager() - var appName: CharSequence = component.packageName - val resumeAction = getResumeAction(component) - try { - appName = pm.getApplicationLabel( - pm.getApplicationInfo(component.packageName, 0)) - } catch (e: PackageManager.NameNotFoundException) { - Log.e(TAG, "Error getting package information", e) - } + private val mediaBrowserCallback = + object : ResumeMediaBrowser.Callback() { + override fun addTrack( + desc: MediaDescription, + component: ComponentName, + browser: ResumeMediaBrowser + ) { + val token = browser.token + val appIntent = browser.appIntent + val pm = context.getPackageManager() + var appName: CharSequence = component.packageName + val resumeAction = getResumeAction(component) + try { + appName = + pm.getApplicationLabel(pm.getApplicationInfo(component.packageName, 0)) + } catch (e: PackageManager.NameNotFoundException) { + Log.e(TAG, "Error getting package information", e) + } - Log.d(TAG, "Adding resume controls $desc") - mediaDataManager.addResumptionControls(currentUserId, desc, resumeAction, token, - appName.toString(), appIntent, component.packageName) + Log.d(TAG, "Adding resume controls $desc") + mediaDataManager.addResumptionControls( + currentUserId, + desc, + resumeAction, + token, + appName.toString(), + appIntent, + component.packageName + ) + } } - } init { if (useMediaResumption) { @@ -116,8 +127,12 @@ class MediaResumeListener @Inject constructor( val unlockFilter = IntentFilter() unlockFilter.addAction(Intent.ACTION_USER_UNLOCKED) unlockFilter.addAction(Intent.ACTION_USER_SWITCHED) - broadcastDispatcher.registerReceiver(userChangeReceiver, unlockFilter, null, - UserHandle.ALL) + broadcastDispatcher.registerReceiver( + userChangeReceiver, + unlockFilter, + null, + UserHandle.ALL + ) loadSavedComponents() } } @@ -126,12 +141,15 @@ class MediaResumeListener @Inject constructor( mediaDataManager = manager // Add listener for resumption setting changes - tunerService.addTunable(object : TunerService.Tunable { - override fun onTuningChanged(key: String?, newValue: String?) { - useMediaResumption = Utils.useMediaResumption(context) - mediaDataManager.setMediaResumptionEnabled(useMediaResumption) - } - }, Settings.Secure.MEDIA_CONTROLS_RESUME) + tunerService.addTunable( + object : TunerService.Tunable { + override fun onTuningChanged(key: String?, newValue: String?) { + useMediaResumption = Utils.useMediaResumption(context) + mediaDataManager.setMediaResumptionEnabled(useMediaResumption) + } + }, + Settings.Secure.MEDIA_CONTROLS_RESUME + ) } private fun loadSavedComponents() { @@ -139,8 +157,10 @@ class MediaResumeListener @Inject constructor( resumeComponents.clear() val prefs = context.getSharedPreferences(MEDIA_PREFERENCES, Context.MODE_PRIVATE) val listString = prefs.getString(MEDIA_PREFERENCE_KEY + currentUserId, null) - val components = listString?.split(ResumeMediaBrowser.DELIMITER.toRegex()) - ?.dropLastWhile { it.isEmpty() } + val components = + listString?.split(ResumeMediaBrowser.DELIMITER.toRegex())?.dropLastWhile { + it.isEmpty() + } var needsUpdate = false components?.forEach { val info = it.split("/") @@ -148,17 +168,18 @@ class MediaResumeListener @Inject constructor( val className = info[1] val component = ComponentName(packageName, className) - val lastPlayed = if (info.size == 3) { - try { - info[2].toLong() - } catch (e: NumberFormatException) { + val lastPlayed = + if (info.size == 3) { + try { + info[2].toLong() + } catch (e: NumberFormatException) { + needsUpdate = true + systemClock.currentTimeMillis() + } + } else { needsUpdate = true systemClock.currentTimeMillis() } - } else { - needsUpdate = true - systemClock.currentTimeMillis() - } resumeComponents.add(component to lastPlayed) } Log.d(TAG, "loaded resume components ${resumeComponents.toArray().contentToString()}") @@ -169,9 +190,7 @@ class MediaResumeListener @Inject constructor( } } - /** - * Load controls for resuming media, if available - */ + /** Load controls for resuming media, if available */ private fun loadMediaResumptionControls() { if (!useMediaResumption) { return @@ -207,9 +226,7 @@ class MediaResumeListener @Inject constructor( val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE) val resumeInfo = pm.queryIntentServices(serviceIntent, 0) - val inf = resumeInfo?.filter { - it.serviceInfo.packageName == data.packageName - } + val inf = resumeInfo?.filter { it.serviceInfo.packageName == data.packageName } if (inf != null && inf.size > 0) { backgroundExecutor.execute { tryUpdateResumptionList(key, inf!!.get(0).componentInfo.componentName) @@ -230,7 +247,8 @@ class MediaResumeListener @Inject constructor( Log.d(TAG, "Testing if we can connect to $componentName") // Set null action to prevent additional attempts to connect mediaDataManager.setResumeAction(key, null) - mediaBrowser = mediaBrowserFactory.create( + mediaBrowser = + mediaBrowserFactory.create( object : ResumeMediaBrowser.Callback() { override fun onConnected() { Log.d(TAG, "Connected to $componentName") @@ -253,7 +271,8 @@ class MediaResumeListener @Inject constructor( mediaBrowser = null } }, - componentName) + componentName + ) mediaBrowser?.testConnection() } @@ -288,9 +307,7 @@ class MediaResumeListener @Inject constructor( prefs.edit().putString(MEDIA_PREFERENCE_KEY + currentUserId, sb.toString()).apply() } - /** - * Get a runnable which will resume media playback - */ + /** Get a runnable which will resume media playback */ private fun getResumeAction(componentName: ComponentName): Runnable { return Runnable { mediaBrowser = mediaBrowserFactory.create(null, componentName) @@ -299,8 +316,6 @@ class MediaResumeListener @Inject constructor( } override fun dump(pw: PrintWriter, args: Array<out String>) { - pw.apply { - println("resumeComponents: $resumeComponents") - } + pw.apply { println("resumeComponents: $resumeComponents") } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt index b633de9d836a..335ce1d3d694 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt @@ -25,50 +25,49 @@ import javax.inject.Inject /** A logger for events in [ResumeMediaBrowser]. */ @SysUISingleton -class ResumeMediaBrowserLogger @Inject constructor( - @MediaBrowserLog private val buffer: LogBuffer -) { +class ResumeMediaBrowserLogger @Inject constructor(@MediaBrowserLog private val buffer: LogBuffer) { /** Logs that we've initiated a connection to a [android.media.browse.MediaBrowser]. */ - fun logConnection(componentName: ComponentName, reason: String) = buffer.log( - TAG, - LogLevel.DEBUG, - { - str1 = componentName.toShortString() - str2 = reason - }, - { "Connecting browser for component $str1 due to $str2" } - ) + fun logConnection(componentName: ComponentName, reason: String) = + buffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = componentName.toShortString() + str2 = reason + }, + { "Connecting browser for component $str1 due to $str2" } + ) /** Logs that we've disconnected from a [android.media.browse.MediaBrowser]. */ - fun logDisconnect(componentName: ComponentName) = buffer.log( - TAG, - LogLevel.DEBUG, - { - str1 = componentName.toShortString() - }, - { "Disconnecting browser for component $str1" } - ) + fun logDisconnect(componentName: ComponentName) = + buffer.log( + TAG, + LogLevel.DEBUG, + { str1 = componentName.toShortString() }, + { "Disconnecting browser for component $str1" } + ) /** * Logs that we received a [android.media.session.MediaController.Callback.onSessionDestroyed] * event. * * @param isBrowserConnected true if there's a currently connected + * ``` * [android.media.browse.MediaBrowser] and false otherwise. - * @param componentName the component name for the [ResumeMediaBrowser] that triggered this log. + * @param componentName + * ``` + * the component name for the [ResumeMediaBrowser] that triggered this log. */ - fun logSessionDestroyed( - isBrowserConnected: Boolean, - componentName: ComponentName - ) = buffer.log( - TAG, - LogLevel.DEBUG, - { - bool1 = isBrowserConnected - str1 = componentName.toShortString() - }, - { "Session destroyed. Active browser = $bool1. Browser component = $str1." } - ) + fun logSessionDestroyed(isBrowserConnected: Boolean, componentName: ComponentName) = + buffer.log( + TAG, + LogLevel.DEBUG, + { + bool1 = isBrowserConnected + str1 = componentName.toShortString() + }, + { "Session destroyed. Active browser = $bool1. Browser component = $str1." } + ) } private const val TAG = "MediaBrowser" diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/AnimationBindHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/AnimationBindHandler.kt index a64d08fe9f01..d2793bca867b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/AnimationBindHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/AnimationBindHandler.kt @@ -20,13 +20,17 @@ import android.graphics.drawable.Animatable2 import android.graphics.drawable.Drawable /** - * AnimationBindHandler is responsible for tracking the bound animation state and preventing - * jank and conflicts due to media notifications arriving at any time during an animation. It - * does this in two parts. - * - Exit animations fired as a result of user input are tracked. When these are running, any + * AnimationBindHandler is responsible for tracking the bound animation state and preventing jank + * and conflicts due to media notifications arriving at any time during an animation. It does this + * in two parts. + * - Exit animations fired as a result of user input are tracked. When these are running, any + * ``` * bind actions are delayed until the animation completes (and then fired in sequence). - * - Continuous animations are tracked using their rebind id. Later calls using the same + * ``` + * - Continuous animations are tracked using their rebind id. Later calls using the same + * ``` * rebind id will be totally ignored to prevent the continuous animation from restarting. + * ``` */ internal class AnimationBindHandler : Animatable2.AnimationCallback() { private val onAnimationsComplete = mutableListOf<() -> Unit>() @@ -37,10 +41,10 @@ internal class AnimationBindHandler : Animatable2.AnimationCallback() { get() = registrations.any { it.isRunning } /** - * This check prevents rebinding to the action button if the identifier has not changed. A - * null value is always considered to be changed. This is used to prevent the connecting - * animation from rebinding (and restarting) if multiple buffer PlaybackStates are pushed by - * an application in a row. + * This check prevents rebinding to the action button if the identifier has not changed. A null + * value is always considered to be changed. This is used to prevent the connecting animation + * from rebinding (and restarting) if multiple buffer PlaybackStates are pushed by an + * application in a row. */ fun updateRebindId(newRebindId: Int?): Boolean { if (rebindId == null || newRebindId == null || rebindId != newRebindId) { @@ -78,4 +82,4 @@ internal class AnimationBindHandler : Animatable2.AnimationCallback() { onAnimationsComplete.clear() } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt index dda465a7a695..61ef2f1838e7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt @@ -39,12 +39,11 @@ interface ColorTransition { } /** - * A [ColorTransition] that animates between two specific colors. - * It uses a ValueAnimator to execute the animation and interpolate between the source color and - * the target color. + * A [ColorTransition] that animates between two specific colors. It uses a ValueAnimator to execute + * the animation and interpolate between the source color and the target color. * - * Selection of the target color from the scheme, and application of the interpolated color - * are delegated to callbacks. + * Selection of the target color from the scheme, and application of the interpolated color are + * delegated to callbacks. */ open class AnimatingColorTransition( private val defaultColor: Int, @@ -59,9 +58,8 @@ open class AnimatingColorTransition( var targetColor: Int = defaultColor override fun onAnimationUpdate(animation: ValueAnimator) { - currentColor = argbEvaluator.evaluate( - animation.animatedFraction, sourceColor, targetColor - ) as Int + currentColor = + argbEvaluator.evaluate(animation.animatedFraction, sourceColor, targetColor) as Int applyColor(currentColor) } @@ -91,111 +89,126 @@ open class AnimatingColorTransition( } typealias AnimatingColorTransitionFactory = - (Int, (ColorScheme) -> Int, (Int) -> Unit) -> AnimatingColorTransition + (Int, (ColorScheme) -> Int, (Int) -> Unit) -> AnimatingColorTransition /** - * ColorSchemeTransition constructs a ColorTransition for each color in the scheme - * that needs to be transitioned when changed. It also sets up the assignment functions for sending - * the sending the interpolated colors to the appropriate views. + * ColorSchemeTransition constructs a ColorTransition for each color in the scheme that needs to be + * transitioned when changed. It also sets up the assignment functions for sending the sending the + * interpolated colors to the appropriate views. */ -class ColorSchemeTransition internal constructor( +class ColorSchemeTransition +internal constructor( private val context: Context, private val mediaViewHolder: MediaViewHolder, animatingColorTransitionFactory: AnimatingColorTransitionFactory ) { - constructor(context: Context, mediaViewHolder: MediaViewHolder) : - this(context, mediaViewHolder, ::AnimatingColorTransition) + constructor( + context: Context, + mediaViewHolder: MediaViewHolder + ) : this(context, mediaViewHolder, ::AnimatingColorTransition) val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95) - val surfaceColor = animatingColorTransitionFactory( - bgColor, - ::surfaceFromScheme - ) { surfaceColor -> - val colorList = ColorStateList.valueOf(surfaceColor) - mediaViewHolder.seamlessIcon.imageTintList = colorList - mediaViewHolder.seamlessText.setTextColor(surfaceColor) - mediaViewHolder.albumView.backgroundTintList = colorList - mediaViewHolder.gutsViewHolder.setSurfaceColor(surfaceColor) - } - - val accentPrimary = animatingColorTransitionFactory( - loadDefaultColor(R.attr.textColorPrimary), - ::accentPrimaryFromScheme - ) { accentPrimary -> - val accentColorList = ColorStateList.valueOf(accentPrimary) - mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList - mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary) - } + val surfaceColor = + animatingColorTransitionFactory(bgColor, ::surfaceFromScheme) { surfaceColor -> + val colorList = ColorStateList.valueOf(surfaceColor) + mediaViewHolder.seamlessIcon.imageTintList = colorList + mediaViewHolder.seamlessText.setTextColor(surfaceColor) + mediaViewHolder.albumView.backgroundTintList = colorList + mediaViewHolder.gutsViewHolder.setSurfaceColor(surfaceColor) + } - val accentSecondary = animatingColorTransitionFactory( - loadDefaultColor(R.attr.textColorPrimary), - ::accentSecondaryFromScheme - ) { accentSecondary -> - val colorList = ColorStateList.valueOf(accentSecondary) - (mediaViewHolder.seamlessButton.background as? RippleDrawable)?.let { - it.setColor(colorList) - it.effectColor = colorList + val accentPrimary = + animatingColorTransitionFactory( + loadDefaultColor(R.attr.textColorPrimary), + ::accentPrimaryFromScheme + ) { accentPrimary -> + val accentColorList = ColorStateList.valueOf(accentPrimary) + mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList + mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary) } - } - val colorSeamless = animatingColorTransitionFactory( - loadDefaultColor(R.attr.textColorPrimary), - { colorScheme: ColorScheme -> - // A1-100 dark in dark theme, A1-200 in light theme - if (context.resources.configuration.uiMode and - Configuration.UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES) - colorScheme.accent1[2] - else colorScheme.accent1[3] - }, { seamlessColor: Int -> - val accentColorList = ColorStateList.valueOf(seamlessColor) - mediaViewHolder.seamlessButton.backgroundTintList = accentColorList - }) - - val textPrimary = animatingColorTransitionFactory( - loadDefaultColor(R.attr.textColorPrimary), - ::textPrimaryFromScheme - ) { textPrimary -> - mediaViewHolder.titleText.setTextColor(textPrimary) - val textColorList = ColorStateList.valueOf(textPrimary) - mediaViewHolder.seekBar.thumb.setTintList(textColorList) - mediaViewHolder.seekBar.progressTintList = textColorList - mediaViewHolder.scrubbingElapsedTimeView.setTextColor(textColorList) - mediaViewHolder.scrubbingTotalTimeView.setTextColor(textColorList) - for (button in mediaViewHolder.getTransparentActionButtons()) { - button.imageTintList = textColorList + val accentSecondary = + animatingColorTransitionFactory( + loadDefaultColor(R.attr.textColorPrimary), + ::accentSecondaryFromScheme + ) { accentSecondary -> + val colorList = ColorStateList.valueOf(accentSecondary) + (mediaViewHolder.seamlessButton.background as? RippleDrawable)?.let { + it.setColor(colorList) + it.effectColor = colorList + } } - mediaViewHolder.gutsViewHolder.setTextPrimaryColor(textPrimary) - } - val textPrimaryInverse = animatingColorTransitionFactory( - loadDefaultColor(R.attr.textColorPrimaryInverse), - ::textPrimaryInverseFromScheme - ) { textPrimaryInverse -> - mediaViewHolder.actionPlayPause.imageTintList = ColorStateList.valueOf(textPrimaryInverse) - } + val colorSeamless = + animatingColorTransitionFactory( + loadDefaultColor(R.attr.textColorPrimary), + { colorScheme: ColorScheme -> + // A1-100 dark in dark theme, A1-200 in light theme + if ( + context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == + UI_MODE_NIGHT_YES + ) + colorScheme.accent1[2] + else colorScheme.accent1[3] + }, + { seamlessColor: Int -> + val accentColorList = ColorStateList.valueOf(seamlessColor) + mediaViewHolder.seamlessButton.backgroundTintList = accentColorList + } + ) + + val textPrimary = + animatingColorTransitionFactory( + loadDefaultColor(R.attr.textColorPrimary), + ::textPrimaryFromScheme + ) { textPrimary -> + mediaViewHolder.titleText.setTextColor(textPrimary) + val textColorList = ColorStateList.valueOf(textPrimary) + mediaViewHolder.seekBar.thumb.setTintList(textColorList) + mediaViewHolder.seekBar.progressTintList = textColorList + mediaViewHolder.scrubbingElapsedTimeView.setTextColor(textColorList) + mediaViewHolder.scrubbingTotalTimeView.setTextColor(textColorList) + for (button in mediaViewHolder.getTransparentActionButtons()) { + button.imageTintList = textColorList + } + mediaViewHolder.gutsViewHolder.setTextPrimaryColor(textPrimary) + } - val textSecondary = animatingColorTransitionFactory( - loadDefaultColor(R.attr.textColorSecondary), - ::textSecondaryFromScheme - ) { textSecondary -> mediaViewHolder.artistText.setTextColor(textSecondary) } + val textPrimaryInverse = + animatingColorTransitionFactory( + loadDefaultColor(R.attr.textColorPrimaryInverse), + ::textPrimaryInverseFromScheme + ) { textPrimaryInverse -> + mediaViewHolder.actionPlayPause.imageTintList = + ColorStateList.valueOf(textPrimaryInverse) + } - val textTertiary = animatingColorTransitionFactory( - loadDefaultColor(R.attr.textColorTertiary), - ::textTertiaryFromScheme - ) { textTertiary -> - mediaViewHolder.seekBar.progressBackgroundTintList = ColorStateList.valueOf(textTertiary) - } + val textSecondary = + animatingColorTransitionFactory( + loadDefaultColor(R.attr.textColorSecondary), + ::textSecondaryFromScheme + ) { textSecondary -> mediaViewHolder.artistText.setTextColor(textSecondary) } + + val textTertiary = + animatingColorTransitionFactory( + loadDefaultColor(R.attr.textColorTertiary), + ::textTertiaryFromScheme + ) { textTertiary -> + mediaViewHolder.seekBar.progressBackgroundTintList = + ColorStateList.valueOf(textTertiary) + } - val colorTransitions = arrayOf( - surfaceColor, - colorSeamless, - accentPrimary, - accentSecondary, - textPrimary, - textPrimaryInverse, - textSecondary, - textTertiary, - ) + val colorTransitions = + arrayOf( + surfaceColor, + colorSeamless, + accentPrimary, + accentSecondary, + textPrimary, + textPrimaryInverse, + textSecondary, + textTertiary, + ) private fun loadDefaultColor(id: Int): Int { return Utils.getColorAttr(context, id).defaultColor diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt index 2440c2481ed7..9f86cd88788b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt @@ -42,22 +42,20 @@ import org.xmlpull.v1.XmlPullParser private const val BACKGROUND_ANIM_DURATION = 370L -/** - * Drawable that can draw an animated gradient when tapped. - */ +/** Drawable that can draw an animated gradient when tapped. */ @Keep class IlluminationDrawable : Drawable() { private var themeAttrs: IntArray? = null private var cornerRadiusOverride = -1f var cornerRadius = 0f - get() { - return if (cornerRadiusOverride >= 0) { - cornerRadiusOverride - } else { - field + get() { + return if (cornerRadiusOverride >= 0) { + cornerRadiusOverride + } else { + field + } } - } private var highlightColor = Color.TRANSPARENT private var tmpHsl = floatArrayOf(0f, 0f, 0f) private var paint = Paint() @@ -65,22 +63,27 @@ class IlluminationDrawable : Drawable() { private val lightSources = arrayListOf<LightSourceDrawable>() private var backgroundColor = Color.TRANSPARENT - set(value) { - if (value == field) { - return + set(value) { + if (value == field) { + return + } + field = value + animateBackground() } - field = value - animateBackground() - } private var backgroundAnimation: ValueAnimator? = null - /** - * Draw background and gradient. - */ + /** Draw background and gradient. */ override fun draw(canvas: Canvas) { - canvas.drawRoundRect(0f, 0f, bounds.width().toFloat(), bounds.height().toFloat(), - cornerRadius, cornerRadius, paint) + canvas.drawRoundRect( + 0f, + 0f, + bounds.width().toFloat(), + bounds.height().toFloat(), + cornerRadius, + cornerRadius, + paint + ) } override fun getOutline(outline: Outline) { @@ -105,12 +108,11 @@ class IlluminationDrawable : Drawable() { private fun updateStateFromTypedArray(a: TypedArray) { if (a.hasValue(R.styleable.IlluminationDrawable_cornerRadius)) { - cornerRadius = a.getDimension(R.styleable.IlluminationDrawable_cornerRadius, - cornerRadius) + cornerRadius = + a.getDimension(R.styleable.IlluminationDrawable_cornerRadius, cornerRadius) } if (a.hasValue(R.styleable.IlluminationDrawable_highlight)) { - highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) / - 100f + highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) / 100f } } @@ -163,34 +165,42 @@ class IlluminationDrawable : Drawable() { private fun animateBackground() { ColorUtils.colorToHSL(backgroundColor, tmpHsl) val L = tmpHsl[2] - tmpHsl[2] = MathUtils.constrain(if (L < 1f - highlight) { - L + highlight - } else { - L - highlight - }, 0f, 1f) + tmpHsl[2] = + MathUtils.constrain( + if (L < 1f - highlight) { + L + highlight + } else { + L - highlight + }, + 0f, + 1f + ) val initialBackground = paint.color val initialHighlight = highlightColor val finalHighlight = ColorUtils.HSLToColor(tmpHsl) backgroundAnimation?.cancel() - backgroundAnimation = ValueAnimator.ofFloat(0f, 1f).apply { - duration = BACKGROUND_ANIM_DURATION - interpolator = Interpolators.FAST_OUT_LINEAR_IN - addUpdateListener { - val progress = it.animatedValue as Float - paint.color = blendARGB(initialBackground, backgroundColor, progress) - highlightColor = blendARGB(initialHighlight, finalHighlight, progress) - lightSources.forEach { it.highlightColor = highlightColor } - invalidateSelf() - } - addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator?) { - backgroundAnimation = null + backgroundAnimation = + ValueAnimator.ofFloat(0f, 1f).apply { + duration = BACKGROUND_ANIM_DURATION + interpolator = Interpolators.FAST_OUT_LINEAR_IN + addUpdateListener { + val progress = it.animatedValue as Float + paint.color = blendARGB(initialBackground, backgroundColor, progress) + highlightColor = blendARGB(initialHighlight, finalHighlight, progress) + lightSources.forEach { it.highlightColor = highlightColor } + invalidateSelf() } - }) - start() - } + addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + backgroundAnimation = null + } + } + ) + start() + } } override fun setTintList(tint: ColorStateList?) { @@ -215,4 +225,4 @@ class IlluminationDrawable : Drawable() { fun setCornerRadiusOverride(cornerRadius: Float?) { cornerRadiusOverride = cornerRadius ?: -1f } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt index c9aa999a0a2d..899148b0014c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt @@ -45,7 +45,9 @@ import javax.inject.Named * switches media player positioning between split pane container vs single pane container */ @SysUISingleton -class KeyguardMediaController @Inject constructor( +class KeyguardMediaController +@Inject +constructor( @param:Named(KEYGUARD) private val mediaHost: MediaHost, private val bypassController: KeyguardBypassController, private val statusBarStateController: SysuiStatusBarStateController, @@ -56,34 +58,40 @@ class KeyguardMediaController @Inject constructor( ) { init { - statusBarStateController.addCallback(object : StatusBarStateController.StateListener { - override fun onStateChanged(newState: Int) { - refreshMediaPosition() + statusBarStateController.addCallback( + object : StatusBarStateController.StateListener { + override fun onStateChanged(newState: Int) { + refreshMediaPosition() + } } - }) - configurationController.addCallback(object : ConfigurationController.ConfigurationListener { - override fun onConfigChanged(newConfig: Configuration?) { - updateResources() + ) + configurationController.addCallback( + object : ConfigurationController.ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration?) { + updateResources() + } } - }) + ) - val settingsObserver: ContentObserver = object : ContentObserver(handler) { - override fun onChange(selfChange: Boolean, uri: Uri?) { - if (uri == lockScreenMediaPlayerUri) { - allowMediaPlayerOnLockScreen = + val settingsObserver: ContentObserver = + object : ContentObserver(handler) { + override fun onChange(selfChange: Boolean, uri: Uri?) { + if (uri == lockScreenMediaPlayerUri) { + allowMediaPlayerOnLockScreen = secureSettings.getBoolForUser( - Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, - true, - UserHandle.USER_CURRENT + Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, + true, + UserHandle.USER_CURRENT ) - refreshMediaPosition() + refreshMediaPosition() + } } } - } secureSettings.registerContentObserverForUser( - Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, - settingsObserver, - UserHandle.USER_ALL) + Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, + settingsObserver, + UserHandle.USER_ALL + ) // First let's set the desired state that we want for this host mediaHost.expansion = MediaHostState.EXPANDED @@ -110,27 +118,21 @@ class KeyguardMediaController @Inject constructor( refreshMediaPosition() } - /** - * Is the media player visible? - */ + /** Is the media player visible? */ var visible = false private set var visibilityChangedListener: ((Boolean) -> Unit)? = null - /** - * single pane media container placed at the top of the notifications list - */ + /** single pane media container placed at the top of the notifications list */ var singlePaneContainer: MediaContainerView? = null private set private var splitShadeContainer: ViewGroup? = null - /** - * Track the media player setting status on lock screen. - */ + /** Track the media player setting status on lock screen. */ private var allowMediaPlayerOnLockScreen: Boolean = true private val lockScreenMediaPlayerUri = - secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN) + secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN) /** * Attaches media container in single pane mode, situated at the top of the notifications list @@ -146,9 +148,7 @@ class KeyguardMediaController @Inject constructor( onMediaHostVisibilityChanged(mediaHost.visible) } - /** - * Called whenever the media hosts visibility changes - */ + /** Called whenever the media hosts visibility changes */ private fun onMediaHostVisibilityChanged(visible: Boolean) { refreshMediaPosition() if (visible) { @@ -159,9 +159,7 @@ class KeyguardMediaController @Inject constructor( } } - /** - * Attaches media container in split shade mode, situated to the left of notifications - */ + /** Attaches media container in split shade mode, situated to the left of notifications */ fun attachSplitShadeContainer(container: ViewGroup) { splitShadeContainer = container reattachHostView() @@ -183,9 +181,7 @@ class KeyguardMediaController @Inject constructor( } if (activeContainer?.childCount == 0) { // Detach the hostView from its parent view if exists - mediaHost.hostView.parent?.let { - (it as? ViewGroup)?.removeView(mediaHost.hostView) - } + mediaHost.hostView.parent?.let { (it as? ViewGroup)?.removeView(mediaHost.hostView) } activeContainer.addView(mediaHost.hostView) } } @@ -193,7 +189,8 @@ class KeyguardMediaController @Inject constructor( fun refreshMediaPosition() { val keyguardOrUserSwitcher = (statusBarStateController.state == StatusBarState.KEYGUARD) // mediaHost.visible required for proper animations handling - visible = mediaHost.visible && + visible = + mediaHost.visible && !bypassController.bypassEnabled && keyguardOrUserSwitcher && allowMediaPlayerOnLockScreen diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt index 0d2efdf37b25..dd5c2bf497cb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt @@ -55,9 +55,7 @@ private data class RippleData( var highlight: Float ) -/** - * Drawable that can draw an animated gradient when tapped. - */ +/** Drawable that can draw an animated gradient when tapped. */ @Keep class LightSourceDrawable : Drawable() { @@ -67,17 +65,15 @@ class LightSourceDrawable : Drawable() { private var paint = Paint() var highlightColor = Color.WHITE - set(value) { - if (field == value) { - return + set(value) { + if (field == value) { + return + } + field = value + invalidateSelf() } - field = value - invalidateSelf() - } - /** - * Draw a small highlight under the finger before expanding (or cancelling) it. - */ + /** Draw a small highlight under the finger before expanding (or cancelling) it. */ private var active: Boolean = false set(value) { if (value == field) { @@ -91,46 +87,54 @@ class LightSourceDrawable : Drawable() { rippleData.progress = RIPPLE_DOWN_PROGRESS } else { rippleAnimation?.cancel() - rippleAnimation = ValueAnimator.ofFloat(rippleData.alpha, 0f).apply { - duration = RIPPLE_CANCEL_DURATION - interpolator = Interpolators.LINEAR_OUT_SLOW_IN - addUpdateListener { - rippleData.alpha = it.animatedValue as Float - invalidateSelf() - } - addListener(object : AnimatorListenerAdapter() { - var cancelled = false - override fun onAnimationCancel(animation: Animator?) { - cancelled = true - } - - override fun onAnimationEnd(animation: Animator?) { - if (cancelled) { - return - } - rippleData.progress = 0f - rippleData.alpha = 0f - rippleAnimation = null + rippleAnimation = + ValueAnimator.ofFloat(rippleData.alpha, 0f).apply { + duration = RIPPLE_CANCEL_DURATION + interpolator = Interpolators.LINEAR_OUT_SLOW_IN + addUpdateListener { + rippleData.alpha = it.animatedValue as Float invalidateSelf() } - }) - start() - } + addListener( + object : AnimatorListenerAdapter() { + var cancelled = false + override fun onAnimationCancel(animation: Animator?) { + cancelled = true + } + + override fun onAnimationEnd(animation: Animator?) { + if (cancelled) { + return + } + rippleData.progress = 0f + rippleData.alpha = 0f + rippleAnimation = null + invalidateSelf() + } + } + ) + start() + } } invalidateSelf() } private var rippleAnimation: Animator? = null - /** - * Draw background and gradient. - */ + /** Draw background and gradient. */ override fun draw(canvas: Canvas) { val radius = lerp(rippleData.minSize, rippleData.maxSize, rippleData.progress) val centerColor = - ColorUtils.setAlphaComponent(highlightColor, (rippleData.alpha * 255).toInt()) - paint.shader = RadialGradient(rippleData.x, rippleData.y, radius, - intArrayOf(centerColor, Color.TRANSPARENT), GRADIENT_STOPS, Shader.TileMode.CLAMP) + ColorUtils.setAlphaComponent(highlightColor, (rippleData.alpha * 255).toInt()) + paint.shader = + RadialGradient( + rippleData.x, + rippleData.y, + radius, + intArrayOf(centerColor, Color.TRANSPARENT), + GRADIENT_STOPS, + Shader.TileMode.CLAMP + ) canvas.drawCircle(rippleData.x, rippleData.y, radius, paint) } @@ -162,8 +166,8 @@ class LightSourceDrawable : Drawable() { rippleData.maxSize = a.getDimension(R.styleable.IlluminationDrawable_rippleMaxSize, 0f) } if (a.hasValue(R.styleable.IlluminationDrawable_highlight)) { - rippleData.highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) / - 100f + rippleData.highlight = + a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) / 100f } } @@ -193,40 +197,44 @@ class LightSourceDrawable : Drawable() { invalidateSelf() } - /** - * Draws an animated ripple that expands fading away. - */ + /** Draws an animated ripple that expands fading away. */ private fun illuminate() { rippleData.alpha = 1f invalidateSelf() rippleAnimation?.cancel() - rippleAnimation = AnimatorSet().apply { - playTogether(ValueAnimator.ofFloat(1f, 0f).apply { - startDelay = 133 - duration = RIPPLE_ANIM_DURATION - startDelay - interpolator = Interpolators.LINEAR_OUT_SLOW_IN - addUpdateListener { - rippleData.alpha = it.animatedValue as Float - invalidateSelf() - } - }, ValueAnimator.ofFloat(rippleData.progress, 1f).apply { - duration = RIPPLE_ANIM_DURATION - interpolator = Interpolators.LINEAR_OUT_SLOW_IN - addUpdateListener { - rippleData.progress = it.animatedValue as Float - invalidateSelf() - } - }) - addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator?) { - rippleData.progress = 0f - rippleAnimation = null - invalidateSelf() - } - }) - start() - } + rippleAnimation = + AnimatorSet().apply { + playTogether( + ValueAnimator.ofFloat(1f, 0f).apply { + startDelay = 133 + duration = RIPPLE_ANIM_DURATION - startDelay + interpolator = Interpolators.LINEAR_OUT_SLOW_IN + addUpdateListener { + rippleData.alpha = it.animatedValue as Float + invalidateSelf() + } + }, + ValueAnimator.ofFloat(rippleData.progress, 1f).apply { + duration = RIPPLE_ANIM_DURATION + interpolator = Interpolators.LINEAR_OUT_SLOW_IN + addUpdateListener { + rippleData.progress = it.animatedValue as Float + invalidateSelf() + } + } + ) + addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + rippleData.progress = 0f + rippleAnimation = null + invalidateSelf() + } + } + ) + start() + } } override fun setHotspot(x: Float, y: Float) { @@ -251,8 +259,13 @@ class LightSourceDrawable : Drawable() { override fun getDirtyBounds(): Rect { val radius = lerp(rippleData.minSize, rippleData.maxSize, rippleData.progress) - val bounds = Rect((rippleData.x - radius).toInt(), (rippleData.y - radius).toInt(), - (rippleData.x + radius).toInt(), (rippleData.y + radius).toInt()) + val bounds = + Rect( + (rippleData.x - radius).toInt(), + (rippleData.y - radius).toInt(), + (rippleData.x + radius).toInt(), + (rippleData.y + radius).toInt() + ) bounds.union(super.getDirtyBounds()) return bounds } @@ -293,4 +306,4 @@ class LightSourceDrawable : Drawable() { return changed } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt index 96fe335c9ab3..e38c1baaeae9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.media.controls.ui import android.app.PendingIntent @@ -52,11 +68,13 @@ private val settingsIntent = Intent().setAction(ACTION_MEDIA_CONTROLS_SETTINGS) private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) /** - * Class that is responsible for keeping the view carousel up to date. - * This also handles changes in state and applies them to the media carousel like the expansion. + * Class that is responsible for keeping the view carousel up to date. This also handles changes in + * state and applies them to the media carousel like the expansion. */ @SysUISingleton -class MediaCarouselController @Inject constructor( +class MediaCarouselController +@Inject +constructor( private val context: Context, private val mediaControlPanelFactory: Provider<MediaControlPanel>, private val visualStabilityProvider: VisualStabilityProvider, @@ -72,60 +90,42 @@ class MediaCarouselController @Inject constructor( private val logger: MediaUiEventLogger, private val debugLogger: MediaCarouselControllerLogger ) : Dumpable { - /** - * The current width of the carousel - */ + /** The current width of the carousel */ private var currentCarouselWidth: Int = 0 - /** - * The current height of the carousel - */ + /** The current height of the carousel */ private var currentCarouselHeight: Int = 0 - /** - * Are we currently showing only active players - */ + /** Are we currently showing only active players */ private var currentlyShowingOnlyActive: Boolean = false - /** - * Is the player currently visible (at the end of the transformation - */ + /** Is the player currently visible (at the end of the transformation */ private var playersVisible: Boolean = false /** * The desired location where we'll be at the end of the transformation. Usually this matches * the end location, except when we're still waiting on a state update call. */ - @MediaLocation - private var desiredLocation: Int = -1 + @MediaLocation private var desiredLocation: Int = -1 /** * The ending location of the view where it ends when all animations and transitions have * finished */ - @MediaLocation - @VisibleForTesting - var currentEndLocation: Int = -1 + @MediaLocation @VisibleForTesting var currentEndLocation: Int = -1 /** * The ending location of the view where it ends when all animations and transitions have * finished */ - @MediaLocation - private var currentStartLocation: Int = -1 + @MediaLocation private var currentStartLocation: Int = -1 - /** - * The progress of the transition or 1.0 if there is no transition happening - */ + /** The progress of the transition or 1.0 if there is no transition happening */ private var currentTransitionProgress: Float = 1.0f - /** - * The measured width of the carousel - */ + /** The measured width of the carousel */ private var carouselMeasureWidth: Int = 0 - /** - * The measured height of the carousel - */ + /** The measured height of the carousel */ private var carouselMeasureHeight: Int = 0 private var desiredHostState: MediaHostState? = null private val mediaCarousel: MediaScrollView @@ -135,8 +135,7 @@ class MediaCarouselController @Inject constructor( lateinit var settingsButton: View private set private val mediaContent: ViewGroup - @VisibleForTesting - val pageIndicator: PageIndicator + @VisibleForTesting val pageIndicator: PageIndicator private val visualStabilityCallback: OnReorderingAllowedListener private var needsReordering: Boolean = false private var keysNeedRemoval = mutableSetOf<String>() @@ -146,7 +145,7 @@ class MediaCarouselController @Inject constructor( if (value != field) { field = value mediaFrame.layoutDirection = - if (value) View.LAYOUT_DIRECTION_RTL else View.LAYOUT_DIRECTION_LTR + if (value) View.LAYOUT_DIRECTION_RTL else View.LAYOUT_DIRECTION_LTR mediaCarouselScrollHandler.scrollToStart() } } @@ -168,40 +167,45 @@ class MediaCarouselController @Inject constructor( const val PAGINATION_DELAY = 1900f const val MEDIATITLES_DELAY = 1000f const val MEDIACONTAINERS_DELAY = 967f - val TRANSFORM_BEZIER = PathInterpolator (0.68F, 0F, 0F, 1F) - val REVERSE_BEZIER = PathInterpolator (0F, 0.68F, 1F, 0F) + val TRANSFORM_BEZIER = PathInterpolator(0.68F, 0F, 0F, 1F) + val REVERSE_BEZIER = PathInterpolator(0F, 0.68F, 1F, 0F) fun calculateAlpha(squishinessFraction: Float, delay: Float, duration: Float): Float { val transformStartFraction = delay / ANIMATION_BASE_DURATION val transformDurationFraction = duration / ANIMATION_BASE_DURATION val squishinessToTime = REVERSE_BEZIER.getInterpolation(squishinessFraction) - return MathUtils.constrain((squishinessToTime - transformStartFraction) / - transformDurationFraction, 0F, 1F) + return MathUtils.constrain( + (squishinessToTime - transformStartFraction) / transformDurationFraction, + 0F, + 1F + ) } } - private val configListener = object : ConfigurationController.ConfigurationListener { - override fun onDensityOrFontScaleChanged() { - // System font changes should only happen when UMO is offscreen or a flicker may occur - updatePlayers(recreateMedia = true) - inflateSettingsButton() - } + private val configListener = + object : ConfigurationController.ConfigurationListener { + override fun onDensityOrFontScaleChanged() { + // System font changes should only happen when UMO is offscreen or a flicker may + // occur + updatePlayers(recreateMedia = true) + inflateSettingsButton() + } - override fun onThemeChanged() { - updatePlayers(recreateMedia = false) - inflateSettingsButton() - } + override fun onThemeChanged() { + updatePlayers(recreateMedia = false) + inflateSettingsButton() + } - override fun onConfigChanged(newConfig: Configuration?) { - if (newConfig == null) return - isRtl = newConfig.layoutDirection == View.LAYOUT_DIRECTION_RTL - } + override fun onConfigChanged(newConfig: Configuration?) { + if (newConfig == null) return + isRtl = newConfig.layoutDirection == View.LAYOUT_DIRECTION_RTL + } - override fun onUiModeChanged() { - updatePlayers(recreateMedia = false) - inflateSettingsButton() + override fun onUiModeChanged() { + updatePlayers(recreateMedia = false) + inflateSettingsButton() + } } - } /** * Update MediaCarouselScrollHandler.visibleToUser to reflect media card container visibility. @@ -218,10 +222,19 @@ class MediaCarouselController @Inject constructor( mediaFrame = inflateMediaCarousel() mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller) pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator) - mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator, - executor, this::onSwipeToDismiss, this::updatePageIndicatorLocation, - this::closeGuts, falsingCollector, falsingManager, this::logSmartspaceImpression, - logger) + mediaCarouselScrollHandler = + MediaCarouselScrollHandler( + mediaCarousel, + pageIndicator, + executor, + this::onSwipeToDismiss, + this::updatePageIndicatorLocation, + this::closeGuts, + falsingCollector, + falsingManager, + this::logSmartspaceImpression, + logger + ) isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL inflateSettingsButton() mediaContent = mediaCarousel.requireViewById(R.id.media_carousel) @@ -232,9 +245,7 @@ class MediaCarouselController @Inject constructor( reorderAllPlayers(previousVisiblePlayerKey = null) } - keysNeedRemoval.forEach { - removePlayer(it) - } + keysNeedRemoval.forEach { removePlayer(it) } if (keysNeedRemoval.size > 0) { // Carousel visibility may need to be updated after late removals updateHostVisibility() @@ -251,176 +262,226 @@ class MediaCarouselController @Inject constructor( mediaCarouselScrollHandler.scrollToStart() } visualStabilityProvider.addPersistentReorderingAllowedListener(visualStabilityCallback) - mediaManager.addListener(object : MediaDataManager.Listener { - override fun onMediaDataLoaded( - key: String, - oldKey: String?, - data: MediaData, - immediately: Boolean, - receivedSmartspaceCardLatency: Int, - isSsReactivated: Boolean - ) { - debugLogger.logMediaLoaded(key) - if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated)) { - // Log card received if a new resumable media card is added - MediaPlayerData.getMediaPlayer(key)?.let { - /* ktlint-disable max-line-length */ - logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED + mediaManager.addListener( + object : MediaDataManager.Listener { + override fun onMediaDataLoaded( + key: String, + oldKey: String?, + data: MediaData, + immediately: Boolean, + receivedSmartspaceCardLatency: Int, + isSsReactivated: Boolean + ) { + debugLogger.logMediaLoaded(key) + if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated)) { + // Log card received if a new resumable media card is added + MediaPlayerData.getMediaPlayer(key)?.let { + /* ktlint-disable max-line-length */ + logSmartspaceCardReported( + 759, // SMARTSPACE_CARD_RECEIVED it.mSmartspaceId, it.mUid, - surfaces = intArrayOf( - SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, - SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN, - SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY), - rank = MediaPlayerData.getMediaPlayerIndex(key)) - /* ktlint-disable max-line-length */ - } - if (mediaCarouselScrollHandler.visibleToUser && - mediaCarouselScrollHandler.visibleMediaIndex - == MediaPlayerData.getMediaPlayerIndex(key)) { - logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded) - } - } else if (receivedSmartspaceCardLatency != 0) { - // Log resume card received if resumable media card is reactivated and - // resume card is ranked first - MediaPlayerData.players().forEachIndexed { index, it -> - if (it.recommendationViewHolder == null) { - it.mSmartspaceId = SmallHash.hash(it.mUid + - systemClock.currentTimeMillis().toInt()) - it.mIsImpressed = false + surfaces = + intArrayOf( + SysUiStatsLog + .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, + SysUiStatsLog + .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN, + SysUiStatsLog + .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY + ), + rank = MediaPlayerData.getMediaPlayerIndex(key) + ) /* ktlint-disable max-line-length */ - logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED + } + if ( + mediaCarouselScrollHandler.visibleToUser && + mediaCarouselScrollHandler.visibleMediaIndex == + MediaPlayerData.getMediaPlayerIndex(key) + ) { + logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded) + } + } else if (receivedSmartspaceCardLatency != 0) { + // Log resume card received if resumable media card is reactivated and + // resume card is ranked first + MediaPlayerData.players().forEachIndexed { index, it -> + if (it.recommendationViewHolder == null) { + it.mSmartspaceId = + SmallHash.hash( + it.mUid + systemClock.currentTimeMillis().toInt() + ) + it.mIsImpressed = false + /* ktlint-disable max-line-length */ + logSmartspaceCardReported( + 759, // SMARTSPACE_CARD_RECEIVED it.mSmartspaceId, it.mUid, - surfaces = intArrayOf( - SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, - SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN, - SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY), + surfaces = + intArrayOf( + SysUiStatsLog + .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, + SysUiStatsLog + .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN, + SysUiStatsLog + .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY + ), rank = index, - receivedLatencyMillis = receivedSmartspaceCardLatency) - /* ktlint-disable max-line-length */ + receivedLatencyMillis = receivedSmartspaceCardLatency + ) + /* ktlint-disable max-line-length */ + } + } + // If media container area already visible to the user, log impression for + // reactivated card. + if ( + mediaCarouselScrollHandler.visibleToUser && + !mediaCarouselScrollHandler.qsExpanded + ) { + logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded) } } - // If media container area already visible to the user, log impression for - // reactivated card. - if (mediaCarouselScrollHandler.visibleToUser && - !mediaCarouselScrollHandler.qsExpanded) { - logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded) - } - } - val canRemove = data.isPlaying?.let { !it } ?: data.isClearable && !data.active - if (canRemove && !Utils.useMediaResumption(context)) { - // This view isn't playing, let's remove this! This happens e.g when - // dismissing/timing out a view. We still have the data around because - // resumption could be on, but we should save the resources and release this. - if (isReorderingAllowed) { - onMediaDataRemoved(key) + val canRemove = data.isPlaying?.let { !it } ?: data.isClearable && !data.active + if (canRemove && !Utils.useMediaResumption(context)) { + // This view isn't playing, let's remove this! This happens e.g. when + // dismissing/timing out a view. We still have the data around because + // resumption could be on, but we should save the resources and release + // this. + if (isReorderingAllowed) { + onMediaDataRemoved(key) + } else { + keysNeedRemoval.add(key) + } } else { - keysNeedRemoval.add(key) + keysNeedRemoval.remove(key) } - } else { - keysNeedRemoval.remove(key) } - } - override fun onSmartspaceMediaDataLoaded( - key: String, - data: SmartspaceMediaData, - shouldPrioritize: Boolean - ) { - debugLogger.logRecommendationLoaded(key) - // Log the case where the hidden media carousel with the existed inactive resume - // media is shown by the Smartspace signal. - if (data.isActive) { - val hasActivatedExistedResumeMedia = + override fun onSmartspaceMediaDataLoaded( + key: String, + data: SmartspaceMediaData, + shouldPrioritize: Boolean + ) { + debugLogger.logRecommendationLoaded(key) + // Log the case where the hidden media carousel with the existed inactive resume + // media is shown by the Smartspace signal. + if (data.isActive) { + val hasActivatedExistedResumeMedia = !mediaManager.hasActiveMedia() && - mediaManager.hasAnyMedia() && - shouldPrioritize - if (hasActivatedExistedResumeMedia) { - // Log resume card received if resumable media card is reactivated and - // recommendation card is valid and ranked first - MediaPlayerData.players().forEachIndexed { index, it -> - if (it.recommendationViewHolder == null) { - it.mSmartspaceId = SmallHash.hash(it.mUid + - systemClock.currentTimeMillis().toInt()) - it.mIsImpressed = false - /* ktlint-disable max-line-length */ - logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED + mediaManager.hasAnyMedia() && + shouldPrioritize + if (hasActivatedExistedResumeMedia) { + // Log resume card received if resumable media card is reactivated and + // recommendation card is valid and ranked first + MediaPlayerData.players().forEachIndexed { index, it -> + if (it.recommendationViewHolder == null) { + it.mSmartspaceId = + SmallHash.hash( + it.mUid + systemClock.currentTimeMillis().toInt() + ) + it.mIsImpressed = false + /* ktlint-disable max-line-length */ + logSmartspaceCardReported( + 759, // SMARTSPACE_CARD_RECEIVED it.mSmartspaceId, it.mUid, - surfaces = intArrayOf( - SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, - SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN, - SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY), + surfaces = + intArrayOf( + SysUiStatsLog + .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, + SysUiStatsLog + .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN, + SysUiStatsLog + .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY + ), rank = index, - receivedLatencyMillis = (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis).toInt()) - /* ktlint-disable max-line-length */ + receivedLatencyMillis = + (systemClock.currentTimeMillis() - + data.headphoneConnectionTimeMillis) + .toInt() + ) + /* ktlint-disable max-line-length */ + } } } - } - addSmartspaceMediaRecommendations(key, data, shouldPrioritize) - MediaPlayerData.getMediaPlayer(key)?.let { - /* ktlint-disable max-line-length */ - logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED + addSmartspaceMediaRecommendations(key, data, shouldPrioritize) + MediaPlayerData.getMediaPlayer(key)?.let { + /* ktlint-disable max-line-length */ + logSmartspaceCardReported( + 759, // SMARTSPACE_CARD_RECEIVED it.mSmartspaceId, it.mUid, - surfaces = intArrayOf( - SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, - SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN, - SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY), + surfaces = + intArrayOf( + SysUiStatsLog + .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE, + SysUiStatsLog + .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__LOCKSCREEN, + SysUiStatsLog + .SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DREAM_OVERLAY + ), rank = MediaPlayerData.getMediaPlayerIndex(key), - receivedLatencyMillis = (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis).toInt()) - /* ktlint-disable max-line-length */ - } - if (mediaCarouselScrollHandler.visibleToUser && - mediaCarouselScrollHandler.visibleMediaIndex - == MediaPlayerData.getMediaPlayerIndex(key)) { - logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded) + receivedLatencyMillis = + (systemClock.currentTimeMillis() - + data.headphoneConnectionTimeMillis) + .toInt() + ) + /* ktlint-disable max-line-length */ + } + if ( + mediaCarouselScrollHandler.visibleToUser && + mediaCarouselScrollHandler.visibleMediaIndex == + MediaPlayerData.getMediaPlayerIndex(key) + ) { + logSmartspaceImpression(mediaCarouselScrollHandler.qsExpanded) + } + } else { + onSmartspaceMediaDataRemoved(data.targetId, immediately = true) } - } else { - onSmartspaceMediaDataRemoved(data.targetId, immediately = true) } - } - - override fun onMediaDataRemoved(key: String) { - debugLogger.logMediaRemoved(key) - removePlayer(key) - } - override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) { - debugLogger.logRecommendationRemoved(key, immediately) - if (immediately || isReorderingAllowed) { + override fun onMediaDataRemoved(key: String) { + debugLogger.logMediaRemoved(key) removePlayer(key) - if (!immediately) { - // Although it wasn't requested, we were able to process the removal - // immediately since reordering is allowed. So, notify hosts to update - if (this@MediaCarouselController::updateHostVisibility.isInitialized) { - updateHostVisibility() + } + + override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) { + debugLogger.logRecommendationRemoved(key, immediately) + if (immediately || isReorderingAllowed) { + removePlayer(key) + if (!immediately) { + // Although it wasn't requested, we were able to process the removal + // immediately since reordering is allowed. So, notify hosts to update + if (this@MediaCarouselController::updateHostVisibility.isInitialized) { + updateHostVisibility() + } } + } else { + keysNeedRemoval.add(key) } - } else { - keysNeedRemoval.add(key) } } - }) + ) mediaFrame.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> // The pageIndicator is not laid out yet when we get the current state update, // Lets make sure we have the right dimensions updatePageIndicatorLocation() } - mediaHostStatesManager.addCallback(object : MediaHostStatesManager.Callback { - override fun onHostStateChanged(location: Int, mediaHostState: MediaHostState) { - if (location == desiredLocation) { - onDesiredLocationChanged(desiredLocation, mediaHostState, animate = false) + mediaHostStatesManager.addCallback( + object : MediaHostStatesManager.Callback { + override fun onHostStateChanged(location: Int, mediaHostState: MediaHostState) { + if (location == desiredLocation) { + onDesiredLocationChanged(desiredLocation, mediaHostState, animate = false) + } } } - }) + ) } private fun inflateSettingsButton() { - val settings = LayoutInflater.from(context).inflate(R.layout.media_carousel_settings_button, - mediaFrame, false) as View + val settings = + LayoutInflater.from(context) + .inflate(R.layout.media_carousel_settings_button, mediaFrame, false) as View if (this::settingsButton.isInitialized) { mediaFrame.removeView(settingsButton) } @@ -434,8 +495,9 @@ class MediaCarouselController @Inject constructor( } private fun inflateMediaCarousel(): ViewGroup { - val mediaCarousel = LayoutInflater.from(context).inflate(R.layout.media_carousel, - UniqueObjectHostView(context), false) as ViewGroup + val mediaCarousel = + LayoutInflater.from(context) + .inflate(R.layout.media_carousel, UniqueObjectHostView(context), false) as ViewGroup // Because this is inflated when not attached to the true view hierarchy, it resolves some // potential issues to force that the layout direction is defined by the locale // (rather than inherited from the parent, which would resolve to LTR when unattached). @@ -444,16 +506,15 @@ class MediaCarouselController @Inject constructor( } private fun reorderAllPlayers( - previousVisiblePlayerKey: MediaPlayerData.MediaSortKey?, - key: String? = null + previousVisiblePlayerKey: MediaPlayerData.MediaSortKey?, + key: String? = null ) { mediaContent.removeAllViews() for (mediaPlayer in MediaPlayerData.players()) { - mediaPlayer.mediaViewHolder?.let { - mediaContent.addView(it.player) - } ?: mediaPlayer.recommendationViewHolder?.let { - mediaContent.addView(it.recommendations) - } + mediaPlayer.mediaViewHolder?.let { mediaContent.addView(it.player) } + ?: mediaPlayer.recommendationViewHolder?.let { + mediaContent.addView(it.recommendations) + } } mediaCarouselScrollHandler.onPlayersChanged() MediaPlayerData.updateVisibleMediaPlayers() @@ -463,11 +524,11 @@ class MediaCarouselController @Inject constructor( val mediaIndex = key?.let { MediaPlayerData.getMediaPlayerIndex(it) } ?: -1 if (mediaIndex != -1) { previousVisiblePlayerKey?.let { - val previousVisibleIndex = MediaPlayerData.playerKeys() - .indexOfFirst { key -> it == key } - mediaCarouselScrollHandler - .scrollToPlayer(previousVisibleIndex, mediaIndex) - } ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = mediaIndex) + val previousVisibleIndex = + MediaPlayerData.playerKeys().indexOfFirst { key -> it == key } + mediaCarouselScrollHandler.scrollToPlayer(previousVisibleIndex, mediaIndex) + } + ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = mediaIndex) } } } @@ -478,102 +539,137 @@ class MediaCarouselController @Inject constructor( oldKey: String?, data: MediaData, isSsReactivated: Boolean - ): Boolean = traceSection("MediaCarouselController#addOrUpdatePlayer") { - MediaPlayerData.moveIfExists(oldKey, key) - val existingPlayer = MediaPlayerData.getMediaPlayer(key) - val curVisibleMediaKey = MediaPlayerData.visiblePlayerKeys() - .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex) - if (existingPlayer == null) { - val newPlayer = mediaControlPanelFactory.get() - newPlayer.attachPlayer(MediaViewHolder.create( - LayoutInflater.from(context), mediaContent)) - newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions - val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT) - newPlayer.mediaViewHolder?.player?.setLayoutParams(lp) - newPlayer.bindPlayer(data, key) - newPlayer.setListening(currentlyExpanded) - MediaPlayerData.addMediaPlayer( - key, data, newPlayer, systemClock, isSsReactivated, debugLogger - ) - updatePlayerToState(newPlayer, noAnimation = true) - // Media data added from a recommendation card should starts playing. - if ((shouldScrollToKey && data.isPlaying == true) || - (!shouldScrollToKey && data.active)) { - reorderAllPlayers(curVisibleMediaKey, key) + ): Boolean = + traceSection("MediaCarouselController#addOrUpdatePlayer") { + MediaPlayerData.moveIfExists(oldKey, key) + val existingPlayer = MediaPlayerData.getMediaPlayer(key) + val curVisibleMediaKey = + MediaPlayerData.visiblePlayerKeys() + .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex) + if (existingPlayer == null) { + val newPlayer = mediaControlPanelFactory.get() + newPlayer.attachPlayer( + MediaViewHolder.create(LayoutInflater.from(context), mediaContent) + ) + newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions + val lp = + LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + newPlayer.mediaViewHolder?.player?.setLayoutParams(lp) + newPlayer.bindPlayer(data, key) + newPlayer.setListening(currentlyExpanded) + MediaPlayerData.addMediaPlayer( + key, + data, + newPlayer, + systemClock, + isSsReactivated, + debugLogger + ) + updatePlayerToState(newPlayer, noAnimation = true) + // Media data added from a recommendation card should starts playing. + if ( + (shouldScrollToKey && data.isPlaying == true) || + (!shouldScrollToKey && data.active) + ) { + reorderAllPlayers(curVisibleMediaKey, key) + } else { + needsReordering = true + } } else { - needsReordering = true + existingPlayer.bindPlayer(data, key) + MediaPlayerData.addMediaPlayer( + key, + data, + existingPlayer, + systemClock, + isSsReactivated, + debugLogger + ) + val packageName = MediaPlayerData.smartspaceMediaData?.packageName ?: String() + // In case of recommendations hits. + // Check the playing status of media player and the package name. + // To make sure we scroll to the right app's media player. + if ( + isReorderingAllowed || + shouldScrollToKey && + data.isPlaying == true && + packageName == data.packageName + ) { + reorderAllPlayers(curVisibleMediaKey, key) + } else { + needsReordering = true + } } - } else { - existingPlayer.bindPlayer(data, key) - MediaPlayerData.addMediaPlayer( - key, data, existingPlayer, systemClock, isSsReactivated, debugLogger - ) - val packageName = MediaPlayerData.smartspaceMediaData?.packageName ?: String() - // In case of recommendations hits. - // Check the playing status of media player and the package name. - // To make sure we scroll to the right app's media player. - if (isReorderingAllowed || - shouldScrollToKey && - data.isPlaying == true && - packageName == data.packageName - ) { - reorderAllPlayers(curVisibleMediaKey, key) - } else { - needsReordering = true + updatePageIndicator() + mediaCarouselScrollHandler.onPlayersChanged() + mediaFrame.requiresRemeasuring = true + // Check postcondition: mediaContent should have the same number of children as there + // are + // elements in mediaPlayers. + if (MediaPlayerData.players().size != mediaContent.childCount) { + Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync") } + return existingPlayer == null } - updatePageIndicator() - mediaCarouselScrollHandler.onPlayersChanged() - mediaFrame.requiresRemeasuring = true - // Check postcondition: mediaContent should have the same number of children as there are - // elements in mediaPlayers. - if (MediaPlayerData.players().size != mediaContent.childCount) { - Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync") - } - return existingPlayer == null - } private fun addSmartspaceMediaRecommendations( key: String, data: SmartspaceMediaData, shouldPrioritize: Boolean - ) = traceSection("MediaCarouselController#addSmartspaceMediaRecommendations") { - if (DEBUG) Log.d(TAG, "Updating smartspace target in carousel") - if (MediaPlayerData.getMediaPlayer(key) != null) { - Log.w(TAG, "Skip adding smartspace target in carousel") - return - } + ) = + traceSection("MediaCarouselController#addSmartspaceMediaRecommendations") { + if (DEBUG) Log.d(TAG, "Updating smartspace target in carousel") + if (MediaPlayerData.getMediaPlayer(key) != null) { + Log.w(TAG, "Skip adding smartspace target in carousel") + return + } - val existingSmartspaceMediaKey = MediaPlayerData.smartspaceMediaKey() - existingSmartspaceMediaKey?.let { - val removedPlayer = MediaPlayerData.removeMediaPlayer(existingSmartspaceMediaKey, true) - removedPlayer?.run { debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey) } - } + val existingSmartspaceMediaKey = MediaPlayerData.smartspaceMediaKey() + existingSmartspaceMediaKey?.let { + val removedPlayer = + MediaPlayerData.removeMediaPlayer(existingSmartspaceMediaKey, true) + removedPlayer?.run { + debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey) + } + } - val newRecs = mediaControlPanelFactory.get() - newRecs.attachRecommendation( - RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent)) - newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions - val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT) - newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp) - newRecs.bindRecommendation(data) - val curVisibleMediaKey = MediaPlayerData.visiblePlayerKeys() - .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex) - MediaPlayerData.addMediaRecommendation( - key, data, newRecs, shouldPrioritize, systemClock, debugLogger - ) - updatePlayerToState(newRecs, noAnimation = true) - reorderAllPlayers(curVisibleMediaKey) - updatePageIndicator() - mediaFrame.requiresRemeasuring = true - // Check postcondition: mediaContent should have the same number of children as there are - // elements in mediaPlayers. - if (MediaPlayerData.players().size != mediaContent.childCount) { - Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync") + val newRecs = mediaControlPanelFactory.get() + newRecs.attachRecommendation( + RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent) + ) + newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions + val lp = + LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp) + newRecs.bindRecommendation(data) + val curVisibleMediaKey = + MediaPlayerData.visiblePlayerKeys() + .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex) + MediaPlayerData.addMediaRecommendation( + key, + data, + newRecs, + shouldPrioritize, + systemClock, + debugLogger + ) + updatePlayerToState(newRecs, noAnimation = true) + reorderAllPlayers(curVisibleMediaKey) + updatePageIndicator() + mediaFrame.requiresRemeasuring = true + // Check postcondition: mediaContent should have the same number of children as there + // are + // elements in mediaPlayers. + if (MediaPlayerData.players().size != mediaContent.childCount) { + Log.wtf(TAG, "Size of players list and number of views in carousel are out of sync") + } } - } fun removePlayer( key: String, @@ -585,10 +681,8 @@ class MediaCarouselController @Inject constructor( logger.logRecommendationRemoved(it.packageName, it.instanceId) } } - val removed = MediaPlayerData.removeMediaPlayer( - key, - dismissMediaData || dismissRecommendation - ) + val removed = + MediaPlayerData.removeMediaPlayer(key, dismissMediaData || dismissRecommendation) removed?.apply { mediaCarouselScrollHandler.onPrePlayerRemoved(removed) mediaContent.removeView(removed.mediaViewHolder?.player) @@ -609,9 +703,8 @@ class MediaCarouselController @Inject constructor( } private fun updatePlayers(recreateMedia: Boolean) { - pageIndicator.tintList = ColorStateList.valueOf( - context.getColor(R.color.media_paging_indicator) - ) + pageIndicator.tintList = + ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator)) MediaPlayerData.mediaData().forEach { (key, data, isSsMediaRec) -> if (isSsMediaRec) { @@ -619,7 +712,10 @@ class MediaCarouselController @Inject constructor( removePlayer(key, dismissMediaData = false, dismissRecommendation = false) smartspaceMediaData?.let { addSmartspaceMediaRecommendations( - it.targetId, it, MediaPlayerData.shouldPrioritizeSs) + it.targetId, + it, + MediaPlayerData.shouldPrioritizeSs + ) } } else { val isSsReactivated = MediaPlayerData.isSsReactivated(key) @@ -627,7 +723,11 @@ class MediaCarouselController @Inject constructor( removePlayer(key, dismissMediaData = false, dismissRecommendation = false) } addOrUpdatePlayer( - key = key, oldKey = null, data = data, isSsReactivated = isSsReactivated) + key = key, + oldKey = null, + data = data, + isSsReactivated = isSsReactivated + ) } } } @@ -642,14 +742,17 @@ class MediaCarouselController @Inject constructor( } /** - * Set a new interpolated state for all players. This is a state that is usually controlled - * by a finger movement where the user drags from one state to the next. + * Set a new interpolated state for all players. This is a state that is usually controlled by a + * finger movement where the user drags from one state to the next. * * @param startLocation the start location of our state or -1 if this is directly set * @param endLocation the ending location of our state. * @param progress the progress of the transition between startLocation and endlocation. If + * ``` * this is not a guided transformation, this will be 1.0f - * @param immediately should this state be applied immediately, canceling all animations? + * @param immediately + * ``` + * should this state be applied immediately, canceling all animations? */ fun setCurrentState( @MediaLocation startLocation: Int, @@ -657,7 +760,8 @@ class MediaCarouselController @Inject constructor( progress: Float, immediately: Boolean ) { - if (startLocation != currentStartLocation || + if ( + startLocation != currentStartLocation || endLocation != currentEndLocation || progress != currentTransitionProgress || immediately @@ -682,7 +786,8 @@ class MediaCarouselController @Inject constructor( // when squishing in split shade, only use endState, which keeps changing // to provide squishFraction val squishFraction = hostStates[currentEndLocation]?.squishFraction ?: 1.0F - val endAlpha = (if (endIsVisible) 1.0f else 0.0f) * + val endAlpha = + (if (endIsVisible) 1.0f else 0.0f) * calculateAlpha(squishFraction, PAGINATION_DELAY, DURATION) var alpha = 1.0f if (!endIsVisible || !startIsVisible) { @@ -691,10 +796,8 @@ class MediaCarouselController @Inject constructor( progress = 1.0f - progress } // Let's fade in quickly at the end where the view is visible - progress = MathUtils.constrain( - MathUtils.map(0.95f, 1.0f, 0.0f, 1.0f, progress), - 0.0f, - 1.0f) + progress = + MathUtils.constrain(MathUtils.map(0.95f, 1.0f, 0.0f, 1.0f, progress), 0.0f, 1.0f) alpha = MathUtils.lerp(startAlpha, endAlpha, progress) } pageIndicator.alpha = alpha @@ -702,20 +805,19 @@ class MediaCarouselController @Inject constructor( private fun updatePageIndicatorLocation() { // Update the location of the page indicator, carousel clipping - val translationX = if (isRtl) { - (pageIndicator.width - currentCarouselWidth) / 2.0f - } else { - (currentCarouselWidth - pageIndicator.width) / 2.0f - } + val translationX = + if (isRtl) { + (pageIndicator.width - currentCarouselWidth) / 2.0f + } else { + (currentCarouselWidth - pageIndicator.width) / 2.0f + } pageIndicator.translationX = translationX + mediaCarouselScrollHandler.contentTranslation val layoutParams = pageIndicator.layoutParams as ViewGroup.MarginLayoutParams - pageIndicator.translationY = (currentCarouselHeight - pageIndicator.height - - layoutParams.bottomMargin).toFloat() + pageIndicator.translationY = + (currentCarouselHeight - pageIndicator.height - layoutParams.bottomMargin).toFloat() } - /** - * Update the dimension of this carousel. - */ + /** Update the dimension of this carousel. */ private fun updateCarouselDimensions() { var width = 0 var height = 0 @@ -730,7 +832,9 @@ class MediaCarouselController @Inject constructor( currentCarouselWidth = width currentCarouselHeight = height mediaCarouselScrollHandler.setCarouselBounds( - currentCarouselWidth, currentCarouselHeight) + currentCarouselWidth, + currentCarouselHeight + ) updatePageIndicatorLocation() updatePageIndicatorAlpha() } @@ -738,13 +842,14 @@ class MediaCarouselController @Inject constructor( private fun maybeResetSettingsCog() { val hostStates = mediaHostStatesManager.mediaHostStates - val endShowsActive = hostStates[currentEndLocation]?.showsOnlyActiveMedia - ?: true - val startShowsActive = hostStates[currentStartLocation]?.showsOnlyActiveMedia - ?: endShowsActive - if (currentlyShowingOnlyActive != endShowsActive || + val endShowsActive = hostStates[currentEndLocation]?.showsOnlyActiveMedia ?: true + val startShowsActive = + hostStates[currentStartLocation]?.showsOnlyActiveMedia ?: endShowsActive + if ( + currentlyShowingOnlyActive != endShowsActive || ((currentTransitionProgress != 1.0f && currentTransitionProgress != 0.0f) && - startShowsActive != endShowsActive)) { + startShowsActive != endShowsActive) + ) { // Whenever we're transitioning from between differing states or the endstate differs // we reset the translation currentlyShowingOnlyActive = endShowsActive @@ -754,17 +859,18 @@ class MediaCarouselController @Inject constructor( private fun updatePlayerToState(mediaPlayer: MediaControlPanel, noAnimation: Boolean) { mediaPlayer.mediaViewController.setCurrentState( - startLocation = currentStartLocation, - endLocation = currentEndLocation, - transitionProgress = currentTransitionProgress, - applyImmediately = noAnimation) + startLocation = currentStartLocation, + endLocation = currentEndLocation, + transitionProgress = currentTransitionProgress, + applyImmediately = noAnimation + ) } /** - * The desired location of this view has changed. We should remeasure the view to match - * the new bounds and kick off bounds animations if necessary. - * If an animation is happening, an animation is kicked of externally, which sets a new - * current state until we reach the targetState. + * The desired location of this view has changed. We should remeasure the view to match the new + * bounds and kick off bounds animations if necessary. If an animation is happening, an + * animation is kicked of externally, which sets a new current state until we reach the + * targetState. * * @param desiredLocation the location we're going to * @param desiredHostState the target state we're transitioning to @@ -776,64 +882,66 @@ class MediaCarouselController @Inject constructor( animate: Boolean, duration: Long = 200, startDelay: Long = 0 - ) = traceSection("MediaCarouselController#onDesiredLocationChanged") { - desiredHostState?.let { - if (this.desiredLocation != desiredLocation) { - // Only log an event when location changes - logger.logCarouselPosition(desiredLocation) - } + ) = + traceSection("MediaCarouselController#onDesiredLocationChanged") { + desiredHostState?.let { + if (this.desiredLocation != desiredLocation) { + // Only log an event when location changes + logger.logCarouselPosition(desiredLocation) + } - // This is a hosting view, let's remeasure our players - this.desiredLocation = desiredLocation - this.desiredHostState = it - currentlyExpanded = it.expansion > 0 + // This is a hosting view, let's remeasure our players + this.desiredLocation = desiredLocation + this.desiredHostState = it + currentlyExpanded = it.expansion > 0 - val shouldCloseGuts = !currentlyExpanded && - !mediaManager.hasActiveMediaOrRecommendation() && - desiredHostState.showsOnlyActiveMedia + val shouldCloseGuts = + !currentlyExpanded && + !mediaManager.hasActiveMediaOrRecommendation() && + desiredHostState.showsOnlyActiveMedia - for (mediaPlayer in MediaPlayerData.players()) { - if (animate) { - mediaPlayer.mediaViewController.animatePendingStateChange( + for (mediaPlayer in MediaPlayerData.players()) { + if (animate) { + mediaPlayer.mediaViewController.animatePendingStateChange( duration = duration, - delay = startDelay) - } - if (shouldCloseGuts && mediaPlayer.mediaViewController.isGutsVisible) { - mediaPlayer.closeGuts(!animate) - } + delay = startDelay + ) + } + if (shouldCloseGuts && mediaPlayer.mediaViewController.isGutsVisible) { + mediaPlayer.closeGuts(!animate) + } - mediaPlayer.mediaViewController.onLocationPreChange(desiredLocation) - } - mediaCarouselScrollHandler.showsSettingsButton = !it.showsOnlyActiveMedia - mediaCarouselScrollHandler.falsingProtectionNeeded = it.falsingProtectionNeeded - val nowVisible = it.visible - if (nowVisible != playersVisible) { - playersVisible = nowVisible - if (nowVisible) { - mediaCarouselScrollHandler.resetTranslation() + mediaPlayer.mediaViewController.onLocationPreChange(desiredLocation) } + mediaCarouselScrollHandler.showsSettingsButton = !it.showsOnlyActiveMedia + mediaCarouselScrollHandler.falsingProtectionNeeded = it.falsingProtectionNeeded + val nowVisible = it.visible + if (nowVisible != playersVisible) { + playersVisible = nowVisible + if (nowVisible) { + mediaCarouselScrollHandler.resetTranslation() + } + } + updateCarouselSize() } - updateCarouselSize() } - } fun closeGuts(immediate: Boolean = true) { - MediaPlayerData.players().forEach { - it.closeGuts(immediate) - } + MediaPlayerData.players().forEach { it.closeGuts(immediate) } } - /** - * Update the size of the carousel, remeasuring it if necessary. - */ + /** Update the size of the carousel, remeasuring it if necessary. */ private fun updateCarouselSize() { val width = desiredHostState?.measurementInput?.width ?: 0 val height = desiredHostState?.measurementInput?.height ?: 0 - if (width != carouselMeasureWidth && width != 0 || - height != carouselMeasureHeight && height != 0) { + if ( + width != carouselMeasureWidth && width != 0 || + height != carouselMeasureHeight && height != 0 + ) { carouselMeasureWidth = width carouselMeasureHeight = height - val playerWidthPlusPadding = carouselMeasureWidth + + val playerWidthPlusPadding = + carouselMeasureWidth + context.resources.getDimensionPixelSize(R.dimen.qs_media_padding) // Let's remeasure the carousel val widthSpec = desiredHostState?.measurementInput?.widthMeasureSpec ?: 0 @@ -845,24 +953,24 @@ class MediaCarouselController @Inject constructor( } } - /** - * Log the user impression for media card at visibleMediaIndex. - */ + /** Log the user impression for media card at visibleMediaIndex. */ fun logSmartspaceImpression(qsExpanded: Boolean) { val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex if (MediaPlayerData.players().size > visibleMediaIndex) { val mediaControlPanel = MediaPlayerData.getMediaControlPanel(visibleMediaIndex) val hasActiveMediaOrRecommendationCard = - MediaPlayerData.hasActiveMediaOrRecommendationCard() + MediaPlayerData.hasActiveMediaOrRecommendationCard() if (!hasActiveMediaOrRecommendationCard && !qsExpanded) { // Skip logging if on LS or QQS, and there is no active media card return } mediaControlPanel?.let { - logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN - it.mSmartspaceId, - it.mUid, - intArrayOf(it.surfaceForSmartspaceLogging)) + logSmartspaceCardReported( + 800, // SMARTSPACE_CARD_SEEN + it.mSmartspaceId, + it.mUid, + intArrayOf(it.surfaceForSmartspaceLogging) + ) it.mIsImpressed = true } } @@ -880,13 +988,12 @@ class MediaCarouselController @Inject constructor( * the event happened * @param interactedSubcardRank the rank for interacted media item for recommendation card, -1 * for tapping on card but not on any media item, 0 for first media item, 1 for second, etc. - * @param interactedSubcardCardinality how many media items were shown to the user when there - * is user interaction + * @param interactedSubcardCardinality how many media items were shown to the user when there is + * user interaction * @param rank the rank for media card in the media carousel, starting from 0 * @param receivedLatencyMillis latency in milliseconds for card received events. E.g. latency * between headphone connection to sysUI displays media recommendation card * @param isSwipeToDismiss whether is to log swipe-to-dismiss event - * */ fun logSmartspaceCardReported( eventId: Int, @@ -905,62 +1012,66 @@ class MediaCarouselController @Inject constructor( val mediaControlKey = MediaPlayerData.visiblePlayerKeys().elementAt(rank) // Only log media resume card when Smartspace data is available - if (!mediaControlKey.isSsMediaRec && + if ( + !mediaControlKey.isSsMediaRec && !mediaManager.smartspaceMediaData.isActive && - MediaPlayerData.smartspaceMediaData == null) { + MediaPlayerData.smartspaceMediaData == null + ) { return } val cardinality = mediaContent.getChildCount() surfaces.forEach { surface -> /* ktlint-disable max-line-length */ - SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED, - eventId, - instanceId, - // Deprecated, replaced with AiAi feature type so we don't need to create logging - // card type for each new feature. - SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD, - surface, - // Use -1 as rank value to indicate user swipe to dismiss the card - if (isSwipeToDismiss) -1 else rank, - cardinality, - if (mediaControlKey.isSsMediaRec) - 15 // MEDIA_RECOMMENDATION - else if (mediaControlKey.isSsReactivated) - 43 // MEDIA_RESUME_SS_ACTIVATED - else - 31, // MEDIA_RESUME - uid, - interactedSubcardRank, - interactedSubcardCardinality, - receivedLatencyMillis, - null, // Media cards cannot have subcards. - null // Media cards don't have dimensions today. + SysUiStatsLog.write( + SysUiStatsLog.SMARTSPACE_CARD_REPORTED, + eventId, + instanceId, + // Deprecated, replaced with AiAi feature type so we don't need to create logging + // card type for each new feature. + SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD, + surface, + // Use -1 as rank value to indicate user swipe to dismiss the card + if (isSwipeToDismiss) -1 else rank, + cardinality, + if (mediaControlKey.isSsMediaRec) 15 // MEDIA_RECOMMENDATION + else if (mediaControlKey.isSsReactivated) 43 // MEDIA_RESUME_SS_ACTIVATED + else 31, // MEDIA_RESUME + uid, + interactedSubcardRank, + interactedSubcardCardinality, + receivedLatencyMillis, + null, // Media cards cannot have subcards. + null // Media cards don't have dimensions today. ) /* ktlint-disable max-line-length */ if (DEBUG) { - Log.d(TAG, "Log Smartspace card event id: $eventId instance id: $instanceId" + + Log.d( + TAG, + "Log Smartspace card event id: $eventId instance id: $instanceId" + " surface: $surface rank: $rank cardinality: $cardinality " + "isRecommendationCard: ${mediaControlKey.isSsMediaRec} " + "isSsReactivated: ${mediaControlKey.isSsReactivated}" + "uid: $uid " + "interactedSubcardRank: $interactedSubcardRank " + "interactedSubcardCardinality: $interactedSubcardCardinality " + - "received_latency_millis: $receivedLatencyMillis") + "received_latency_millis: $receivedLatencyMillis" + ) } } } private fun onSwipeToDismiss() { - MediaPlayerData.players().forEachIndexed { - index, it -> + MediaPlayerData.players().forEachIndexed { index, it -> if (it.mIsImpressed) { - logSmartspaceCardReported(SMARTSPACE_CARD_DISMISS_EVENT, - it.mSmartspaceId, - it.mUid, - intArrayOf(it.surfaceForSmartspaceLogging), - rank = index, - isSwipeToDismiss = true) + logSmartspaceCardReported( + SMARTSPACE_CARD_DISMISS_EVENT, + it.mSmartspaceId, + it.mUid, + intArrayOf(it.surfaceForSmartspaceLogging), + rank = index, + isSwipeToDismiss = true + ) // Reset card impressed state when swipe to dismissed it.mIsImpressed = false } @@ -971,7 +1082,9 @@ class MediaCarouselController @Inject constructor( fun getCurrentVisibleMediaContentIntent(): PendingIntent? { return MediaPlayerData.playerKeys() - .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)?.data?.clickIntent + .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex) + ?.data + ?.clickIntent } override fun dump(pw: PrintWriter, args: Array<out String>) { @@ -984,15 +1097,18 @@ class MediaCarouselController @Inject constructor( println("shouldPrioritizeSs: ${MediaPlayerData.shouldPrioritizeSs}") println("current size: $currentCarouselWidth x $currentCarouselHeight") println("location: $desiredLocation") - println("state: ${desiredHostState?.expansion}, " + - "only active ${desiredHostState?.showsOnlyActiveMedia}") + println( + "state: ${desiredHostState?.expansion}, " + + "only active ${desiredHostState?.showsOnlyActiveMedia}" + ) } } } @VisibleForTesting internal object MediaPlayerData { - private val EMPTY = MediaData( + private val EMPTY = + MediaData( userId = -1, initialized = false, app = null, @@ -1009,7 +1125,8 @@ internal object MediaPlayerData { active = true, resumeAction = null, instanceId = InstanceId.fakeInstanceId(-1), - appUid = -1) + appUid = -1 + ) // Whether should prioritize Smartspace card. internal var shouldPrioritizeSs: Boolean = false private set @@ -1024,17 +1141,21 @@ internal object MediaPlayerData { val isSsReactivated: Boolean = false ) - private val comparator = compareByDescending<MediaSortKey> { - it.data.isPlaying == true && it.data.playbackLocation == MediaData.PLAYBACK_LOCAL } - .thenByDescending { - it.data.isPlaying == true && it.data.playbackLocation == MediaData.PLAYBACK_CAST_LOCAL } - .thenByDescending { it.data.active } - .thenByDescending { shouldPrioritizeSs == it.isSsMediaRec } - .thenByDescending { !it.data.resumption } - .thenByDescending { it.data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE } - .thenByDescending { it.data.lastActive } - .thenByDescending { it.updateTime } - .thenByDescending { it.data.notificationKey } + private val comparator = + compareByDescending<MediaSortKey> { + it.data.isPlaying == true && it.data.playbackLocation == MediaData.PLAYBACK_LOCAL + } + .thenByDescending { + it.data.isPlaying == true && + it.data.playbackLocation == MediaData.PLAYBACK_CAST_LOCAL + } + .thenByDescending { it.data.active } + .thenByDescending { shouldPrioritizeSs == it.isSsMediaRec } + .thenByDescending { !it.data.resumption } + .thenByDescending { it.data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE } + .thenByDescending { it.data.lastActive } + .thenByDescending { it.updateTime } + .thenByDescending { it.data.notificationKey } private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator) private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf() @@ -1053,8 +1174,14 @@ internal object MediaPlayerData { if (removedPlayer != null && removedPlayer != player) { debugLogger?.logPotentialMemoryLeak(key) } - val sortKey = MediaSortKey(isSsMediaRec = false, - data, key, clock.currentTimeMillis(), isSsReactivated = isSsReactivated) + val sortKey = + MediaSortKey( + isSsMediaRec = false, + data, + key, + clock.currentTimeMillis(), + isSsReactivated = isSsReactivated + ) mediaData.put(key, sortKey) mediaPlayers.put(sortKey, player) visibleMediaPlayers.put(key, sortKey) @@ -1073,13 +1200,14 @@ internal object MediaPlayerData { if (removedPlayer != null && removedPlayer != player) { debugLogger?.logPotentialMemoryLeak(key) } - val sortKey = MediaSortKey( - isSsMediaRec = true, - EMPTY.copy(isPlaying = false), - key, - clock.currentTimeMillis(), - isSsReactivated = true - ) + val sortKey = + MediaSortKey( + isSsMediaRec = true, + EMPTY.copy(isPlaying = false), + key, + clock.currentTimeMillis(), + isSsReactivated = true + ) mediaData.put(key, sortKey) mediaPlayers.put(sortKey, player) visibleMediaPlayers.put(key, sortKey) @@ -1126,17 +1254,19 @@ internal object MediaPlayerData { * Removes media player given the key. * @param isDismissed determines whether the media player is removed from the carousel. */ - fun removeMediaPlayer(key: String, isDismissed: Boolean = false) = mediaData.remove(key)?.let { - if (it.isSsMediaRec) { - smartspaceMediaData = null - } - if (isDismissed) { - visibleMediaPlayers.remove(key) + fun removeMediaPlayer(key: String, isDismissed: Boolean = false) = + mediaData.remove(key)?.let { + if (it.isSsMediaRec) { + smartspaceMediaData = null + } + if (isDismissed) { + visibleMediaPlayers.remove(key) + } + mediaPlayers.remove(it) } - mediaPlayers.remove(it) - } - fun mediaData() = mediaData.entries.map { e -> Triple(e.key, e.value.data, e.value.isSsMediaRec) } + fun mediaData() = + mediaData.entries.map { e -> Triple(e.key, e.value.data, e.value.isSsMediaRec) } fun dataKeys() = mediaData.keys @@ -1187,14 +1317,11 @@ internal object MediaPlayerData { fun isSsReactivated(key: String): Boolean = mediaData.get(key)?.isSsReactivated ?: false /** - * This method is called when media players are reordered. - * To make sure we have the new version of the order of - * media players visible to user. + * This method is called when media players are reordered. To make sure we have the new version + * of the order of media players visible to user. */ fun updateVisibleMediaPlayers() { visibleMediaPlayers.clear() - playerKeys().forEach { - visibleMediaPlayers.put(it.key, it) - } + playerKeys().forEach { visibleMediaPlayers.put(it.key, it) } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt index 21e3da9f3dc8..eed1bd743938 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt @@ -24,53 +24,43 @@ import javax.inject.Inject /** A debug logger for [MediaCarouselController]. */ @SysUISingleton -class MediaCarouselControllerLogger @Inject constructor( - @MediaCarouselControllerLog private val buffer: LogBuffer -) { +class MediaCarouselControllerLogger +@Inject +constructor(@MediaCarouselControllerLog private val buffer: LogBuffer) { /** * Log that there might be a potential memory leak for the [MediaControlPanel] and/or * [MediaViewController] related to [key]. */ - fun logPotentialMemoryLeak(key: String) = buffer.log( - TAG, - LogLevel.DEBUG, - { str1 = key }, - { - "Potential memory leak: " + + fun logPotentialMemoryLeak(key: String) = + buffer.log( + TAG, + LogLevel.DEBUG, + { str1 = key }, + { + "Potential memory leak: " + "Removing control panel for $str1 from map without calling #onDestroy" - } - ) + } + ) - fun logMediaLoaded(key: String) = buffer.log( - TAG, - LogLevel.DEBUG, - { str1 = key }, - { "add player $str1" } - ) + fun logMediaLoaded(key: String) = + buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "add player $str1" }) - fun logMediaRemoved(key: String) = buffer.log( - TAG, - LogLevel.DEBUG, - { str1 = key }, - { "removing player $str1" } - ) + fun logMediaRemoved(key: String) = + buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "removing player $str1" }) - fun logRecommendationLoaded(key: String) = buffer.log( - TAG, - LogLevel.DEBUG, - { str1 = key }, - { "add recommendation $str1" } - ) + fun logRecommendationLoaded(key: String) = + buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "add recommendation $str1" }) - fun logRecommendationRemoved(key: String, immediately: Boolean) = buffer.log( - TAG, - LogLevel.DEBUG, - { - str1 = key - bool1 = immediately - }, - { "removing recommendation $str1, immediate=$bool1" } - ) + fun logRecommendationRemoved(key: String, immediately: Boolean) = + buffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = key + bool1 = immediately + }, + { "removing recommendation $str1, immediate=$bool1" } + ) } private const val TAG = "MediaCarouselCtlrLog" diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt index 1257e05a06a6..36b2eda65fab 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt @@ -44,16 +44,13 @@ private const val RUBBERBAND_FACTOR = 0.2f private const val SETTINGS_BUTTON_TRANSLATION_FRACTION = 0.3f /** - * Default spring configuration to use for animations where stiffness and/or damping ratio - * were not provided, and a default spring was not set via [PhysicsAnimator.setDefaultSpringConfig]. + * Default spring configuration to use for animations where stiffness and/or damping ratio were not + * provided, and a default spring was not set via [PhysicsAnimator.setDefaultSpringConfig]. */ -private val translationConfig = PhysicsAnimator.SpringConfig( - SpringForce.STIFFNESS_LOW, - SpringForce.DAMPING_RATIO_LOW_BOUNCY) +private val translationConfig = + PhysicsAnimator.SpringConfig(SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY) -/** - * A controller class for the media scrollview, responsible for touch handling - */ +/** A controller class for the media scrollview, responsible for touch handling */ class MediaCarouselScrollHandler( private val scrollView: MediaScrollView, private val pageIndicator: PageIndicator, @@ -66,57 +63,36 @@ class MediaCarouselScrollHandler( private val logSmartspaceImpression: (Boolean) -> Unit, private val logger: MediaUiEventLogger ) { - /** - * Is the view in RTL - */ - val isRtl: Boolean get() = scrollView.isLayoutRtl - /** - * Do we need falsing protection? - */ + /** Is the view in RTL */ + val isRtl: Boolean + get() = scrollView.isLayoutRtl + /** Do we need falsing protection? */ var falsingProtectionNeeded: Boolean = false - /** - * The width of the carousel - */ + /** The width of the carousel */ private var carouselWidth: Int = 0 - /** - * The height of the carousel - */ + /** The height of the carousel */ private var carouselHeight: Int = 0 - /** - * How much are we scrolled into the current media? - */ + /** How much are we scrolled into the current media? */ private var cornerRadius: Int = 0 - /** - * The content where the players are added - */ + /** The content where the players are added */ private var mediaContent: ViewGroup - /** - * The gesture detector to detect touch gestures - */ + /** The gesture detector to detect touch gestures */ private val gestureDetector: GestureDetectorCompat - /** - * The settings button view - */ + /** The settings button view */ private lateinit var settingsButton: View - /** - * What's the currently visible player index? - */ + /** What's the currently visible player index? */ var visibleMediaIndex: Int = 0 private set - /** - * How much are we scrolled into the current media? - */ + /** How much are we scrolled into the current media? */ private var scrollIntoCurrentMedia: Int = 0 - /** - * how much is the content translated in X - */ + /** how much is the content translated in X */ var contentTranslation = 0.0f private set(value) { field = value @@ -126,9 +102,7 @@ class MediaCarouselScrollHandler( updateClipToOutline() } - /** - * The width of a player including padding - */ + /** The width of a player including padding */ var playerWidthPlusPadding: Int = 0 set(value) { field = value @@ -136,82 +110,75 @@ class MediaCarouselScrollHandler( // it's still at the same place var newRelativeScroll = visibleMediaIndex * playerWidthPlusPadding if (scrollIntoCurrentMedia > playerWidthPlusPadding) { - newRelativeScroll += playerWidthPlusPadding - - (scrollIntoCurrentMedia - playerWidthPlusPadding) + newRelativeScroll += + playerWidthPlusPadding - (scrollIntoCurrentMedia - playerWidthPlusPadding) } else { newRelativeScroll += scrollIntoCurrentMedia } scrollView.relativeScrollX = newRelativeScroll } - /** - * Does the dismiss currently show the setting cog? - */ + /** Does the dismiss currently show the setting cog? */ var showsSettingsButton: Boolean = false - /** - * A utility to detect gestures, used in the touch listener - */ - private val gestureListener = object : GestureDetector.SimpleOnGestureListener() { - override fun onFling( - eStart: MotionEvent?, - eCurrent: MotionEvent?, - vX: Float, - vY: Float - ) = onFling(vX, vY) - - override fun onScroll( - down: MotionEvent?, - lastMotion: MotionEvent?, - distanceX: Float, - distanceY: Float - ) = onScroll(down!!, lastMotion!!, distanceX) - - override fun onDown(e: MotionEvent?): Boolean { - if (falsingProtectionNeeded) { - falsingCollector.onNotificationStartDismissing() + /** A utility to detect gestures, used in the touch listener */ + private val gestureListener = + object : GestureDetector.SimpleOnGestureListener() { + override fun onFling( + eStart: MotionEvent?, + eCurrent: MotionEvent?, + vX: Float, + vY: Float + ) = onFling(vX, vY) + + override fun onScroll( + down: MotionEvent?, + lastMotion: MotionEvent?, + distanceX: Float, + distanceY: Float + ) = onScroll(down!!, lastMotion!!, distanceX) + + override fun onDown(e: MotionEvent?): Boolean { + if (falsingProtectionNeeded) { + falsingCollector.onNotificationStartDismissing() + } + return false } - return false } - } - /** - * The touch listener for the scroll view - */ - private val touchListener = object : Gefingerpoken { - override fun onTouchEvent(motionEvent: MotionEvent?) = onTouch(motionEvent!!) - override fun onInterceptTouchEvent(ev: MotionEvent?) = onInterceptTouch(ev!!) - } + /** The touch listener for the scroll view */ + private val touchListener = + object : Gefingerpoken { + override fun onTouchEvent(motionEvent: MotionEvent?) = onTouch(motionEvent!!) + override fun onInterceptTouchEvent(ev: MotionEvent?) = onInterceptTouch(ev!!) + } - /** - * A listener that is invoked when the scrolling changes to update player visibilities - */ - private val scrollChangedListener = object : View.OnScrollChangeListener { - override fun onScrollChange( - v: View?, - scrollX: Int, - scrollY: Int, - oldScrollX: Int, - oldScrollY: Int - ) { - if (playerWidthPlusPadding == 0) { - return - } + /** A listener that is invoked when the scrolling changes to update player visibilities */ + private val scrollChangedListener = + object : View.OnScrollChangeListener { + override fun onScrollChange( + v: View?, + scrollX: Int, + scrollY: Int, + oldScrollX: Int, + oldScrollY: Int + ) { + if (playerWidthPlusPadding == 0) { + return + } - val relativeScrollX = scrollView.relativeScrollX - onMediaScrollingChanged(relativeScrollX / playerWidthPlusPadding, - relativeScrollX % playerWidthPlusPadding) + val relativeScrollX = scrollView.relativeScrollX + onMediaScrollingChanged( + relativeScrollX / playerWidthPlusPadding, + relativeScrollX % playerWidthPlusPadding + ) + } } - } - /** - * Whether the media card is visible to user if any - */ + /** Whether the media card is visible to user if any */ var visibleToUser: Boolean = false - /** - * Whether the quick setting is expanded or not - */ + /** Whether the quick setting is expanded or not */ var qsExpanded: Boolean = false init { @@ -220,47 +187,61 @@ class MediaCarouselScrollHandler( scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER) mediaContent = scrollView.contentContainer scrollView.setOnScrollChangeListener(scrollChangedListener) - scrollView.outlineProvider = object : ViewOutlineProvider() { - override fun getOutline(view: View?, outline: Outline?) { - outline?.setRoundRect(0, 0, carouselWidth, carouselHeight, cornerRadius.toFloat()) + scrollView.outlineProvider = + object : ViewOutlineProvider() { + override fun getOutline(view: View?, outline: Outline?) { + outline?.setRoundRect( + 0, + 0, + carouselWidth, + carouselHeight, + cornerRadius.toFloat() + ) + } } - } } fun onSettingsButtonUpdated(button: View) { settingsButton = button // We don't have a context to resolve, lets use the settingsbuttons one since that is // reinflated appropriately - cornerRadius = settingsButton.resources.getDimensionPixelSize( - Utils.getThemeAttr(settingsButton.context, android.R.attr.dialogCornerRadius)) + cornerRadius = + settingsButton.resources.getDimensionPixelSize( + Utils.getThemeAttr(settingsButton.context, android.R.attr.dialogCornerRadius) + ) updateSettingsPresentation() scrollView.invalidateOutline() } private fun updateSettingsPresentation() { if (showsSettingsButton && settingsButton.width > 0) { - val settingsOffset = MathUtils.map( + val settingsOffset = + MathUtils.map( 0.0f, getMaxTranslation().toFloat(), 0.0f, 1.0f, - Math.abs(contentTranslation)) - val settingsTranslation = (1.0f - settingsOffset) * -settingsButton.width * + Math.abs(contentTranslation) + ) + val settingsTranslation = + (1.0f - settingsOffset) * + -settingsButton.width * SETTINGS_BUTTON_TRANSLATION_FRACTION - val newTranslationX = if (isRtl) { - // In RTL, the 0-placement is on the right side of the view, not the left... - if (contentTranslation > 0) { - -(scrollView.width - settingsTranslation - settingsButton.width) - } else { - -settingsTranslation - } - } else { - if (contentTranslation > 0) { - settingsTranslation + val newTranslationX = + if (isRtl) { + // In RTL, the 0-placement is on the right side of the view, not the left... + if (contentTranslation > 0) { + -(scrollView.width - settingsTranslation - settingsButton.width) + } else { + -settingsTranslation + } } else { - scrollView.width - settingsTranslation - settingsButton.width + if (contentTranslation > 0) { + settingsTranslation + } else { + scrollView.width - settingsTranslation - settingsButton.width + } } - } val rotation = (1.0f - settingsOffset) * 50 settingsButton.rotation = rotation * -Math.signum(contentTranslation) val alpha = MathUtils.saturate(MathUtils.map(0.5f, 1.0f, 0.0f, 1.0f, settingsOffset)) @@ -307,16 +288,14 @@ class MediaCarouselScrollHandler( val newScrollX = scrollView.relativeScrollX + dx // Delay the scrolling since scrollView calls springback which cancels // the animation again.. - mainExecutor.execute { - scrollView.smoothScrollTo(newScrollX, scrollView.scrollY) - } + mainExecutor.execute { scrollView.smoothScrollTo(newScrollX, scrollView.scrollY) } } val currentTranslation = scrollView.getContentTranslation() if (currentTranslation != 0.0f) { // We started a Swipe but didn't end up with a fling. Let's either go to the // dismissed position or go back. - val springBack = Math.abs(currentTranslation) < getMaxTranslation() / 2 || - isFalseTouch() + val springBack = + Math.abs(currentTranslation) < getMaxTranslation() / 2 || isFalseTouch() val newTranslation: Float if (springBack) { newTranslation = 0.0f @@ -325,13 +304,17 @@ class MediaCarouselScrollHandler( if (!showsSettingsButton) { // Delay the dismiss a bit to avoid too much overlap. Waiting until the // animation has finished also feels a bit too slow here. - mainExecutor.executeDelayed({ - dismissCallback.invoke() - }, DISMISS_DELAY) + mainExecutor.executeDelayed({ dismissCallback.invoke() }, DISMISS_DELAY) } } - PhysicsAnimator.getInstance(this).spring(CONTENT_TRANSLATION, - newTranslation, startVelocity = 0.0f, config = translationConfig).start() + PhysicsAnimator.getInstance(this) + .spring( + CONTENT_TRANSLATION, + newTranslation, + startVelocity = 0.0f, + config = translationConfig + ) + .start() scrollView.animationTargetX = newTranslation } } @@ -339,10 +322,11 @@ class MediaCarouselScrollHandler( return false } - private fun isFalseTouch() = falsingProtectionNeeded && - falsingManager.isFalseTouch(NOTIFICATION_DISMISS) + private fun isFalseTouch() = + falsingProtectionNeeded && falsingManager.isFalseTouch(NOTIFICATION_DISMISS) - private fun getMaxTranslation() = if (showsSettingsButton) { + private fun getMaxTranslation() = + if (showsSettingsButton) { settingsButton.width } else { playerWidthPlusPadding @@ -352,15 +336,10 @@ class MediaCarouselScrollHandler( return gestureDetector.onTouchEvent(motionEvent) } - fun onScroll( - down: MotionEvent, - lastMotion: MotionEvent, - distanceX: Float - ): Boolean { + fun onScroll(down: MotionEvent, lastMotion: MotionEvent, distanceX: Float): Boolean { val totalX = lastMotion.x - down.x val currentTranslation = scrollView.getContentTranslation() - if (currentTranslation != 0.0f || - !scrollView.canScrollHorizontally((-totalX).toInt())) { + if (currentTranslation != 0.0f || !scrollView.canScrollHorizontally((-totalX).toInt())) { var newTranslation = currentTranslation - distanceX val absTranslation = Math.abs(newTranslation) if (absTranslation > getMaxTranslation()) { @@ -374,14 +353,18 @@ class MediaCarouselScrollHandler( newTranslation = currentTranslation - distanceX * RUBBERBAND_FACTOR } else { // We just crossed the boundary, let's rubberband it all - newTranslation = Math.signum(newTranslation) * (getMaxTranslation() + - (absTranslation - getMaxTranslation()) * RUBBERBAND_FACTOR) + newTranslation = + Math.signum(newTranslation) * + (getMaxTranslation() + + (absTranslation - getMaxTranslation()) * RUBBERBAND_FACTOR) } } // Otherwise we don't have do do anything, and will remove the unrubberbanded // translation } - if (Math.signum(newTranslation) != Math.signum(currentTranslation) && - currentTranslation != 0.0f) { + if ( + Math.signum(newTranslation) != Math.signum(currentTranslation) && + currentTranslation != 0.0f + ) { // We crossed the 0.0 threshold of the translation. Let's see if we're allowed // to scroll into the new direction if (scrollView.canScrollHorizontally(-newTranslation.toInt())) { @@ -392,8 +375,14 @@ class MediaCarouselScrollHandler( } val physicsAnimator = PhysicsAnimator.getInstance(this) if (physicsAnimator.isRunning()) { - physicsAnimator.spring(CONTENT_TRANSLATION, - newTranslation, startVelocity = 0.0f, config = translationConfig).start() + physicsAnimator + .spring( + CONTENT_TRANSLATION, + newTranslation, + startVelocity = 0.0f, + config = translationConfig + ) + .start() } else { contentTranslation = newTranslation } @@ -403,10 +392,7 @@ class MediaCarouselScrollHandler( return false } - private fun onFling( - vX: Float, - vY: Float - ): Boolean { + private fun onFling(vX: Float, vY: Float): Boolean { if (vX * vX < 0.5 * vY * vY) { return false } @@ -425,13 +411,17 @@ class MediaCarouselScrollHandler( // Delay the dismiss a bit to avoid too much overlap. Waiting until the animation // has finished also feels a bit too slow here. if (!showsSettingsButton) { - mainExecutor.executeDelayed({ - dismissCallback.invoke() - }, DISMISS_DELAY) + mainExecutor.executeDelayed({ dismissCallback.invoke() }, DISMISS_DELAY) } } - PhysicsAnimator.getInstance(this).spring(CONTENT_TRANSLATION, - newTranslation, startVelocity = vX, config = translationConfig).start() + PhysicsAnimator.getInstance(this) + .spring( + CONTENT_TRANSLATION, + newTranslation, + startVelocity = vX, + config = translationConfig + ) + .start() scrollView.animationTargetX = newTranslation } else { // We're flinging the player! Let's go either to the previous or to the next player @@ -444,21 +434,18 @@ class MediaCarouselScrollHandler( val view = mediaContent.getChildAt(destIndex) // We need to post this since we're dispatching a touch to the underlying view to cancel // but canceling will actually abort the animation. - mainExecutor.execute { - scrollView.smoothScrollTo(view.left, scrollView.scrollY) - } + mainExecutor.execute { scrollView.smoothScrollTo(view.left, scrollView.scrollY) } } return true } - /** - * Reset the translation of the players when swiped - */ + /** Reset the translation of the players when swiped */ fun resetTranslation(animate: Boolean = false) { if (scrollView.getContentTranslation() != 0.0f) { if (animate) { - PhysicsAnimator.getInstance(this).spring(CONTENT_TRANSLATION, - 0.0f, config = translationConfig).start() + PhysicsAnimator.getInstance(this) + .spring(CONTENT_TRANSLATION, 0.0f, config = translationConfig) + .start() scrollView.animationTargetX = 0.0f } else { PhysicsAnimator.getInstance(this).cancel() @@ -486,21 +473,22 @@ class MediaCarouselScrollHandler( closeGuts(false) updatePlayerVisibilities() } - val relativeLocation = visibleMediaIndex.toFloat() + if (playerWidthPlusPadding > 0) - scrollInAmount.toFloat() / playerWidthPlusPadding else 0f + val relativeLocation = + visibleMediaIndex.toFloat() + + if (playerWidthPlusPadding > 0) scrollInAmount.toFloat() / playerWidthPlusPadding + else 0f // Fix the location, because PageIndicator does not handle RTL internally - val location = if (isRtl) { - mediaContent.childCount - relativeLocation - 1 - } else { - relativeLocation - } + val location = + if (isRtl) { + mediaContent.childCount - relativeLocation - 1 + } else { + relativeLocation + } pageIndicator.setLocation(location) updateClipToOutline() } - /** - * Notified whenever the players or their order has changed - */ + /** Notified whenever the players or their order has changed */ fun onPlayersChanged() { updatePlayerVisibilities() updateMediaPaddings() @@ -530,8 +518,8 @@ class MediaCarouselScrollHandler( } /** - * Notify that a player will be removed right away. This gives us the opporunity to look - * where it was and update our scroll position. + * Notify that a player will be removed right away. This gives us the opporunity to look where + * it was and update our scroll position. */ fun onPrePlayerRemoved(removed: MediaControlPanel) { val removedIndex = mediaContent.indexOfChild(removed.mediaViewHolder?.player) @@ -551,9 +539,7 @@ class MediaCarouselScrollHandler( } } - /** - * Update the bounds of the carousel - */ + /** Update the bounds of the carousel */ fun setCarouselBounds(currentCarouselWidth: Int, currentCarouselHeight: Int) { if (currentCarouselHeight != carouselHeight || currentCarouselWidth != carouselHeight) { carouselWidth = currentCarouselWidth @@ -562,9 +548,7 @@ class MediaCarouselScrollHandler( } } - /** - * Reset the MediaScrollView to the start. - */ + /** Reset the MediaScrollView to the start. */ fun scrollToStart() { scrollView.relativeScrollX = 0 } @@ -582,21 +566,22 @@ class MediaCarouselScrollHandler( val destIndex = Math.min(mediaContent.getChildCount() - 1, destIndex) val view = mediaContent.getChildAt(destIndex) // We need to post this to wait for the active player becomes visible. - mainExecutor.executeDelayed({ - scrollView.smoothScrollTo(view.left, scrollView.scrollY) - }, SCROLL_DELAY) + mainExecutor.executeDelayed( + { scrollView.smoothScrollTo(view.left, scrollView.scrollY) }, + SCROLL_DELAY + ) } companion object { - private val CONTENT_TRANSLATION = object : FloatPropertyCompat<MediaCarouselScrollHandler>( - "contentTranslation") { - override fun getValue(handler: MediaCarouselScrollHandler): Float { - return handler.contentTranslation - } + private val CONTENT_TRANSLATION = + object : FloatPropertyCompat<MediaCarouselScrollHandler>("contentTranslation") { + override fun getValue(handler: MediaCarouselScrollHandler): Float { + return handler.contentTranslation + } - override fun setValue(handler: MediaCarouselScrollHandler, value: Float) { - handler.contentTranslation = value + override fun setValue(handler: MediaCarouselScrollHandler, value: Float) { + handler.contentTranslation = value + } } - } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt index e17c1608929c..6b46d8f30cad 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt @@ -59,9 +59,7 @@ import javax.inject.Inject private val TAG: String = MediaHierarchyManager::class.java.simpleName -/** - * Similarly to isShown but also excludes views that have 0 alpha - */ +/** Similarly to isShown but also excludes views that have 0 alpha */ val View.isShownNotFaded: Boolean get() { var current: View = this @@ -86,7 +84,9 @@ val View.isShownNotFaded: Boolean * and animate the positions of the views to achieve seamless transitions. */ @SysUISingleton -class MediaHierarchyManager @Inject constructor( +class MediaHierarchyManager +@Inject +constructor( private val context: Context, private val statusBarStateController: SysuiStatusBarStateController, private val keyguardStateController: KeyguardStateController, @@ -101,12 +101,10 @@ class MediaHierarchyManager @Inject constructor( @Main private val handler: Handler, ) { - /** - * Track the media player setting status on lock screen. - */ + /** Track the media player setting status on lock screen. */ private var allowMediaPlayerOnLockScreen: Boolean = true private val lockScreenMediaPlayerUri = - secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN) + secureSettings.getUriFor(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN) /** * Whether we "skip" QQS during panel expansion. @@ -118,8 +116,8 @@ class MediaHierarchyManager @Inject constructor( /** * The root overlay of the hierarchy. This is where the media notification is attached to - * whenever the view is transitioning from one host to another. It also make sure that the - * view is always in its final state when it is attached to a view host. + * whenever the view is transitioning from one host to another. It also make sure that the view + * is always in its final state when it is attached to a view host. */ private var rootOverlay: ViewGroupOverlay? = null @@ -138,69 +136,68 @@ class MediaHierarchyManager @Inject constructor( */ private var animationStartCrossFadeProgress = 0.0f - /** - * The starting alpha of the animation - */ + /** The starting alpha of the animation */ private var animationStartAlpha = 0.0f - /** - * The starting location of the cross fade if an animation is running right now. - */ - @MediaLocation - private var crossFadeAnimationStartLocation = -1 + /** The starting location of the cross fade if an animation is running right now. */ + @MediaLocation private var crossFadeAnimationStartLocation = -1 - /** - * The end location of the cross fade if an animation is running right now. - */ - @MediaLocation - private var crossFadeAnimationEndLocation = -1 + /** The end location of the cross fade if an animation is running right now. */ + @MediaLocation private var crossFadeAnimationEndLocation = -1 private var targetBounds: Rect = Rect() private val mediaFrame get() = mediaCarouselController.mediaFrame private var statusbarState: Int = statusBarStateController.state - private var animator = ValueAnimator.ofFloat(0.0f, 1.0f).apply { - interpolator = Interpolators.FAST_OUT_SLOW_IN - addUpdateListener { - updateTargetState() - val currentAlpha: Float - var boundsProgress = animatedFraction - if (isCrossFadeAnimatorRunning) { - animationCrossFadeProgress = MathUtils.lerp(animationStartCrossFadeProgress, 1.0f, - animatedFraction) - // When crossfading, let's keep the bounds at the right location during fading - boundsProgress = if (animationCrossFadeProgress < 0.5f) 0.0f else 1.0f - currentAlpha = calculateAlphaFromCrossFade(animationCrossFadeProgress) - } else { - // If we're not crossfading, let's interpolate from the start alpha to 1.0f - currentAlpha = MathUtils.lerp(animationStartAlpha, 1.0f, animatedFraction) + private var animator = + ValueAnimator.ofFloat(0.0f, 1.0f).apply { + interpolator = Interpolators.FAST_OUT_SLOW_IN + addUpdateListener { + updateTargetState() + val currentAlpha: Float + var boundsProgress = animatedFraction + if (isCrossFadeAnimatorRunning) { + animationCrossFadeProgress = + MathUtils.lerp(animationStartCrossFadeProgress, 1.0f, animatedFraction) + // When crossfading, let's keep the bounds at the right location during fading + boundsProgress = if (animationCrossFadeProgress < 0.5f) 0.0f else 1.0f + currentAlpha = calculateAlphaFromCrossFade(animationCrossFadeProgress) + } else { + // If we're not crossfading, let's interpolate from the start alpha to 1.0f + currentAlpha = MathUtils.lerp(animationStartAlpha, 1.0f, animatedFraction) + } + interpolateBounds( + animationStartBounds, + targetBounds, + boundsProgress, + result = currentBounds + ) + resolveClipping(currentClipping) + applyState(currentBounds, currentAlpha, clipBounds = currentClipping) } - interpolateBounds(animationStartBounds, targetBounds, boundsProgress, - result = currentBounds) - resolveClipping(currentClipping) - applyState(currentBounds, currentAlpha, clipBounds = currentClipping) - } - addListener(object : AnimatorListenerAdapter() { - private var cancelled: Boolean = false + addListener( + object : AnimatorListenerAdapter() { + private var cancelled: Boolean = false + + override fun onAnimationCancel(animation: Animator?) { + cancelled = true + animationPending = false + rootView?.removeCallbacks(startAnimation) + } - override fun onAnimationCancel(animation: Animator?) { - cancelled = true - animationPending = false - rootView?.removeCallbacks(startAnimation) - } + override fun onAnimationEnd(animation: Animator?) { + isCrossFadeAnimatorRunning = false + if (!cancelled) { + applyTargetStateIfNotAnimating() + } + } - override fun onAnimationEnd(animation: Animator?) { - isCrossFadeAnimatorRunning = false - if (!cancelled) { - applyTargetStateIfNotAnimating() + override fun onAnimationStart(animation: Animator?) { + cancelled = false + animationPending = false + } } - } - - override fun onAnimationStart(animation: Animator?) { - cancelled = false - animationPending = false - } - }) - } + ) + } private fun resolveClipping(result: Rect) { if (animationStartClipping.isEmpty) result.set(targetClipping) @@ -210,42 +207,31 @@ class MediaHierarchyManager @Inject constructor( private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_DREAM_OVERLAY + 1) /** - * The last location where this view was at before going to the desired location. This is - * useful for guided transitions. + * The last location where this view was at before going to the desired location. This is useful + * for guided transitions. */ - @MediaLocation - private var previousLocation = -1 - /** - * The desired location where the view will be at the end of the transition. - */ - @MediaLocation - private var desiredLocation = -1 + @MediaLocation private var previousLocation = -1 + /** The desired location where the view will be at the end of the transition. */ + @MediaLocation private var desiredLocation = -1 /** - * The current attachment location where the view is currently attached. - * Usually this matches the desired location except for animations whenever a view moves - * to the new desired location, during which it is in [IN_OVERLAY]. + * The current attachment location where the view is currently attached. Usually this matches + * the desired location except for animations whenever a view moves to the new desired location, + * during which it is in [IN_OVERLAY]. */ - @MediaLocation - private var currentAttachmentLocation = -1 + @MediaLocation private var currentAttachmentLocation = -1 private var inSplitShade = false - /** - * Is there any active media in the carousel? - */ + /** Is there any active media in the carousel? */ private var hasActiveMedia: Boolean = false get() = mediaHosts.get(LOCATION_QQS)?.visible == true - /** - * Are we currently waiting on an animation to start? - */ + /** Are we currently waiting on an animation to start? */ private var animationPending: Boolean = false private val startAnimation: Runnable = Runnable { animator.start() } - /** - * The expansion of quick settings - */ + /** The expansion of quick settings */ var qsExpansion: Float = 0.0f set(value) { if (field != value) { @@ -258,9 +244,7 @@ class MediaHierarchyManager @Inject constructor( } } - /** - * Is quick setting expanded? - */ + /** Is quick setting expanded? */ var qsExpanded: Boolean = false set(value) { if (field != value) { @@ -281,9 +265,8 @@ class MediaHierarchyManager @Inject constructor( private var distanceForFullShadeTransition = 0 /** - * The amount of progress we are currently in if we're transitioning to the full shade. - * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full - * shade. + * The amount of progress we are currently in if we're transitioning to the full shade. 0.0f + * means we're not transitioning yet, while 1 means we're all the way in the full shade. */ private var fullShadeTransitionProgress = 0f set(value) { @@ -305,9 +288,7 @@ class MediaHierarchyManager @Inject constructor( } } - /** - * Is there currently a cross-fade animation running driven by an animator? - */ + /** Is there currently a cross-fade animation running driven by an animator? */ private var isCrossFadeAnimatorRunning = false /** @@ -316,8 +297,10 @@ class MediaHierarchyManager @Inject constructor( * the transition starts, this will no longer return true. */ private val isTransitioningToFullShade: Boolean - get() = fullShadeTransitionProgress != 0f && !bypassController.bypassEnabled && - statusbarState == StatusBarState.KEYGUARD + get() = + fullShadeTransitionProgress != 0f && + !bypassController.bypassEnabled && + statusbarState == StatusBarState.KEYGUARD /** * Set the amount of pixels we have currently dragged down if we're transitioning to the full @@ -354,17 +337,13 @@ class MediaHierarchyManager @Inject constructor( } } - /** - * Are location changes currently blocked? - */ + /** Are location changes currently blocked? */ private val blockLocationChanges: Boolean get() { return goingToSleep || dozeAnimationRunning } - /** - * Are we currently going to sleep - */ + /** Are we currently going to sleep */ private var goingToSleep: Boolean = false set(value) { if (field != value) { @@ -375,9 +354,7 @@ class MediaHierarchyManager @Inject constructor( } } - /** - * Are we currently fullyAwake - */ + /** Are we currently fullyAwake */ private var fullyAwake: Boolean = false set(value) { if (field != value) { @@ -388,9 +365,7 @@ class MediaHierarchyManager @Inject constructor( } } - /** - * Is the doze animation currently Running - */ + /** Is the doze animation currently Running */ private var dozeAnimationRunning: Boolean = false private set(value) { if (field != value) { @@ -401,9 +376,7 @@ class MediaHierarchyManager @Inject constructor( } } - /** - * Is the dream overlay currently active - */ + /** Is the dream overlay currently active */ private var dreamOverlayActive: Boolean = false private set(value) { if (field != value) { @@ -412,9 +385,7 @@ class MediaHierarchyManager @Inject constructor( } } - /** - * Is the dream media complication currently active - */ + /** Is the dream media complication currently active */ private var dreamMediaComplicationActive: Boolean = false private set(value) { if (field != value) { @@ -424,16 +395,13 @@ class MediaHierarchyManager @Inject constructor( } /** - * The current cross fade progress. 0.5f means it's just switching - * between the start and the end location and the content is fully faded, while 0.75f means - * that we're halfway faded in again in the target state. - * This is only valid while [isCrossFadeAnimatorRunning] is true. + * The current cross fade progress. 0.5f means it's just switching between the start and the end + * location and the content is fully faded, while 0.75f means that we're halfway faded in again + * in the target state. This is only valid while [isCrossFadeAnimatorRunning] is true. */ private var animationCrossFadeProgress = 1.0f - /** - * The current carousel Alpha. - */ + /** The current carousel Alpha. */ private var carouselAlpha: Float = 1.0f set(value) { if (field == value) { @@ -447,8 +415,8 @@ class MediaHierarchyManager @Inject constructor( * Calculate the alpha of the view when given a cross-fade progress. * * @param crossFadeProgress The current cross fade progress. 0.5f means it's just switching - * between the start and the end location and the content is fully faded, while 0.75f means - * that we're halfway faded in again in the target state. + * between the start and the end location and the content is fully faded, while 0.75f means that + * we're halfway faded in again in the target state. */ private fun calculateAlphaFromCrossFade(crossFadeProgress: Float): Float { if (crossFadeProgress <= 0.5f) { @@ -460,132 +428,152 @@ class MediaHierarchyManager @Inject constructor( init { updateConfiguration() - configurationController.addCallback(object : ConfigurationController.ConfigurationListener { - override fun onConfigChanged(newConfig: Configuration?) { - updateConfiguration() - updateDesiredLocation(forceNoAnimation = true, forceStateUpdate = true) - } - }) - statusBarStateController.addCallback(object : StatusBarStateController.StateListener { - override fun onStatePreChange(oldState: Int, newState: Int) { - // We're updating the location before the state change happens, since we want the - // location of the previous state to still be up to date when the animation starts - statusbarState = newState - updateDesiredLocation() - } - - override fun onStateChanged(newState: Int) { - updateTargetState() - // Enters shade from lock screen - if (newState == StatusBarState.SHADE_LOCKED && isLockScreenShadeVisibleToUser()) { - mediaCarouselController.logSmartspaceImpression(qsExpanded) + configurationController.addCallback( + object : ConfigurationController.ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration?) { + updateConfiguration() + updateDesiredLocation(forceNoAnimation = true, forceStateUpdate = true) } - mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser() - } - - override fun onDozeAmountChanged(linear: Float, eased: Float) { - dozeAnimationRunning = linear != 0.0f && linear != 1.0f } + ) + statusBarStateController.addCallback( + object : StatusBarStateController.StateListener { + override fun onStatePreChange(oldState: Int, newState: Int) { + // We're updating the location before the state change happens, since we want + // the + // location of the previous state to still be up to date when the animation + // starts + statusbarState = newState + updateDesiredLocation() + } - override fun onDozingChanged(isDozing: Boolean) { - if (!isDozing) { - dozeAnimationRunning = false - // Enters lock screen from screen off - if (isLockScreenVisibleToUser()) { + override fun onStateChanged(newState: Int) { + updateTargetState() + // Enters shade from lock screen + if ( + newState == StatusBarState.SHADE_LOCKED && isLockScreenShadeVisibleToUser() + ) { mediaCarouselController.logSmartspaceImpression(qsExpanded) } - } else { - updateDesiredLocation() - qsExpanded = false - closeGuts() + mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = + isVisibleToUser() } - mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser() - } - override fun onExpandedChanged(isExpanded: Boolean) { - // Enters shade from home screen - if (isHomeScreenShadeVisibleToUser()) { - mediaCarouselController.logSmartspaceImpression(qsExpanded) + override fun onDozeAmountChanged(linear: Float, eased: Float) { + dozeAnimationRunning = linear != 0.0f && linear != 1.0f } - mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser() - } - }) - dreamOverlayStateController.addCallback(object : DreamOverlayStateController.Callback { - override fun onComplicationsChanged() { - dreamMediaComplicationActive = dreamOverlayStateController.complications.any { - it is MediaDreamComplication + override fun onDozingChanged(isDozing: Boolean) { + if (!isDozing) { + dozeAnimationRunning = false + // Enters lock screen from screen off + if (isLockScreenVisibleToUser()) { + mediaCarouselController.logSmartspaceImpression(qsExpanded) + } + } else { + updateDesiredLocation() + qsExpanded = false + closeGuts() + } + mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = + isVisibleToUser() } - } - override fun onStateChanged() { - dreamOverlayStateController.isOverlayActive.also { dreamOverlayActive = it } + override fun onExpandedChanged(isExpanded: Boolean) { + // Enters shade from home screen + if (isHomeScreenShadeVisibleToUser()) { + mediaCarouselController.logSmartspaceImpression(qsExpanded) + } + mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = + isVisibleToUser() + } } - }) + ) + + dreamOverlayStateController.addCallback( + object : DreamOverlayStateController.Callback { + override fun onComplicationsChanged() { + dreamMediaComplicationActive = + dreamOverlayStateController.complications.any { + it is MediaDreamComplication + } + } - wakefulnessLifecycle.addObserver(object : WakefulnessLifecycle.Observer { - override fun onFinishedGoingToSleep() { - goingToSleep = false + override fun onStateChanged() { + dreamOverlayStateController.isOverlayActive.also { dreamOverlayActive = it } + } } + ) - override fun onStartedGoingToSleep() { - goingToSleep = true - fullyAwake = false - } + wakefulnessLifecycle.addObserver( + object : WakefulnessLifecycle.Observer { + override fun onFinishedGoingToSleep() { + goingToSleep = false + } - override fun onFinishedWakingUp() { - goingToSleep = false - fullyAwake = true - } + override fun onStartedGoingToSleep() { + goingToSleep = true + fullyAwake = false + } + + override fun onFinishedWakingUp() { + goingToSleep = false + fullyAwake = true + } - override fun onStartedWakingUp() { - goingToSleep = false + override fun onStartedWakingUp() { + goingToSleep = false + } } - }) + ) mediaCarouselController.updateUserVisibility = { mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser() } mediaCarouselController.updateHostVisibility = { - mediaHosts.forEach { - it?.updateViewVisibility() - } + mediaHosts.forEach { it?.updateViewVisibility() } } - panelEventsEvents.registerListener(object : NotifPanelEvents.Listener { - override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) { - skipQqsOnExpansion = isExpandImmediateEnabled - updateDesiredLocation() + panelEventsEvents.registerListener( + object : NotifPanelEvents.Listener { + override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) { + skipQqsOnExpansion = isExpandImmediateEnabled + updateDesiredLocation() + } } - }) + ) - val settingsObserver: ContentObserver = object : ContentObserver(handler) { - override fun onChange(selfChange: Boolean, uri: Uri?) { - if (uri == lockScreenMediaPlayerUri) { - allowMediaPlayerOnLockScreen = + val settingsObserver: ContentObserver = + object : ContentObserver(handler) { + override fun onChange(selfChange: Boolean, uri: Uri?) { + if (uri == lockScreenMediaPlayerUri) { + allowMediaPlayerOnLockScreen = secureSettings.getBoolForUser( - Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, - true, - UserHandle.USER_CURRENT + Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, + true, + UserHandle.USER_CURRENT ) + } } } - } secureSettings.registerContentObserverForUser( - Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, - settingsObserver, - UserHandle.USER_ALL) + Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, + settingsObserver, + UserHandle.USER_ALL + ) } private fun updateConfiguration() { - distanceForFullShadeTransition = context.resources.getDimensionPixelSize( - R.dimen.lockscreen_shade_media_transition_distance) + distanceForFullShadeTransition = + context.resources.getDimensionPixelSize( + R.dimen.lockscreen_shade_media_transition_distance + ) inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(context.resources) } /** - * Register a media host and create a view can be attached to a view hierarchy - * and where the players will be placed in when the host is the currently desired state. + * Register a media host and create a view can be attached to a view hierarchy and where the + * players will be placed in when the host is the currently desired state. * * @return the hostView associated with this location */ @@ -613,27 +601,26 @@ class MediaHierarchyManager @Inject constructor( return viewHost } - /** - * Close the guts in all players in [MediaCarouselController]. - */ + /** Close the guts in all players in [MediaCarouselController]. */ fun closeGuts() { mediaCarouselController.closeGuts() } private fun createUniqueObjectHost(): UniqueObjectHostView { val viewHost = UniqueObjectHostView(context) - viewHost.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { - override fun onViewAttachedToWindow(p0: View?) { - if (rootOverlay == null) { - rootView = viewHost.viewRootImpl.view - rootOverlay = (rootView!!.overlay as ViewGroupOverlay) + viewHost.addOnAttachStateChangeListener( + object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(p0: View?) { + if (rootOverlay == null) { + rootView = viewHost.viewRootImpl.view + rootOverlay = (rootView!!.overlay as ViewGroupOverlay) + } + viewHost.removeOnAttachStateChangeListener(this) } - viewHost.removeOnAttachStateChangeListener(this) - } - override fun onViewDetachedFromWindow(p0: View?) { + override fun onViewDetachedFromWindow(p0: View?) {} } - }) + ) return viewHost } @@ -643,127 +630,141 @@ class MediaHierarchyManager @Inject constructor( * * @param forceNoAnimation optional parameter telling the system not to animate * @param forceStateUpdate optional parameter telling the system to update transition state + * ``` * even if location did not change + * ``` */ private fun updateDesiredLocation( forceNoAnimation: Boolean = false, forceStateUpdate: Boolean = false - ) = traceSection("MediaHierarchyManager#updateDesiredLocation") { - val desiredLocation = calculateLocation() - if (desiredLocation != this.desiredLocation || forceStateUpdate) { - if (this.desiredLocation >= 0 && desiredLocation != this.desiredLocation) { - // Only update previous location when it actually changes - previousLocation = this.desiredLocation - } else if (forceStateUpdate) { - val onLockscreen = (!bypassController.bypassEnabled && - (statusbarState == StatusBarState.KEYGUARD)) - if (desiredLocation == LOCATION_QS && previousLocation == LOCATION_LOCKSCREEN && - !onLockscreen) { - // If media active state changed and the device is now unlocked, update the - // previous location so we animate between the correct hosts - previousLocation = LOCATION_QQS + ) = + traceSection("MediaHierarchyManager#updateDesiredLocation") { + val desiredLocation = calculateLocation() + if (desiredLocation != this.desiredLocation || forceStateUpdate) { + if (this.desiredLocation >= 0 && desiredLocation != this.desiredLocation) { + // Only update previous location when it actually changes + previousLocation = this.desiredLocation + } else if (forceStateUpdate) { + val onLockscreen = + (!bypassController.bypassEnabled && + (statusbarState == StatusBarState.KEYGUARD)) + if ( + desiredLocation == LOCATION_QS && + previousLocation == LOCATION_LOCKSCREEN && + !onLockscreen + ) { + // If media active state changed and the device is now unlocked, update the + // previous location so we animate between the correct hosts + previousLocation = LOCATION_QQS + } } + val isNewView = this.desiredLocation == -1 + this.desiredLocation = desiredLocation + // Let's perform a transition + val animate = + !forceNoAnimation && shouldAnimateTransition(desiredLocation, previousLocation) + val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation) + val host = getHost(desiredLocation) + val willFade = calculateTransformationType() == TRANSFORMATION_TYPE_FADE + if (!willFade || isCurrentlyInGuidedTransformation() || !animate) { + // if we're fading, we want the desired location / measurement only to change + // once fully faded. This is happening in the host attachment + mediaCarouselController.onDesiredLocationChanged( + desiredLocation, + host, + animate, + animDuration, + delay + ) + } + performTransitionToNewLocation(isNewView, animate) } - val isNewView = this.desiredLocation == -1 - this.desiredLocation = desiredLocation - // Let's perform a transition - val animate = !forceNoAnimation && - shouldAnimateTransition(desiredLocation, previousLocation) - val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation) - val host = getHost(desiredLocation) - val willFade = calculateTransformationType() == TRANSFORMATION_TYPE_FADE - if (!willFade || isCurrentlyInGuidedTransformation() || !animate) { - // if we're fading, we want the desired location / measurement only to change - // once fully faded. This is happening in the host attachment - mediaCarouselController.onDesiredLocationChanged(desiredLocation, host, - animate, animDuration, delay) - } - performTransitionToNewLocation(isNewView, animate) } - } - private fun performTransitionToNewLocation( - isNewView: Boolean, - animate: Boolean - ) = traceSection("MediaHierarchyManager#performTransitionToNewLocation") { - if (previousLocation < 0 || isNewView) { - cancelAnimationAndApplyDesiredState() - return - } - val currentHost = getHost(desiredLocation) - val previousHost = getHost(previousLocation) - if (currentHost == null || previousHost == null) { - cancelAnimationAndApplyDesiredState() - return - } - updateTargetState() - if (isCurrentlyInGuidedTransformation()) { - applyTargetStateIfNotAnimating() - } else if (animate) { - val wasCrossFading = isCrossFadeAnimatorRunning - val previewsCrossFadeProgress = animationCrossFadeProgress - animator.cancel() - if (currentAttachmentLocation != previousLocation || - !previousHost.hostView.isAttachedToWindow) { - // Let's animate to the new position, starting from the current position - // We also go in here in case the view was detached, since the bounds wouldn't - // be correct anymore - animationStartBounds.set(currentBounds) - animationStartClipping.set(currentClipping) - } else { - // otherwise, let's take the freshest state, since the current one could - // be outdated - animationStartBounds.set(previousHost.currentBounds) - animationStartClipping.set(previousHost.currentClipping) + private fun performTransitionToNewLocation(isNewView: Boolean, animate: Boolean) = + traceSection("MediaHierarchyManager#performTransitionToNewLocation") { + if (previousLocation < 0 || isNewView) { + cancelAnimationAndApplyDesiredState() + return } - val transformationType = calculateTransformationType() - var needsCrossFade = transformationType == TRANSFORMATION_TYPE_FADE - var crossFadeStartProgress = 0.0f - // The alpha is only relevant when not cross fading - var newCrossFadeStartLocation = previousLocation - if (wasCrossFading) { - if (currentAttachmentLocation == crossFadeAnimationEndLocation) { - if (needsCrossFade) { - // We were previously crossFading and we've already reached - // the end view, Let's start crossfading from the same position there - crossFadeStartProgress = 1.0f - previewsCrossFadeProgress - } - // Otherwise let's fade in from the current alpha, but not cross fade + val currentHost = getHost(desiredLocation) + val previousHost = getHost(previousLocation) + if (currentHost == null || previousHost == null) { + cancelAnimationAndApplyDesiredState() + return + } + updateTargetState() + if (isCurrentlyInGuidedTransformation()) { + applyTargetStateIfNotAnimating() + } else if (animate) { + val wasCrossFading = isCrossFadeAnimatorRunning + val previewsCrossFadeProgress = animationCrossFadeProgress + animator.cancel() + if ( + currentAttachmentLocation != previousLocation || + !previousHost.hostView.isAttachedToWindow + ) { + // Let's animate to the new position, starting from the current position + // We also go in here in case the view was detached, since the bounds wouldn't + // be correct anymore + animationStartBounds.set(currentBounds) + animationStartClipping.set(currentClipping) } else { - // We haven't reached the previous location yet, let's still cross fade from - // where we were. - newCrossFadeStartLocation = crossFadeAnimationStartLocation - if (newCrossFadeStartLocation == desiredLocation) { - // we're crossFading back to where we were, let's start at the end position - crossFadeStartProgress = 1.0f - previewsCrossFadeProgress + // otherwise, let's take the freshest state, since the current one could + // be outdated + animationStartBounds.set(previousHost.currentBounds) + animationStartClipping.set(previousHost.currentClipping) + } + val transformationType = calculateTransformationType() + var needsCrossFade = transformationType == TRANSFORMATION_TYPE_FADE + var crossFadeStartProgress = 0.0f + // The alpha is only relevant when not cross fading + var newCrossFadeStartLocation = previousLocation + if (wasCrossFading) { + if (currentAttachmentLocation == crossFadeAnimationEndLocation) { + if (needsCrossFade) { + // We were previously crossFading and we've already reached + // the end view, Let's start crossfading from the same position there + crossFadeStartProgress = 1.0f - previewsCrossFadeProgress + } + // Otherwise let's fade in from the current alpha, but not cross fade } else { - // Let's start from where we are right now - crossFadeStartProgress = previewsCrossFadeProgress - // We need to force cross fading as we haven't reached the end location yet - needsCrossFade = true + // We haven't reached the previous location yet, let's still cross fade from + // where we were. + newCrossFadeStartLocation = crossFadeAnimationStartLocation + if (newCrossFadeStartLocation == desiredLocation) { + // we're crossFading back to where we were, let's start at the end + // position + crossFadeStartProgress = 1.0f - previewsCrossFadeProgress + } else { + // Let's start from where we are right now + crossFadeStartProgress = previewsCrossFadeProgress + // We need to force cross fading as we haven't reached the end location + // yet + needsCrossFade = true + } } + } else if (needsCrossFade) { + // let's not flicker and start with the same alpha + crossFadeStartProgress = (1.0f - carouselAlpha) / 2.0f } - } else if (needsCrossFade) { - // let's not flicker and start with the same alpha - crossFadeStartProgress = (1.0f - carouselAlpha) / 2.0f - } - isCrossFadeAnimatorRunning = needsCrossFade - crossFadeAnimationStartLocation = newCrossFadeStartLocation - crossFadeAnimationEndLocation = desiredLocation - animationStartAlpha = carouselAlpha - animationStartCrossFadeProgress = crossFadeStartProgress - adjustAnimatorForTransition(desiredLocation, previousLocation) - if (!animationPending) { - rootView?.let { - // Let's delay the animation start until we finished laying out - animationPending = true - it.postOnAnimation(startAnimation) + isCrossFadeAnimatorRunning = needsCrossFade + crossFadeAnimationStartLocation = newCrossFadeStartLocation + crossFadeAnimationEndLocation = desiredLocation + animationStartAlpha = carouselAlpha + animationStartCrossFadeProgress = crossFadeStartProgress + adjustAnimatorForTransition(desiredLocation, previousLocation) + if (!animationPending) { + rootView?.let { + // Let's delay the animation start until we finished laying out + animationPending = true + it.postOnAnimation(startAnimation) + } } + } else { + cancelAnimationAndApplyDesiredState() } - } else { - cancelAnimationAndApplyDesiredState() } - } private fun shouldAnimateTransition( @MediaLocation currentLocation: Int, @@ -777,23 +778,29 @@ class MediaHierarchyManager @Inject constructor( } // This is an invalid transition, and can happen when using the camera gesture from the // lock screen. Disallow. - if (previousLocation == LOCATION_LOCKSCREEN && - desiredLocation == LOCATION_QQS && - statusbarState == StatusBarState.SHADE) { + if ( + previousLocation == LOCATION_LOCKSCREEN && + desiredLocation == LOCATION_QQS && + statusbarState == StatusBarState.SHADE + ) { return false } - if (currentLocation == LOCATION_QQS && + if ( + currentLocation == LOCATION_QQS && previousLocation == LOCATION_LOCKSCREEN && (statusBarStateController.leaveOpenOnKeyguardHide() || - statusbarState == StatusBarState.SHADE_LOCKED)) { + statusbarState == StatusBarState.SHADE_LOCKED) + ) { // Usually listening to the isShown is enough to determine this, but there is some // non-trivial reattaching logic happening that will make the view not-shown earlier return true } - if (statusbarState == StatusBarState.KEYGUARD && (currentLocation == LOCATION_LOCKSCREEN || - previousLocation == LOCATION_LOCKSCREEN)) { + if ( + statusbarState == StatusBarState.KEYGUARD && + (currentLocation == LOCATION_LOCKSCREEN || previousLocation == LOCATION_LOCKSCREEN) + ) { // We're always fading from lockscreen to keyguard in situations where the player // is already fully hidden return false @@ -814,8 +821,10 @@ class MediaHierarchyManager @Inject constructor( var delay = 0L if (previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QQS) { // Going to the full shade, let's adjust the animation duration - if (statusbarState == StatusBarState.SHADE && - keyguardStateController.isKeyguardFadingAway) { + if ( + statusbarState == StatusBarState.SHADE && + keyguardStateController.isKeyguardFadingAway + ) { delay = keyguardStateController.keyguardFadingAwayDelay } animDuration = (StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE / 2f).toLong() @@ -834,14 +843,16 @@ class MediaHierarchyManager @Inject constructor( } } - /** - * Updates the bounds that the view wants to be in at the end of the animation. - */ + /** Updates the bounds that the view wants to be in at the end of the animation. */ private fun updateTargetState() { var starthost = getHost(previousLocation) var endHost = getHost(desiredLocation) - if (isCurrentlyInGuidedTransformation() && !isCurrentlyFading() && starthost != null && - endHost != null) { + if ( + isCurrentlyInGuidedTransformation() && + !isCurrentlyFading() && + starthost != null && + endHost != null + ) { val progress = getTransformationProgress() // If either of the hosts are invisible, let's keep them at the other host location to // have a nicer disappear animation. Otherwise the currentBounds of the state might @@ -868,14 +879,15 @@ class MediaHierarchyManager @Inject constructor( progress: Float, result: Rect? = null ): Rect { - val left = MathUtils.lerp(startBounds.left.toFloat(), - endBounds.left.toFloat(), progress).toInt() - val top = MathUtils.lerp(startBounds.top.toFloat(), - endBounds.top.toFloat(), progress).toInt() - val right = MathUtils.lerp(startBounds.right.toFloat(), - endBounds.right.toFloat(), progress).toInt() - val bottom = MathUtils.lerp(startBounds.bottom.toFloat(), - endBounds.bottom.toFloat(), progress).toInt() + val left = + MathUtils.lerp(startBounds.left.toFloat(), endBounds.left.toFloat(), progress).toInt() + val top = + MathUtils.lerp(startBounds.top.toFloat(), endBounds.top.toFloat(), progress).toInt() + val right = + MathUtils.lerp(startBounds.right.toFloat(), endBounds.right.toFloat(), progress).toInt() + val bottom = + MathUtils.lerp(startBounds.bottom.toFloat(), endBounds.bottom.toFloat(), progress) + .toInt() val resultBounds = result ?: Rect() resultBounds.set(left, top, right, bottom) return resultBounds @@ -884,17 +896,15 @@ class MediaHierarchyManager @Inject constructor( /** @return true if this transformation is guided by an external progress like a finger */ fun isCurrentlyInGuidedTransformation(): Boolean { return hasValidStartAndEndLocations() && - getTransformationProgress() >= 0 && - areGuidedTransitionHostsVisible() + getTransformationProgress() >= 0 && + areGuidedTransitionHostsVisible() } private fun hasValidStartAndEndLocations(): Boolean { return previousLocation != -1 && desiredLocation != -1 } - /** - * Calculate the transformation type for the current animation - */ + /** Calculate the transformation type for the current animation */ @VisibleForTesting @TransformationType fun calculateTransformationType(): Int { @@ -904,8 +914,10 @@ class MediaHierarchyManager @Inject constructor( } return TRANSFORMATION_TYPE_FADE } - if (previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QS || - previousLocation == LOCATION_QS && desiredLocation == LOCATION_LOCKSCREEN) { + if ( + previousLocation == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QS || + previousLocation == LOCATION_QS && desiredLocation == LOCATION_LOCKSCREEN + ) { // animating between ls and qs should fade, as QS is clipped. return TRANSFORMATION_TYPE_FADE } @@ -918,7 +930,7 @@ class MediaHierarchyManager @Inject constructor( private fun areGuidedTransitionHostsVisible(): Boolean { return getHost(previousLocation)?.visible == true && - getHost(desiredLocation)?.visible == true + getHost(desiredLocation)?.visible == true } /** @@ -966,103 +978,115 @@ class MediaHierarchyManager @Inject constructor( } } - /** - * Apply the current state to the view, updating it's bounds and desired state - */ + /** Apply the current state to the view, updating it's bounds and desired state */ private fun applyState( bounds: Rect, alpha: Float, immediately: Boolean = false, clipBounds: Rect = EMPTY_RECT - ) = traceSection("MediaHierarchyManager#applyState") { - currentBounds.set(bounds) - currentClipping = clipBounds - carouselAlpha = if (isCurrentlyFading()) alpha else 1.0f - val onlyUseEndState = !isCurrentlyInGuidedTransformation() || isCurrentlyFading() - val startLocation = if (onlyUseEndState) -1 else previousLocation - val progress = if (onlyUseEndState) 1.0f else getTransformationProgress() - val endLocation = resolveLocationForFading() - mediaCarouselController.setCurrentState(startLocation, endLocation, progress, immediately) - updateHostAttachment() - if (currentAttachmentLocation == IN_OVERLAY) { - // Setting the clipping on the hierarchy of `mediaFrame` does not work - if (!currentClipping.isEmpty) { - currentBounds.intersect(currentClipping) - } - mediaFrame.setLeftTopRightBottom( + ) = + traceSection("MediaHierarchyManager#applyState") { + currentBounds.set(bounds) + currentClipping = clipBounds + carouselAlpha = if (isCurrentlyFading()) alpha else 1.0f + val onlyUseEndState = !isCurrentlyInGuidedTransformation() || isCurrentlyFading() + val startLocation = if (onlyUseEndState) -1 else previousLocation + val progress = if (onlyUseEndState) 1.0f else getTransformationProgress() + val endLocation = resolveLocationForFading() + mediaCarouselController.setCurrentState( + startLocation, + endLocation, + progress, + immediately + ) + updateHostAttachment() + if (currentAttachmentLocation == IN_OVERLAY) { + // Setting the clipping on the hierarchy of `mediaFrame` does not work + if (!currentClipping.isEmpty) { + currentBounds.intersect(currentClipping) + } + mediaFrame.setLeftTopRightBottom( currentBounds.left, currentBounds.top, currentBounds.right, - currentBounds.bottom) + currentBounds.bottom + ) + } } - } - private fun updateHostAttachment() = traceSection( - "MediaHierarchyManager#updateHostAttachment" - ) { - var newLocation = resolveLocationForFading() - var canUseOverlay = !isCurrentlyFading() - if (isCrossFadeAnimatorRunning) { - if (getHost(newLocation)?.visible == true && - getHost(newLocation)?.hostView?.isShown == false && - newLocation != desiredLocation) { - // We're crossfading but the view is already hidden. Let's move to the overlay - // instead. This happens when animating to the full shade using a button click. - canUseOverlay = true + private fun updateHostAttachment() = + traceSection("MediaHierarchyManager#updateHostAttachment") { + var newLocation = resolveLocationForFading() + var canUseOverlay = !isCurrentlyFading() + if (isCrossFadeAnimatorRunning) { + if ( + getHost(newLocation)?.visible == true && + getHost(newLocation)?.hostView?.isShown == false && + newLocation != desiredLocation + ) { + // We're crossfading but the view is already hidden. Let's move to the overlay + // instead. This happens when animating to the full shade using a button click. + canUseOverlay = true + } } - } - val inOverlay = isTransitionRunning() && rootOverlay != null && canUseOverlay - newLocation = if (inOverlay) IN_OVERLAY else newLocation - if (currentAttachmentLocation != newLocation) { - currentAttachmentLocation = newLocation + val inOverlay = isTransitionRunning() && rootOverlay != null && canUseOverlay + newLocation = if (inOverlay) IN_OVERLAY else newLocation + if (currentAttachmentLocation != newLocation) { + currentAttachmentLocation = newLocation - // Remove the carousel from the old host - (mediaFrame.parent as ViewGroup?)?.removeView(mediaFrame) + // Remove the carousel from the old host + (mediaFrame.parent as ViewGroup?)?.removeView(mediaFrame) - // Add it to the new one - if (inOverlay) { - rootOverlay!!.add(mediaFrame) - } else { - val targetHost = getHost(newLocation)!!.hostView - // When adding back to the host, let's make sure to reset the bounds. - // Usually adding the view will trigger a layout that does this automatically, - // but we sometimes suppress this. - targetHost.addView(mediaFrame) - val left = targetHost.paddingLeft - val top = targetHost.paddingTop - mediaFrame.setLeftTopRightBottom( + // Add it to the new one + if (inOverlay) { + rootOverlay!!.add(mediaFrame) + } else { + val targetHost = getHost(newLocation)!!.hostView + // When adding back to the host, let's make sure to reset the bounds. + // Usually adding the view will trigger a layout that does this automatically, + // but we sometimes suppress this. + targetHost.addView(mediaFrame) + val left = targetHost.paddingLeft + val top = targetHost.paddingTop + mediaFrame.setLeftTopRightBottom( left, top, left + currentBounds.width(), - top + currentBounds.height()) - - if (mediaFrame.childCount > 0) { - val child = mediaFrame.getChildAt(0) - if (mediaFrame.height < child.height) { - Log.wtf(TAG, "mediaFrame height is too small for child: " + - "${mediaFrame.height} vs ${child.height}") + top + currentBounds.height() + ) + + if (mediaFrame.childCount > 0) { + val child = mediaFrame.getChildAt(0) + if (mediaFrame.height < child.height) { + Log.wtf( + TAG, + "mediaFrame height is too small for child: " + + "${mediaFrame.height} vs ${child.height}" + ) + } } } - } - if (isCrossFadeAnimatorRunning) { - // When cross-fading with an animation, we only notify the media carousel of the - // location change, once the view is reattached to the new place and not immediately - // when the desired location changes. This callback will update the measurement - // of the carousel, only once we've faded out at the old location and then reattach - // to fade it in at the new location. - mediaCarouselController.onDesiredLocationChanged( - newLocation, - getHost(newLocation), - animate = false - ) + if (isCrossFadeAnimatorRunning) { + // When cross-fading with an animation, we only notify the media carousel of the + // location change, once the view is reattached to the new place and not + // immediately + // when the desired location changes. This callback will update the measurement + // of the carousel, only once we've faded out at the old location and then + // reattach + // to fade it in at the new location. + mediaCarouselController.onDesiredLocationChanged( + newLocation, + getHost(newLocation), + animate = false + ) + } } } - } /** - * Calculate the location when cross fading between locations. While fading out, - * the content should remain in the previous location, while after the switch it should - * be at the desired location. + * Calculate the location when cross fading between locations. While fading out, the content + * should remain in the previous location, while after the switch it should be at the desired + * location. */ private fun resolveLocationForFading(): Int { if (isCrossFadeAnimatorRunning) { @@ -1079,7 +1103,8 @@ class MediaHierarchyManager @Inject constructor( private fun isTransitionRunning(): Boolean { return isCurrentlyInGuidedTransformation() && getTransformationProgress() != 1.0f || - animator.isRunning || animationPending + animator.isRunning || + animationPending } @MediaLocation @@ -1088,31 +1113,39 @@ class MediaHierarchyManager @Inject constructor( // Keep the current location until we're allowed to again return desiredLocation } - val onLockscreen = (!bypassController.bypassEnabled && - (statusbarState == StatusBarState.KEYGUARD)) - val location = when { - dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY - (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS - qsExpansion > 0.4f && onLockscreen -> LOCATION_QS - !hasActiveMedia -> LOCATION_QS - onLockscreen && isSplitShadeExpanding() -> LOCATION_QS - onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS - onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN - else -> LOCATION_QQS - } + val onLockscreen = + (!bypassController.bypassEnabled && (statusbarState == StatusBarState.KEYGUARD)) + val location = + when { + dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY + (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS + qsExpansion > 0.4f && onLockscreen -> LOCATION_QS + !hasActiveMedia -> LOCATION_QS + onLockscreen && isSplitShadeExpanding() -> LOCATION_QS + onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS + onLockscreen && allowMediaPlayerOnLockScreen -> LOCATION_LOCKSCREEN + else -> LOCATION_QQS + } // When we're on lock screen and the player is not active, we should keep it in QS. // Otherwise it will try to animate a transition that doesn't make sense. - if (location == LOCATION_LOCKSCREEN && getHost(location)?.visible != true && - !statusBarStateController.isDozing) { + if ( + location == LOCATION_LOCKSCREEN && + getHost(location)?.visible != true && + !statusBarStateController.isDozing + ) { return LOCATION_QS } - if (location == LOCATION_LOCKSCREEN && desiredLocation == LOCATION_QS && - collapsingShadeFromQS) { + if ( + location == LOCATION_LOCKSCREEN && + desiredLocation == LOCATION_QS && + collapsingShadeFromQS + ) { // When collapsing on the lockscreen, we want to remain in QS return LOCATION_QS } - if (location != LOCATION_LOCKSCREEN && desiredLocation == LOCATION_LOCKSCREEN && - !fullyAwake) { + if ( + location != LOCATION_LOCKSCREEN && desiredLocation == LOCATION_LOCKSCREEN && !fullyAwake + ) { // When unlocking from dozing / while waking up, the media shouldn't be transitioning // in an animated way. Let's keep it in the lockscreen until we're fully awake and // reattach it without an animation @@ -1129,9 +1162,7 @@ class MediaHierarchyManager @Inject constructor( return inSplitShade && isTransitioningToFullShade } - /** - * Are we currently transforming to the full shade and already in QQS - */ + /** Are we currently transforming to the full shade and already in QQS */ private fun isTransformingToFullShadeAndInQQS(): Boolean { if (!isTransitioningToFullShade) { return false @@ -1143,9 +1174,7 @@ class MediaHierarchyManager @Inject constructor( return fullShadeTransitionProgress > 0.5f } - /** - * Is the current transformationType fading - */ + /** Is the current transformationType fading */ private fun isCurrentlyFading(): Boolean { if (isSplitShadeExpanding()) { // Split shade always uses transition instead of fade. @@ -1157,60 +1186,49 @@ class MediaHierarchyManager @Inject constructor( return isCrossFadeAnimatorRunning } - /** - * Returns true when the media card could be visible to the user if existed. - */ + /** Returns true when the media card could be visible to the user if existed. */ private fun isVisibleToUser(): Boolean { - return isLockScreenVisibleToUser() || isLockScreenShadeVisibleToUser() || - isHomeScreenShadeVisibleToUser() + return isLockScreenVisibleToUser() || + isLockScreenShadeVisibleToUser() || + isHomeScreenShadeVisibleToUser() } private fun isLockScreenVisibleToUser(): Boolean { return !statusBarStateController.isDozing && - !keyguardViewController.isBouncerShowing && - statusBarStateController.state == StatusBarState.KEYGUARD && - allowMediaPlayerOnLockScreen && - statusBarStateController.isExpanded && - !qsExpanded + !keyguardViewController.isBouncerShowing && + statusBarStateController.state == StatusBarState.KEYGUARD && + allowMediaPlayerOnLockScreen && + statusBarStateController.isExpanded && + !qsExpanded } private fun isLockScreenShadeVisibleToUser(): Boolean { return !statusBarStateController.isDozing && - !keyguardViewController.isBouncerShowing && - (statusBarStateController.state == StatusBarState.SHADE_LOCKED || - (statusBarStateController.state == StatusBarState.KEYGUARD && qsExpanded)) + !keyguardViewController.isBouncerShowing && + (statusBarStateController.state == StatusBarState.SHADE_LOCKED || + (statusBarStateController.state == StatusBarState.KEYGUARD && qsExpanded)) } private fun isHomeScreenShadeVisibleToUser(): Boolean { return !statusBarStateController.isDozing && - statusBarStateController.state == StatusBarState.SHADE && - statusBarStateController.isExpanded + statusBarStateController.state == StatusBarState.SHADE && + statusBarStateController.isExpanded } companion object { - /** - * Attached in expanded quick settings - */ + /** Attached in expanded quick settings */ const val LOCATION_QS = 0 - /** - * Attached in the collapsed QS - */ + /** Attached in the collapsed QS */ const val LOCATION_QQS = 1 - /** - * Attached on the lock screen - */ + /** Attached on the lock screen */ const val LOCATION_LOCKSCREEN = 2 - /** - * Attached on the dream overlay - */ + /** Attached on the dream overlay */ const val LOCATION_DREAM_OVERLAY = 3 - /** - * Attached at the root of the hierarchy in an overlay - */ + /** Attached at the root of the hierarchy in an overlay */ const val IN_OVERLAY = -1000 /** @@ -1226,18 +1244,29 @@ class MediaHierarchyManager @Inject constructor( const val TRANSFORMATION_TYPE_FADE = 1 } } + private val EMPTY_RECT = Rect() -@IntDef(prefix = ["TRANSFORMATION_TYPE_"], value = [ - MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION, - MediaHierarchyManager.TRANSFORMATION_TYPE_FADE]) +@IntDef( + prefix = ["TRANSFORMATION_TYPE_"], + value = + [ + MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION, + MediaHierarchyManager.TRANSFORMATION_TYPE_FADE + ] +) @Retention(AnnotationRetention.SOURCE) private annotation class TransformationType -@IntDef(prefix = ["LOCATION_"], value = [ - MediaHierarchyManager.LOCATION_QS, - MediaHierarchyManager.LOCATION_QQS, - MediaHierarchyManager.LOCATION_LOCKSCREEN, - MediaHierarchyManager.LOCATION_DREAM_OVERLAY]) +@IntDef( + prefix = ["LOCATION_"], + value = + [ + MediaHierarchyManager.LOCATION_QS, + MediaHierarchyManager.LOCATION_QQS, + MediaHierarchyManager.LOCATION_LOCKSCREEN, + MediaHierarchyManager.LOCATION_DREAM_OVERLAY + ] +) @Retention(AnnotationRetention.SOURCE) annotation class MediaLocation diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt index 87b87fd38e04..455b7de3dc0c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt @@ -30,7 +30,8 @@ import com.android.systemui.util.animation.UniqueObjectHostView import java.util.Objects import javax.inject.Inject -class MediaHost constructor( +class MediaHost +constructor( private val state: MediaHostStateHolder, private val mediaHierarchyManager: MediaHierarchyManager, private val mediaDataManager: MediaDataManager, @@ -45,14 +46,10 @@ class MediaHost constructor( private var inited: Boolean = false - /** - * Are we listening to media data changes? - */ + /** Are we listening to media data changes? */ private var listeningToMediaData = false - /** - * Get the current bounds on the screen. This makes sure the state is fresh and up to date - */ + /** Get the current bounds on the screen. This makes sure the state is fresh and up to date */ val currentBounds: Rect = Rect() get() { hostView.getLocationOnScreen(tmpLocationOnScreen) @@ -81,38 +78,39 @@ class MediaHost constructor( */ val currentClipping = Rect() - private val listener = object : MediaDataManager.Listener { - override fun onMediaDataLoaded( - key: String, - oldKey: String?, - data: MediaData, - immediately: Boolean, - receivedSmartspaceCardLatency: Int, - isSsReactivated: Boolean - ) { - if (immediately) { - updateViewVisibility() + private val listener = + object : MediaDataManager.Listener { + override fun onMediaDataLoaded( + key: String, + oldKey: String?, + data: MediaData, + immediately: Boolean, + receivedSmartspaceCardLatency: Int, + isSsReactivated: Boolean + ) { + if (immediately) { + updateViewVisibility() + } } - } - - override fun onSmartspaceMediaDataLoaded( - key: String, - data: SmartspaceMediaData, - shouldPrioritize: Boolean - ) { - updateViewVisibility() - } - override fun onMediaDataRemoved(key: String) { - updateViewVisibility() - } + override fun onSmartspaceMediaDataLoaded( + key: String, + data: SmartspaceMediaData, + shouldPrioritize: Boolean + ) { + updateViewVisibility() + } - override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) { - if (immediately) { + override fun onMediaDataRemoved(key: String) { updateViewVisibility() } + + override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) { + if (immediately) { + updateViewVisibility() + } + } } - } fun addVisibilityChangeListener(listener: (Boolean) -> Unit) { visibleChangedListeners.add(listener) @@ -123,12 +121,14 @@ class MediaHost constructor( } /** - * Initialize this MediaObject and create a host view. - * All state should already be set on this host before calling this method in order to avoid - * unnecessary state changes which lead to remeasurings later on. + * Initialize this MediaObject and create a host view. All state should already be set on this + * host before calling this method in order to avoid unnecessary state changes which lead to + * remeasurings later on. * * @param location the location this host name has. Used to identify the host during + * ``` * transitions. + * ``` */ fun init(@MediaLocation location: Int) { if (inited) { @@ -141,36 +141,42 @@ class MediaHost constructor( // Listen by default, as the host might not be attached by our clients, until // they get a visibility change. We still want to stay up to date in that case! setListeningToMediaData(true) - hostView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener { - override fun onViewAttachedToWindow(v: View?) { - setListeningToMediaData(true) - updateViewVisibility() - } + hostView.addOnAttachStateChangeListener( + object : OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View?) { + setListeningToMediaData(true) + updateViewVisibility() + } - override fun onViewDetachedFromWindow(v: View?) { - setListeningToMediaData(false) + override fun onViewDetachedFromWindow(v: View?) { + setListeningToMediaData(false) + } } - }) + ) // Listen to measurement updates and update our state with it - hostView.measurementManager = object : UniqueObjectHostView.MeasurementManager { - override fun onMeasure(input: MeasurementInput): MeasurementOutput { - // Modify the measurement to exactly match the dimensions - if (View.MeasureSpec.getMode(input.widthMeasureSpec) == View.MeasureSpec.AT_MOST) { - input.widthMeasureSpec = View.MeasureSpec.makeMeasureSpec( - View.MeasureSpec.getSize(input.widthMeasureSpec), - View.MeasureSpec.EXACTLY) + hostView.measurementManager = + object : UniqueObjectHostView.MeasurementManager { + override fun onMeasure(input: MeasurementInput): MeasurementOutput { + // Modify the measurement to exactly match the dimensions + if ( + View.MeasureSpec.getMode(input.widthMeasureSpec) == View.MeasureSpec.AT_MOST + ) { + input.widthMeasureSpec = + View.MeasureSpec.makeMeasureSpec( + View.MeasureSpec.getSize(input.widthMeasureSpec), + View.MeasureSpec.EXACTLY + ) + } + // This will trigger a state change that ensures that we now have a state + // available + state.measurementInput = input + return mediaHostStatesManager.updateCarouselDimensions(location, state) } - // This will trigger a state change that ensures that we now have a state available - state.measurementInput = input - return mediaHostStatesManager.updateCarouselDimensions(location, state) } - } // Whenever the state changes, let our state manager know - state.changedListener = { - mediaHostStatesManager.updateHostState(location, state) - } + state.changedListener = { mediaHostStatesManager.updateHostState(location, state) } updateViewVisibility() } @@ -191,17 +197,16 @@ class MediaHost constructor( * the visibility has changed */ fun updateViewVisibility() { - state.visible = if (showsOnlyActiveMedia) { - mediaDataManager.hasActiveMediaOrRecommendation() - } else { - mediaDataManager.hasAnyMediaOrRecommendation() - } + state.visible = + if (showsOnlyActiveMedia) { + mediaDataManager.hasActiveMediaOrRecommendation() + } else { + mediaDataManager.hasAnyMediaOrRecommendation() + } val newVisibility = if (visible) View.VISIBLE else View.GONE if (newVisibility != hostView.visibility) { hostView.visibility = newVisibility - visibleChangedListeners.forEach { - it.invoke(visible) - } + visibleChangedListeners.forEach { it.invoke(visible) } } } @@ -269,14 +274,10 @@ class MediaHost constructor( private var lastDisappearHash = disappearParameters.hashCode() - /** - * A listener for all changes. This won't be copied over when invoking [copy] - */ + /** A listener for all changes. This won't be copied over when invoking [copy] */ var changedListener: (() -> Unit)? = null - /** - * Get a copy of this state. This won't copy any listeners it may have set - */ + /** Get a copy of this state. This won't copy any listeners it may have set */ override fun copy(): MediaHostState { val mediaHostState = MediaHostStateHolder() mediaHostState.expansion = expansion @@ -331,15 +332,13 @@ class MediaHost constructor( } /** - * A description of a media host state that describes the behavior whenever the media carousel - * is hosted. The HostState notifies the media players of changes to their properties, who - * in turn will create view states from it. - * When adding a new property to this, make sure to update the listener and notify them - * about the changes. - * In case you need to have a different rendering based on the state, you can add a new - * constraintState to the [MediaViewController]. Otherwise, similar host states will resolve - * to the same viewstate, a behavior that is described in [CacheKey]. Make sure to only update - * that key if the underlying view needs to have a different measurement. + * A description of a media host state that describes the behavior whenever the media carousel is + * hosted. The HostState notifies the media players of changes to their properties, who in turn will + * create view states from it. When adding a new property to this, make sure to update the listener + * and notify them about the changes. In case you need to have a different rendering based on the + * state, you can add a new constraintState to the [MediaViewController]. Otherwise, similar host + * states will resolve to the same viewstate, a behavior that is described in [CacheKey]. Make sure + * to only update that key if the underlying view needs to have a different measurement. */ interface MediaHostState { @@ -349,46 +348,36 @@ interface MediaHostState { } /** - * The last measurement input that this state was measured with. Infers width and height of - * the players. + * The last measurement input that this state was measured with. Infers width and height of the + * players. */ var measurementInput: MeasurementInput? /** - * The expansion of the player, [COLLAPSED] for fully collapsed (up to 3 actions), - * [EXPANDED] for fully expanded (up to 5 actions). + * The expansion of the player, [COLLAPSED] for fully collapsed (up to 3 actions), [EXPANDED] + * for fully expanded (up to 5 actions). */ var expansion: Float - /** - * Fraction of the height animation. - */ + /** Fraction of the height animation. */ var squishFraction: Float - /** - * Is this host only showing active media or is it showing all of them including resumption? - */ + /** Is this host only showing active media or is it showing all of them including resumption? */ var showsOnlyActiveMedia: Boolean - /** - * If the view should be VISIBLE or GONE. - */ + /** If the view should be VISIBLE or GONE. */ val visible: Boolean - /** - * Does this host need any falsing protection? - */ + /** Does this host need any falsing protection? */ var falsingProtectionNeeded: Boolean /** * The parameters how the view disappears from this location when going to a host that's not - * visible. If modified, make sure to set this value again on the host to ensure the values - * are propagated + * visible. If modified, make sure to set this value again on the host to ensure the values are + * propagated */ var disappearParameters: DisappearParameters - /** - * Get a copy of this view state, deepcopying all appropriate members - */ + /** Get a copy of this view state, deepcopying all appropriate members */ fun copy(): MediaHostState } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt index 72899e59579b..ae3ce333d41d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt @@ -38,85 +38,76 @@ class MediaHostStatesManager @Inject constructor() { */ val carouselSizes: MutableMap<Int, MeasurementOutput> = mutableMapOf() - /** - * A map with all media states of all locations. - */ + /** A map with all media states of all locations. */ val mediaHostStates: MutableMap<Int, MediaHostState> = mutableMapOf() /** - * Notify that a media state for a given location has changed. Should only be called from - * Media hosts themselves. + * Notify that a media state for a given location has changed. Should only be called from Media + * hosts themselves. */ - fun updateHostState( - @MediaLocation location: Int, - hostState: MediaHostState - ) = traceSection("MediaHostStatesManager#updateHostState") { - val currentState = mediaHostStates.get(location) - if (!hostState.equals(currentState)) { - val newState = hostState.copy() - mediaHostStates.put(location, newState) - updateCarouselDimensions(location, hostState) - // First update all the controllers to ensure they get the chance to measure - for (controller in controllers) { - controller.stateCallback.onHostStateChanged(location, newState) - } + fun updateHostState(@MediaLocation location: Int, hostState: MediaHostState) = + traceSection("MediaHostStatesManager#updateHostState") { + val currentState = mediaHostStates.get(location) + if (!hostState.equals(currentState)) { + val newState = hostState.copy() + mediaHostStates.put(location, newState) + updateCarouselDimensions(location, hostState) + // First update all the controllers to ensure they get the chance to measure + for (controller in controllers) { + controller.stateCallback.onHostStateChanged(location, newState) + } - // Then update all other callbacks which may depend on the controllers above - for (callback in callbacks) { - callback.onHostStateChanged(location, newState) + // Then update all other callbacks which may depend on the controllers above + for (callback in callbacks) { + callback.onHostStateChanged(location, newState) + } } } - } /** - * Get the dimensions of all players combined, which determines the overall height of the - * media carousel and the media hosts. + * Get the dimensions of all players combined, which determines the overall height of the media + * carousel and the media hosts. */ fun updateCarouselDimensions( @MediaLocation location: Int, hostState: MediaHostState - ): MeasurementOutput = traceSection("MediaHostStatesManager#updateCarouselDimensions") { - val result = MeasurementOutput(0, 0) - for (controller in controllers) { - val measurement = controller.getMeasurementsForState(hostState) - measurement?.let { - if (it.measuredHeight > result.measuredHeight) { - result.measuredHeight = it.measuredHeight - } - if (it.measuredWidth > result.measuredWidth) { - result.measuredWidth = it.measuredWidth + ): MeasurementOutput = + traceSection("MediaHostStatesManager#updateCarouselDimensions") { + val result = MeasurementOutput(0, 0) + for (controller in controllers) { + val measurement = controller.getMeasurementsForState(hostState) + measurement?.let { + if (it.measuredHeight > result.measuredHeight) { + result.measuredHeight = it.measuredHeight + } + if (it.measuredWidth > result.measuredWidth) { + result.measuredWidth = it.measuredWidth + } } } + carouselSizes[location] = result + return result } - carouselSizes[location] = result - return result - } - /** - * Add a callback to be called when a MediaState has updated - */ + /** Add a callback to be called when a MediaState has updated */ fun addCallback(callback: Callback) { callbacks.add(callback) } - /** - * Remove a callback that listens to media states - */ + /** Remove a callback that listens to media states */ fun removeCallback(callback: Callback) { callbacks.remove(callback) } /** - * Register a controller that listens to media states and is used to determine the size of - * the media carousel + * Register a controller that listens to media states and is used to determine the size of the + * media carousel */ fun addController(controller: MediaViewController) { controllers.add(controller) } - /** - * Notify the manager about the removal of a controller. - */ + /** Notify the manager about the removal of a controller. */ fun removeController(controller: MediaViewController) { controllers.remove(controller) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaScrollView.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaScrollView.kt index 268961ca56aa..0e0746590776 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaScrollView.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaScrollView.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.media.controls.ui import android.content.Context @@ -11,14 +27,13 @@ import com.android.systemui.Gefingerpoken import com.android.wm.shell.animation.physicsAnimator /** - * A ScrollView used in Media that doesn't limit itself to the childs bounds. This is useful - * when only measuring children but not the parent, when trying to apply a new scroll position + * A ScrollView used in Media that doesn't limit itself to the childs bounds. This is useful when + * only measuring children but not the parent, when trying to apply a new scroll position */ -class MediaScrollView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : HorizontalScrollView(context, attrs, defStyleAttr) { +class MediaScrollView +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : + HorizontalScrollView(context, attrs, defStyleAttr) { lateinit var contentContainer: ViewGroup private set @@ -33,35 +48,33 @@ class MediaScrollView @JvmOverloads constructor( * Get the current content translation. This is usually the normal translationX of the content, * but when animating, it might differ */ - fun getContentTranslation() = if (contentContainer.physicsAnimator.isRunning()) { - animationTargetX - } else { - contentContainer.translationX - } + fun getContentTranslation() = + if (contentContainer.physicsAnimator.isRunning()) { + animationTargetX + } else { + contentContainer.translationX + } /** * Convert between the absolute (left-to-right) and relative (start-to-end) scrollX of the media - * carousel. The player indices are always relative (start-to-end) and the scrollView.scrollX - * is always absolute. This function is its own inverse. + * carousel. The player indices are always relative (start-to-end) and the scrollView.scrollX is + * always absolute. This function is its own inverse. */ - private fun transformScrollX(scrollX: Int): Int = if (isLayoutRtl) { - contentContainer.width - width - scrollX - } else { - scrollX - } + private fun transformScrollX(scrollX: Int): Int = + if (isLayoutRtl) { + contentContainer.width - width - scrollX + } else { + scrollX + } - /** - * Get the layoutDirection-relative (start-to-end) scroll X position of the carousel. - */ + /** Get the layoutDirection-relative (start-to-end) scroll X position of the carousel. */ var relativeScrollX: Int get() = transformScrollX(scrollX) set(value) { scrollX = transformScrollX(value) } - /** - * Allow all scrolls to go through, use base implementation - */ + /** Allow all scrolls to go through, use base implementation */ override fun scrollTo(x: Int, y: Int) { if (mScrollX != x || mScrollY != y) { val oldX: Int = mScrollX @@ -78,17 +91,13 @@ class MediaScrollView @JvmOverloads constructor( override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { var intercept = false - touchListener?.let { - intercept = it.onInterceptTouchEvent(ev) - } + touchListener?.let { intercept = it.onInterceptTouchEvent(ev) } return super.onInterceptTouchEvent(ev) || intercept } override fun onTouchEvent(ev: MotionEvent?): Boolean { var touch = false - touchListener?.let { - touch = it.onTouchEvent(ev) - } + touchListener?.let { touch = it.onTouchEvent(ev) } return super.onTouchEvent(ev) || touch } @@ -112,19 +121,25 @@ class MediaScrollView @JvmOverloads constructor( // When we're dismissing we ignore all the scrolling return false } - return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, - scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent) + return super.overScrollBy( + deltaX, + deltaY, + scrollX, + scrollY, + scrollRangeX, + scrollRangeY, + maxOverScrollX, + maxOverScrollY, + isTouchEvent + ) } - /** - * Cancel the current touch event going on. - */ + /** Cancel the current touch event going on. */ fun cancelCurrentScroll() { val now = SystemClock.uptimeMillis() - val event = MotionEvent.obtain(now, now, - MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0) + val event = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0) event.source = InputDevice.SOURCE_TOUCHSCREEN super.onTouchEvent(event) event.recycle() } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt index 0a13d44f1b26..4bf3031c02b4 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt @@ -42,7 +42,9 @@ import javax.inject.Inject * A class responsible for controlling a single instance of a media player handling interactions * with the view instance and keeping the media view states up to date. */ -class MediaViewController @Inject constructor( +class MediaViewController +@Inject +constructor( private val context: Context, private val configurationController: ConfigurationController, private val mediaHostStatesManager: MediaHostStatesManager, @@ -50,17 +52,18 @@ class MediaViewController @Inject constructor( ) { /** - * Indicating that the media view controller is for a notification-based player, - * session-based player, or recommendation + * Indicating that the media view controller is for a notification-based player, session-based + * player, or recommendation */ enum class TYPE { - PLAYER, RECOMMENDATION + PLAYER, + RECOMMENDATION } companion object { - @JvmField - val GUTS_ANIMATION_DURATION = 500L - val controlIds = setOf( + @JvmField val GUTS_ANIMATION_DURATION = 500L + val controlIds = + setOf( R.id.media_progress_bar, R.id.actionNext, R.id.actionPrev, @@ -71,22 +74,20 @@ class MediaViewController @Inject constructor( R.id.action4, R.id.media_scrubbing_elapsed_time, R.id.media_scrubbing_total_time - ) + ) - val detailIds = setOf( + val detailIds = + setOf( R.id.header_title, R.id.header_artist, R.id.actionPlayPause, - ) + ) } - /** - * A listener when the current dimensions of the player change - */ + /** A listener when the current dimensions of the player change */ lateinit var sizeChangedListener: () -> Unit private var firstRefresh: Boolean = true - @VisibleForTesting - private var transitionLayout: TransitionLayout? = null + @VisibleForTesting private var transitionLayout: TransitionLayout? = null private val layoutController = TransitionLayoutController() private var animationDelay: Long = 0 private var animationDuration: Long = 0 @@ -94,116 +95,98 @@ class MediaViewController @Inject constructor( private val measurement = MeasurementOutput(0, 0) private var type: TYPE = TYPE.PLAYER - /** - * A map containing all viewStates for all locations of this mediaState - */ + /** A map containing all viewStates for all locations of this mediaState */ private val viewStates: MutableMap<CacheKey, TransitionViewState?> = mutableMapOf() /** * The ending location of the view where it ends when all animations and transitions have * finished */ - @MediaLocation - var currentEndLocation: Int = -1 + @MediaLocation var currentEndLocation: Int = -1 - /** - * The starting location of the view where it starts for all animations and transitions - */ - @MediaLocation - private var currentStartLocation: Int = -1 + /** The starting location of the view where it starts for all animations and transitions */ + @MediaLocation private var currentStartLocation: Int = -1 - /** - * The progress of the transition or 1.0 if there is no transition happening - */ + /** The progress of the transition or 1.0 if there is no transition happening */ private var currentTransitionProgress: Float = 1.0f - /** - * A temporary state used to store intermediate measurements. - */ + /** A temporary state used to store intermediate measurements. */ private val tmpState = TransitionViewState() - /** - * A temporary state used to store intermediate measurements. - */ + /** A temporary state used to store intermediate measurements. */ private val tmpState2 = TransitionViewState() - /** - * A temporary state used to store intermediate measurements. - */ + /** A temporary state used to store intermediate measurements. */ private val tmpState3 = TransitionViewState() - /** - * A temporary cache key to be used to look up cache entries - */ + /** A temporary cache key to be used to look up cache entries */ private val tmpKey = CacheKey() /** - * The current width of the player. This might not factor in case the player is animating - * to the current state, but represents the end state + * The current width of the player. This might not factor in case the player is animating to the + * current state, but represents the end state */ var currentWidth: Int = 0 /** - * The current height of the player. This might not factor in case the player is animating - * to the current state, but represents the end state + * The current height of the player. This might not factor in case the player is animating to + * the current state, but represents the end state */ var currentHeight: Int = 0 - /** - * Get the translationX of the layout - */ + /** Get the translationX of the layout */ var translationX: Float = 0.0f private set get() { return transitionLayout?.translationX ?: 0.0f } - /** - * Get the translationY of the layout - */ + /** Get the translationY of the layout */ var translationY: Float = 0.0f private set get() { return transitionLayout?.translationY ?: 0.0f } - /** - * A callback for RTL config changes - */ - private val configurationListener = object : ConfigurationController.ConfigurationListener { - override fun onConfigChanged(newConfig: Configuration?) { - // Because the TransitionLayout is not always attached (and calculates/caches layout - // results regardless of attach state), we have to force the layoutDirection of the view - // to the correct value for the user's current locale to ensure correct recalculation - // when/after calling refreshState() - newConfig?.apply { - if (transitionLayout?.rawLayoutDirection != layoutDirection) { - transitionLayout?.layoutDirection = layoutDirection - refreshState() + /** A callback for RTL config changes */ + private val configurationListener = + object : ConfigurationController.ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration?) { + // Because the TransitionLayout is not always attached (and calculates/caches layout + // results regardless of attach state), we have to force the layoutDirection of the + // view + // to the correct value for the user's current locale to ensure correct + // recalculation + // when/after calling refreshState() + newConfig?.apply { + if (transitionLayout?.rawLayoutDirection != layoutDirection) { + transitionLayout?.layoutDirection = layoutDirection + refreshState() + } } } } - } - /** - * A callback for media state changes - */ - val stateCallback = object : MediaHostStatesManager.Callback { - override fun onHostStateChanged( - @MediaLocation location: Int, - mediaHostState: MediaHostState - ) { - if (location == currentEndLocation || location == currentStartLocation) { - setCurrentState(currentStartLocation, + /** A callback for media state changes */ + val stateCallback = + object : MediaHostStatesManager.Callback { + override fun onHostStateChanged( + @MediaLocation location: Int, + mediaHostState: MediaHostState + ) { + if (location == currentEndLocation || location == currentStartLocation) { + setCurrentState( + currentStartLocation, currentEndLocation, currentTransitionProgress, - applyImmediately = false) + applyImmediately = false + ) + } } } - } /** - * The expanded constraint set used to render a expanded player. If it is modified, make sure - * to call [refreshState] + * The expanded constraint set used to render a expanded player. If it is modified, make sure to + * call [refreshState] */ val collapsedLayout = ConstraintSet() @@ -213,9 +196,7 @@ class MediaViewController @Inject constructor( */ val expandedLayout = ConstraintSet() - /** - * Whether the guts are visible for the associated player. - */ + /** Whether the guts are visible for the associated player. */ var isGutsVisible = false private set @@ -237,17 +218,17 @@ class MediaViewController @Inject constructor( configurationController.removeCallback(configurationListener) } - /** - * Show guts with an animated transition. - */ + /** Show guts with an animated transition. */ fun openGuts() { if (isGutsVisible) return isGutsVisible = true animatePendingStateChange(GUTS_ANIMATION_DURATION, 0L) - setCurrentState(currentStartLocation, - currentEndLocation, - currentTransitionProgress, - applyImmediately = false) + setCurrentState( + currentStartLocation, + currentEndLocation, + currentTransitionProgress, + applyImmediately = false + ) } /** @@ -262,10 +243,12 @@ class MediaViewController @Inject constructor( if (!immediate) { animatePendingStateChange(GUTS_ANIMATION_DURATION, 0L) } - setCurrentState(currentStartLocation, - currentEndLocation, - currentTransitionProgress, - applyImmediately = immediate) + setCurrentState( + currentStartLocation, + currentEndLocation, + currentTransitionProgress, + applyImmediately = immediate + ) } private fun ensureAllMeasurements() { @@ -275,21 +258,20 @@ class MediaViewController @Inject constructor( } } - /** - * Get the constraintSet for a given expansion - */ + /** Get the constraintSet for a given expansion */ private fun constraintSetForExpansion(expansion: Float): ConstraintSet = - if (expansion > 0) expandedLayout else collapsedLayout + if (expansion > 0) expandedLayout else collapsedLayout /** * Set the views to be showing/hidden based on the [isGutsVisible] for a given * [TransitionViewState]. */ private fun setGutsViewState(viewState: TransitionViewState) { - val controlsIds = when (type) { - TYPE.PLAYER -> MediaViewHolder.controlsIds - TYPE.RECOMMENDATION -> RecommendationViewHolder.controlsIds - } + val controlsIds = + when (type) { + TYPE.PLAYER -> MediaViewHolder.controlsIds + TYPE.RECOMMENDATION -> RecommendationViewHolder.controlsIds + } val gutsIds = GutsViewHolder.ids controlsIds.forEach { id -> viewState.widgetStates.get(id)?.let { state -> @@ -307,9 +289,7 @@ class MediaViewController @Inject constructor( } } - /** - * Apply squishFraction to a copy of viewState such that the cached version is untouched. - */ + /** Apply squishFraction to a copy of viewState such that the cached version is untouched. */ internal fun squishViewState( viewState: TransitionViewState, squishFraction: Float @@ -347,8 +327,8 @@ class MediaViewController @Inject constructor( * Obtain a new viewState for a given media state. This usually returns a cached state, but if * it's not available, it will recreate one by measuring, which may be expensive. */ - @VisibleForTesting - fun obtainViewState(state: MediaHostState?): TransitionViewState? { + @VisibleForTesting + fun obtainViewState(state: MediaHostState?): TransitionViewState? { if (state == null || state.measurementInput == null) { return null } @@ -371,10 +351,12 @@ class MediaViewController @Inject constructor( } // Let's create a new measurement if (state.expansion == 0.0f || state.expansion == 1.0f) { - result = transitionLayout!!.calculateViewState( + result = + transitionLayout!!.calculateViewState( state.measurementInput!!, constraintSetForExpansion(state.expansion), - TransitionViewState()) + TransitionViewState() + ) setGutsViewState(result) // We don't want to cache interpolated or null states as this could quickly fill up @@ -390,10 +372,8 @@ class MediaViewController @Inject constructor( val startViewState = obtainViewState(startState) as TransitionViewState val endState = state.copy().also { it.expansion = 1.0f } val endViewState = obtainViewState(endState) as TransitionViewState - result = layoutController.getInterpolatedState( - startViewState, - endViewState, - state.expansion) + result = + layoutController.getInterpolatedState(startViewState, endViewState, state.expansion) } if (state.squishFraction <= 1f) { return squishViewState(result, state.squishFraction) @@ -401,11 +381,7 @@ class MediaViewController @Inject constructor( return result } - private fun getKey( - state: MediaHostState, - guts: Boolean, - result: CacheKey - ): CacheKey { + private fun getKey(state: MediaHostState, guts: Boolean, result: CacheKey): CacheKey { result.apply { heightMeasureSpec = state.measurementInput?.heightMeasureSpec ?: 0 widthMeasureSpec = state.measurementInput?.widthMeasureSpec ?: 0 @@ -416,41 +392,39 @@ class MediaViewController @Inject constructor( } /** - * Attach a view to this controller. This may perform measurements if it's not available yet - * and should therefore be done carefully. + * Attach a view to this controller. This may perform measurements if it's not available yet and + * should therefore be done carefully. */ - fun attach( - transitionLayout: TransitionLayout, - type: TYPE - ) = traceSection("MediaViewController#attach") { - updateMediaViewControllerType(type) - logger.logMediaLocation("attach $type", currentStartLocation, currentEndLocation) - this.transitionLayout = transitionLayout - layoutController.attach(transitionLayout) - if (currentEndLocation == -1) { - return - } - // Set the previously set state immediately to the view, now that it's finally attached - setCurrentState( + fun attach(transitionLayout: TransitionLayout, type: TYPE) = + traceSection("MediaViewController#attach") { + updateMediaViewControllerType(type) + logger.logMediaLocation("attach $type", currentStartLocation, currentEndLocation) + this.transitionLayout = transitionLayout + layoutController.attach(transitionLayout) + if (currentEndLocation == -1) { + return + } + // Set the previously set state immediately to the view, now that it's finally attached + setCurrentState( startLocation = currentStartLocation, endLocation = currentEndLocation, transitionProgress = currentTransitionProgress, - applyImmediately = true) - } + applyImmediately = true + ) + } /** - * Obtain a measurement for a given location. This makes sure that the state is up to date - * and all widgets know their location. Calling this method may create a measurement if we - * don't have a cached value available already. + * Obtain a measurement for a given location. This makes sure that the state is up to date and + * all widgets know their location. Calling this method may create a measurement if we don't + * have a cached value available already. */ - fun getMeasurementsForState( - hostState: MediaHostState - ): MeasurementOutput? = traceSection("MediaViewController#getMeasurementsForState") { - val viewState = obtainViewState(hostState) ?: return null - measurement.measuredWidth = viewState.width - measurement.measuredHeight = viewState.height - return measurement - } + fun getMeasurementsForState(hostState: MediaHostState): MeasurementOutput? = + traceSection("MediaViewController#getMeasurementsForState") { + val viewState = obtainViewState(hostState) ?: return null + measurement.measuredWidth = viewState.width + measurement.measuredHeight = viewState.height + return measurement + } /** * Set a new state for the controlled view which can be an interpolation between multiple @@ -461,67 +435,85 @@ class MediaViewController @Inject constructor( @MediaLocation endLocation: Int, transitionProgress: Float, applyImmediately: Boolean - ) = traceSection("MediaViewController#setCurrentState") { - currentEndLocation = endLocation - currentStartLocation = startLocation - currentTransitionProgress = transitionProgress - logger.logMediaLocation("setCurrentState", startLocation, endLocation) - - val shouldAnimate = animateNextStateChange && !applyImmediately - - val endHostState = mediaHostStatesManager.mediaHostStates[endLocation] ?: return - val startHostState = mediaHostStatesManager.mediaHostStates[startLocation] - - // Obtain the view state that we'd want to be at the end - // The view might not be bound yet or has never been measured and in that case will be - // reset once the state is fully available - var endViewState = obtainViewState(endHostState) ?: return - endViewState = updateViewStateToCarouselSize(endViewState, endLocation, tmpState2)!! - layoutController.setMeasureState(endViewState) - - // If the view isn't bound, we can drop the animation, otherwise we'll execute it - animateNextStateChange = false - if (transitionLayout == null) { - return - } - - val result: TransitionViewState - var startViewState = obtainViewState(startHostState) - startViewState = updateViewStateToCarouselSize(startViewState, startLocation, tmpState3) + ) = + traceSection("MediaViewController#setCurrentState") { + currentEndLocation = endLocation + currentStartLocation = startLocation + currentTransitionProgress = transitionProgress + logger.logMediaLocation("setCurrentState", startLocation, endLocation) + + val shouldAnimate = animateNextStateChange && !applyImmediately + + val endHostState = mediaHostStatesManager.mediaHostStates[endLocation] ?: return + val startHostState = mediaHostStatesManager.mediaHostStates[startLocation] + + // Obtain the view state that we'd want to be at the end + // The view might not be bound yet or has never been measured and in that case will be + // reset once the state is fully available + var endViewState = obtainViewState(endHostState) ?: return + endViewState = updateViewStateToCarouselSize(endViewState, endLocation, tmpState2)!! + layoutController.setMeasureState(endViewState) + + // If the view isn't bound, we can drop the animation, otherwise we'll execute it + animateNextStateChange = false + if (transitionLayout == null) { + return + } - if (!endHostState.visible) { - // Let's handle the case where the end is gone first. In this case we take the - // start viewState and will make it gone - if (startViewState == null || startHostState == null || !startHostState.visible) { - // the start isn't a valid state, let's use the endstate directly + val result: TransitionViewState + var startViewState = obtainViewState(startHostState) + startViewState = updateViewStateToCarouselSize(startViewState, startLocation, tmpState3) + + if (!endHostState.visible) { + // Let's handle the case where the end is gone first. In this case we take the + // start viewState and will make it gone + if (startViewState == null || startHostState == null || !startHostState.visible) { + // the start isn't a valid state, let's use the endstate directly + result = endViewState + } else { + // Let's get the gone presentation from the start state + result = + layoutController.getGoneState( + startViewState, + startHostState.disappearParameters, + transitionProgress, + tmpState + ) + } + } else if (startHostState != null && !startHostState.visible) { + // We have a start state and it is gone. + // Let's get presentation from the endState + result = + layoutController.getGoneState( + endViewState, + endHostState.disappearParameters, + 1.0f - transitionProgress, + tmpState + ) + } else if (transitionProgress == 1.0f || startViewState == null) { + // We're at the end. Let's use that state result = endViewState + } else if (transitionProgress == 0.0f) { + // We're at the start. Let's use that state + result = startViewState } else { - // Let's get the gone presentation from the start state - result = layoutController.getGoneState(startViewState, - startHostState.disappearParameters, + result = + layoutController.getInterpolatedState( + startViewState, + endViewState, transitionProgress, - tmpState) + tmpState + ) } - } else if (startHostState != null && !startHostState.visible) { - // We have a start state and it is gone. - // Let's get presentation from the endState - result = layoutController.getGoneState(endViewState, endHostState.disappearParameters, - 1.0f - transitionProgress, - tmpState) - } else if (transitionProgress == 1.0f || startViewState == null) { - // We're at the end. Let's use that state - result = endViewState - } else if (transitionProgress == 0.0f) { - // We're at the start. Let's use that state - result = startViewState - } else { - result = layoutController.getInterpolatedState(startViewState, endViewState, - transitionProgress, tmpState) + logger.logMediaSize("setCurrentState", result.width, result.height) + layoutController.setState( + result, + applyImmediately, + shouldAnimate, + animationDuration, + animationDelay + ) } - logger.logMediaSize("setCurrentState", result.width, result.height) - layoutController.setState(result, applyImmediately, shouldAnimate, animationDuration, - animationDelay) - } private fun updateViewStateToCarouselSize( viewState: TransitionViewState?, @@ -558,8 +550,8 @@ class MediaViewController @Inject constructor( } /** - * Retrieves the [TransitionViewState] and [MediaHostState] of a [@MediaLocation]. - * In the event of [location] not being visible, [locationWhenHidden] will be used instead. + * Retrieves the [TransitionViewState] and [MediaHostState] of a [@MediaLocation]. In the event + * of [location] not being visible, [locationWhenHidden] will be used instead. * * @param location Target * @param locationWhenHidden Location that will be used when the target is not @@ -576,40 +568,37 @@ class MediaViewController @Inject constructor( * This updates the width the view will me measured with. */ fun onLocationPreChange(@MediaLocation newLocation: Int) { - obtainViewStateForLocation(newLocation)?.let { - layoutController.setMeasureState(it) - } + obtainViewStateForLocation(newLocation)?.let { layoutController.setMeasureState(it) } } - /** - * Request that the next state change should be animated with the given parameters. - */ + /** Request that the next state change should be animated with the given parameters. */ fun animatePendingStateChange(duration: Long, delay: Long) { animateNextStateChange = true animationDuration = duration animationDelay = delay } - /** - * Clear all existing measurements and refresh the state to match the view. - */ - fun refreshState() = traceSection("MediaViewController#refreshState") { - // Let's clear all of our measurements and recreate them! - viewStates.clear() - if (firstRefresh) { - // This is the first bind, let's ensure we pre-cache all measurements. Otherwise - // We'll just load these on demand. - ensureAllMeasurements() - firstRefresh = false + /** Clear all existing measurements and refresh the state to match the view. */ + fun refreshState() = + traceSection("MediaViewController#refreshState") { + // Let's clear all of our measurements and recreate them! + viewStates.clear() + if (firstRefresh) { + // This is the first bind, let's ensure we pre-cache all measurements. Otherwise + // We'll just load these on demand. + ensureAllMeasurements() + firstRefresh = false + } + setCurrentState( + currentStartLocation, + currentEndLocation, + currentTransitionProgress, + applyImmediately = true + ) } - setCurrentState(currentStartLocation, currentEndLocation, currentTransitionProgress, - applyImmediately = true) - } } -/** - * An internal key for the cache of mediaViewStates. This is a subset of the full host state. - */ +/** An internal key for the cache of mediaViewStates. This is a subset of the full host state. */ private data class CacheKey( var widthMeasureSpec: Int = -1, var heightMeasureSpec: Int = -1, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt index 3929e894d971..fdac33ac20b0 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt @@ -24,40 +24,32 @@ import javax.inject.Inject private const val TAG = "MediaView" -/** - * A buffered log for media view events that are too noisy for regular logging - */ +/** A buffered log for media view events that are too noisy for regular logging */ @SysUISingleton -class MediaViewLogger @Inject constructor( - @MediaViewLog private val buffer: LogBuffer -) { +class MediaViewLogger @Inject constructor(@MediaViewLog private val buffer: LogBuffer) { fun logMediaSize(reason: String, width: Int, height: Int) { buffer.log( - TAG, - LogLevel.DEBUG, - { - str1 = reason - int1 = width - int2 = height - }, - { - "size ($str1): $int1 x $int2" - } + TAG, + LogLevel.DEBUG, + { + str1 = reason + int1 = width + int2 = height + }, + { "size ($str1): $int1 x $int2" } ) } fun logMediaLocation(reason: String, startLocation: Int, endLocation: Int) { buffer.log( - TAG, - LogLevel.DEBUG, - { - str1 = reason - int1 = startLocation - int2 = endLocation - }, - { - "location ($str1): $int1 -> $int2" - } + TAG, + LogLevel.DEBUG, + { + str1 = reason + int1 = startLocation + int2 = endLocation + }, + { "location ($str1): $int1 -> $int2" } ) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MetadataAnimationHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MetadataAnimationHandler.kt index dc49fe42575e..1cdcf5ed2702 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MetadataAnimationHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MetadataAnimationHandler.kt @@ -73,4 +73,4 @@ internal open class MetadataAnimationHandler( exitAnimator.addListener(this) enterAnimator.addListener(this) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt index 41d19e2fb1f1..e9b2cf2b18d1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.media.controls.ui import android.animation.Animator @@ -23,8 +39,7 @@ import kotlin.math.cos private const val TAG = "Squiggly" private const val TWO_PI = (Math.PI * 2f).toFloat() -@VisibleForTesting -internal const val DISABLED_ALPHA = 77 +@VisibleForTesting internal const val DISABLED_ALPHA = 77 class SquigglyProgress : Drawable() { @@ -86,26 +101,29 @@ class SquigglyProgress : Drawable() { lastFrameTime = SystemClock.uptimeMillis() } heightAnimator?.cancel() - heightAnimator = ValueAnimator.ofFloat(heightFraction, if (animate) 1f else 0f).apply { - if (animate) { - startDelay = 60 - duration = 800 - interpolator = Interpolators.EMPHASIZED_DECELERATE - } else { - duration = 550 - interpolator = Interpolators.STANDARD_DECELERATE - } - addUpdateListener { - heightFraction = it.animatedValue as Float - invalidateSelf() - } - addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator?) { - heightAnimator = null + heightAnimator = + ValueAnimator.ofFloat(heightFraction, if (animate) 1f else 0f).apply { + if (animate) { + startDelay = 60 + duration = 800 + interpolator = Interpolators.EMPHASIZED_DECELERATE + } else { + duration = 550 + interpolator = Interpolators.STANDARD_DECELERATE } - }) - start() - } + addUpdateListener { + heightFraction = it.animatedValue as Float + invalidateSelf() + } + addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + heightAnimator = null + } + } + ) + start() + } } override fun draw(canvas: Canvas) { @@ -120,9 +138,15 @@ class SquigglyProgress : Drawable() { val progress = level / 10_000f val totalWidth = bounds.width().toFloat() val totalProgressPx = totalWidth * progress - val waveProgressPx = totalWidth * ( - if (!transitionEnabled || progress > matchedWaveEndpoint) progress else - lerp(minWaveEndpoint, matchedWaveEndpoint, lerpInv(0f, matchedWaveEndpoint, progress))) + val waveProgressPx = + totalWidth * + (if (!transitionEnabled || progress > matchedWaveEndpoint) progress + else + lerp( + minWaveEndpoint, + matchedWaveEndpoint, + lerpInv(0f, matchedWaveEndpoint, progress) + )) // Build Wiggly Path val waveStart = -phaseOffset - waveLength / 2f @@ -132,10 +156,8 @@ class SquigglyProgress : Drawable() { val computeAmplitude: (Float, Float) -> Float = { x, sign -> if (transitionEnabled) { val length = transitionPeriods * waveLength - val coeff = lerpInvSat( - waveProgressPx + length / 2f, - waveProgressPx - length / 2f, - x) + val coeff = + lerpInvSat(waveProgressPx + length / 2f, waveProgressPx - length / 2f, x) sign * heightFraction * lineAmplitude * coeff } else { sign * heightFraction * lineAmplitude @@ -156,10 +178,7 @@ class SquigglyProgress : Drawable() { val nextX = currentX + dist val midX = currentX + dist / 2 val nextAmp = computeAmplitude(nextX, waveSign) - path.cubicTo( - midX, currentAmp, - midX, nextAmp, - nextX, nextAmp) + path.cubicTo(midX, currentAmp, midX, nextAmp, nextX, nextAmp) currentAmp = nextAmp currentX = nextX } @@ -229,7 +248,7 @@ class SquigglyProgress : Drawable() { private fun updateColors(tintColor: Int, alpha: Int) { wavePaint.color = ColorUtils.setAlphaComponent(tintColor, alpha) - linePaint.color = ColorUtils.setAlphaComponent(tintColor, - (DISABLED_ALPHA * (alpha / 255f)).toInt()) + linePaint.color = + ColorUtils.setAlphaComponent(tintColor, (DISABLED_ALPHA * (alpha / 255f)).toInt()) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFeatureFlag.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFeatureFlag.kt index 2ba70c03a6d0..91dac6f1a528 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFeatureFlag.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFeatureFlag.kt @@ -20,9 +20,7 @@ import android.content.Context import com.android.systemui.util.Utils import javax.inject.Inject -/** - * Provides access to the current value of the feature flag. - */ +/** Provides access to the current value of the feature flag. */ class MediaFeatureFlag @Inject constructor(private val context: Context) { val enabled get() = Utils.useQsMediaPlayer(context) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt index 579ba473c08b..8d4931a5d08c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt @@ -34,9 +34,7 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) { return enabled || featureFlags.isEnabled(Flags.MEDIA_SESSION_ACTIONS) } - /** - * Check whether we support displaying information about mute await connections. - */ + /** Check whether we support displaying information about mute await connections. */ fun areMuteAwaitConnectionsEnabled() = featureFlags.isEnabled(Flags.MEDIA_MUTE_AWAIT) /** diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt index 08a566983115..3ad8c21e8a1e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt @@ -30,17 +30,13 @@ import javax.inject.Inject private const val INSTANCE_ID_MAX = 1 shl 20 -/** - * A helper class to log events related to the media controls - */ +/** A helper class to log events related to the media controls */ @SysUISingleton class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger) { private val instanceIdSequence = InstanceIdSequence(INSTANCE_ID_MAX) - /** - * Get a new instance ID for a new media control - */ + /** Get a new instance ID for a new media control */ fun getNewInstanceId(): InstanceId { return instanceIdSequence.newInstanceId() } @@ -51,12 +47,13 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger) instanceId: InstanceId, playbackLocation: Int ) { - val event = when (playbackLocation) { - MediaData.PLAYBACK_LOCAL -> MediaUiEvent.LOCAL_MEDIA_ADDED - MediaData.PLAYBACK_CAST_LOCAL -> MediaUiEvent.CAST_MEDIA_ADDED - MediaData.PLAYBACK_CAST_REMOTE -> MediaUiEvent.REMOTE_MEDIA_ADDED - else -> throw IllegalArgumentException("Unknown playback location") - } + val event = + when (playbackLocation) { + MediaData.PLAYBACK_LOCAL -> MediaUiEvent.LOCAL_MEDIA_ADDED + MediaData.PLAYBACK_CAST_LOCAL -> MediaUiEvent.CAST_MEDIA_ADDED + MediaData.PLAYBACK_CAST_REMOTE -> MediaUiEvent.REMOTE_MEDIA_ADDED + else -> throw IllegalArgumentException("Unknown playback location") + } logger.logWithInstanceId(event, uid, packageName, instanceId) } @@ -66,12 +63,13 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger) instanceId: InstanceId, playbackLocation: Int ) { - val event = when (playbackLocation) { - MediaData.PLAYBACK_LOCAL -> MediaUiEvent.TRANSFER_TO_LOCAL - MediaData.PLAYBACK_CAST_LOCAL -> MediaUiEvent.TRANSFER_TO_CAST - MediaData.PLAYBACK_CAST_REMOTE -> MediaUiEvent.TRANSFER_TO_REMOTE - else -> throw IllegalArgumentException("Unknown playback location") - } + val event = + when (playbackLocation) { + MediaData.PLAYBACK_LOCAL -> MediaUiEvent.TRANSFER_TO_LOCAL + MediaData.PLAYBACK_CAST_LOCAL -> MediaUiEvent.TRANSFER_TO_CAST + MediaData.PLAYBACK_CAST_REMOTE -> MediaUiEvent.TRANSFER_TO_REMOTE + else -> throw IllegalArgumentException("Unknown playback location") + } logger.logWithInstanceId(event, uid, packageName, instanceId) } @@ -110,8 +108,12 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger) } fun logLongPressSettings(uid: Int, packageName: String, instanceId: InstanceId) { - logger.logWithInstanceId(MediaUiEvent.OPEN_SETTINGS_LONG_PRESS, uid, packageName, - instanceId) + logger.logWithInstanceId( + MediaUiEvent.OPEN_SETTINGS_LONG_PRESS, + uid, + packageName, + instanceId + ) } fun logCarouselSettings() { @@ -120,12 +122,13 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger) } fun logTapAction(buttonId: Int, uid: Int, packageName: String, instanceId: InstanceId) { - val event = when (buttonId) { - R.id.actionPlayPause -> MediaUiEvent.TAP_ACTION_PLAY_PAUSE - R.id.actionPrev -> MediaUiEvent.TAP_ACTION_PREV - R.id.actionNext -> MediaUiEvent.TAP_ACTION_NEXT - else -> MediaUiEvent.TAP_ACTION_OTHER - } + val event = + when (buttonId) { + R.id.actionPlayPause -> MediaUiEvent.TAP_ACTION_PLAY_PAUSE + R.id.actionPrev -> MediaUiEvent.TAP_ACTION_PREV + R.id.actionNext -> MediaUiEvent.TAP_ACTION_NEXT + else -> MediaUiEvent.TAP_ACTION_OTHER + } logger.logWithInstanceId(event, uid, packageName, instanceId) } @@ -143,148 +146,130 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger) } fun logCarouselPosition(@MediaLocation location: Int) { - val event = when (location) { - MediaHierarchyManager.LOCATION_QQS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QQS - MediaHierarchyManager.LOCATION_QS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QS - MediaHierarchyManager.LOCATION_LOCKSCREEN -> - MediaUiEvent.MEDIA_CAROUSEL_LOCATION_LOCKSCREEN - MediaHierarchyManager.LOCATION_DREAM_OVERLAY -> - MediaUiEvent.MEDIA_CAROUSEL_LOCATION_DREAM - else -> throw IllegalArgumentException("Unknown media carousel location $location") - } + val event = + when (location) { + MediaHierarchyManager.LOCATION_QQS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QQS + MediaHierarchyManager.LOCATION_QS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QS + MediaHierarchyManager.LOCATION_LOCKSCREEN -> + MediaUiEvent.MEDIA_CAROUSEL_LOCATION_LOCKSCREEN + MediaHierarchyManager.LOCATION_DREAM_OVERLAY -> + MediaUiEvent.MEDIA_CAROUSEL_LOCATION_DREAM + else -> throw IllegalArgumentException("Unknown media carousel location $location") + } logger.log(event) } fun logRecommendationAdded(packageName: String, instanceId: InstanceId) { - logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_ADDED, 0, packageName, - instanceId) + logger.logWithInstanceId( + MediaUiEvent.MEDIA_RECOMMENDATION_ADDED, + 0, + packageName, + instanceId + ) } fun logRecommendationRemoved(packageName: String, instanceId: InstanceId) { - logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_REMOVED, 0, packageName, - instanceId) + logger.logWithInstanceId( + MediaUiEvent.MEDIA_RECOMMENDATION_REMOVED, + 0, + packageName, + instanceId + ) } fun logRecommendationActivated(uid: Int, packageName: String, instanceId: InstanceId) { - logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_ACTIVATED, uid, packageName, - instanceId) + logger.logWithInstanceId( + MediaUiEvent.MEDIA_RECOMMENDATION_ACTIVATED, + uid, + packageName, + instanceId + ) } fun logRecommendationItemTap(packageName: String, instanceId: InstanceId, position: Int) { - logger.logWithInstanceIdAndPosition(MediaUiEvent.MEDIA_RECOMMENDATION_ITEM_TAP, 0, - packageName, instanceId, position) + logger.logWithInstanceIdAndPosition( + MediaUiEvent.MEDIA_RECOMMENDATION_ITEM_TAP, + 0, + packageName, + instanceId, + position + ) } fun logRecommendationCardTap(packageName: String, instanceId: InstanceId) { - logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_CARD_TAP, 0, packageName, - instanceId) + 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) + logger.logWithInstanceId( + MediaUiEvent.MEDIA_OPEN_BROADCAST_DIALOG, + uid, + packageName, + instanceId + ) } } enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum { @UiEvent(doc = "A new media control was added for media playing locally on the device") LOCAL_MEDIA_ADDED(1029), - @UiEvent(doc = "A new media control was added for media cast from the device") CAST_MEDIA_ADDED(1030), - @UiEvent(doc = "A new media control was added for media playing remotely") REMOTE_MEDIA_ADDED(1031), - @UiEvent(doc = "The media for an existing control was transferred to local playback") TRANSFER_TO_LOCAL(1032), - @UiEvent(doc = "The media for an existing control was transferred to a cast device") TRANSFER_TO_CAST(1033), - @UiEvent(doc = "The media for an existing control was transferred to a remote device") TRANSFER_TO_REMOTE(1034), - - @UiEvent(doc = "A new resumable media control was added") - RESUME_MEDIA_ADDED(1013), - + @UiEvent(doc = "A new resumable media control was added") RESUME_MEDIA_ADDED(1013), @UiEvent(doc = "An existing active media control was converted into resumable media") ACTIVE_TO_RESUME(1014), - - @UiEvent(doc = "A media control timed out") - MEDIA_TIMEOUT(1015), - - @UiEvent(doc = "A media control was removed from the carousel") - MEDIA_REMOVED(1016), - - @UiEvent(doc = "User swiped to another control within the media carousel") - CAROUSEL_PAGE(1017), - - @UiEvent(doc = "The user swiped away the media carousel") - DISMISS_SWIPE(1018), - - @UiEvent(doc = "The user long pressed on a media control") - OPEN_LONG_PRESS(1019), - + @UiEvent(doc = "A media control timed out") MEDIA_TIMEOUT(1015), + @UiEvent(doc = "A media control was removed from the carousel") MEDIA_REMOVED(1016), + @UiEvent(doc = "User swiped to another control within the media carousel") CAROUSEL_PAGE(1017), + @UiEvent(doc = "The user swiped away the media carousel") DISMISS_SWIPE(1018), + @UiEvent(doc = "The user long pressed on a media control") OPEN_LONG_PRESS(1019), @UiEvent(doc = "The user dismissed a media control via its long press menu") DISMISS_LONG_PRESS(1020), - @UiEvent(doc = "The user opened media settings from a media control's long press menu") OPEN_SETTINGS_LONG_PRESS(1021), - @UiEvent(doc = "The user opened media settings from the media carousel") OPEN_SETTINGS_CAROUSEL(1022), - @UiEvent(doc = "The play/pause button on a media control was tapped") TAP_ACTION_PLAY_PAUSE(1023), - - @UiEvent(doc = "The previous button on a media control was tapped") - TAP_ACTION_PREV(1024), - - @UiEvent(doc = "The next button on a media control was tapped") - TAP_ACTION_NEXT(1025), - + @UiEvent(doc = "The previous button on a media control was tapped") TAP_ACTION_PREV(1024), + @UiEvent(doc = "The next button on a media control was tapped") TAP_ACTION_NEXT(1025), @UiEvent(doc = "A custom or generic action button on a media control was tapped") TAP_ACTION_OTHER(1026), - - @UiEvent(doc = "The user seeked on a media control using the seekbar") - ACTION_SEEK(1027), - + @UiEvent(doc = "The user seeked on a media control using the seekbar") ACTION_SEEK(1027), @UiEvent(doc = "The user opened the output switcher from a media control") OPEN_OUTPUT_SWITCHER(1028), - - @UiEvent(doc = "The user tapped on a media control view") - MEDIA_TAP_CONTENT_VIEW(1036), - - @UiEvent(doc = "The media carousel moved to QQS") - MEDIA_CAROUSEL_LOCATION_QQS(1037), - - @UiEvent(doc = "THe media carousel moved to QS") - MEDIA_CAROUSEL_LOCATION_QS(1038), - + @UiEvent(doc = "The user tapped on a media control view") MEDIA_TAP_CONTENT_VIEW(1036), + @UiEvent(doc = "The media carousel moved to QQS") MEDIA_CAROUSEL_LOCATION_QQS(1037), + @UiEvent(doc = "THe media carousel moved to QS") MEDIA_CAROUSEL_LOCATION_QS(1038), @UiEvent(doc = "The media carousel moved to the lockscreen") MEDIA_CAROUSEL_LOCATION_LOCKSCREEN(1039), - @UiEvent(doc = "The media carousel moved to the dream state") MEDIA_CAROUSEL_LOCATION_DREAM(1040), - @UiEvent(doc = "A media recommendation card was added to the media carousel") MEDIA_RECOMMENDATION_ADDED(1041), - @UiEvent(doc = "A media recommendation card was removed from the media carousel") MEDIA_RECOMMENDATION_REMOVED(1042), - @UiEvent(doc = "An existing media control was made active as a recommendation") MEDIA_RECOMMENDATION_ACTIVATED(1043), - @UiEvent(doc = "User tapped on an item in a media recommendation card") MEDIA_RECOMMENDATION_ITEM_TAP(1044), - @UiEvent(doc = "User tapped on a media recommendation card") 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 +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/MediaTestUtils.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/MediaTestUtils.kt index 04c4bc8edc43..3437365d9902 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/MediaTestUtils.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/MediaTestUtils.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.media.controls import com.android.internal.logging.InstanceId @@ -5,24 +21,26 @@ import com.android.systemui.media.controls.models.player.MediaData class MediaTestUtils { companion object { - val emptyMediaData = MediaData( - userId = 0, - initialized = true, - app = null, - appIcon = null, - artist = null, - song = null, - artwork = null, - actions = emptyList(), - actionsToShowInCompact = emptyList(), - packageName = "", - token = null, - clickIntent = null, - device = null, - active = true, - resumeAction = null, - isPlaying = false, - instanceId = InstanceId.fakeInstanceId(-1), - appUid = -1) + val emptyMediaData = + MediaData( + userId = 0, + initialized = true, + app = null, + appIcon = null, + artist = null, + song = null, + artwork = null, + actions = emptyList(), + actionsToShowInCompact = emptyList(), + packageName = "", + token = null, + clickIntent = null, + device = null, + active = true, + resumeAction = null, + isPlaying = false, + instanceId = InstanceId.fakeInstanceId(-1), + appUid = -1 + ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/MediaViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/MediaViewHolderTest.kt index e158966e3987..c829d4cbfb71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/MediaViewHolderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/MediaViewHolderTest.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.media.controls.models.player import android.testing.AndroidTestingRunner @@ -21,4 +37,4 @@ class MediaViewHolderTest : SysuiTestCase() { MediaViewHolder.create(inflater, parent) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt index ff09d97b96be..97b18e214550 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt @@ -34,8 +34,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify -import org.mockito.junit.MockitoJUnit import org.mockito.Mockito.`when` as whenever +import org.mockito.junit.MockitoJUnit @SmallTest @RunWith(AndroidTestingRunner::class) @@ -57,10 +57,14 @@ class SeekBarObserverTest : SysuiTestCase() { @Before fun setUp() { - context.orCreateTestableResources - .addOverride(R.dimen.qs_media_enabled_seekbar_height, enabledHeight) - context.orCreateTestableResources - .addOverride(R.dimen.qs_media_disabled_seekbar_height, disabledHeight) + context.orCreateTestableResources.addOverride( + R.dimen.qs_media_enabled_seekbar_height, + enabledHeight + ) + context.orCreateTestableResources.addOverride( + R.dimen.qs_media_disabled_seekbar_height, + disabledHeight + ) seekBarView = SeekBar(context) seekBarView.progressDrawable = mockSquigglyProgress @@ -70,11 +74,12 @@ class SeekBarObserverTest : SysuiTestCase() { whenever(mockHolder.scrubbingElapsedTimeView).thenReturn(scrubbingElapsedTimeView) whenever(mockHolder.scrubbingTotalTimeView).thenReturn(scrubbingTotalTimeView) - observer = object : SeekBarObserver(mockHolder) { - override fun buildResetAnimator(targetTime: Int): Animator { - return mockSeekbarAnimator + observer = + object : SeekBarObserver(mockHolder) { + override fun buildResetAnimator(targetTime: Int): Animator { + return mockSeekbarAnimator + } } - } } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt index aa50db342303..7cd8e749a6e9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt @@ -57,17 +57,18 @@ public class SeekBarViewModelTest : SysuiTestCase() { private lateinit var viewModel: SeekBarViewModel private lateinit var fakeExecutor: FakeExecutor - private val taskExecutor: TaskExecutor = object : TaskExecutor() { - override fun executeOnDiskIO(runnable: Runnable) { - runnable.run() - } - override fun postToMainThread(runnable: Runnable) { - runnable.run() - } - override fun isMainThread(): Boolean { - return true + private val taskExecutor: TaskExecutor = + object : TaskExecutor() { + override fun executeOnDiskIO(runnable: Runnable) { + runnable.run() + } + override fun postToMainThread(runnable: Runnable) { + runnable.run() + } + override fun isMainThread(): Boolean { + return true + } } - } @Mock private lateinit var mockController: MediaController @Mock private lateinit var mockTransport: MediaController.TransportControls @Mock private lateinit var falsingManager: FalsingManager @@ -81,7 +82,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { fun setUp() { fakeExecutor = FakeExecutor(FakeSystemClock()) viewModel = SeekBarViewModel(FakeRepeatableExecutor(fakeExecutor), falsingManager) - viewModel.logSeek = { } + viewModel.logSeek = {} whenever(mockController.sessionToken).thenReturn(token1) whenever(mockBar.context).thenReturn(context) @@ -135,16 +136,18 @@ public class SeekBarViewModelTest : SysuiTestCase() { fun updateDurationWithPlayback() { // GIVEN that the duration is contained within the metadata val duration = 12000L - val metadata = MediaMetadata.Builder().run { - putLong(MediaMetadata.METADATA_KEY_DURATION, duration) - build() - } + val metadata = + MediaMetadata.Builder().run { + putLong(MediaMetadata.METADATA_KEY_DURATION, duration) + build() + } whenever(mockController.getMetadata()).thenReturn(metadata) // AND a valid playback state (ie. media session is not destroyed) - val state = PlaybackState.Builder().run { - setState(PlaybackState.STATE_PLAYING, 200L, 1f) - build() - } + val state = + PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 200L, 1f) + build() + } whenever(mockController.getPlaybackState()).thenReturn(state) // WHEN the controller is updated viewModel.updateController(mockController) @@ -158,10 +161,11 @@ public class SeekBarViewModelTest : SysuiTestCase() { fun updateDurationWithoutPlayback() { // GIVEN that the duration is contained within the metadata val duration = 12000L - val metadata = MediaMetadata.Builder().run { - putLong(MediaMetadata.METADATA_KEY_DURATION, duration) - build() - } + val metadata = + MediaMetadata.Builder().run { + putLong(MediaMetadata.METADATA_KEY_DURATION, duration) + build() + } whenever(mockController.getMetadata()).thenReturn(metadata) // WHEN the controller is updated viewModel.updateController(mockController) @@ -174,16 +178,18 @@ public class SeekBarViewModelTest : SysuiTestCase() { fun updateDurationNegative() { // GIVEN that the duration is negative val duration = -1L - val metadata = MediaMetadata.Builder().run { - putLong(MediaMetadata.METADATA_KEY_DURATION, duration) - build() - } + val metadata = + MediaMetadata.Builder().run { + putLong(MediaMetadata.METADATA_KEY_DURATION, duration) + build() + } whenever(mockController.getMetadata()).thenReturn(metadata) // AND a valid playback state (ie. media session is not destroyed) - val state = PlaybackState.Builder().run { - setState(PlaybackState.STATE_PLAYING, 200L, 1f) - build() - } + val state = + PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 200L, 1f) + build() + } whenever(mockController.getPlaybackState()).thenReturn(state) // WHEN the controller is updated viewModel.updateController(mockController) @@ -195,16 +201,18 @@ public class SeekBarViewModelTest : SysuiTestCase() { fun updateDurationZero() { // GIVEN that the duration is zero val duration = 0L - val metadata = MediaMetadata.Builder().run { - putLong(MediaMetadata.METADATA_KEY_DURATION, duration) - build() - } + val metadata = + MediaMetadata.Builder().run { + putLong(MediaMetadata.METADATA_KEY_DURATION, duration) + build() + } whenever(mockController.getMetadata()).thenReturn(metadata) // AND a valid playback state (ie. media session is not destroyed) - val state = PlaybackState.Builder().run { - setState(PlaybackState.STATE_PLAYING, 200L, 1f) - build() - } + val state = + PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 200L, 1f) + build() + } whenever(mockController.getPlaybackState()).thenReturn(state) // WHEN the controller is updated viewModel.updateController(mockController) @@ -218,10 +226,11 @@ public class SeekBarViewModelTest : SysuiTestCase() { // GIVEN that the metadata is null whenever(mockController.getMetadata()).thenReturn(null) // AND a valid playback state (ie. media session is not destroyed) - val state = PlaybackState.Builder().run { - setState(PlaybackState.STATE_PLAYING, 200L, 1f) - build() - } + val state = + PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 200L, 1f) + build() + } whenever(mockController.getPlaybackState()).thenReturn(state) // WHEN the controller is updated viewModel.updateController(mockController) @@ -233,10 +242,11 @@ public class SeekBarViewModelTest : SysuiTestCase() { fun updateElapsedTime() { // GIVEN that the PlaybackState contains the current position val position = 200L - val state = PlaybackState.Builder().run { - setState(PlaybackState.STATE_PLAYING, position, 1f) - build() - } + val state = + PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, position, 1f) + build() + } whenever(mockController.getPlaybackState()).thenReturn(state) // WHEN the controller is updated viewModel.updateController(mockController) @@ -248,10 +258,11 @@ public class SeekBarViewModelTest : SysuiTestCase() { @Ignore fun updateSeekAvailable() { // GIVEN that seek is included in actions - val state = PlaybackState.Builder().run { - setActions(PlaybackState.ACTION_SEEK_TO) - build() - } + val state = + PlaybackState.Builder().run { + setActions(PlaybackState.ACTION_SEEK_TO) + build() + } whenever(mockController.getPlaybackState()).thenReturn(state) // WHEN the controller is updated viewModel.updateController(mockController) @@ -263,10 +274,11 @@ public class SeekBarViewModelTest : SysuiTestCase() { @Ignore fun updateSeekNotAvailable() { // GIVEN that seek is not included in actions - val state = PlaybackState.Builder().run { - setActions(PlaybackState.ACTION_PLAY) - build() - } + val state = + PlaybackState.Builder().run { + setActions(PlaybackState.ACTION_PLAY) + build() + } whenever(mockController.getPlaybackState()).thenReturn(state) // WHEN the controller is updated viewModel.updateController(mockController) @@ -318,9 +330,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { @Ignore fun onSeekProgressWithSeekStarting() { val pos = 42L - with(viewModel) { - onSeekProgress(pos) - } + with(viewModel) { onSeekProgress(pos) } fakeExecutor.runAllReady() // THEN then elapsed time should not be updated assertThat(viewModel.progress.value!!.elapsedTime).isNull() @@ -329,11 +339,12 @@ public class SeekBarViewModelTest : SysuiTestCase() { @Test fun seekStarted_listenerNotified() { var isScrubbing: Boolean? = null - val listener = object : SeekBarViewModel.ScrubbingChangeListener { - override fun onScrubbingChanged(scrubbing: Boolean) { - isScrubbing = scrubbing + val listener = + object : SeekBarViewModel.ScrubbingChangeListener { + override fun onScrubbingChanged(scrubbing: Boolean) { + isScrubbing = scrubbing + } } - } viewModel.setScrubbingChangeListener(listener) viewModel.onSeekStarting() @@ -345,11 +356,12 @@ public class SeekBarViewModelTest : SysuiTestCase() { @Test fun seekEnded_listenerNotified() { var isScrubbing: Boolean? = null - val listener = object : SeekBarViewModel.ScrubbingChangeListener { - override fun onScrubbingChanged(scrubbing: Boolean) { - isScrubbing = scrubbing + val listener = + object : SeekBarViewModel.ScrubbingChangeListener { + override fun onScrubbingChanged(scrubbing: Boolean) { + isScrubbing = scrubbing + } } - } viewModel.setScrubbingChangeListener(listener) // Start seeking @@ -385,9 +397,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { val bar = SeekBar(context) // WHEN we get an onProgressChanged event without an onStartTrackingTouch event - with(viewModel.seekBarListener) { - onProgressChanged(bar, pos, true) - } + with(viewModel.seekBarListener) { onProgressChanged(bar, pos, true) } fakeExecutor.runAllReady() // THEN we immediately update the transport @@ -412,9 +422,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { viewModel.updateController(mockController) // WHEN user starts dragging the seek bar val pos = 42 - val bar = SeekBar(context).apply { - progress = pos - } + val bar = SeekBar(context).apply { progress = pos } viewModel.seekBarListener.onStartTrackingTouch(bar) fakeExecutor.runAllReady() // THEN transport controls should be used @@ -427,9 +435,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { viewModel.updateController(mockController) // WHEN user ends drag val pos = 42 - val bar = SeekBar(context).apply { - progress = pos - } + val bar = SeekBar(context).apply { progress = pos } viewModel.seekBarListener.onStopTrackingTouch(bar) fakeExecutor.runAllReady() // THEN transport controls should be used @@ -443,9 +449,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { // WHEN user starts dragging the seek bar val pos = 42 val progPos = 84 - val bar = SeekBar(context).apply { - progress = pos - } + val bar = SeekBar(context).apply { progress = pos } with(viewModel.seekBarListener) { onStartTrackingTouch(bar) onProgressChanged(bar, progPos, true) @@ -478,10 +482,11 @@ public class SeekBarViewModelTest : SysuiTestCase() { @Test fun queuePollTaskWhenPlaying() { // GIVEN that the track is playing - val state = PlaybackState.Builder().run { - setState(PlaybackState.STATE_PLAYING, 100L, 1f) - build() - } + val state = + PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 100L, 1f) + build() + } whenever(mockController.getPlaybackState()).thenReturn(state) // WHEN the controller is updated viewModel.updateController(mockController) @@ -492,10 +497,11 @@ public class SeekBarViewModelTest : SysuiTestCase() { @Test fun noQueuePollTaskWhenStopped() { // GIVEN that the playback state is stopped - val state = PlaybackState.Builder().run { - setState(PlaybackState.STATE_STOPPED, 200L, 1f) - build() - } + val state = + PlaybackState.Builder().run { + setState(PlaybackState.STATE_STOPPED, 200L, 1f) + build() + } whenever(mockController.getPlaybackState()).thenReturn(state) // WHEN updated viewModel.updateController(mockController) @@ -512,10 +518,11 @@ public class SeekBarViewModelTest : SysuiTestCase() { runAllReady() } // AND the playback state is playing - val state = PlaybackState.Builder().run { - setState(PlaybackState.STATE_PLAYING, 200L, 1f) - build() - } + val state = + PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 200L, 1f) + build() + } whenever(mockController.getPlaybackState()).thenReturn(state) // WHEN updated viewModel.updateController(mockController) @@ -532,10 +539,11 @@ public class SeekBarViewModelTest : SysuiTestCase() { runAllReady() } // AND the playback state is playing - val state = PlaybackState.Builder().run { - setState(PlaybackState.STATE_PLAYING, 200L, 1f) - build() - } + val state = + PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 200L, 1f) + build() + } whenever(mockController.getPlaybackState()).thenReturn(state) // WHEN updated viewModel.updateController(mockController) @@ -546,10 +554,11 @@ public class SeekBarViewModelTest : SysuiTestCase() { @Test fun pollTaskQueuesAnotherPollTaskWhenPlaying() { // GIVEN that the track is playing - val state = PlaybackState.Builder().run { - setState(PlaybackState.STATE_PLAYING, 100L, 1f) - build() - } + val state = + PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 100L, 1f) + build() + } whenever(mockController.getPlaybackState()).thenReturn(state) viewModel.updateController(mockController) // WHEN the next task runs @@ -566,10 +575,11 @@ public class SeekBarViewModelTest : SysuiTestCase() { // GIVEN listening viewModel.listening = true // AND the playback state is playing - val state = PlaybackState.Builder().run { - setState(PlaybackState.STATE_PLAYING, 200L, 1f) - build() - } + val state = + PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 200L, 1f) + build() + } whenever(mockController.getPlaybackState()).thenReturn(state) viewModel.updateController(mockController) with(fakeExecutor) { @@ -592,10 +602,11 @@ public class SeekBarViewModelTest : SysuiTestCase() { // GIVEN listening viewModel.listening = true // AND the playback state is playing - val state = PlaybackState.Builder().run { - setState(PlaybackState.STATE_PLAYING, 200L, 1f) - build() - } + val state = + PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 200L, 1f) + build() + } whenever(mockController.getPlaybackState()).thenReturn(state) viewModel.updateController(mockController) with(fakeExecutor) { @@ -621,10 +632,11 @@ public class SeekBarViewModelTest : SysuiTestCase() { // GIVEN listening viewModel.listening = true // AND the playback state is playing - val state = PlaybackState.Builder().run { - setState(PlaybackState.STATE_PLAYING, 200L, 1f) - build() - } + val state = + PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 200L, 1f) + build() + } whenever(mockController.getPlaybackState()).thenReturn(state) viewModel.updateController(mockController) with(fakeExecutor) { @@ -654,10 +666,11 @@ public class SeekBarViewModelTest : SysuiTestCase() { runAllReady() } // AND the playback state is playing - val state = PlaybackState.Builder().run { - setState(PlaybackState.STATE_STOPPED, 200L, 1f) - build() - } + val state = + PlaybackState.Builder().run { + setState(PlaybackState.STATE_STOPPED, 200L, 1f) + build() + } whenever(mockController.getPlaybackState()).thenReturn(state) viewModel.updateController(mockController) // WHEN start listening @@ -673,10 +686,11 @@ public class SeekBarViewModelTest : SysuiTestCase() { verify(mockController).registerCallback(captor.capture()) val callback = captor.value // WHEN the callback receives an new state - val state = PlaybackState.Builder().run { - setState(PlaybackState.STATE_PLAYING, 100L, 1f) - build() - } + val state = + PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 100L, 1f) + build() + } callback.onPlaybackStateChanged(state) with(fakeExecutor) { advanceClockToNext() @@ -690,16 +704,18 @@ public class SeekBarViewModelTest : SysuiTestCase() { @Ignore fun clearSeekBar() { // GIVEN that the duration is contained within the metadata - val metadata = MediaMetadata.Builder().run { - putLong(MediaMetadata.METADATA_KEY_DURATION, 12000L) - build() - } + val metadata = + MediaMetadata.Builder().run { + putLong(MediaMetadata.METADATA_KEY_DURATION, 12000L) + build() + } whenever(mockController.getMetadata()).thenReturn(metadata) // AND a valid playback state (ie. media session is not destroyed) - val state = PlaybackState.Builder().run { - setState(PlaybackState.STATE_PLAYING, 200L, 1f) - build() - } + val state = + PlaybackState.Builder().run { + setState(PlaybackState.STATE_PLAYING, 200L, 1f) + build() + } whenever(mockController.getPlaybackState()).thenReturn(state) // AND the controller has been updated viewModel.updateController(mockController) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt index 959bb6c3eb7b..1d6e980bdb86 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.media.controls.models.recommendation import android.app.smartspace.SmartspaceAction @@ -36,11 +52,11 @@ class SmartspaceMediaDataTest : SysuiTestCase() { @Test fun isValid_tooFewRecs_returnsFalse() { - val data = DEFAULT_DATA.copy( - recommendations = listOf( - SmartspaceAction.Builder("id", "title").setIcon(icon).build() + val data = + DEFAULT_DATA.copy( + recommendations = + listOf(SmartspaceAction.Builder("id", "title").setIcon(icon).build()) ) - ) assertThat(data.isValid()).isFalse() } @@ -50,14 +66,10 @@ class SmartspaceMediaDataTest : SysuiTestCase() { val recommendations = mutableListOf<SmartspaceAction>() // Add one fewer recommendation w/ icon than the number required for (i in 1 until NUM_REQUIRED_RECOMMENDATIONS) { - recommendations.add( - SmartspaceAction.Builder("id", "title").setIcon(icon).build() - ) + recommendations.add(SmartspaceAction.Builder("id", "title").setIcon(icon).build()) } for (i in 1 until 3) { - recommendations.add( - SmartspaceAction.Builder("id", "title").setIcon(null).build() - ) + recommendations.add(SmartspaceAction.Builder("id", "title").setIcon(null).build()) } val data = DEFAULT_DATA.copy(recommendations = recommendations) @@ -70,9 +82,7 @@ class SmartspaceMediaDataTest : SysuiTestCase() { val recommendations = mutableListOf<SmartspaceAction>() // Add the number of required recommendations for (i in 0 until NUM_REQUIRED_RECOMMENDATIONS) { - recommendations.add( - SmartspaceAction.Builder("id", "title").setIcon(icon).build() - ) + recommendations.add(SmartspaceAction.Builder("id", "title").setIcon(icon).build()) } val data = DEFAULT_DATA.copy(recommendations = recommendations) @@ -85,9 +95,7 @@ class SmartspaceMediaDataTest : SysuiTestCase() { val recommendations = mutableListOf<SmartspaceAction>() // Add more than enough recommendations for (i in 0 until NUM_REQUIRED_RECOMMENDATIONS + 3) { - recommendations.add( - SmartspaceAction.Builder("id", "title").setIcon(icon).build() - ) + recommendations.add(SmartspaceAction.Builder("id", "title").setIcon(icon).build()) } val data = DEFAULT_DATA.copy(recommendations = recommendations) @@ -96,13 +104,14 @@ class SmartspaceMediaDataTest : SysuiTestCase() { } } -private val DEFAULT_DATA = SmartspaceMediaData( - targetId = "INVALID", - isActive = false, - packageName = "INVALID", - cardAction = null, - recommendations = emptyList(), - dismissIntent = null, - headphoneConnectionTimeMillis = 0, - instanceId = InstanceId.fakeInstanceId(-1) -) +private val DEFAULT_DATA = + SmartspaceMediaData( + targetId = "INVALID", + isActive = false, + packageName = "INVALID", + cardAction = null, + recommendations = emptyList(), + dismissIntent = null, + headphoneConnectionTimeMillis = 0, + instanceId = InstanceId.fakeInstanceId(-1) + ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt index ac5e131ee068..575b1c6b126e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt @@ -63,24 +63,15 @@ private val SMARTSPACE_INSTANCE_ID = InstanceId.fakeInstanceId(456)!! @TestableLooper.RunWithLooper class MediaDataFilterTest : SysuiTestCase() { - @Mock - private lateinit var listener: MediaDataManager.Listener - @Mock - private lateinit var broadcastDispatcher: BroadcastDispatcher - @Mock - private lateinit var broadcastSender: BroadcastSender - @Mock - private lateinit var mediaDataManager: MediaDataManager - @Mock - private lateinit var lockscreenUserManager: NotificationLockscreenUserManager - @Mock - private lateinit var executor: Executor - @Mock - private lateinit var smartspaceData: SmartspaceMediaData - @Mock - private lateinit var smartspaceMediaRecommendationItem: SmartspaceAction - @Mock - private lateinit var logger: MediaUiEventLogger + @Mock private lateinit var listener: MediaDataManager.Listener + @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock private lateinit var broadcastSender: BroadcastSender + @Mock private lateinit var mediaDataManager: MediaDataManager + @Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager + @Mock private lateinit var executor: Executor + @Mock private lateinit var smartspaceData: SmartspaceMediaData + @Mock private lateinit var smartspaceMediaRecommendationItem: SmartspaceAction + @Mock private lateinit var logger: MediaUiEventLogger private lateinit var mediaDataFilter: MediaDataFilter private lateinit var dataMain: MediaData @@ -91,14 +82,16 @@ class MediaDataFilterTest : SysuiTestCase() { fun setup() { MockitoAnnotations.initMocks(this) MediaPlayerData.clear() - mediaDataFilter = MediaDataFilter( - context, - broadcastDispatcher, - broadcastSender, - lockscreenUserManager, - executor, - clock, - logger) + mediaDataFilter = + MediaDataFilter( + context, + broadcastDispatcher, + broadcastSender, + lockscreenUserManager, + executor, + clock, + logger + ) mediaDataFilter.mediaDataManager = mediaDataManager mediaDataFilter.addListener(listener) @@ -106,11 +99,13 @@ class MediaDataFilterTest : SysuiTestCase() { setUser(USER_MAIN) // Set up test media data - dataMain = MediaTestUtils.emptyMediaData.copy( + dataMain = + MediaTestUtils.emptyMediaData.copy( userId = USER_MAIN, packageName = PACKAGE, instanceId = INSTANCE_ID, - appUid = APP_UID) + appUid = APP_UID + ) dataGuest = dataMain.copy(userId = USER_GUEST) `when`(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY) @@ -118,8 +113,8 @@ class MediaDataFilterTest : SysuiTestCase() { `when`(smartspaceData.isValid()).thenReturn(true) `when`(smartspaceData.packageName).thenReturn(SMARTSPACE_PACKAGE) `when`(smartspaceData.recommendations).thenReturn(listOf(smartspaceMediaRecommendationItem)) - `when`(smartspaceData.headphoneConnectionTimeMillis).thenReturn( - clock.currentTimeMillis() - 100) + `when`(smartspaceData.headphoneConnectionTimeMillis) + .thenReturn(clock.currentTimeMillis() - 100) `when`(smartspaceData.instanceId).thenReturn(SMARTSPACE_INSTANCE_ID) } @@ -135,8 +130,8 @@ class MediaDataFilterTest : SysuiTestCase() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) // THEN we should tell the listener - verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true), - eq(0), eq(false)) + verify(listener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true), eq(0), eq(false)) } @Test @@ -145,8 +140,8 @@ class MediaDataFilterTest : SysuiTestCase() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest) // THEN we should NOT tell the listener - verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean(), - anyInt(), anyBoolean()) + verify(listener, never()) + .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean()) } @Test @@ -192,12 +187,12 @@ class MediaDataFilterTest : SysuiTestCase() { setUser(USER_GUEST) // THEN we should add back the guest user media - verify(listener).onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest), eq(true), - eq(0), eq(false)) + verify(listener) + .onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest), eq(true), eq(0), eq(false)) // but not the main user's - verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), eq(dataMain), anyBoolean(), - anyInt(), anyBoolean()) + verify(listener, never()) + .onMediaDataLoaded(eq(KEY), any(), eq(dataMain), anyBoolean(), anyInt(), anyBoolean()) } @Test @@ -345,7 +340,7 @@ class MediaDataFilterTest : SysuiTestCase() { mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) verify(listener) - .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true)) + .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true)) assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue() assertThat(mediaDataFilter.hasActiveMedia()).isFalse() verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID) @@ -358,8 +353,8 @@ class MediaDataFilterTest : SysuiTestCase() { mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean(), - anyInt(), anyBoolean()) + verify(listener, never()) + .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean()) verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse() assertThat(mediaDataFilter.hasActiveMedia()).isFalse() @@ -375,7 +370,7 @@ class MediaDataFilterTest : SysuiTestCase() { mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) verify(listener) - .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true)) + .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true)) assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue() assertThat(mediaDataFilter.hasActiveMedia()).isFalse() verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID) @@ -405,15 +400,15 @@ class MediaDataFilterTest : SysuiTestCase() { // WHEN we have media that was recently played, but not currently active val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) - verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), - eq(0), eq(false)) + verify(listener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) // AND we get a smartspace signal mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN we should tell listeners to treat the media as not active instead - verify(listener, never()).onMediaDataLoaded(eq(KEY), eq(KEY), any(), anyBoolean(), - anyInt(), anyBoolean()) + verify(listener, never()) + .onMediaDataLoaded(eq(KEY), eq(KEY), any(), anyBoolean(), anyInt(), anyBoolean()) verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse() assertThat(mediaDataFilter.hasActiveMedia()).isFalse() @@ -428,16 +423,23 @@ class MediaDataFilterTest : SysuiTestCase() { // WHEN we have media that was recently played, but not currently active val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) - verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), - eq(0), eq(false)) + verify(listener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) // AND we get a smartspace signal mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN we should tell listeners to treat the media as active instead val dataCurrentAndActive = dataCurrent.copy(active = true) - verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true), - eq(100), eq(true)) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(KEY), + eq(dataCurrentAndActive), + eq(true), + eq(100), + eq(true) + ) assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue() // Smartspace update shouldn't be propagated for the empty rec list. verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) @@ -450,20 +452,27 @@ class MediaDataFilterTest : SysuiTestCase() { // WHEN we have media that was recently played, but not currently active val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) - verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), - eq(0), eq(false)) + verify(listener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) // AND we get a smartspace signal mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN we should tell listeners to treat the media as active instead val dataCurrentAndActive = dataCurrent.copy(active = true) - verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true), - eq(100), eq(true)) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(KEY), + eq(dataCurrentAndActive), + eq(true), + eq(100), + eq(true) + ) assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue() // Smartspace update should also be propagated but not prioritized. verify(listener) - .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) + .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID) verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID)) } @@ -482,14 +491,21 @@ class MediaDataFilterTest : SysuiTestCase() { fun testOnSmartspaceMediaDataRemoved_usedMediaAndSmartspace_clearsBoth() { val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) - verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), - eq(0), eq(false)) + verify(listener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) val dataCurrentAndActive = dataCurrent.copy(active = true) - verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true), - eq(100), eq(true)) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(KEY), + eq(dataCurrentAndActive), + eq(true), + eq(100), + eq(true) + ) mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt index 1f31aaece8a8..11eb26b1da02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.media.controls.pipeline import android.app.Notification @@ -118,58 +134,68 @@ class MediaDataManagerTest : SysuiTestCase() { private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20) - private val originalSmartspaceSetting = Settings.Secure.getInt(context.contentResolver, - Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 1) + private val originalSmartspaceSetting = + Settings.Secure.getInt( + context.contentResolver, + Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, + 1 + ) @Before fun setup() { foregroundExecutor = FakeExecutor(clock) backgroundExecutor = FakeExecutor(clock) smartspaceMediaDataProvider = SmartspaceMediaDataProvider() - Settings.Secure.putInt(context.contentResolver, - Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 1) - mediaDataManager = MediaDataManager( - context = context, - backgroundExecutor = backgroundExecutor, - foregroundExecutor = foregroundExecutor, - mediaControllerFactory = mediaControllerFactory, - broadcastDispatcher = broadcastDispatcher, - dumpManager = dumpManager, - mediaTimeoutListener = mediaTimeoutListener, - mediaResumeListener = mediaResumeListener, - mediaSessionBasedFilter = mediaSessionBasedFilter, - mediaDeviceManager = mediaDeviceManager, - mediaDataCombineLatest = mediaDataCombineLatest, - mediaDataFilter = mediaDataFilter, - activityStarter = activityStarter, - smartspaceMediaDataProvider = smartspaceMediaDataProvider, - useMediaResumption = true, - useQsMediaPlayer = true, - systemClock = clock, - tunerService = tunerService, - mediaFlags = mediaFlags, - logger = logger + Settings.Secure.putInt( + context.contentResolver, + Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, + 1 ) - verify(tunerService).addTunable(capture(tunableCaptor), - eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION)) + mediaDataManager = + MediaDataManager( + context = context, + backgroundExecutor = backgroundExecutor, + foregroundExecutor = foregroundExecutor, + mediaControllerFactory = mediaControllerFactory, + broadcastDispatcher = broadcastDispatcher, + dumpManager = dumpManager, + mediaTimeoutListener = mediaTimeoutListener, + mediaResumeListener = mediaResumeListener, + mediaSessionBasedFilter = mediaSessionBasedFilter, + mediaDeviceManager = mediaDeviceManager, + mediaDataCombineLatest = mediaDataCombineLatest, + mediaDataFilter = mediaDataFilter, + activityStarter = activityStarter, + smartspaceMediaDataProvider = smartspaceMediaDataProvider, + useMediaResumption = true, + useQsMediaPlayer = true, + systemClock = clock, + tunerService = tunerService, + mediaFlags = mediaFlags, + logger = logger + ) + verify(tunerService) + .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION)) session = MediaSession(context, "MediaDataManagerTestSession") - mediaNotification = SbnBuilder().run { - setPkg(PACKAGE_NAME) - modifyNotification(context).also { - it.setSmallIcon(android.R.drawable.ic_media_pause) - it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) }) + mediaNotification = + SbnBuilder().run { + setPkg(PACKAGE_NAME) + modifyNotification(context).also { + it.setSmallIcon(android.R.drawable.ic_media_pause) + it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) }) + } + build() + } + metadataBuilder = + MediaMetadata.Builder().apply { + putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST) + putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE) } - build() - } - metadataBuilder = MediaMetadata.Builder().apply { - putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST) - putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE) - } whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller) whenever(controller.transportControls).thenReturn(transportControls) whenever(controller.playbackInfo).thenReturn(playbackInfo) - whenever(playbackInfo.playbackType).thenReturn( - MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) + whenever(playbackInfo.playbackType) + .thenReturn(MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) // This is an ugly hack for now. The mediaSessionBasedFilter is one of the internal // listeners in the internal processing pipeline. It receives events, but ince it is a @@ -177,18 +203,18 @@ class MediaDataManagerTest : SysuiTestCase() { // treat mediaSessionBasedFilter as a listener for testing. listener = mediaSessionBasedFilter - val recommendationExtras = Bundle().apply { - putString("package_name", PACKAGE_NAME) - putParcelable("dismiss_intent", DISMISS_INTENT) - } + val recommendationExtras = + Bundle().apply { + putString("package_name", PACKAGE_NAME) + putParcelable("dismiss_intent", DISMISS_INTENT) + } val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play) whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras) whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction) whenever(mediaRecommendationItem.extras).thenReturn(recommendationExtras) whenever(mediaRecommendationItem.icon).thenReturn(icon) - validRecommendationList = listOf( - mediaRecommendationItem, mediaRecommendationItem, mediaRecommendationItem - ) + validRecommendationList = + listOf(mediaRecommendationItem, mediaRecommendationItem, mediaRecommendationItem) whenever(mediaSmartspaceTarget.smartspaceTargetId).thenReturn(KEY_MEDIA_SMARTSPACE) whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA) whenever(mediaSmartspaceTarget.iconGrid).thenReturn(validRecommendationList) @@ -201,8 +227,11 @@ class MediaDataManagerTest : SysuiTestCase() { fun tearDown() { session.release() mediaDataManager.destroy() - Settings.Secure.putInt(context.contentResolver, - Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, originalSmartspaceSetting) + Settings.Secure.putInt( + context.contentResolver, + Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, + originalSmartspaceSetting + ) } @Test @@ -219,21 +248,36 @@ class MediaDataManagerTest : SysuiTestCase() { @Test fun testSetTimedOut_resume_dismissesMedia() { // WHEN resume controls are present, and time out - val desc = MediaDescription.Builder().run { - setTitle(SESSION_TITLE) - build() - } - mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken, - APP_NAME, pendingIntent, PACKAGE_NAME) + val desc = + MediaDescription.Builder().run { + setTitle(SESSION_TITLE) + build() + } + mediaDataManager.addResumptionControls( + USER_ID, + desc, + Runnable {}, + session.sessionToken, + APP_NAME, + pendingIntent, + PACKAGE_NAME + ) backgroundExecutor.runAllReady() foregroundExecutor.runAllReady() - verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor), - eq(true), eq(0), eq(false)) + verify(listener) + .onMediaDataLoaded( + eq(PACKAGE_NAME), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) mediaDataManager.setTimedOut(PACKAGE_NAME, timedOut = true) - verify(logger).logMediaTimeout(anyInt(), eq(PACKAGE_NAME), - eq(mediaDataCaptor.value.instanceId)) + verify(logger) + .logMediaTimeout(anyInt(), eq(PACKAGE_NAME), eq(mediaDataCaptor.value.instanceId)) // THEN it is removed and listeners are informed foregroundExecutor.advanceClockToLast() @@ -250,8 +294,13 @@ class MediaDataManagerTest : SysuiTestCase() { @Test fun testOnMetaDataLoaded_callsListener() { addNotificationAndLoad() - verify(logger).logActiveMediaAdded(anyInt(), eq(PACKAGE_NAME), - eq(mediaDataCaptor.value.instanceId), eq(MediaData.PLAYBACK_LOCAL)) + verify(logger) + .logActiveMediaAdded( + anyInt(), + eq(PACKAGE_NAME), + eq(mediaDataCaptor.value.instanceId), + eq(MediaData.PLAYBACK_LOCAL) + ) } @Test @@ -262,56 +311,85 @@ class MediaDataManagerTest : SysuiTestCase() { mediaDataManager.onNotificationAdded(KEY, mediaNotification) assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), - eq(0), eq(false)) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) assertThat(mediaDataCaptor.value!!.active).isTrue() } @Test fun testOnNotificationAdded_isRcn_markedRemote() { - val rcn = SbnBuilder().run { - setPkg(SYSTEM_PACKAGE_NAME) - modifyNotification(context).also { - it.setSmallIcon(android.R.drawable.ic_media_pause) - it.setStyle(MediaStyle().apply { - setMediaSession(session.sessionToken) - setRemotePlaybackInfo("Remote device", 0, null) - }) + val rcn = + SbnBuilder().run { + setPkg(SYSTEM_PACKAGE_NAME) + modifyNotification(context).also { + it.setSmallIcon(android.R.drawable.ic_media_pause) + it.setStyle( + MediaStyle().apply { + setMediaSession(session.sessionToken) + setRemotePlaybackInfo("Remote device", 0, null) + } + ) + } + build() } - build() - } mediaDataManager.onNotificationAdded(KEY, rcn) assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), - eq(0), eq(false)) - assertThat(mediaDataCaptor.value!!.playbackLocation).isEqualTo( - MediaData.PLAYBACK_CAST_REMOTE) - verify(logger).logActiveMediaAdded(anyInt(), eq(SYSTEM_PACKAGE_NAME), - eq(mediaDataCaptor.value.instanceId), eq(MediaData.PLAYBACK_CAST_REMOTE)) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + assertThat(mediaDataCaptor.value!!.playbackLocation) + .isEqualTo(MediaData.PLAYBACK_CAST_REMOTE) + verify(logger) + .logActiveMediaAdded( + anyInt(), + eq(SYSTEM_PACKAGE_NAME), + eq(mediaDataCaptor.value.instanceId), + eq(MediaData.PLAYBACK_CAST_REMOTE) + ) } @Test fun testOnNotificationAdded_hasSubstituteName_isUsed() { val subName = "Substitute Name" - val notif = SbnBuilder().run { - modifyNotification(context).also { - it.extras = Bundle().apply { - putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, subName) + val notif = + SbnBuilder().run { + modifyNotification(context).also { + it.extras = + Bundle().apply { + putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, subName) + } + it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) }) } - it.setStyle(MediaStyle().apply { - setMediaSession(session.sessionToken) - }) + build() } - build() - } mediaDataManager.onNotificationAdded(KEY, notif) assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), - eq(0), eq(false)) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) assertThat(mediaDataCaptor.value!!.app).isEqualTo(subName) } @@ -321,17 +399,18 @@ class MediaDataManagerTest : SysuiTestCase() { val bundle = Bundle() // wrong data type bundle.putParcelable(Notification.EXTRA_MEDIA_SESSION, Bundle()) - val rcn = SbnBuilder().run { - setPkg(SYSTEM_PACKAGE_NAME) - modifyNotification(context).also { - it.setSmallIcon(android.R.drawable.ic_media_pause) - it.addExtras(bundle) - it.setStyle(MediaStyle().apply { - setRemotePlaybackInfo("Remote device", 0, null) - }) + val rcn = + SbnBuilder().run { + setPkg(SYSTEM_PACKAGE_NAME) + modifyNotification(context).also { + it.setSmallIcon(android.R.drawable.ic_media_pause) + it.addExtras(bundle) + it.setStyle( + MediaStyle().apply { setRemotePlaybackInfo("Remote device", 0, null) } + ) + } + build() } - build() - } mediaDataManager.loadMediaDataInBg(KEY, rcn, null) // no crash even though the data structure is incorrect @@ -342,18 +421,21 @@ class MediaDataManagerTest : SysuiTestCase() { val bundle = Bundle() // wrong data type bundle.putParcelable(Notification.EXTRA_MEDIA_REMOTE_INTENT, Bundle()) - val rcn = SbnBuilder().run { - setPkg(SYSTEM_PACKAGE_NAME) - modifyNotification(context).also { - it.setSmallIcon(android.R.drawable.ic_media_pause) - it.addExtras(bundle) - it.setStyle(MediaStyle().apply { - setMediaSession(session.sessionToken) - setRemotePlaybackInfo("Remote device", 0, null) - }) + val rcn = + SbnBuilder().run { + setPkg(SYSTEM_PACKAGE_NAME) + modifyNotification(context).also { + it.setSmallIcon(android.R.drawable.ic_media_pause) + it.addExtras(bundle) + it.setStyle( + MediaStyle().apply { + setMediaSession(session.sessionToken) + setRemotePlaybackInfo("Remote device", 0, null) + } + ) + } + build() } - build() - } mediaDataManager.loadMediaDataInBg(KEY, rcn, null) // no crash even though the data structure is incorrect @@ -380,8 +462,14 @@ class MediaDataManagerTest : SysuiTestCase() { mediaDataManager.onNotificationRemoved(KEY) // THEN the media data indicates that it is for resumption verify(listener) - .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true), - eq(0), eq(false)) + .onMediaDataLoaded( + eq(PACKAGE_NAME), + eq(KEY), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.isPlaying).isFalse() verify(logger).logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) @@ -396,8 +484,14 @@ class MediaDataManagerTest : SysuiTestCase() { assertThat(backgroundExecutor.runAllReady()).isEqualTo(2) assertThat(foregroundExecutor.runAllReady()).isEqualTo(2) verify(listener) - .onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), - eq(0), eq(false)) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) val data = mediaDataCaptor.value assertThat(data.resumption).isFalse() val resumableData = data.copy(resumeAction = Runnable {}) @@ -408,8 +502,14 @@ class MediaDataManagerTest : SysuiTestCase() { mediaDataManager.onNotificationRemoved(KEY) // THEN the data is for resumption and the key is migrated to the package name verify(listener) - .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true), - eq(0), eq(false)) + .onMediaDataLoaded( + eq(PACKAGE_NAME), + eq(KEY), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) assertThat(mediaDataCaptor.value.resumption).isTrue() verify(listener, never()).onMediaDataRemoved(eq(KEY)) // WHEN the second is removed @@ -417,8 +517,13 @@ class MediaDataManagerTest : SysuiTestCase() { // THEN the data is for resumption and the second key is removed verify(listener) .onMediaDataLoaded( - eq(PACKAGE_NAME), eq(PACKAGE_NAME), capture(mediaDataCaptor), eq(true), - eq(0), eq(false)) + eq(PACKAGE_NAME), + eq(PACKAGE_NAME), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) assertThat(mediaDataCaptor.value.resumption).isTrue() verify(listener).onMediaDataRemoved(eq(KEY_2)) } @@ -427,15 +532,20 @@ class MediaDataManagerTest : SysuiTestCase() { fun testOnNotificationRemoved_withResumption_butNotLocal() { // GIVEN that the manager has a notification with a resume action, but is not local whenever(controller.metadata).thenReturn(metadataBuilder.build()) - whenever(playbackInfo.playbackType).thenReturn( - MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE) + whenever(playbackInfo.playbackType) + .thenReturn(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE) addNotificationAndLoad() val data = mediaDataCaptor.value - val dataRemoteWithResume = data.copy(resumeAction = Runnable {}, - playbackLocation = MediaData.PLAYBACK_CAST_LOCAL) + val dataRemoteWithResume = + data.copy(resumeAction = Runnable {}, playbackLocation = MediaData.PLAYBACK_CAST_LOCAL) mediaDataManager.onMediaDataLoaded(KEY, null, dataRemoteWithResume) - verify(logger).logActiveMediaAdded(anyInt(), eq(PACKAGE_NAME), - eq(mediaDataCaptor.value.instanceId), eq(MediaData.PLAYBACK_CAST_LOCAL)) + verify(logger) + .logActiveMediaAdded( + anyInt(), + eq(PACKAGE_NAME), + eq(mediaDataCaptor.value.instanceId), + eq(MediaData.PLAYBACK_CAST_LOCAL) + ) // WHEN the notification is removed mediaDataManager.onNotificationRemoved(KEY) @@ -447,19 +557,33 @@ class MediaDataManagerTest : SysuiTestCase() { @Test fun testAddResumptionControls() { // WHEN resumption controls are added - val desc = MediaDescription.Builder().run { - setTitle(SESSION_TITLE) - build() - } + val desc = + MediaDescription.Builder().run { + setTitle(SESSION_TITLE) + build() + } val currentTime = clock.elapsedRealtime() - mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken, - APP_NAME, pendingIntent, PACKAGE_NAME) + mediaDataManager.addResumptionControls( + USER_ID, + desc, + Runnable {}, + session.sessionToken, + APP_NAME, + pendingIntent, + PACKAGE_NAME + ) assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) // THEN the media data indicates that it is for resumption verify(listener) - .onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor), eq(true), - eq(0), eq(false)) + .onMediaDataLoaded( + eq(PACKAGE_NAME), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) val data = mediaDataCaptor.value assertThat(data.resumption).isTrue() assertThat(data.song).isEqualTo(SESSION_TITLE) @@ -473,16 +597,31 @@ class MediaDataManagerTest : SysuiTestCase() { @Test fun testResumptionDisabled_dismissesResumeControls() { // WHEN there are resume controls and resumption is switched off - val desc = MediaDescription.Builder().run { - setTitle(SESSION_TITLE) - build() - } - mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken, - APP_NAME, pendingIntent, PACKAGE_NAME) + val desc = + MediaDescription.Builder().run { + setTitle(SESSION_TITLE) + build() + } + mediaDataManager.addResumptionControls( + USER_ID, + desc, + Runnable {}, + session.sessionToken, + APP_NAME, + pendingIntent, + PACKAGE_NAME + ) assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - verify(listener).onMediaDataLoaded(eq(PACKAGE_NAME), eq(null), capture(mediaDataCaptor), - eq(true), eq(0), eq(false)) + verify(listener) + .onMediaDataLoaded( + eq(PACKAGE_NAME), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) val data = mediaDataCaptor.value mediaDataManager.setMediaResumptionEnabled(false) @@ -515,23 +654,30 @@ class MediaDataManagerTest : SysuiTestCase() { fun testBadArtwork_doesNotUse() { // WHEN notification has a too-small artwork val artwork = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) - val notif = SbnBuilder().run { - setPkg(PACKAGE_NAME) - modifyNotification(context).also { - it.setSmallIcon(android.R.drawable.ic_media_pause) - it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) }) - it.setLargeIcon(artwork) + val notif = + SbnBuilder().run { + setPkg(PACKAGE_NAME) + modifyNotification(context).also { + it.setSmallIcon(android.R.drawable.ic_media_pause) + it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) }) + it.setLargeIcon(artwork) + } + build() } - build() - } mediaDataManager.onNotificationAdded(KEY, notif) // THEN it still loads assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) verify(listener) - .onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), - eq(0), eq(false)) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) } @Test @@ -540,18 +686,23 @@ class MediaDataManagerTest : SysuiTestCase() { verify(logger).getNewInstanceId() val instanceId = instanceIdSequence.lastInstanceId - verify(listener).onSmartspaceMediaDataLoaded( - eq(KEY_MEDIA_SMARTSPACE), - eq(SmartspaceMediaData( - targetId = KEY_MEDIA_SMARTSPACE, - isActive = true, - packageName = PACKAGE_NAME, - cardAction = mediaSmartspaceBaseAction, - recommendations = validRecommendationList, - dismissIntent = DISMISS_INTENT, - headphoneConnectionTimeMillis = 1234L, - instanceId = InstanceId.fakeInstanceId(instanceId))), - eq(false)) + verify(listener) + .onSmartspaceMediaDataLoaded( + eq(KEY_MEDIA_SMARTSPACE), + eq( + SmartspaceMediaData( + targetId = KEY_MEDIA_SMARTSPACE, + isActive = true, + packageName = PACKAGE_NAME, + cardAction = mediaSmartspaceBaseAction, + recommendations = validRecommendationList, + dismissIntent = DISMISS_INTENT, + headphoneConnectionTimeMillis = 1234L, + instanceId = InstanceId.fakeInstanceId(instanceId) + ) + ), + eq(false) + ) } @Test @@ -561,23 +712,29 @@ class MediaDataManagerTest : SysuiTestCase() { verify(logger).getNewInstanceId() val instanceId = instanceIdSequence.lastInstanceId - verify(listener).onSmartspaceMediaDataLoaded( - eq(KEY_MEDIA_SMARTSPACE), - eq(EMPTY_SMARTSPACE_MEDIA_DATA.copy( - targetId = KEY_MEDIA_SMARTSPACE, - isActive = true, - dismissIntent = DISMISS_INTENT, - headphoneConnectionTimeMillis = 1234L, - instanceId = InstanceId.fakeInstanceId(instanceId))), - eq(false)) + verify(listener) + .onSmartspaceMediaDataLoaded( + eq(KEY_MEDIA_SMARTSPACE), + eq( + EMPTY_SMARTSPACE_MEDIA_DATA.copy( + targetId = KEY_MEDIA_SMARTSPACE, + isActive = true, + dismissIntent = DISMISS_INTENT, + headphoneConnectionTimeMillis = 1234L, + instanceId = InstanceId.fakeInstanceId(instanceId) + ) + ), + eq(false) + ) } @Test fun testOnSmartspaceMediaDataLoaded_hasNullIntent_callsListener() { - val recommendationExtras = Bundle().apply { - putString("package_name", PACKAGE_NAME) - putParcelable("dismiss_intent", null) - } + val recommendationExtras = + Bundle().apply { + putString("package_name", PACKAGE_NAME) + putParcelable("dismiss_intent", null) + } whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras) whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction) whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf()) @@ -586,15 +743,20 @@ class MediaDataManagerTest : SysuiTestCase() { verify(logger).getNewInstanceId() val instanceId = instanceIdSequence.lastInstanceId - verify(listener).onSmartspaceMediaDataLoaded( - eq(KEY_MEDIA_SMARTSPACE), - eq(EMPTY_SMARTSPACE_MEDIA_DATA.copy( - targetId = KEY_MEDIA_SMARTSPACE, - isActive = true, - dismissIntent = null, - headphoneConnectionTimeMillis = 1234L, - instanceId = InstanceId.fakeInstanceId(instanceId))), - eq(false)) + verify(listener) + .onSmartspaceMediaDataLoaded( + eq(KEY_MEDIA_SMARTSPACE), + eq( + EMPTY_SMARTSPACE_MEDIA_DATA.copy( + targetId = KEY_MEDIA_SMARTSPACE, + isActive = true, + dismissIntent = null, + headphoneConnectionTimeMillis = 1234L, + instanceId = InstanceId.fakeInstanceId(instanceId) + ) + ), + eq(false) + ) } @Test @@ -602,7 +764,7 @@ class MediaDataManagerTest : SysuiTestCase() { smartspaceMediaDataProvider.onTargetsAvailable(listOf()) verify(logger, never()).getNewInstanceId() verify(listener, never()) - .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean()) + .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean()) } @Ignore("b/233283726") @@ -622,15 +784,18 @@ class MediaDataManagerTest : SysuiTestCase() { @Test fun testOnSmartspaceMediaDataLoaded_settingDisabled_doesNothing() { // WHEN media recommendation setting is off - Settings.Secure.putInt(context.contentResolver, - Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0) + Settings.Secure.putInt( + context.contentResolver, + Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, + 0 + ) tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0") smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget)) // THEN smartspace signal is ignored verify(listener, never()) - .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean()) + .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean()) } @Ignore("b/229838140") @@ -638,12 +803,15 @@ class MediaDataManagerTest : SysuiTestCase() { fun testMediaRecommendationDisabled_removesSmartspaceData() { // GIVEN a media recommendation card is present smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget)) - verify(listener).onSmartspaceMediaDataLoaded(eq(KEY_MEDIA_SMARTSPACE), anyObject(), - anyBoolean()) + verify(listener) + .onSmartspaceMediaDataLoaded(eq(KEY_MEDIA_SMARTSPACE), anyObject(), anyBoolean()) // WHEN the media recommendation setting is turned off - Settings.Secure.putInt(context.contentResolver, - Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, 0) + Settings.Secure.putInt( + context.contentResolver, + Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, + 0 + ) tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0") // THEN listeners are notified @@ -672,8 +840,15 @@ class MediaDataManagerTest : SysuiTestCase() { mediaDataManager.setTimedOut(KEY, true, true) // THEN the last active time is not changed - verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), capture(mediaDataCaptor), eq(true), - eq(0), eq(false)) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(KEY), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime) } @@ -694,8 +869,14 @@ class MediaDataManagerTest : SysuiTestCase() { // THEN the last active time is not changed verify(listener) - .onMediaDataLoaded(eq(PACKAGE_NAME), eq(KEY), capture(mediaDataCaptor), eq(true), - eq(0), eq(false)) + .onMediaDataLoaded( + eq(PACKAGE_NAME), + eq(KEY), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) assertThat(mediaDataCaptor.value.resumption).isTrue() assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime) @@ -707,17 +888,20 @@ class MediaDataManagerTest : SysuiTestCase() { @Test fun testTooManyCompactActions_isTruncated() { // GIVEN a notification where too many compact actions were specified - val notif = SbnBuilder().run { - setPkg(PACKAGE_NAME) - modifyNotification(context).also { - it.setSmallIcon(android.R.drawable.ic_media_pause) - it.setStyle(MediaStyle().apply { - setMediaSession(session.sessionToken) - setShowActionsInCompactView(0, 1, 2, 3, 4) - }) + val notif = + SbnBuilder().run { + setPkg(PACKAGE_NAME) + modifyNotification(context).also { + it.setSmallIcon(android.R.drawable.ic_media_pause) + it.setStyle( + MediaStyle().apply { + setMediaSession(session.sessionToken) + setShowActionsInCompactView(0, 1, 2, 3, 4) + } + ) + } + build() } - build() - } // WHEN the notification is loaded mediaDataManager.onNotificationAdded(KEY, notif) @@ -725,29 +909,35 @@ class MediaDataManagerTest : SysuiTestCase() { assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) // THEN only the first MAX_COMPACT_ACTIONS are actually set - verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), - eq(0), eq(false)) - assertThat(mediaDataCaptor.value.actionsToShowInCompact.size).isEqualTo( - MediaDataManager.MAX_COMPACT_ACTIONS) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + assertThat(mediaDataCaptor.value.actionsToShowInCompact.size) + .isEqualTo(MediaDataManager.MAX_COMPACT_ACTIONS) } @Test fun testTooManyNotificationActions_isTruncated() { // GIVEN a notification where too many notification actions are added val action = Notification.Action(R.drawable.ic_android, "action", null) - val notif = SbnBuilder().run { - setPkg(PACKAGE_NAME) - modifyNotification(context).also { - it.setSmallIcon(android.R.drawable.ic_media_pause) - it.setStyle(MediaStyle().apply { - setMediaSession(session.sessionToken) - }) - for (i in 0..MediaDataManager.MAX_NOTIFICATION_ACTIONS) { - it.addAction(action) + val notif = + SbnBuilder().run { + setPkg(PACKAGE_NAME) + modifyNotification(context).also { + it.setSmallIcon(android.R.drawable.ic_media_pause) + it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) }) + for (i in 0..MediaDataManager.MAX_NOTIFICATION_ACTIONS) { + it.addAction(action) + } } + build() } - build() - } // WHEN the notification is loaded mediaDataManager.onNotificationAdded(KEY, notif) @@ -755,10 +945,17 @@ class MediaDataManagerTest : SysuiTestCase() { assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) // THEN only the first MAX_NOTIFICATION_ACTIONS are actually included - verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), - eq(0), eq(false)) - assertThat(mediaDataCaptor.value.actions.size).isEqualTo( - MediaDataManager.MAX_NOTIFICATION_ACTIONS) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + assertThat(mediaDataCaptor.value.actions.size) + .isEqualTo(MediaDataManager.MAX_NOTIFICATION_ACTIONS) } @Test @@ -767,21 +964,29 @@ class MediaDataManagerTest : SysuiTestCase() { whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true) whenever(controller.playbackState).thenReturn(null) - val notifWithAction = SbnBuilder().run { - setPkg(PACKAGE_NAME) - modifyNotification(context).also { - it.setSmallIcon(android.R.drawable.ic_media_pause) - it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) }) - it.addAction(android.R.drawable.ic_media_play, desc, null) + val notifWithAction = + SbnBuilder().run { + setPkg(PACKAGE_NAME) + modifyNotification(context).also { + it.setSmallIcon(android.R.drawable.ic_media_pause) + it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) }) + it.addAction(android.R.drawable.ic_media_play, desc, null) + } + build() } - build() - } mediaDataManager.onNotificationAdded(KEY, notifWithAction) assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), - eq(0), eq(false)) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) assertThat(mediaDataCaptor.value!!.semanticActions).isNull() assertThat(mediaDataCaptor.value!!.actions).hasSize(1) @@ -792,11 +997,11 @@ class MediaDataManagerTest : SysuiTestCase() { fun testPlaybackActions_hasPrevNext() { val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4") whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true) - val stateActions = PlaybackState.ACTION_PLAY or + val stateActions = + PlaybackState.ACTION_PLAY or PlaybackState.ACTION_SKIP_TO_PREVIOUS or PlaybackState.ACTION_SKIP_TO_NEXT - val stateBuilder = PlaybackState.Builder() - .setActions(stateActions) + val stateBuilder = PlaybackState.Builder().setActions(stateActions) customDesc.forEach { stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause) } @@ -808,20 +1013,20 @@ class MediaDataManagerTest : SysuiTestCase() { val actions = mediaDataCaptor.value!!.semanticActions!! assertThat(actions.playOrPause).isNotNull() - assertThat(actions.playOrPause!!.contentDescription).isEqualTo( - context.getString(R.string.controls_media_button_play)) + assertThat(actions.playOrPause!!.contentDescription) + .isEqualTo(context.getString(R.string.controls_media_button_play)) actions.playOrPause!!.action!!.run() verify(transportControls).play() assertThat(actions.prevOrCustom).isNotNull() - assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo( - context.getString(R.string.controls_media_button_prev)) + assertThat(actions.prevOrCustom!!.contentDescription) + .isEqualTo(context.getString(R.string.controls_media_button_prev)) actions.prevOrCustom!!.action!!.run() verify(transportControls).skipToPrevious() assertThat(actions.nextOrCustom).isNotNull() - assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo( - context.getString(R.string.controls_media_button_next)) + assertThat(actions.nextOrCustom!!.contentDescription) + .isEqualTo(context.getString(R.string.controls_media_button_next)) actions.nextOrCustom!!.action!!.run() verify(transportControls).skipToNext() @@ -837,8 +1042,7 @@ class MediaDataManagerTest : SysuiTestCase() { val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4", "custom 5") whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true) val stateActions = PlaybackState.ACTION_PLAY - val stateBuilder = PlaybackState.Builder() - .setActions(stateActions) + val stateBuilder = PlaybackState.Builder().setActions(stateActions) customDesc.forEach { stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause) } @@ -850,8 +1054,8 @@ class MediaDataManagerTest : SysuiTestCase() { val actions = mediaDataCaptor.value!!.semanticActions!! assertThat(actions.playOrPause).isNotNull() - assertThat(actions.playOrPause!!.contentDescription).isEqualTo( - context.getString(R.string.controls_media_button_play)) + assertThat(actions.playOrPause!!.contentDescription) + .isEqualTo(context.getString(R.string.controls_media_button_play)) assertThat(actions.prevOrCustom).isNotNull() assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo(customDesc[0]) @@ -870,7 +1074,8 @@ class MediaDataManagerTest : SysuiTestCase() { fun testPlaybackActions_connecting() { whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true) val stateActions = PlaybackState.ACTION_PLAY - val stateBuilder = PlaybackState.Builder() + val stateBuilder = + PlaybackState.Builder() .setState(PlaybackState.STATE_BUFFERING, 0, 10f) .setActions(stateActions) whenever(controller.playbackState).thenReturn(stateBuilder.build()) @@ -881,8 +1086,8 @@ class MediaDataManagerTest : SysuiTestCase() { val actions = mediaDataCaptor.value!!.semanticActions!! assertThat(actions.playOrPause).isNotNull() - assertThat(actions.playOrPause!!.contentDescription).isEqualTo( - context.getString(R.string.controls_media_button_connecting)) + assertThat(actions.playOrPause!!.contentDescription) + .isEqualTo(context.getString(R.string.controls_media_button_connecting)) } @Test @@ -890,15 +1095,15 @@ class MediaDataManagerTest : SysuiTestCase() { val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4") whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true) val stateActions = PlaybackState.ACTION_PLAY - val stateBuilder = PlaybackState.Builder() - .setActions(stateActions) + val stateBuilder = PlaybackState.Builder().setActions(stateActions) customDesc.forEach { stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause) } - val extras = Bundle().apply { - putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true) - putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true) - } + val extras = + Bundle().apply { + putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true) + putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true) + } whenever(controller.playbackState).thenReturn(stateBuilder.build()) whenever(controller.extras).thenReturn(extras) @@ -908,8 +1113,8 @@ class MediaDataManagerTest : SysuiTestCase() { val actions = mediaDataCaptor.value!!.semanticActions!! assertThat(actions.playOrPause).isNotNull() - assertThat(actions.playOrPause!!.contentDescription).isEqualTo( - context.getString(R.string.controls_media_button_play)) + assertThat(actions.playOrPause!!.contentDescription) + .isEqualTo(context.getString(R.string.controls_media_button_play)) assertThat(actions.prevOrCustom).isNull() assertThat(actions.nextOrCustom).isNull() @@ -937,8 +1142,8 @@ class MediaDataManagerTest : SysuiTestCase() { val actions = mediaDataCaptor.value!!.semanticActions!! assertThat(actions.playOrPause).isNotNull() - assertThat(actions.playOrPause!!.contentDescription).isEqualTo( - context.getString(R.string.controls_media_button_play)) + assertThat(actions.playOrPause!!.contentDescription) + .isEqualTo(context.getString(R.string.controls_media_button_play)) actions.playOrPause!!.action!!.run() verify(transportControls).play() } @@ -951,30 +1156,43 @@ class MediaDataManagerTest : SysuiTestCase() { // Location is updated to local cast whenever(controller.metadata).thenReturn(metadataBuilder.build()) - whenever(playbackInfo.playbackType).thenReturn( - MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE) + whenever(playbackInfo.playbackType) + .thenReturn(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE) addNotificationAndLoad() - verify(logger).logPlaybackLocationChange(anyInt(), eq(PACKAGE_NAME), - eq(instanceId), eq(MediaData.PLAYBACK_CAST_LOCAL)) + verify(logger) + .logPlaybackLocationChange( + anyInt(), + eq(PACKAGE_NAME), + eq(instanceId), + eq(MediaData.PLAYBACK_CAST_LOCAL) + ) // update to remote cast - val rcn = SbnBuilder().run { - setPkg(SYSTEM_PACKAGE_NAME) // System package - modifyNotification(context).also { - it.setSmallIcon(android.R.drawable.ic_media_pause) - it.setStyle(MediaStyle().apply { - setMediaSession(session.sessionToken) - setRemotePlaybackInfo("Remote device", 0, null) - }) + val rcn = + SbnBuilder().run { + setPkg(SYSTEM_PACKAGE_NAME) // System package + modifyNotification(context).also { + it.setSmallIcon(android.R.drawable.ic_media_pause) + it.setStyle( + MediaStyle().apply { + setMediaSession(session.sessionToken) + setRemotePlaybackInfo("Remote device", 0, null) + } + ) + } + build() } - build() - } mediaDataManager.onNotificationAdded(KEY, rcn) assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - verify(logger).logPlaybackLocationChange(anyInt(), eq(SYSTEM_PACKAGE_NAME), - eq(instanceId), eq(MediaData.PLAYBACK_CAST_REMOTE)) + verify(logger) + .logPlaybackLocationChange( + anyInt(), + eq(SYSTEM_PACKAGE_NAME), + eq(instanceId), + eq(MediaData.PLAYBACK_CAST_REMOTE) + ) } @Test @@ -984,14 +1202,19 @@ class MediaDataManagerTest : SysuiTestCase() { verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor) // Callback gets an updated state - val state = PlaybackState.Builder() - .setState(PlaybackState.STATE_PLAYING, 0L, 1f) - .build() + val state = PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build() callbackCaptor.value.invoke(KEY, state) // Listener is notified of updated state - verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), - capture(mediaDataCaptor), eq(true), eq(0), eq(false)) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(KEY), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) assertThat(mediaDataCaptor.value.isPlaying).isTrue() } @@ -1003,8 +1226,8 @@ class MediaDataManagerTest : SysuiTestCase() { // No media added with this key callbackCaptor.value.invoke(KEY, state) - verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), - anyBoolean()) + verify(listener, never()) + .onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean()) } @Test @@ -1022,35 +1245,42 @@ class MediaDataManagerTest : SysuiTestCase() { // Then no changes are made callbackCaptor.value.invoke(KEY, state) - verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), - anyBoolean()) + verify(listener, never()) + .onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean()) } @Test fun testPlaybackState_PauseWhenFlagTrue_keyExists_callsListener() { whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true) - val state = PlaybackState.Builder() - .setState(PlaybackState.STATE_PAUSED, 0L, 1f) - .build() + val state = PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 1f).build() whenever(controller.playbackState).thenReturn(state) addNotificationAndLoad() verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor) callbackCaptor.value.invoke(KEY, state) - verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), - capture(mediaDataCaptor), eq(true), eq(0), eq(false)) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(KEY), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) assertThat(mediaDataCaptor.value.isPlaying).isFalse() assertThat(mediaDataCaptor.value.semanticActions).isNotNull() } @Test fun testPlaybackState_PauseStateAfterAddingResumption_keyExists_callsListener() { - val desc = MediaDescription.Builder().run { - setTitle(SESSION_TITLE) - build() - } - val state = PlaybackState.Builder() + val desc = + MediaDescription.Builder().run { + setTitle(SESSION_TITLE) + build() + } + val state = + PlaybackState.Builder() .setState(PlaybackState.STATE_PAUSED, 0L, 1f) .setActions(PlaybackState.ACTION_PLAY_PAUSE) .build() @@ -1058,13 +1288,13 @@ class MediaDataManagerTest : SysuiTestCase() { // Add resumption controls in order to have semantic actions. // To make sure that they are not null after changing state. mediaDataManager.addResumptionControls( - USER_ID, - desc, - Runnable {}, - session.sessionToken, - APP_NAME, - pendingIntent, - PACKAGE_NAME + USER_ID, + desc, + Runnable {}, + session.sessionToken, + APP_NAME, + pendingIntent, + PACKAGE_NAME ) backgroundExecutor.runAllReady() foregroundExecutor.runAllReady() @@ -1073,14 +1303,14 @@ class MediaDataManagerTest : SysuiTestCase() { callbackCaptor.value.invoke(PACKAGE_NAME, state) verify(listener) - .onMediaDataLoaded( - eq(PACKAGE_NAME), - eq(PACKAGE_NAME), - capture(mediaDataCaptor), - eq(true), - eq(0), - eq(false) - ) + .onMediaDataLoaded( + eq(PACKAGE_NAME), + eq(PACKAGE_NAME), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) assertThat(mediaDataCaptor.value.isPlaying).isFalse() assertThat(mediaDataCaptor.value.semanticActions).isNotNull() } @@ -1088,7 +1318,8 @@ class MediaDataManagerTest : SysuiTestCase() { @Test fun testPlaybackStateNull_Pause_keyExists_callsListener() { whenever(controller.playbackState).thenReturn(null) - val state = PlaybackState.Builder() + val state = + PlaybackState.Builder() .setState(PlaybackState.STATE_PAUSED, 0L, 1f) .setActions(PlaybackState.ACTION_PLAY_PAUSE) .build() @@ -1097,20 +1328,32 @@ class MediaDataManagerTest : SysuiTestCase() { verify(mediaTimeoutListener).stateCallback = capture(callbackCaptor) callbackCaptor.value.invoke(KEY, state) - verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), - capture(mediaDataCaptor), eq(true), eq(0), eq(false)) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(KEY), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) assertThat(mediaDataCaptor.value.isPlaying).isFalse() assertThat(mediaDataCaptor.value.semanticActions).isNull() } - /** - * Helper function to add a media notification and capture the resulting MediaData - */ + /** Helper function to add a media notification and capture the resulting MediaData */ private fun addNotificationAndLoad() { mediaDataManager.onNotificationAdded(KEY, mediaNotification) assertThat(backgroundExecutor.runAllReady()).isEqualTo(1) assertThat(foregroundExecutor.runAllReady()).isEqualTo(1) - verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true), - eq(0), eq(false)) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(null), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt index d708aaaa61ac..a45e9d9fcacf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt @@ -113,7 +113,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { fakeFgExecutor = FakeExecutor(FakeSystemClock()) fakeBgExecutor = FakeExecutor(FakeSystemClock()) localBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager::class.java) - manager = MediaDeviceManager( + manager = + MediaDeviceManager( context, controllerFactory, lmmFactory, @@ -124,7 +125,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { fakeFgExecutor, fakeBgExecutor, dumpster - ) + ) manager.addListener(listener) // Configure mocks. @@ -138,11 +139,9 @@ public class MediaDeviceManagerTest : SysuiTestCase() { // Create a media sesssion and notification for testing. session = MediaSession(context, SESSION_KEY) - mediaData = MediaTestUtils.emptyMediaData.copy( - packageName = PACKAGE, - token = session.sessionToken) - whenever(controllerFactory.create(session.sessionToken)) - .thenReturn(controller) + mediaData = + MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, token = session.sessionToken) + whenever(controllerFactory.create(session.sessionToken)).thenReturn(controller) setupLeAudioConfiguration(false) } @@ -358,7 +357,9 @@ public class MediaDeviceManagerTest : SysuiTestCase() { val deviceCallback = captureCallback() // First set a non-null about-to-connect device deviceCallback.onAboutToConnectDeviceAdded( - "fakeAddress", "AboutToConnectDeviceName", mock(Drawable::class.java) + "fakeAddress", + "AboutToConnectDeviceName", + mock(Drawable::class.java) ) // Run and reset the executors and listeners so we only focus on new events. fakeBgExecutor.runAllReady() @@ -587,8 +588,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun testRemotePlaybackDeviceOverride() { whenever(route.name).thenReturn(DEVICE_NAME) - val deviceData = MediaDeviceData(false, null, REMOTE_DEVICE_NAME, null, - showBroadcastButton = false) + val deviceData = + MediaDeviceData(false, null, REMOTE_DEVICE_NAME, null, showBroadcastButton = false) val mediaDataWithDevice = mediaData.copy(device = deviceData) // GIVEN media data that already has a device set @@ -617,8 +618,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { val data = captureDeviceData(KEY) assertThat(data.showBroadcastButton).isTrue() assertThat(data.enabled).isTrue() - assertThat(data.name).isEqualTo(context.getString( - R.string.broadcasting_description_is_broadcasting)) + assertThat(data.name) + .isEqualTo(context.getString(R.string.broadcasting_description_is_broadcasting)) } @Test @@ -659,20 +660,21 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback { - val callback: BluetoothLeBroadcast.Callback = object : BluetoothLeBroadcast.Callback { - override fun onBroadcastStarted(reason: Int, broadcastId: Int) {} - override fun onBroadcastStartFailed(reason: Int) {} - override fun onBroadcastStopped(reason: Int, broadcastId: Int) {} - override fun onBroadcastStopFailed(reason: Int) {} - override fun onPlaybackStarted(reason: Int, broadcastId: Int) {} - override fun onPlaybackStopped(reason: Int, broadcastId: Int) {} - override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {} - override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {} - override fun onBroadcastMetadataChanged( - broadcastId: Int, - metadata: BluetoothLeBroadcastMetadata - ) {} - } + val callback: BluetoothLeBroadcast.Callback = + object : BluetoothLeBroadcast.Callback { + override fun onBroadcastStarted(reason: Int, broadcastId: Int) {} + override fun onBroadcastStartFailed(reason: Int) {} + override fun onBroadcastStopped(reason: Int, broadcastId: Int) {} + override fun onBroadcastStopFailed(reason: Int) {} + override fun onPlaybackStarted(reason: Int, broadcastId: Int) {} + override fun onPlaybackStopped(reason: Int, broadcastId: Int) {} + override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {} + override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {} + override fun onBroadcastMetadataChanged( + broadcastId: Int, + metadata: BluetoothLeBroadcastMetadata + ) {} + } bluetoothLeBroadcast.registerCallback(fakeFgExecutor, callback) return callback @@ -681,7 +683,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { fun setupLeAudioConfiguration(isLeAudio: Boolean) { whenever(localBluetoothManager.profileManager).thenReturn(localBluetoothProfileManager) whenever(localBluetoothProfileManager.leAudioBroadcastProfile) - .thenReturn(localBluetoothLeBroadcast) + .thenReturn(localBluetoothLeBroadcast) whenever(localBluetoothLeBroadcast.isEnabled(any())).thenReturn(isLeAudio) whenever(localBluetoothLeBroadcast.appSourceName).thenReturn(BROADCAST_APP_NAME) } @@ -689,7 +691,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { fun setupBroadcastPackage(currentName: String) { whenever(lmm.packageName).thenReturn(PACKAGE) whenever(packageManager.getApplicationInfo(eq(PACKAGE), anyInt())) - .thenReturn(applicationInfo) + .thenReturn(applicationInfo) whenever(packageManager.getApplicationLabel(applicationInfo)).thenReturn(currentName) context.setMockPackageManager(packageManager) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilterTest.kt index ede012eb2213..3099609d42f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilterTest.kt @@ -23,14 +23,12 @@ import android.media.session.MediaSessionManager import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest - import com.android.systemui.SysuiTestCase import com.android.systemui.media.controls.MediaTestUtils import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock - import org.junit.After import org.junit.Before import org.junit.Rule @@ -44,17 +42,15 @@ import org.mockito.Mockito.any import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify -import org.mockito.junit.MockitoJUnit import org.mockito.Mockito.`when` as whenever +import org.mockito.junit.MockitoJUnit private const val PACKAGE = "PKG" private const val KEY = "TEST_KEY" private const val NOTIF_KEY = "TEST_KEY" -private val info = MediaTestUtils.emptyMediaData.copy( - packageName = PACKAGE, - notificationKey = NOTIF_KEY -) +private val info = + MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, notificationKey = NOTIF_KEY) @SmallTest @RunWith(AndroidTestingRunner::class) @@ -141,10 +137,10 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { // Capture listener bgExecutor.runAllReady() - val listenerCaptor = ArgumentCaptor.forClass( - MediaSessionManager.OnActiveSessionsChangedListener::class.java) - verify(mediaSessionManager).addOnActiveSessionsChangedListener( - listenerCaptor.capture(), any()) + val listenerCaptor = + ArgumentCaptor.forClass(MediaSessionManager.OnActiveSessionsChangedListener::class.java) + verify(mediaSessionManager) + .addOnActiveSessionsChangedListener(listenerCaptor.capture(), any()) sessionListener = listenerCaptor.value filter.addListener(mediaListener) @@ -163,8 +159,8 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { filter.onMediaDataLoaded(KEY, null, mediaData1) bgExecutor.runAllReady() fgExecutor.runAllReady() - verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), - eq(0), eq(false)) + verify(mediaListener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false)) } @Test @@ -186,8 +182,8 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { bgExecutor.runAllReady() fgExecutor.runAllReady() // THEN the event is not filtered - verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), - eq(0), eq(false)) + verify(mediaListener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false)) } @Test @@ -216,8 +212,8 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { bgExecutor.runAllReady() fgExecutor.runAllReady() // THEN the event is not filtered - verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), - eq(0), eq(false)) + verify(mediaListener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false)) } @Test @@ -232,15 +228,22 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { bgExecutor.runAllReady() fgExecutor.runAllReady() // THEN the event is not filtered - verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), - eq(0), eq(false)) + verify(mediaListener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false)) // WHEN a loaded event is received that matches the local session filter.onMediaDataLoaded(KEY, null, mediaData2) bgExecutor.runAllReady() fgExecutor.runAllReady() // THEN the event is filtered - verify(mediaListener, never()).onMediaDataLoaded( - eq(KEY), eq(null), eq(mediaData2), anyBoolean(), anyInt(), anyBoolean()) + verify(mediaListener, never()) + .onMediaDataLoaded( + eq(KEY), + eq(null), + eq(mediaData2), + anyBoolean(), + anyInt(), + anyBoolean() + ) } @Test @@ -256,8 +259,8 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { fgExecutor.runAllReady() // THEN the event is not filtered because there isn't a notification for the remote // session. - verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), - eq(0), eq(false)) + verify(mediaListener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false)) } @Test @@ -274,16 +277,22 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { bgExecutor.runAllReady() fgExecutor.runAllReady() // THEN the event is not filtered - verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true), - eq(0), eq(false)) + verify(mediaListener) + .onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true), eq(0), eq(false)) // WHEN a loaded event is received that matches the local session filter.onMediaDataLoaded(key2, null, mediaData2) bgExecutor.runAllReady() fgExecutor.runAllReady() // THEN the event is filtered verify(mediaListener, never()) - .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), anyBoolean(), - anyInt(), anyBoolean()) + .onMediaDataLoaded( + eq(key2), + eq(null), + eq(mediaData2), + anyBoolean(), + anyInt(), + anyBoolean() + ) // AND there should be a removed event for key2 verify(mediaListener).onMediaDataRemoved(eq(key2)) } @@ -302,15 +311,15 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { bgExecutor.runAllReady() fgExecutor.runAllReady() // THEN the event is not filtered - verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true), - eq(0), eq(false)) + verify(mediaListener) + .onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true), eq(0), eq(false)) // WHEN a loaded event is received that matches the remote session filter.onMediaDataLoaded(key2, null, mediaData2) bgExecutor.runAllReady() fgExecutor.runAllReady() // THEN the event is not filtered - verify(mediaListener).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), eq(true), - eq(0), eq(false)) + verify(mediaListener) + .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), eq(true), eq(0), eq(false)) } @Test @@ -326,15 +335,15 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { bgExecutor.runAllReady() fgExecutor.runAllReady() // THEN the event is not filtered - verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), - eq(0), eq(false)) + verify(mediaListener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false)) // WHEN a loaded event is received that matches the local session filter.onMediaDataLoaded(KEY, null, mediaData2) bgExecutor.runAllReady() fgExecutor.runAllReady() // THEN the event is not filtered - verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2), eq(true), - eq(0), eq(false)) + verify(mediaListener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2), eq(true), eq(0), eq(false)) } @Test @@ -352,8 +361,8 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { bgExecutor.runAllReady() fgExecutor.runAllReady() // THEN the event is not filtered - verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), - eq(0), eq(false)) + verify(mediaListener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false)) } @Test @@ -375,8 +384,8 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { bgExecutor.runAllReady() fgExecutor.runAllReady() // THEN the key migration event is fired - verify(mediaListener).onMediaDataLoaded(eq(key2), eq(key1), eq(mediaData2), eq(true), - eq(0), eq(false)) + verify(mediaListener) + .onMediaDataLoaded(eq(key2), eq(key1), eq(mediaData2), eq(true), eq(0), eq(false)) } @Test @@ -406,14 +415,20 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { fgExecutor.runAllReady() // THEN the key migration event is filtered verify(mediaListener, never()) - .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), anyBoolean(), - anyInt(), anyBoolean()) + .onMediaDataLoaded( + eq(key2), + eq(null), + eq(mediaData2), + anyBoolean(), + anyInt(), + anyBoolean() + ) // WHEN a loaded event is received that matches the remote session filter.onMediaDataLoaded(key2, null, mediaData1) bgExecutor.runAllReady() fgExecutor.runAllReady() // THEN the key migration event is fired - verify(mediaListener).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData1), eq(true), - eq(0), eq(false)) + verify(mediaListener) + .onMediaDataLoaded(eq(key2), eq(null), eq(mediaData1), eq(true), eq(0), eq(false)) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt index 1408ed07d808..344dffafb448 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt @@ -44,11 +44,11 @@ import org.mockito.ArgumentMatchers.anyString import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito -import org.mockito.Mockito.`when` import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnit private const val KEY = "KEY" @@ -73,7 +73,8 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Mock private lateinit var timeoutCallback: (String, Boolean) -> Unit @Mock private lateinit var stateCallback: (String, PlaybackState) -> Unit @Captor private lateinit var mediaCallbackCaptor: ArgumentCaptor<MediaController.Callback> - @Captor private lateinit var dozingCallbackCaptor: + @Captor + private lateinit var dozingCallbackCaptor: ArgumentCaptor<StatusBarStateController.StateListener> @JvmField @Rule val mockito = MockitoJUnit.rule() private lateinit var metadataBuilder: MediaMetadata.Builder @@ -88,36 +89,41 @@ class MediaTimeoutListenerTest : SysuiTestCase() { fun setup() { `when`(mediaControllerFactory.create(any())).thenReturn(mediaController) executor = FakeExecutor(clock) - mediaTimeoutListener = MediaTimeoutListener( - mediaControllerFactory, - executor, - logger, - statusBarStateController, - clock - ) + mediaTimeoutListener = + MediaTimeoutListener( + mediaControllerFactory, + executor, + logger, + statusBarStateController, + clock + ) mediaTimeoutListener.timeoutCallback = timeoutCallback mediaTimeoutListener.stateCallback = stateCallback // Create a media session and notification for testing. - metadataBuilder = MediaMetadata.Builder().apply { - putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST) - putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE) - } - playbackBuilder = PlaybackState.Builder().apply { - setState(PlaybackState.STATE_PAUSED, 6000L, 1f) - setActions(PlaybackState.ACTION_PLAY) - } - session = MediaSession(context, SESSION_KEY).apply { - setMetadata(metadataBuilder.build()) - setPlaybackState(playbackBuilder.build()) - } + metadataBuilder = + MediaMetadata.Builder().apply { + putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST) + putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE) + } + playbackBuilder = + PlaybackState.Builder().apply { + setState(PlaybackState.STATE_PAUSED, 6000L, 1f) + setActions(PlaybackState.ACTION_PLAY) + } + session = + MediaSession(context, SESSION_KEY).apply { + setMetadata(metadataBuilder.build()) + setPlaybackState(playbackBuilder.build()) + } session.setActive(true) - mediaData = MediaTestUtils.emptyMediaData.copy( - app = PACKAGE, - packageName = PACKAGE, - token = session.sessionToken - ) + mediaData = + MediaTestUtils.emptyMediaData.copy( + app = PACKAGE, + packageName = PACKAGE, + token = session.sessionToken + ) resumeData = mediaData.copy(token = null, active = false, resumption = true) } @@ -215,8 +221,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() { // Assuming we're registered testOnMediaDataLoaded_registersPlaybackListener() - mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder() - .setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()) + mediaCallbackCaptor.value.onPlaybackStateChanged( + PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build() + ) assertThat(executor.numPending()).isEqualTo(1) assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT) } @@ -226,8 +233,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() { // Assuming we have a pending timeout testOnPlaybackStateChanged_schedulesTimeout_whenPaused() - mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder() - .setState(PlaybackState.STATE_PLAYING, 0L, 0f).build()) + mediaCallbackCaptor.value.onPlaybackStateChanged( + PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 0f).build() + ) assertThat(executor.numPending()).isEqualTo(0) verify(logger).logTimeoutCancelled(eq(KEY), any()) } @@ -237,8 +245,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() { // Assuming we have a pending timeout testOnPlaybackStateChanged_schedulesTimeout_whenPaused() - mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder() - .setState(PlaybackState.STATE_STOPPED, 0L, 0f).build()) + mediaCallbackCaptor.value.onPlaybackStateChanged( + PlaybackState.Builder().setState(PlaybackState.STATE_STOPPED, 0L, 0f).build() + ) assertThat(executor.numPending()).isEqualTo(1) } @@ -332,9 +341,8 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Test fun testOnMediaDataLoaded_pausedToResume_updatesTimeout() { // WHEN regular media is paused - val pausedState = PlaybackState.Builder() - .setState(PlaybackState.STATE_PAUSED, 0L, 0f) - .build() + val pausedState = + PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build() `when`(mediaController.playbackState).thenReturn(pausedState) mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) assertThat(executor.numPending()).isEqualTo(1) @@ -365,9 +373,8 @@ class MediaTimeoutListenerTest : SysuiTestCase() { mediaTimeoutListener.onMediaDataLoaded(PACKAGE, null, resumeData) // AND that media is resumed - val playingState = PlaybackState.Builder() - .setState(PlaybackState.STATE_PAUSED, 0L, 0f) - .build() + val playingState = + PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build() `when`(mediaController.playbackState).thenReturn(playingState) mediaTimeoutListener.onMediaDataLoaded(KEY, PACKAGE, mediaData) @@ -389,15 +396,11 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Test fun testOnMediaDataLoaded_playbackActionsChanged_noCallback() { // Load media data once - val pausedState = PlaybackState.Builder() - .setActions(PlaybackState.ACTION_PAUSE) - .build() + val pausedState = PlaybackState.Builder().setActions(PlaybackState.ACTION_PAUSE).build() loadMediaDataWithPlaybackState(pausedState) // When media data is loaded again, with different actions - val playingState = PlaybackState.Builder() - .setActions(PlaybackState.ACTION_PLAY) - .build() + val playingState = PlaybackState.Builder().setActions(PlaybackState.ACTION_PLAY).build() loadMediaDataWithPlaybackState(playingState) // Then the callback is not invoked @@ -407,15 +410,11 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Test fun testOnPlaybackStateChanged_playbackActionsChanged_sendsCallback() { // Load media data once - val pausedState = PlaybackState.Builder() - .setActions(PlaybackState.ACTION_PAUSE) - .build() + val pausedState = PlaybackState.Builder().setActions(PlaybackState.ACTION_PAUSE).build() loadMediaDataWithPlaybackState(pausedState) // When the playback state changes, and has different actions - val playingState = PlaybackState.Builder() - .setActions(PlaybackState.ACTION_PLAY) - .build() + val playingState = PlaybackState.Builder().setActions(PlaybackState.ACTION_PLAY).build() mediaCallbackCaptor.value.onPlaybackStateChanged(playingState) // Then the callback is invoked @@ -424,24 +423,30 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Test fun testOnPlaybackStateChanged_differentCustomActions_sendsCallback() { - val customOne = PlaybackState.CustomAction.Builder( + val customOne = + PlaybackState.CustomAction.Builder( "ACTION_1", "custom action 1", - android.R.drawable.ic_media_ff) + android.R.drawable.ic_media_ff + ) .build() - val pausedState = PlaybackState.Builder() + val pausedState = + PlaybackState.Builder() .setActions(PlaybackState.ACTION_PAUSE) .addCustomAction(customOne) .build() loadMediaDataWithPlaybackState(pausedState) // When the playback state actions change - val customTwo = PlaybackState.CustomAction.Builder( - "ACTION_2", - "custom action 2", - android.R.drawable.ic_media_rew) + val customTwo = + PlaybackState.CustomAction.Builder( + "ACTION_2", + "custom action 2", + android.R.drawable.ic_media_rew + ) .build() - val pausedStateTwoActions = PlaybackState.Builder() + val pausedStateTwoActions = + PlaybackState.Builder() .setActions(PlaybackState.ACTION_PAUSE) .addCustomAction(customOne) .addCustomAction(customTwo) @@ -454,9 +459,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Test fun testOnPlaybackStateChanged_sameActions_noCallback() { - val stateWithActions = PlaybackState.Builder() - .setActions(PlaybackState.ACTION_PLAY) - .build() + val stateWithActions = PlaybackState.Builder().setActions(PlaybackState.ACTION_PLAY).build() loadMediaDataWithPlaybackState(stateWithActions) // When the playback state updates with the same actions @@ -470,18 +473,20 @@ class MediaTimeoutListenerTest : SysuiTestCase() { fun testOnPlaybackStateChanged_sameCustomActions_noCallback() { val actionName = "custom action" val actionIcon = android.R.drawable.ic_media_ff - val customOne = PlaybackState.CustomAction.Builder(actionName, actionName, actionIcon) - .build() - val stateOne = PlaybackState.Builder() + val customOne = + PlaybackState.CustomAction.Builder(actionName, actionName, actionIcon).build() + val stateOne = + PlaybackState.Builder() .setActions(PlaybackState.ACTION_PAUSE) .addCustomAction(customOne) .build() loadMediaDataWithPlaybackState(stateOne) // When the playback state is updated, but has the same actions - val customTwo = PlaybackState.CustomAction.Builder(actionName, actionName, actionIcon) - .build() - val stateTwo = PlaybackState.Builder() + val customTwo = + PlaybackState.CustomAction.Builder(actionName, actionName, actionIcon).build() + val stateTwo = + PlaybackState.Builder() .setActions(PlaybackState.ACTION_PAUSE) .addCustomAction(customTwo) .build() @@ -494,15 +499,13 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Test fun testOnMediaDataLoaded_isPlayingChanged_noCallback() { // Load media data in paused state - val pausedState = PlaybackState.Builder() - .setState(PlaybackState.STATE_PAUSED, 0L, 0f) - .build() + val pausedState = + PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build() loadMediaDataWithPlaybackState(pausedState) // When media data is loaded again but playing - val playingState = PlaybackState.Builder() - .setState(PlaybackState.STATE_PLAYING, 0L, 1f) - .build() + val playingState = + PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build() loadMediaDataWithPlaybackState(playingState) // Then the callback is not invoked @@ -512,15 +515,13 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Test fun testOnPlaybackStateChanged_isPlayingChanged_sendsCallback() { // Load media data in paused state - val pausedState = PlaybackState.Builder() - .setState(PlaybackState.STATE_PAUSED, 0L, 0f) - .build() + val pausedState = + PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build() loadMediaDataWithPlaybackState(pausedState) // When the playback state changes to playing - val playingState = PlaybackState.Builder() - .setState(PlaybackState.STATE_PLAYING, 0L, 1f) - .build() + val playingState = + PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build() mediaCallbackCaptor.value.onPlaybackStateChanged(playingState) // Then the callback is invoked @@ -530,15 +531,13 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Test fun testOnPlaybackStateChanged_isPlayingSame_noCallback() { // Load media data in paused state - val pausedState = PlaybackState.Builder() - .setState(PlaybackState.STATE_PAUSED, 0L, 0f) - .build() + val pausedState = + PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build() loadMediaDataWithPlaybackState(pausedState) // When the playback state is updated, but still not playing - val playingState = PlaybackState.Builder() - .setState(PlaybackState.STATE_STOPPED, 0L, 0f) - .build() + val playingState = + PlaybackState.Builder().setState(PlaybackState.STATE_STOPPED, 0L, 0f).build() mediaCallbackCaptor.value.onPlaybackStateChanged(playingState) // Then the callback is not invoked @@ -549,8 +548,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() { fun testTimeoutCallback_dozedPastTimeout_invokedOnWakeup() { // When paused media is loaded testOnMediaDataLoaded_registersPlaybackListener() - mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder() - .setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()) + mediaCallbackCaptor.value.onPlaybackStateChanged( + PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build() + ) verify(statusBarStateController).addCallback(capture(dozingCallbackCaptor)) // And we doze past the scheduled timeout @@ -574,8 +574,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() { val time = clock.currentTimeMillis() clock.setElapsedRealtime(time) testOnMediaDataLoaded_registersPlaybackListener() - mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder() - .setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()) + mediaCallbackCaptor.value.onPlaybackStateChanged( + PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build() + ) verify(statusBarStateController).addCallback(capture(dozingCallbackCaptor)) // And we doze, but not past the scheduled timeout diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt index 91938a646b94..84fdfd78e9fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt @@ -36,13 +36,13 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.media.controls.MediaTestUtils import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.player.MediaDeviceData -import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT import com.android.systemui.media.controls.pipeline.MediaDataManager +import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT import com.android.systemui.tuner.TunerService import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock -import org.junit.After import com.google.common.truth.Truth.assertThat +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -68,7 +68,9 @@ private const val MEDIA_PREFERENCES = "media_control_prefs" private const val RESUME_COMPONENTS = "package1/class1:package2/class2:package3/class3" private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() + private fun <T> eq(value: T): T = Mockito.eq(value) ?: value + private fun <T> any(): T = Mockito.any<T>() @SmallTest @@ -98,26 +100,32 @@ class MediaResumeListenerTest : SysuiTestCase() { private lateinit var resumeListener: MediaResumeListener private val clock = FakeSystemClock() - private var originalQsSetting = Settings.Global.getInt(context.contentResolver, - Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1) - private var originalResumeSetting = Settings.Secure.getInt(context.contentResolver, - Settings.Secure.MEDIA_CONTROLS_RESUME, 0) + private var originalQsSetting = + Settings.Global.getInt( + context.contentResolver, + Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, + 1 + ) + private var originalResumeSetting = + Settings.Secure.getInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0) @Before fun setup() { MockitoAnnotations.initMocks(this) - Settings.Global.putInt(context.contentResolver, - Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1) - Settings.Secure.putInt(context.contentResolver, - Settings.Secure.MEDIA_CONTROLS_RESUME, 1) + Settings.Global.putInt( + context.contentResolver, + Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, + 1 + ) + Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 1) whenever(resumeBrowserFactory.create(capture(callbackCaptor), any())) - .thenReturn(resumeBrowser) + .thenReturn(resumeBrowser) // resume components are stored in sharedpreferences whenever(mockContext.getSharedPreferences(eq(MEDIA_PREFERENCES), anyInt())) - .thenReturn(sharedPrefs) + .thenReturn(sharedPrefs) whenever(sharedPrefs.getString(any(), any())).thenReturn(RESUME_COMPONENTS) whenever(sharedPrefs.edit()).thenReturn(sharedPrefsEditor) whenever(sharedPrefsEditor.putString(any(), any())).thenReturn(sharedPrefsEditor) @@ -125,36 +133,59 @@ class MediaResumeListenerTest : SysuiTestCase() { whenever(mockContext.contentResolver).thenReturn(context.contentResolver) executor = FakeExecutor(clock) - resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor, - tunerService, resumeBrowserFactory, dumpManager, clock) + resumeListener = + MediaResumeListener( + mockContext, + broadcastDispatcher, + executor, + tunerService, + resumeBrowserFactory, + dumpManager, + clock + ) resumeListener.setManager(mediaDataManager) mediaDataManager.addListener(resumeListener) - data = MediaTestUtils.emptyMediaData.copy( + data = + MediaTestUtils.emptyMediaData.copy( song = TITLE, packageName = PACKAGE_NAME, - token = token) + token = token + ) } @After fun tearDown() { - Settings.Global.putInt(context.contentResolver, - Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, originalQsSetting) - Settings.Secure.putInt(context.contentResolver, - Settings.Secure.MEDIA_CONTROLS_RESUME, originalResumeSetting) + Settings.Global.putInt( + context.contentResolver, + Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, + originalQsSetting + ) + Settings.Secure.putInt( + context.contentResolver, + Settings.Secure.MEDIA_CONTROLS_RESUME, + originalResumeSetting + ) } @Test fun testWhenNoResumption_doesNothing() { - Settings.Secure.putInt(context.contentResolver, - Settings.Secure.MEDIA_CONTROLS_RESUME, 0) + Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0) // When listener is created, we do NOT register a user change listener - val listener = MediaResumeListener(context, broadcastDispatcher, executor, tunerService, - resumeBrowserFactory, dumpManager, clock) + val listener = + MediaResumeListener( + context, + broadcastDispatcher, + executor, + tunerService, + resumeBrowserFactory, + dumpManager, + clock + ) listener.setManager(mediaDataManager) - verify(broadcastDispatcher, never()).registerReceiver(eq(listener.userChangeReceiver), - any(), any(), any(), anyInt(), any()) + verify(broadcastDispatcher, never()) + .registerReceiver(eq(listener.userChangeReceiver), any(), any(), any(), anyInt(), any()) // When data is loaded, we do NOT execute or update anything listener.onMediaDataLoaded(KEY, OLD_KEY, data) @@ -175,9 +206,7 @@ class MediaResumeListenerTest : SysuiTestCase() { fun testOnLoad_checksForResume_badService() { setUpMbsWithValidResolveInfo() - whenever(resumeBrowser.testConnection()).thenAnswer { - callbackCaptor.value.onError() - } + whenever(resumeBrowser.testConnection()).thenAnswer { callbackCaptor.value.onError() } // When media data is loaded that has not been checked yet, and does not have a MBS resumeListener.onMediaDataLoaded(KEY, null, data) @@ -231,7 +260,7 @@ class MediaResumeListenerTest : SysuiTestCase() { // But we do not tell it to add new controls verify(mediaDataManager, never()) - .addResumptionControls(anyInt(), any(), any(), any(), any(), any(), any()) + .addResumptionControls(anyInt(), any(), any(), any(), any(), any(), any()) } @Test @@ -258,8 +287,15 @@ class MediaResumeListenerTest : SysuiTestCase() { // Make sure broadcast receiver is registered resumeListener.setManager(mediaDataManager) - verify(broadcastDispatcher).registerReceiver(eq(resumeListener.userChangeReceiver), - any(), any(), any(), anyInt(), any()) + verify(broadcastDispatcher) + .registerReceiver( + eq(resumeListener.userChangeReceiver), + any(), + any(), + any(), + anyInt(), + any() + ) // When we get an unlock event val intent = Intent(Intent.ACTION_USER_UNLOCKED) @@ -269,8 +305,8 @@ class MediaResumeListenerTest : SysuiTestCase() { verify(resumeBrowser, times(3)).findRecentMedia() // Then since the mock service found media, the manager should be informed - verify(mediaDataManager, times(3)).addResumptionControls(anyInt(), - any(), any(), any(), any(), any(), eq(PACKAGE_NAME)) + verify(mediaDataManager, times(3)) + .addResumptionControls(anyInt(), any(), any(), any(), any(), any(), eq(PACKAGE_NAME)) } @Test @@ -309,12 +345,14 @@ class MediaResumeListenerTest : SysuiTestCase() { // Then we save an update with the current time verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor))) - componentCaptor.value.split(ResumeMediaBrowser.DELIMITER.toRegex()) - .dropLastWhile { it.isEmpty() }.forEach { - val result = it.split("/") - assertThat(result.size).isEqualTo(3) - assertThat(result[2].toLong()).isEqualTo(currentTime) - } + componentCaptor.value + .split(ResumeMediaBrowser.DELIMITER.toRegex()) + .dropLastWhile { it.isEmpty() } + .forEach { + val result = it.split("/") + assertThat(result.size).isEqualTo(3) + assertThat(result[2].toLong()).isEqualTo(currentTime) + } verify(sharedPrefsEditor, times(1)).apply() } @@ -333,8 +371,16 @@ class MediaResumeListenerTest : SysuiTestCase() { val lastPlayed = clock.currentTimeMillis() val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:" whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString) - val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor, - tunerService, resumeBrowserFactory, dumpManager, clock) + val resumeListener = + MediaResumeListener( + mockContext, + broadcastDispatcher, + executor, + tunerService, + resumeBrowserFactory, + dumpManager, + clock + ) resumeListener.setManager(mediaDataManager) mediaDataManager.addListener(resumeListener) @@ -344,8 +390,8 @@ class MediaResumeListenerTest : SysuiTestCase() { // We add its resume controls verify(resumeBrowser, times(1)).findRecentMedia() - verify(mediaDataManager, times(1)).addResumptionControls(anyInt(), - any(), any(), any(), any(), any(), eq(PACKAGE_NAME)) + verify(mediaDataManager, times(1)) + .addResumptionControls(anyInt(), any(), any(), any(), any(), any(), eq(PACKAGE_NAME)) } @Test @@ -354,8 +400,16 @@ class MediaResumeListenerTest : SysuiTestCase() { val lastPlayed = clock.currentTimeMillis() - RESUME_MEDIA_TIMEOUT - 100 val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:" whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString) - val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor, - tunerService, resumeBrowserFactory, dumpManager, clock) + val resumeListener = + MediaResumeListener( + mockContext, + broadcastDispatcher, + executor, + tunerService, + resumeBrowserFactory, + dumpManager, + clock + ) resumeListener.setManager(mediaDataManager) mediaDataManager.addListener(resumeListener) @@ -365,8 +419,8 @@ class MediaResumeListenerTest : SysuiTestCase() { // We do not try to add resume controls verify(resumeBrowser, times(0)).findRecentMedia() - verify(mediaDataManager, times(0)).addResumptionControls(anyInt(), - any(), any(), any(), any(), any(), any()) + verify(mediaDataManager, times(0)) + .addResumptionControls(anyInt(), any(), any(), any(), any(), any(), any()) } @Test @@ -385,8 +439,16 @@ class MediaResumeListenerTest : SysuiTestCase() { val lastPlayed = currentTime - 1000 val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:" whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString) - val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor, - tunerService, resumeBrowserFactory, dumpManager, clock) + val resumeListener = + MediaResumeListener( + mockContext, + broadcastDispatcher, + executor, + tunerService, + resumeBrowserFactory, + dumpManager, + clock + ) resumeListener.setManager(mediaDataManager) mediaDataManager.addListener(resumeListener) @@ -396,12 +458,14 @@ class MediaResumeListenerTest : SysuiTestCase() { // Then we store the new lastPlayed time verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor))) - componentCaptor.value.split(ResumeMediaBrowser.DELIMITER.toRegex()) - .dropLastWhile { it.isEmpty() }.forEach { - val result = it.split("/") - assertThat(result.size).isEqualTo(3) - assertThat(result[2].toLong()).isEqualTo(currentTime) - } + componentCaptor.value + .split(ResumeMediaBrowser.DELIMITER.toRegex()) + .dropLastWhile { it.isEmpty() } + .forEach { + val result = it.split("/") + assertThat(result.size).isEqualTo(3) + assertThat(result[2].toLong()).isEqualTo(currentTime) + } verify(sharedPrefsEditor, times(1)).apply() } @@ -422,9 +486,7 @@ class MediaResumeListenerTest : SysuiTestCase() { setUpMbsWithValidResolveInfo() // Set up mocks to return with an error - whenever(resumeBrowser.testConnection()).thenAnswer { - callbackCaptor.value.onError() - } + whenever(resumeBrowser.testConnection()).thenAnswer { callbackCaptor.value.onError() } resumeListener.onMediaDataLoaded(key = KEY, oldKey = null, data) executor.runAllReady() diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt index c490ae32e014..a04cfd46588b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt @@ -37,8 +37,8 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.reset import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations private const val PACKAGE_NAME = "package" private const val CLASS_NAME = "class" @@ -47,7 +47,9 @@ private const val MEDIA_ID = "media ID" private const val ROOT = "media browser root" private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() + private fun <T> eq(value: T): T = Mockito.eq(value) ?: value + private fun <T> any(): T = Mockito.any<T>() @SmallTest @@ -57,10 +59,8 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { private lateinit var resumeBrowser: TestableResumeMediaBrowser private val component = ComponentName(PACKAGE_NAME, CLASS_NAME) - private val description = MediaDescription.Builder() - .setTitle(TITLE) - .setMediaId(MEDIA_ID) - .build() + private val description = + MediaDescription.Builder().setTitle(TITLE).setMediaId(MEDIA_ID).build() @Mock lateinit var callback: ResumeMediaBrowser.Callback @Mock lateinit var listener: MediaResumeListener @@ -81,19 +81,20 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) whenever(browserFactory.create(any(), capture(connectionCallback), any())) - .thenReturn(browser) + .thenReturn(browser) whenever(mediaController.transportControls).thenReturn(transportControls) whenever(mediaController.sessionToken).thenReturn(token) - resumeBrowser = TestableResumeMediaBrowser( - context, - callback, - component, - browserFactory, - logger, - mediaController - ) + resumeBrowser = + TestableResumeMediaBrowser( + context, + callback, + component, + browserFactory, + logger, + mediaController + ) } @Test @@ -329,30 +330,20 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { verify(oldBrowser).disconnect() } - /** - * Helper function to mock a failed connection - */ + /** Helper function to mock a failed connection */ private fun setupBrowserFailed() { - whenever(browser.connect()).thenAnswer { - connectionCallback.value.onConnectionFailed() - } + whenever(browser.connect()).thenAnswer { connectionCallback.value.onConnectionFailed() } } - /** - * Helper function to mock a successful connection only - */ + /** Helper function to mock a successful connection only */ private fun setupBrowserConnection() { - whenever(browser.connect()).thenAnswer { - connectionCallback.value.onConnected() - } + whenever(browser.connect()).thenAnswer { connectionCallback.value.onConnected() } whenever(browser.isConnected()).thenReturn(true) whenever(browser.getRoot()).thenReturn(ROOT) whenever(browser.sessionToken).thenReturn(token) } - /** - * Helper function to mock a successful connection, but no media results - */ + /** Helper function to mock a successful connection, but no media results */ private fun setupBrowserConnectionNoResults() { setupBrowserConnection() whenever(browser.subscribe(any(), capture(subscriptionCallback))).thenAnswer { @@ -360,9 +351,7 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { } } - /** - * Helper function to mock a successful connection, but no playable results - */ + /** Helper function to mock a successful connection, but no playable results */ private fun setupBrowserConnectionNotPlayable() { setupBrowserConnection() @@ -373,9 +362,7 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { } } - /** - * Helper function to mock a successful connection with playable media - */ + /** Helper function to mock a successful connection with playable media */ private fun setupBrowserConnectionValidMedia() { setupBrowserConnection() @@ -387,9 +374,7 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { } } - /** - * Override so media controller use is testable - */ + /** Override so media controller use is testable */ private class TestableResumeMediaBrowser( context: Context, callback: Callback, @@ -403,4 +388,4 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { return fakeController } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/AnimationBindHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/AnimationBindHandlerTest.kt index a9cae0d8d91f..99f56b16ab8b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/AnimationBindHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/AnimationBindHandlerTest.kt @@ -16,24 +16,24 @@ package com.android.systemui.media.controls.ui -import org.mockito.Mockito.`when` as whenever import android.graphics.drawable.Animatable2 import android.graphics.drawable.Drawable import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper import com.android.systemui.SysuiTestCase -import junit.framework.Assert.assertTrue import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.verify -import org.mockito.Mockito.times import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit @SmallTest @@ -56,8 +56,7 @@ class AnimationBindHandlerTest : SysuiTestCase() { handler = AnimationBindHandler() } - @After - fun tearDown() {} + @After fun tearDown() {} @Test fun registerNoAnimations_executeCallbackImmediately() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt index ece883a39c29..5bb74e5a31f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt @@ -69,21 +69,18 @@ class ColorSchemeTransitionTest : SysuiTestCase() { animatingColorTransitionFactory = { _, _, _ -> mockAnimatingTransition } whenever(extractColor.invoke(colorScheme)).thenReturn(TARGET_COLOR) - colorSchemeTransition = ColorSchemeTransition( - context, mediaViewHolder, animatingColorTransitionFactory - ) - - colorTransition = object : AnimatingColorTransition( - DEFAULT_COLOR, extractColor, applyColor - ) { - override fun buildAnimator(): ValueAnimator { - return valueAnimator + colorSchemeTransition = + ColorSchemeTransition(context, mediaViewHolder, animatingColorTransitionFactory) + + colorTransition = + object : AnimatingColorTransition(DEFAULT_COLOR, extractColor, applyColor) { + override fun buildAnimator(): ValueAnimator { + return valueAnimator + } } - } } - @After - fun tearDown() {} + @After fun tearDown() {} @Test fun testColorTransition_nullColorScheme_keepsDefault() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt index 7f007f14bf0c..20260069c943 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt @@ -48,17 +48,12 @@ import org.mockito.junit.MockitoJUnit @TestableLooper.RunWithLooper class KeyguardMediaControllerTest : SysuiTestCase() { - @Mock - private lateinit var mediaHost: MediaHost - @Mock - private lateinit var bypassController: KeyguardBypassController - @Mock - private lateinit var statusBarStateController: SysuiStatusBarStateController - @Mock - private lateinit var configurationController: ConfigurationController - - @JvmField @Rule - val mockito = MockitoJUnit.rule() + @Mock private lateinit var mediaHost: MediaHost + @Mock private lateinit var bypassController: KeyguardBypassController + @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController + @Mock private lateinit var configurationController: ConfigurationController + + @JvmField @Rule val mockito = MockitoJUnit.rule() private val mediaContainerView: MediaContainerView = MediaContainerView(context, null) private val hostView = UniqueObjectHostView(context) @@ -76,15 +71,16 @@ class KeyguardMediaControllerTest : SysuiTestCase() { hostView.layoutParams = FrameLayout.LayoutParams(100, 100) testableLooper = TestableLooper.get(this) fakeHandler = FakeHandler(testableLooper.looper) - keyguardMediaController = KeyguardMediaController( - mediaHost, - bypassController, - statusBarStateController, - context, - settings, - fakeHandler, - configurationController, - ) + keyguardMediaController = + KeyguardMediaController( + mediaHost, + bypassController, + statusBarStateController, + context, + settings, + fakeHandler, + configurationController, + ) keyguardMediaController.attachSinglePaneContainer(mediaContainerView) keyguardMediaController.useSplitShade = false } @@ -153,8 +149,10 @@ class KeyguardMediaControllerTest : SysuiTestCase() { keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) keyguardMediaController.useSplitShade = true - assertTrue("HostView wasn't attached to the split pane container", - splitShadeContainer.childCount == 1) + assertTrue( + "HostView wasn't attached to the split pane container", + splitShadeContainer.childCount == 1 + ) } @Test @@ -162,8 +160,10 @@ class KeyguardMediaControllerTest : SysuiTestCase() { val splitShadeContainer = FrameLayout(context) keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) - assertTrue("HostView wasn't attached to the single pane container", - mediaContainerView.childCount == 1) + assertTrue( + "HostView wasn't attached to the single pane container", + mediaContainerView.childCount == 1 + ) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt index f170ef27968f..c8e8943689c9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt @@ -93,22 +93,23 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) - mediaCarouselController = MediaCarouselController( - context, - mediaControlPanelFactory, - visualStabilityProvider, - mediaHostStatesManager, - activityStarter, - clock, - executor, - mediaDataManager, - configurationController, - falsingCollector, - falsingManager, - dumpManager, - logger, - debugLogger - ) + mediaCarouselController = + MediaCarouselController( + context, + mediaControlPanelFactory, + visualStabilityProvider, + mediaHostStatesManager, + activityStarter, + clock, + executor, + mediaDataManager, + configurationController, + falsingCollector, + falsingManager, + dumpManager, + logger, + debugLogger + ) verify(mediaDataManager).addListener(capture(listener)) verify(visualStabilityProvider) .addPersistentReorderingAllowedListener(capture(visualStabilityCallback)) @@ -121,60 +122,139 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Test fun testPlayerOrdering() { // Test values: key, data, last active time - val playingLocal = Triple("playing local", - DATA.copy(active = true, isPlaying = true, - playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false), - 4500L) - - val playingCast = Triple("playing cast", - DATA.copy(active = true, isPlaying = true, - playbackLocation = MediaData.PLAYBACK_CAST_LOCAL, resumption = false), - 5000L) - - val pausedLocal = Triple("paused local", - DATA.copy(active = true, isPlaying = false, - playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false), - 1000L) - - val pausedCast = Triple("paused cast", - DATA.copy(active = true, isPlaying = false, - playbackLocation = MediaData.PLAYBACK_CAST_LOCAL, resumption = false), - 2000L) - - val playingRcn = Triple("playing RCN", - DATA.copy(active = true, isPlaying = true, - playbackLocation = MediaData.PLAYBACK_CAST_REMOTE, resumption = false), - 5000L) - - val pausedRcn = Triple("paused RCN", - DATA.copy(active = true, isPlaying = false, - playbackLocation = MediaData.PLAYBACK_CAST_REMOTE, resumption = false), - 5000L) - - val active = Triple("active", - DATA.copy(active = true, isPlaying = false, - playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true), - 250L) - - val resume1 = Triple("resume 1", - DATA.copy(active = false, isPlaying = false, - playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true), - 500L) - - val resume2 = Triple("resume 2", - DATA.copy(active = false, isPlaying = false, - playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true), - 1000L) - - val activeMoreRecent = Triple("active more recent", - DATA.copy(active = false, isPlaying = false, - playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true, lastActive = 2L), - 1000L) - - val activeLessRecent = Triple("active less recent", - DATA.copy(active = false, isPlaying = false, - playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true, lastActive = 1L), - 1000L) + val playingLocal = + Triple( + "playing local", + DATA.copy( + active = true, + isPlaying = true, + playbackLocation = MediaData.PLAYBACK_LOCAL, + resumption = false + ), + 4500L + ) + + val playingCast = + Triple( + "playing cast", + DATA.copy( + active = true, + isPlaying = true, + playbackLocation = MediaData.PLAYBACK_CAST_LOCAL, + resumption = false + ), + 5000L + ) + + val pausedLocal = + Triple( + "paused local", + DATA.copy( + active = true, + isPlaying = false, + playbackLocation = MediaData.PLAYBACK_LOCAL, + resumption = false + ), + 1000L + ) + + val pausedCast = + Triple( + "paused cast", + DATA.copy( + active = true, + isPlaying = false, + playbackLocation = MediaData.PLAYBACK_CAST_LOCAL, + resumption = false + ), + 2000L + ) + + val playingRcn = + Triple( + "playing RCN", + DATA.copy( + active = true, + isPlaying = true, + playbackLocation = MediaData.PLAYBACK_CAST_REMOTE, + resumption = false + ), + 5000L + ) + + val pausedRcn = + Triple( + "paused RCN", + DATA.copy( + active = true, + isPlaying = false, + playbackLocation = MediaData.PLAYBACK_CAST_REMOTE, + resumption = false + ), + 5000L + ) + + val active = + Triple( + "active", + DATA.copy( + active = true, + isPlaying = false, + playbackLocation = MediaData.PLAYBACK_LOCAL, + resumption = true + ), + 250L + ) + + val resume1 = + Triple( + "resume 1", + DATA.copy( + active = false, + isPlaying = false, + playbackLocation = MediaData.PLAYBACK_LOCAL, + resumption = true + ), + 500L + ) + + val resume2 = + Triple( + "resume 2", + DATA.copy( + active = false, + isPlaying = false, + playbackLocation = MediaData.PLAYBACK_LOCAL, + resumption = true + ), + 1000L + ) + + val activeMoreRecent = + Triple( + "active more recent", + DATA.copy( + active = false, + isPlaying = false, + playbackLocation = MediaData.PLAYBACK_LOCAL, + resumption = true, + lastActive = 2L + ), + 1000L + ) + + val activeLessRecent = + Triple( + "active less recent", + DATA.copy( + active = false, + isPlaying = false, + playbackLocation = MediaData.PLAYBACK_LOCAL, + resumption = true, + lastActive = 1L + ), + 1000L + ) // Expected ordering for media players: // Actively playing local sessions // Actively playing cast sessions @@ -182,13 +262,28 @@ class MediaCarouselControllerTest : SysuiTestCase() { // RCNs // Resume controls, by last active - val expected = listOf(playingLocal, playingCast, pausedCast, pausedLocal, playingRcn, - pausedRcn, active, resume2, resume1) + val expected = + listOf( + playingLocal, + playingCast, + pausedCast, + pausedLocal, + playingRcn, + pausedRcn, + active, + resume2, + resume1 + ) expected.forEach { clock.setCurrentTimeMillis(it.third) - MediaPlayerData.addMediaPlayer(it.first, it.second.copy(notificationKey = it.first), - panel, clock, isSsReactivated = false) + MediaPlayerData.addMediaPlayer( + it.first, + it.second.copy(notificationKey = it.first), + panel, + clock, + isSsReactivated = false + ) } for ((index, key) in MediaPlayerData.playerKeys().withIndex()) { @@ -205,8 +300,13 @@ class MediaCarouselControllerTest : SysuiTestCase() { testPlayerOrdering() // If smartspace is prioritized - MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel, - true, clock) + MediaPlayerData.addMediaRecommendation( + SMARTSPACE_KEY, + EMPTY_SMARTSPACE_MEDIA_DATA, + panel, + true, + clock + ) // Then it should be shown immediately after any actively playing controls assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec) @@ -218,9 +318,9 @@ class MediaCarouselControllerTest : SysuiTestCase() { // If smartspace is prioritized listener.value.onSmartspaceMediaDataLoaded( - SMARTSPACE_KEY, - EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true), - true + SMARTSPACE_KEY, + EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true), + true ) // Then it should be shown immediately after any actively playing controls @@ -233,8 +333,13 @@ class MediaCarouselControllerTest : SysuiTestCase() { testPlayerOrdering() // If smartspace is not prioritized - MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel, - false, clock) + MediaPlayerData.addMediaRecommendation( + SMARTSPACE_KEY, + EMPTY_SMARTSPACE_MEDIA_DATA, + panel, + false, + clock + ) // Then it should be shown at the end of the carousel's active entries val idx = MediaPlayerData.playerKeys().count { it.data.active } - 1 @@ -245,25 +350,36 @@ class MediaCarouselControllerTest : SysuiTestCase() { fun testPlayingExistingMediaPlayerFromCarousel_visibleMediaPlayersNotUpdated() { testPlayerOrdering() // playing paused player - listener.value.onMediaDataLoaded("paused local", - "paused local", - DATA.copy(active = true, isPlaying = true, - playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false)) - listener.value.onMediaDataLoaded("playing local", - "playing local", - DATA.copy(active = true, isPlaying = false, - playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true) + listener.value.onMediaDataLoaded( + "paused local", + "paused local", + DATA.copy( + active = true, + isPlaying = true, + playbackLocation = MediaData.PLAYBACK_LOCAL, + resumption = false + ) + ) + listener.value.onMediaDataLoaded( + "playing local", + "playing local", + DATA.copy( + active = true, + isPlaying = false, + playbackLocation = MediaData.PLAYBACK_LOCAL, + resumption = true + ) ) assertEquals( - MediaPlayerData.getMediaPlayerIndex("paused local"), - mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex + MediaPlayerData.getMediaPlayerIndex("paused local"), + mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex ) // paused player order should stays the same in visibleMediaPLayer map. // paused player order should be first in mediaPlayer map. assertEquals( - MediaPlayerData.visiblePlayerKeys().elementAt(3), - MediaPlayerData.playerKeys().elementAt(0) + MediaPlayerData.visiblePlayerKeys().elementAt(3), + MediaPlayerData.playerKeys().elementAt(0) ) } @Test @@ -285,7 +401,8 @@ class MediaCarouselControllerTest : SysuiTestCase() { mediaCarouselController.onDesiredLocationChanged( MediaHierarchyManager.LOCATION_QS, mediaHostState, - animate = false) + animate = false + ) verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QS) } @@ -294,7 +411,8 @@ class MediaCarouselControllerTest : SysuiTestCase() { mediaCarouselController.onDesiredLocationChanged( MediaHierarchyManager.LOCATION_QQS, mediaHostState, - animate = false) + animate = false + ) verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS) } @@ -303,7 +421,8 @@ class MediaCarouselControllerTest : SysuiTestCase() { mediaCarouselController.onDesiredLocationChanged( MediaHierarchyManager.LOCATION_LOCKSCREEN, mediaHostState, - animate = false) + animate = false + ) verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN) } @@ -312,7 +431,8 @@ class MediaCarouselControllerTest : SysuiTestCase() { mediaCarouselController.onDesiredLocationChanged( MediaHierarchyManager.LOCATION_DREAM_OVERLAY, mediaHostState, - animate = false) + animate = false + ) verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY) } @@ -321,10 +441,8 @@ class MediaCarouselControllerTest : SysuiTestCase() { val packageName = "smartspace package" val instanceId = InstanceId.fakeInstanceId(123) - val smartspaceData = EMPTY_SMARTSPACE_MEDIA_DATA.copy( - packageName = packageName, - instanceId = instanceId - ) + val smartspaceData = + EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = packageName, instanceId = instanceId) MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, smartspaceData, panel, true, clock) mediaCarouselController.removePlayer(SMARTSPACE_KEY) @@ -333,64 +451,99 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Test fun testMediaLoaded_ScrollToActivePlayer() { - listener.value.onMediaDataLoaded("playing local", - null, - DATA.copy(active = true, isPlaying = true, - playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false) + listener.value.onMediaDataLoaded( + "playing local", + null, + DATA.copy( + active = true, + isPlaying = true, + playbackLocation = MediaData.PLAYBACK_LOCAL, + resumption = false + ) + ) + listener.value.onMediaDataLoaded( + "paused local", + null, + DATA.copy( + active = true, + isPlaying = false, + playbackLocation = MediaData.PLAYBACK_LOCAL, + resumption = false + ) ) - listener.value.onMediaDataLoaded("paused local", - null, - DATA.copy(active = true, isPlaying = false, - playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false)) // adding a media recommendation card. - listener.value.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, - false) + listener.value.onSmartspaceMediaDataLoaded( + SMARTSPACE_KEY, + EMPTY_SMARTSPACE_MEDIA_DATA, + false + ) mediaCarouselController.shouldScrollToKey = true // switching between media players. - listener.value.onMediaDataLoaded("playing local", - "playing local", - DATA.copy(active = true, isPlaying = false, - playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true) + listener.value.onMediaDataLoaded( + "playing local", + "playing local", + DATA.copy( + active = true, + isPlaying = false, + playbackLocation = MediaData.PLAYBACK_LOCAL, + resumption = true + ) + ) + listener.value.onMediaDataLoaded( + "paused local", + "paused local", + DATA.copy( + active = true, + isPlaying = true, + playbackLocation = MediaData.PLAYBACK_LOCAL, + resumption = false + ) ) - listener.value.onMediaDataLoaded("paused local", - "paused local", - DATA.copy(active = true, isPlaying = true, - playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false)) assertEquals( - MediaPlayerData.getMediaPlayerIndex("paused local"), - mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex + MediaPlayerData.getMediaPlayerIndex("paused local"), + mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex ) } @Test fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() { listener.value.onSmartspaceMediaDataLoaded( - SMARTSPACE_KEY, - EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = "PACKAGE_NAME", isActive = true), - false + SMARTSPACE_KEY, + EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = "PACKAGE_NAME", isActive = true), + false ) - listener.value.onMediaDataLoaded("playing local", - null, - DATA.copy(active = true, isPlaying = true, - playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false) + listener.value.onMediaDataLoaded( + "playing local", + null, + DATA.copy( + active = true, + isPlaying = true, + playbackLocation = MediaData.PLAYBACK_LOCAL, + resumption = false + ) ) var playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local") assertEquals( - playerIndex, - mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex + playerIndex, + mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex ) assertEquals(playerIndex, 0) // Replaying the same media player one more time. // And check that the card stays in its position. mediaCarouselController.shouldScrollToKey = true - listener.value.onMediaDataLoaded("playing local", - null, - DATA.copy(active = true, isPlaying = true, - playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false, - packageName = "PACKAGE_NAME") + listener.value.onMediaDataLoaded( + "playing local", + null, + DATA.copy( + active = true, + isPlaying = true, + playbackLocation = MediaData.PLAYBACK_LOCAL, + resumption = false, + packageName = "PACKAGE_NAME" + ) ) playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local") assertEquals(playerIndex, 0) @@ -423,37 +576,43 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Test fun testGetCurrentVisibleMediaContentIntent() { val clickIntent1 = mock(PendingIntent::class.java) - val player1 = Triple("player1", - DATA.copy(clickIntent = clickIntent1), - 1000L) + val player1 = Triple("player1", DATA.copy(clickIntent = clickIntent1), 1000L) clock.setCurrentTimeMillis(player1.third) - MediaPlayerData.addMediaPlayer(player1.first, - player1.second.copy(notificationKey = player1.first), - panel, clock, isSsReactivated = false) + MediaPlayerData.addMediaPlayer( + player1.first, + player1.second.copy(notificationKey = player1.first), + panel, + clock, + isSsReactivated = false + ) assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent1) val clickIntent2 = mock(PendingIntent::class.java) - val player2 = Triple("player2", - DATA.copy(clickIntent = clickIntent2), - 2000L) + val player2 = Triple("player2", DATA.copy(clickIntent = clickIntent2), 2000L) clock.setCurrentTimeMillis(player2.third) - MediaPlayerData.addMediaPlayer(player2.first, - player2.second.copy(notificationKey = player2.first), - panel, clock, isSsReactivated = false) + MediaPlayerData.addMediaPlayer( + player2.first, + player2.second.copy(notificationKey = player2.first), + panel, + clock, + isSsReactivated = false + ) // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is // added to the front because it was active more recently. assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2) val clickIntent3 = mock(PendingIntent::class.java) - val player3 = Triple("player3", - DATA.copy(clickIntent = clickIntent3), - 500L) + val player3 = Triple("player3", DATA.copy(clickIntent = clickIntent3), 500L) clock.setCurrentTimeMillis(player3.third) - MediaPlayerData.addMediaPlayer(player3.first, - player3.second.copy(notificationKey = player3.first), - panel, clock, isSsReactivated = false) + MediaPlayerData.addMediaPlayer( + player3.first, + player3.second.copy(notificationKey = player3.first), + panel, + clock, + isSsReactivated = false + ) // mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is // added to the end because it was active less recently. @@ -463,10 +622,14 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Test fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() { val delta = 0.0001F - val paginationSquishMiddle = TRANSFORM_BEZIER.getInterpolation( - (PAGINATION_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION) - val paginationSquishEnd = TRANSFORM_BEZIER.getInterpolation( - (PAGINATION_DELAY + DURATION) / ANIMATION_BASE_DURATION) + val paginationSquishMiddle = + TRANSFORM_BEZIER.getInterpolation( + (PAGINATION_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION + ) + val paginationSquishEnd = + TRANSFORM_BEZIER.getInterpolation( + (PAGINATION_DELAY + DURATION) / ANIMATION_BASE_DURATION + ) whenever(mediaHostStatesManager.mediaHostStates) .thenReturn(mutableMapOf(LOCATION_QS to mediaHostState)) whenever(mediaHostState.visible).thenReturn(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt index faa76f423fae..584305334b6f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt @@ -60,7 +60,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.bluetooth.BroadcastDialogController import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.media.controls.MediaTestUtils -import com.android.systemui.media.controls.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA import com.android.systemui.media.controls.models.GutsViewHolder import com.android.systemui.media.controls.models.player.MediaAction import com.android.systemui.media.controls.models.player.MediaButton @@ -68,9 +67,10 @@ import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.player.MediaDeviceData import com.android.systemui.media.controls.models.player.MediaViewHolder import com.android.systemui.media.controls.models.player.SeekBarViewModel +import com.android.systemui.media.controls.models.recommendation.KEY_SMARTSPACE_APP_NAME import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData -import com.android.systemui.media.controls.models.recommendation.KEY_SMARTSPACE_APP_NAME +import com.android.systemui.media.controls.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.media.dialog.MediaOutputDialogFactory @@ -177,8 +177,8 @@ public class MediaControlPanelTest : SysuiTestCase() { private lateinit var session: MediaSession private lateinit var device: MediaDeviceData - private val disabledDevice = MediaDeviceData(false, null, DISABLED_DEVICE_NAME, null, - showBroadcastButton = false) + private val disabledDevice = + MediaDeviceData(false, null, DISABLED_DEVICE_NAME, null, showBroadcastButton = false) private lateinit var mediaData: MediaData private val clock = FakeSystemClock() @Mock private lateinit var logger: MediaUiEventLogger @@ -225,24 +225,27 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE) context.setMockPackageManager(packageManager) - player = object : MediaControlPanel( - context, - bgExecutor, - mainExecutor, - activityStarter, - broadcastSender, - mediaViewController, - seekBarViewModel, - Lazy { mediaDataManager }, - mediaOutputDialogFactory, - mediaCarouselController, - falsingManager, - clock, - logger, - keyguardStateController, - activityIntentHelper, - lockscreenUserManager, - broadcastDialogController) { + player = + object : + MediaControlPanel( + context, + bgExecutor, + mainExecutor, + activityStarter, + broadcastSender, + mediaViewController, + seekBarViewModel, + Lazy { mediaDataManager }, + mediaOutputDialogFactory, + mediaCarouselController, + falsingManager, + clock, + logger, + keyguardStateController, + activityIntentHelper, + lockscreenUserManager, + broadcastDialogController + ) { override fun loadAnimator( animId: Int, otionInterpolator: Interpolator, @@ -263,18 +266,20 @@ public class MediaControlPanelTest : SysuiTestCase() { // Set valid recommendation data val extras = Bundle() extras.putString(KEY_SMARTSPACE_APP_NAME, REC_APP_NAME) - val intent = Intent().apply { - putExtras(extras) - setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - } + val intent = + Intent().apply { + putExtras(extras) + setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } whenever(smartspaceAction.intent).thenReturn(intent) whenever(smartspaceAction.extras).thenReturn(extras) - smartspaceData = EMPTY_SMARTSPACE_MEDIA_DATA.copy( - packageName = PACKAGE, - instanceId = instanceId, - recommendations = listOf(smartspaceAction, smartspaceAction, smartspaceAction), - cardAction = smartspaceAction - ) + smartspaceData = + EMPTY_SMARTSPACE_MEDIA_DATA.copy( + packageName = PACKAGE, + instanceId = instanceId, + recommendations = listOf(smartspaceAction, smartspaceAction, smartspaceAction), + cardAction = smartspaceAction + ) } private fun initGutsViewHolderMocks() { @@ -292,36 +297,39 @@ public class MediaControlPanelTest : SysuiTestCase() { } private fun initDeviceMediaData(shouldShowBroadcastButton: Boolean, name: String) { - device = MediaDeviceData(true, null, name, null, - showBroadcastButton = shouldShowBroadcastButton) + device = + MediaDeviceData(true, null, name, null, showBroadcastButton = shouldShowBroadcastButton) // Create media session - val metadataBuilder = MediaMetadata.Builder().apply { - putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST) - putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE) - } - val playbackBuilder = PlaybackState.Builder().apply { - setState(PlaybackState.STATE_PAUSED, 6000L, 1f) - setActions(PlaybackState.ACTION_PLAY) - } - session = MediaSession(context, SESSION_KEY).apply { - setMetadata(metadataBuilder.build()) - setPlaybackState(playbackBuilder.build()) - } + val metadataBuilder = + MediaMetadata.Builder().apply { + putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST) + putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE) + } + val playbackBuilder = + PlaybackState.Builder().apply { + setState(PlaybackState.STATE_PAUSED, 6000L, 1f) + setActions(PlaybackState.ACTION_PLAY) + } + session = + MediaSession(context, SESSION_KEY).apply { + setMetadata(metadataBuilder.build()) + setPlaybackState(playbackBuilder.build()) + } session.setActive(true) - mediaData = MediaTestUtils.emptyMediaData.copy( + mediaData = + MediaTestUtils.emptyMediaData.copy( artist = ARTIST, song = TITLE, packageName = PACKAGE, token = session.sessionToken, device = device, - instanceId = instanceId) + instanceId = instanceId + ) } - /** - * Initialize elements in media view holder - */ + /** Initialize elements in media view holder */ private fun initMediaViewHolderMocks() { whenever(seekBarViewModel.progress).thenReturn(seekBarData) @@ -362,7 +370,8 @@ public class MediaControlPanelTest : SysuiTestCase() { action1.id, action2.id, action3.id, - action4.id) + action4.id + ) } whenever(viewHolder.player).thenReturn(view) @@ -407,9 +416,7 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(viewHolder.actionsTopBarrier).thenReturn(actionsTopBarrier) } - /** - * Initialize elements for the recommendation view holder - */ + /** Initialize elements for the recommendation view holder */ private fun initRecommendationViewHolderMocks() { recTitle1 = TextView(context) recTitle2 = TextView(context) @@ -432,9 +439,8 @@ public class MediaControlPanelTest : SysuiTestCase() { .thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3)) whenever(recommendationViewHolder.mediaTitles) .thenReturn(listOf(recTitle1, recTitle2, recTitle3)) - whenever(recommendationViewHolder.mediaSubtitles).thenReturn( - listOf(recSubtitle1, recSubtitle2, recSubtitle3) - ) + whenever(recommendationViewHolder.mediaSubtitles) + .thenReturn(listOf(recSubtitle1, recSubtitle2, recSubtitle3)) whenever(recommendationViewHolder.gutsViewHolder).thenReturn(gutsViewHolder) @@ -466,12 +472,13 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindSemanticActions() { val icon = context.getDrawable(android.R.drawable.ic_media_play) val bg = context.getDrawable(R.drawable.qs_media_round_button_background) - val semanticActions = MediaButton( - playOrPause = MediaAction(icon, Runnable {}, "play", bg), - nextOrCustom = MediaAction(icon, Runnable {}, "next", bg), - custom0 = MediaAction(icon, null, "custom 0", bg), - custom1 = MediaAction(icon, null, "custom 1", bg) - ) + val semanticActions = + MediaButton( + playOrPause = MediaAction(icon, Runnable {}, "play", bg), + nextOrCustom = MediaAction(icon, Runnable {}, "next", bg), + custom0 = MediaAction(icon, null, "custom 0", bg), + custom1 = MediaAction(icon, null, "custom 1", bg) + ) val state = mediaData.copy(semanticActions = semanticActions) player.attachPlayer(viewHolder) player.bindPlayer(state, PACKAGE) @@ -514,15 +521,16 @@ public class MediaControlPanelTest : SysuiTestCase() { val bg = context.getDrawable(R.drawable.qs_media_round_button_background) // Setup button state: no prev or next button and their slots reserved - val semanticActions = MediaButton( - playOrPause = MediaAction(icon, Runnable {}, "play", bg), - nextOrCustom = null, - prevOrCustom = null, - custom0 = MediaAction(icon, null, "custom 0", bg), - custom1 = MediaAction(icon, null, "custom 1", bg), - false, - true - ) + val semanticActions = + MediaButton( + playOrPause = MediaAction(icon, Runnable {}, "play", bg), + nextOrCustom = null, + prevOrCustom = null, + custom0 = MediaAction(icon, null, "custom 0", bg), + custom1 = MediaAction(icon, null, "custom 1", bg), + false, + true + ) val state = mediaData.copy(semanticActions = semanticActions) player.attachPlayer(viewHolder) @@ -543,15 +551,16 @@ public class MediaControlPanelTest : SysuiTestCase() { val bg = context.getDrawable(R.drawable.qs_media_round_button_background) // Setup button state: no prev or next button and their slots reserved - val semanticActions = MediaButton( - playOrPause = MediaAction(icon, Runnable {}, "play", bg), - nextOrCustom = null, - prevOrCustom = null, - custom0 = MediaAction(icon, null, "custom 0", bg), - custom1 = MediaAction(icon, null, "custom 1", bg), - true, - false - ) + val semanticActions = + MediaButton( + playOrPause = MediaAction(icon, Runnable {}, "play", bg), + nextOrCustom = null, + prevOrCustom = null, + custom0 = MediaAction(icon, null, "custom 0", bg), + custom1 = MediaAction(icon, null, "custom 1", bg), + true, + false + ) val state = mediaData.copy(semanticActions = semanticActions) player.attachPlayer(viewHolder) @@ -659,10 +668,11 @@ public class MediaControlPanelTest : SysuiTestCase() { useRealConstraintSets() val icon = context.getDrawable(android.R.drawable.ic_media_play) - val semanticActions = MediaButton( - playOrPause = MediaAction(icon, Runnable {}, "play", null), - nextOrCustom = MediaAction(icon, Runnable {}, "next", null) - ) + val semanticActions = + MediaButton( + playOrPause = MediaAction(icon, Runnable {}, "play", null), + nextOrCustom = MediaAction(icon, Runnable {}, "next", null) + ) val state = mediaData.copy(semanticActions = semanticActions) player.attachPlayer(viewHolder) @@ -732,9 +742,8 @@ public class MediaControlPanelTest : SysuiTestCase() { useRealConstraintSets() val icon = context.getDrawable(android.R.drawable.ic_media_play) - val semanticActions = MediaButton( - nextOrCustom = MediaAction(icon, Runnable {}, "next", null) - ) + val semanticActions = + MediaButton(nextOrCustom = MediaAction(icon, Runnable {}, "next", null)) val state = mediaData.copy(semanticActions = semanticActions) player.attachPlayer(viewHolder) @@ -749,10 +758,11 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun bind_notScrubbing_scrubbingViewsGone() { val icon = context.getDrawable(android.R.drawable.ic_media_play) - val semanticActions = MediaButton( - prevOrCustom = MediaAction(icon, {}, "prev", null), - nextOrCustom = MediaAction(icon, {}, "next", null) - ) + val semanticActions = + MediaButton( + prevOrCustom = MediaAction(icon, {}, "prev", null), + nextOrCustom = MediaAction(icon, {}, "next", null) + ) val state = mediaData.copy(semanticActions = semanticActions) player.attachPlayer(viewHolder) @@ -783,10 +793,8 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun setIsScrubbing_noPrevButton_scrubbingTimesNotShown() { val icon = context.getDrawable(android.R.drawable.ic_media_play) - val semanticActions = MediaButton( - prevOrCustom = null, - nextOrCustom = MediaAction(icon, {}, "next", null) - ) + val semanticActions = + MediaButton(prevOrCustom = null, nextOrCustom = MediaAction(icon, {}, "next", null)) val state = mediaData.copy(semanticActions = semanticActions) player.attachPlayer(viewHolder) player.bindPlayer(state, PACKAGE) @@ -803,10 +811,8 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun setIsScrubbing_noNextButton_scrubbingTimesNotShown() { val icon = context.getDrawable(android.R.drawable.ic_media_play) - val semanticActions = MediaButton( - prevOrCustom = MediaAction(icon, {}, "prev", null), - nextOrCustom = null - ) + val semanticActions = + MediaButton(prevOrCustom = MediaAction(icon, {}, "prev", null), nextOrCustom = null) val state = mediaData.copy(semanticActions = semanticActions) player.attachPlayer(viewHolder) player.bindPlayer(state, PACKAGE) @@ -823,10 +829,11 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun setIsScrubbing_true_scrubbingViewsShownAndPrevNextHiddenOnlyInExpanded() { val icon = context.getDrawable(android.R.drawable.ic_media_play) - val semanticActions = MediaButton( - prevOrCustom = MediaAction(icon, {}, "prev", null), - nextOrCustom = MediaAction(icon, {}, "next", null) - ) + val semanticActions = + MediaButton( + prevOrCustom = MediaAction(icon, {}, "prev", null), + nextOrCustom = MediaAction(icon, {}, "next", null) + ) val state = mediaData.copy(semanticActions = semanticActions) player.attachPlayer(viewHolder) player.bindPlayer(state, PACKAGE) @@ -845,10 +852,11 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun setIsScrubbing_trueThenFalse_scrubbingTimeGoneAtEnd() { val icon = context.getDrawable(android.R.drawable.ic_media_play) - val semanticActions = MediaButton( - prevOrCustom = MediaAction(icon, {}, "prev", null), - nextOrCustom = MediaAction(icon, {}, "next", null) - ) + val semanticActions = + MediaButton( + prevOrCustom = MediaAction(icon, {}, "prev", null), + nextOrCustom = MediaAction(icon, {}, "next", null) + ) val state = mediaData.copy(semanticActions = semanticActions) player.attachPlayer(viewHolder) @@ -872,18 +880,20 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindNotificationActions() { val icon = context.getDrawable(android.R.drawable.ic_media_play) val bg = context.getDrawable(R.drawable.qs_media_round_button_background) - val actions = listOf( - MediaAction(icon, Runnable {}, "previous", bg), - MediaAction(icon, Runnable {}, "play", bg), - MediaAction(icon, null, "next", bg), - MediaAction(icon, null, "custom 0", bg), - MediaAction(icon, Runnable {}, "custom 1", bg) - ) - val state = mediaData.copy( - actions = actions, - actionsToShowInCompact = listOf(1, 2), - semanticActions = null - ) + val actions = + listOf( + MediaAction(icon, Runnable {}, "previous", bg), + MediaAction(icon, Runnable {}, "play", bg), + MediaAction(icon, null, "next", bg), + MediaAction(icon, null, "custom 0", bg), + MediaAction(icon, Runnable {}, "custom 1", bg) + ) + val state = + mediaData.copy( + actions = actions, + actionsToShowInCompact = listOf(1, 2), + semanticActions = null + ) player.attachPlayer(viewHolder) player.bindPlayer(state, PACKAGE) @@ -931,15 +941,12 @@ public class MediaControlPanelTest : SysuiTestCase() { val icon = context.getDrawable(R.drawable.ic_media_play) val bg = context.getDrawable(R.drawable.ic_media_play_container) - val semanticActions0 = MediaButton( - playOrPause = MediaAction(mockAvd0, Runnable {}, "play", null) - ) - val semanticActions1 = MediaButton( - playOrPause = MediaAction(mockAvd1, Runnable {}, "pause", null) - ) - val semanticActions2 = MediaButton( - playOrPause = MediaAction(mockAvd2, Runnable {}, "loading", null) - ) + val semanticActions0 = + MediaButton(playOrPause = MediaAction(mockAvd0, Runnable {}, "play", null)) + val semanticActions1 = + MediaButton(playOrPause = MediaAction(mockAvd1, Runnable {}, "pause", null)) + val semanticActions2 = + MediaButton(playOrPause = MediaAction(mockAvd2, Runnable {}, "loading", null)) val state0 = mediaData.copy(semanticActions = semanticActions0) val state1 = mediaData.copy(semanticActions = semanticActions1) val state2 = mediaData.copy(semanticActions = semanticActions2) @@ -1102,11 +1109,10 @@ public class MediaControlPanelTest : SysuiTestCase() { val mockAvd0 = mock(AnimatedVectorDrawable::class.java) whenever(mockAvd0.mutate()).thenReturn(mockAvd0) - val semanticActions0 = MediaButton( - playOrPause = MediaAction(mockAvd0, Runnable {}, "play", null) - ) - val state = mediaData.copy(resumption = true, semanticActions = semanticActions0, - isPlaying = false) + val semanticActions0 = + MediaButton(playOrPause = MediaAction(mockAvd0, Runnable {}, "play", null)) + val state = + mediaData.copy(resumption = true, semanticActions = semanticActions0, isPlaying = false) player.attachPlayer(viewHolder) player.bindPlayer(state, PACKAGE) assertThat(seamlessText.getText()).isEqualTo(APP_NAME) @@ -1445,9 +1451,8 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun actionPlayPauseClick_isLogged() { - val semanticActions = MediaButton( - playOrPause = MediaAction(null, Runnable {}, "play", null) - ) + val semanticActions = + MediaButton(playOrPause = MediaAction(null, Runnable {}, "play", null)) val data = mediaData.copy(semanticActions = semanticActions) player.attachPlayer(viewHolder) @@ -1459,9 +1464,8 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun actionPrevClick_isLogged() { - val semanticActions = MediaButton( - prevOrCustom = MediaAction(null, Runnable {}, "previous", null) - ) + val semanticActions = + MediaButton(prevOrCustom = MediaAction(null, Runnable {}, "previous", null)) val data = mediaData.copy(semanticActions = semanticActions) player.attachPlayer(viewHolder) @@ -1473,9 +1477,8 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun actionNextClick_isLogged() { - val semanticActions = MediaButton( - nextOrCustom = MediaAction(null, Runnable {}, "next", null) - ) + val semanticActions = + MediaButton(nextOrCustom = MediaAction(null, Runnable {}, "next", null)) val data = mediaData.copy(semanticActions = semanticActions) player.attachPlayer(viewHolder) @@ -1487,9 +1490,8 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun actionCustom0Click_isLogged() { - val semanticActions = MediaButton( - custom0 = MediaAction(null, Runnable {}, "custom 0", null) - ) + val semanticActions = + MediaButton(custom0 = MediaAction(null, Runnable {}, "custom 0", null)) val data = mediaData.copy(semanticActions = semanticActions) player.attachPlayer(viewHolder) @@ -1501,9 +1503,8 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun actionCustom1Click_isLogged() { - val semanticActions = MediaButton( - custom1 = MediaAction(null, Runnable {}, "custom 1", null) - ) + val semanticActions = + MediaButton(custom1 = MediaAction(null, Runnable {}, "custom 1", null)) val data = mediaData.copy(semanticActions = semanticActions) player.attachPlayer(viewHolder) @@ -1515,13 +1516,14 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun actionCustom2Click_isLogged() { - val actions = listOf( - MediaAction(null, Runnable {}, "action 0", null), - MediaAction(null, Runnable {}, "action 1", null), - MediaAction(null, Runnable {}, "action 2", null), - MediaAction(null, Runnable {}, "action 3", null), - MediaAction(null, Runnable {}, "action 4", null) - ) + val actions = + listOf( + MediaAction(null, Runnable {}, "action 0", null), + MediaAction(null, Runnable {}, "action 1", null), + MediaAction(null, Runnable {}, "action 2", null), + MediaAction(null, Runnable {}, "action 3", null), + MediaAction(null, Runnable {}, "action 4", null) + ) val data = mediaData.copy(actions = actions) player.attachPlayer(viewHolder) @@ -1533,13 +1535,14 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun actionCustom3Click_isLogged() { - val actions = listOf( - MediaAction(null, Runnable {}, "action 0", null), - MediaAction(null, Runnable {}, "action 1", null), - MediaAction(null, Runnable {}, "action 2", null), - MediaAction(null, Runnable {}, "action 3", null), - MediaAction(null, Runnable {}, "action 4", null) - ) + val actions = + listOf( + MediaAction(null, Runnable {}, "action 0", null), + MediaAction(null, Runnable {}, "action 1", null), + MediaAction(null, Runnable {}, "action 2", null), + MediaAction(null, Runnable {}, "action 3", null), + MediaAction(null, Runnable {}, "action 4", null) + ) val data = mediaData.copy(actions = actions) player.attachPlayer(viewHolder) @@ -1551,13 +1554,14 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun actionCustom4Click_isLogged() { - val actions = listOf( - MediaAction(null, Runnable {}, "action 0", null), - MediaAction(null, Runnable {}, "action 1", null), - MediaAction(null, Runnable {}, "action 2", null), - MediaAction(null, Runnable {}, "action 3", null), - MediaAction(null, Runnable {}, "action 4", null) - ) + val actions = + listOf( + MediaAction(null, Runnable {}, "action 0", null), + MediaAction(null, Runnable {}, "action 1", null), + MediaAction(null, Runnable {}, "action 2", null), + MediaAction(null, Runnable {}, "action 3", null), + MediaAction(null, Runnable {}, "action 4", null) + ) val data = mediaData.copy(actions = actions) player.attachPlayer(viewHolder) @@ -1621,8 +1625,7 @@ public class MediaControlPanelTest : SysuiTestCase() { // THEN it shows without dismissing keyguard first captor.value.onClick(viewHolder.player) - verify(activityStarter).startActivity(eq(clickIntent), eq(true), - nullable(), eq(true)) + verify(activityStarter).startActivity(eq(clickIntent), eq(true), nullable(), eq(true)) } @Test @@ -1710,20 +1713,22 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindRecommendation_listHasTooFewRecs_notDisplayed() { player.attachRecommendation(recommendationViewHolder) val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata) - val data = smartspaceData.copy( - recommendations = listOf( - SmartspaceAction.Builder("id1", "title1") - .setSubtitle("subtitle1") - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id2", "title2") - .setSubtitle("subtitle2") - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), + val data = + smartspaceData.copy( + recommendations = + listOf( + SmartspaceAction.Builder("id1", "title1") + .setSubtitle("subtitle1") + .setIcon(icon) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id2", "title2") + .setSubtitle("subtitle2") + .setIcon(icon) + .setExtras(Bundle.EMPTY) + .build(), + ) ) - ) player.bindRecommendation(data) @@ -1735,30 +1740,32 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindRecommendation_listHasTooFewRecsWithIcons_notDisplayed() { player.attachRecommendation(recommendationViewHolder) val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata) - val data = smartspaceData.copy( - recommendations = listOf( - SmartspaceAction.Builder("id1", "title1") - .setSubtitle("subtitle1") - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id2", "title2") - .setSubtitle("subtitle2") - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id2", "empty icon 1") - .setSubtitle("subtitle2") - .setIcon(null) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id2", "empty icon 2") - .setSubtitle("subtitle2") - .setIcon(null) - .setExtras(Bundle.EMPTY) - .build(), + val data = + smartspaceData.copy( + recommendations = + listOf( + SmartspaceAction.Builder("id1", "title1") + .setSubtitle("subtitle1") + .setIcon(icon) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id2", "title2") + .setSubtitle("subtitle2") + .setIcon(icon) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id2", "empty icon 1") + .setSubtitle("subtitle2") + .setIcon(null) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id2", "empty icon 2") + .setSubtitle("subtitle2") + .setIcon(null) + .setExtras(Bundle.EMPTY) + .build(), + ) ) - ) player.bindRecommendation(data) @@ -1778,25 +1785,27 @@ public class MediaControlPanelTest : SysuiTestCase() { val subtitle3 = "Subtitle3" val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata) - val data = smartspaceData.copy( - recommendations = listOf( - SmartspaceAction.Builder("id1", title1) - .setSubtitle(subtitle1) - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id2", title2) - .setSubtitle(subtitle2) - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id3", title3) - .setSubtitle(subtitle3) - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build() + val data = + smartspaceData.copy( + recommendations = + listOf( + SmartspaceAction.Builder("id1", title1) + .setSubtitle(subtitle1) + .setIcon(icon) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id2", title2) + .setSubtitle(subtitle2) + .setIcon(icon) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id3", title3) + .setSubtitle(subtitle3) + .setIcon(icon) + .setExtras(Bundle.EMPTY) + .build() + ) ) - ) player.bindRecommendation(data) assertThat(recTitle1.text).isEqualTo(title1) @@ -1811,15 +1820,17 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindRecommendation_noTitle_subtitleNotShown() { player.attachRecommendation(recommendationViewHolder) - val data = smartspaceData.copy( - recommendations = listOf( - SmartspaceAction.Builder("id1", "") - .setSubtitle("fake subtitle") - .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)) - .setExtras(Bundle.EMPTY) - .build() + val data = + smartspaceData.copy( + recommendations = + listOf( + SmartspaceAction.Builder("id1", "") + .setSubtitle("fake subtitle") + .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)) + .setExtras(Bundle.EMPTY) + .build() + ) ) - ) player.bindRecommendation(data) assertThat(recSubtitle1.text).isEqualTo("") @@ -1831,25 +1842,27 @@ public class MediaControlPanelTest : SysuiTestCase() { player.attachRecommendation(recommendationViewHolder) val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata) - val data = smartspaceData.copy( - recommendations = listOf( - SmartspaceAction.Builder("id1", "") - .setSubtitle("fake subtitle") - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id2", "title2") - .setSubtitle("fake subtitle") - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id3", "") - .setSubtitle("fake subtitle") - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build() + val data = + smartspaceData.copy( + recommendations = + listOf( + SmartspaceAction.Builder("id1", "") + .setSubtitle("fake subtitle") + .setIcon(icon) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id2", "title2") + .setSubtitle("fake subtitle") + .setIcon(icon) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id3", "") + .setSubtitle("fake subtitle") + .setIcon(icon) + .setExtras(Bundle.EMPTY) + .build() + ) ) - ) player.bindRecommendation(data) assertThat(expandedSet.getVisibility(recTitle1.id)).isEqualTo(ConstraintSet.VISIBLE) @@ -1863,25 +1876,27 @@ public class MediaControlPanelTest : SysuiTestCase() { player.attachRecommendation(recommendationViewHolder) val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata) - val data = smartspaceData.copy( - recommendations = listOf( - SmartspaceAction.Builder("id1", "") - .setSubtitle("") - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id2", "title2") - .setSubtitle("") - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id3", "title3") - .setSubtitle("subtitle3") - .setIcon(icon) - .setExtras(Bundle.EMPTY) - .build() + val data = + smartspaceData.copy( + recommendations = + listOf( + SmartspaceAction.Builder("id1", "") + .setSubtitle("") + .setIcon(icon) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id2", "title2") + .setSubtitle("") + .setIcon(icon) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id3", "title3") + .setSubtitle("subtitle3") + .setIcon(icon) + .setExtras(Bundle.EMPTY) + .build() + ) ) - ) player.bindRecommendation(data) assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.VISIBLE) @@ -1893,25 +1908,27 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindRecommendation_noneHaveSubtitles_subtitleViewsGone() { useRealConstraintSets() player.attachRecommendation(recommendationViewHolder) - val data = smartspaceData.copy( - recommendations = listOf( - SmartspaceAction.Builder("id1", "title1") - .setSubtitle("") - .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id2", "title2") - .setSubtitle("") - .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm)) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id3", "title3") - .setSubtitle("") - .setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata)) - .setExtras(Bundle.EMPTY) - .build() + val data = + smartspaceData.copy( + recommendations = + listOf( + SmartspaceAction.Builder("id1", "title1") + .setSubtitle("") + .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id2", "title2") + .setSubtitle("") + .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm)) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id3", "title3") + .setSubtitle("") + .setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata)) + .setExtras(Bundle.EMPTY) + .build() + ) ) - ) player.bindRecommendation(data) @@ -1924,25 +1941,27 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindRecommendation_noneHaveTitles_titleAndSubtitleViewsGone() { useRealConstraintSets() player.attachRecommendation(recommendationViewHolder) - val data = smartspaceData.copy( - recommendations = listOf( - SmartspaceAction.Builder("id1", "") - .setSubtitle("subtitle1") - .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id2", "") - .setSubtitle("subtitle2") - .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm)) - .setExtras(Bundle.EMPTY) - .build(), - SmartspaceAction.Builder("id3", "") - .setSubtitle("subtitle3") - .setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata)) - .setExtras(Bundle.EMPTY) - .build() + val data = + smartspaceData.copy( + recommendations = + listOf( + SmartspaceAction.Builder("id1", "") + .setSubtitle("subtitle1") + .setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id2", "") + .setSubtitle("subtitle2") + .setIcon(Icon.createWithResource(context, R.drawable.ic_alarm)) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id3", "") + .setSubtitle("subtitle3") + .setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata)) + .setExtras(Bundle.EMPTY) + .build() + ) ) - ) player.bindRecommendation(data) @@ -1955,20 +1974,23 @@ public class MediaControlPanelTest : SysuiTestCase() { } private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener = - withArgCaptor { verify(seekBarViewModel).setScrubbingChangeListener(capture()) } + withArgCaptor { + verify(seekBarViewModel).setScrubbingChangeListener(capture()) + } - private fun getEnabledChangeListener(): SeekBarViewModel.EnabledChangeListener = - withArgCaptor { verify(seekBarViewModel).setEnabledChangeListener(capture()) } + private fun getEnabledChangeListener(): SeekBarViewModel.EnabledChangeListener = withArgCaptor { + verify(seekBarViewModel).setEnabledChangeListener(capture()) + } /** - * Update our test to use real ConstraintSets instead of mocks. + * Update our test to use real ConstraintSets instead of mocks. * - * Some item visibilities, such as the seekbar visibility, are dependent on other action's - * visibilities. If we use mocks for the ConstraintSets, then action visibility changes are - * just thrown away instead of being saved for reference later. This method sets us up to use - * ConstraintSets so that we do save visibility changes. + * Some item visibilities, such as the seekbar visibility, are dependent on other action's + * visibilities. If we use mocks for the ConstraintSets, then action visibility changes are just + * thrown away instead of being saved for reference later. This method sets us up to use + * ConstraintSets so that we do save visibility changes. * - * TODO(b/229740380): Can/should we use real expanded and collapsed sets for all tests? + * TODO(b/229740380): Can/should we use real expanded and collapsed sets for all tests? */ private fun useRealConstraintSets() { expandedSet = ConstraintSet() diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt index 2a46f037431f..071604dc5790 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt @@ -84,10 +84,8 @@ class MediaHierarchyManagerTest : SysuiTestCase() { private lateinit var statusBarCallback: ArgumentCaptor<(StatusBarStateController.StateListener)> @Captor private lateinit var dreamOverlayCallback: - ArgumentCaptor<(DreamOverlayStateController.Callback)> - @JvmField - @Rule - val mockito = MockitoJUnit.rule() + ArgumentCaptor<(DreamOverlayStateController.Callback)> + @JvmField @Rule val mockito = MockitoJUnit.rule() private lateinit var mediaHierarchyManager: MediaHierarchyManager private lateinit var mediaFrame: ViewGroup private val configurationController = FakeConfigurationController() @@ -98,13 +96,15 @@ class MediaHierarchyManagerTest : SysuiTestCase() { @Before fun setup() { - context.getOrCreateTestableResources().addOverride( - R.bool.config_use_split_notification_shade, false) + context + .getOrCreateTestableResources() + .addOverride(R.bool.config_use_split_notification_shade, false) mediaFrame = FrameLayout(context) testableLooper = TestableLooper.get(this) fakeHandler = FakeHandler(testableLooper.looper) whenever(mediaCarouselController.mediaFrame).thenReturn(mediaFrame) - mediaHierarchyManager = MediaHierarchyManager( + mediaHierarchyManager = + MediaHierarchyManager( context, statusBarStateController, keyguardStateController, @@ -116,7 +116,8 @@ class MediaHierarchyManagerTest : SysuiTestCase() { wakefulnessLifecycle, notifPanelEvents, settings, - fakeHandler,) + fakeHandler, + ) verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture()) verify(statusBarStateController).addCallback(statusBarCallback.capture()) verify(dreamOverlayStateController).addCallback(dreamOverlayCallback.capture()) @@ -125,7 +126,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS, QQS_TOP) whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE) whenever(mediaCarouselController.mediaCarouselScrollHandler) - .thenReturn(mediaCarouselScrollHandler) + .thenReturn(mediaCarouselScrollHandler) val observer = wakefullnessObserver.value assertNotNull("lifecycle observer wasn't registered", observer) observer.onFinishedWakingUp() @@ -151,30 +152,53 @@ class MediaHierarchyManagerTest : SysuiTestCase() { fun testBlockedWhenScreenTurningOff() { // Let's set it onto QS: mediaHierarchyManager.qsExpansion = 1.0f - verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(), - any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong()) + verify(mediaCarouselController) + .onDesiredLocationChanged( + ArgumentMatchers.anyInt(), + any(MediaHostState::class.java), + anyBoolean(), + anyLong(), + anyLong() + ) val observer = wakefullnessObserver.value assertNotNull("lifecycle observer wasn't registered", observer) observer.onStartedGoingToSleep() clearInvocations(mediaCarouselController) mediaHierarchyManager.qsExpansion = 0.0f verify(mediaCarouselController, times(0)) - .onDesiredLocationChanged(ArgumentMatchers.anyInt(), - any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong()) + .onDesiredLocationChanged( + ArgumentMatchers.anyInt(), + any(MediaHostState::class.java), + anyBoolean(), + anyLong(), + anyLong() + ) } @Test fun testAllowedWhenNotTurningOff() { // Let's set it onto QS: mediaHierarchyManager.qsExpansion = 1.0f - verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(), - any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong()) + verify(mediaCarouselController) + .onDesiredLocationChanged( + ArgumentMatchers.anyInt(), + any(MediaHostState::class.java), + anyBoolean(), + anyLong(), + anyLong() + ) val observer = wakefullnessObserver.value assertNotNull("lifecycle observer wasn't registered", observer) clearInvocations(mediaCarouselController) mediaHierarchyManager.qsExpansion = 0.0f - verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(), - any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong()) + verify(mediaCarouselController) + .onDesiredLocationChanged( + ArgumentMatchers.anyInt(), + any(MediaHostState::class.java), + anyBoolean(), + anyLong(), + anyLong() + ) } @Test @@ -183,22 +207,26 @@ class MediaHierarchyManagerTest : SysuiTestCase() { // Let's transition all the way to full shade mediaHierarchyManager.setTransitionToFullShadeAmount(100000f) - verify(mediaCarouselController).onDesiredLocationChanged( - eq(MediaHierarchyManager.LOCATION_QQS), - any(MediaHostState::class.java), - eq(false), - anyLong(), - anyLong()) + verify(mediaCarouselController) + .onDesiredLocationChanged( + eq(MediaHierarchyManager.LOCATION_QQS), + any(MediaHostState::class.java), + eq(false), + anyLong(), + anyLong() + ) clearInvocations(mediaCarouselController) // Let's go back to the lock screen mediaHierarchyManager.setTransitionToFullShadeAmount(0.0f) - verify(mediaCarouselController).onDesiredLocationChanged( - eq(MediaHierarchyManager.LOCATION_LOCKSCREEN), - any(MediaHostState::class.java), - eq(false), - anyLong(), - anyLong()) + verify(mediaCarouselController) + .onDesiredLocationChanged( + eq(MediaHierarchyManager.LOCATION_LOCKSCREEN), + any(MediaHostState::class.java), + eq(false), + anyLong(), + anyLong() + ) // Let's make sure alpha is set mediaHierarchyManager.setTransitionToFullShadeAmount(2.0f) @@ -302,7 +330,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { val expectedTranslation = LOCKSCREEN_TOP - QS_TOP assertThat(mediaHierarchyManager.getGuidedTransformationTranslationY()) - .isEqualTo(expectedTranslation) + .isEqualTo(expectedTranslation) } @Test @@ -343,27 +371,31 @@ class MediaHierarchyManagerTest : SysuiTestCase() { fun testDream() { goToDream() setMediaDreamComplicationEnabled(true) - verify(mediaCarouselController).onDesiredLocationChanged( + verify(mediaCarouselController) + .onDesiredLocationChanged( eq(MediaHierarchyManager.LOCATION_DREAM_OVERLAY), nullable(), eq(false), anyLong(), - anyLong()) + anyLong() + ) clearInvocations(mediaCarouselController) setMediaDreamComplicationEnabled(false) - verify(mediaCarouselController).onDesiredLocationChanged( + verify(mediaCarouselController) + .onDesiredLocationChanged( eq(MediaHierarchyManager.LOCATION_QQS), any(MediaHostState::class.java), eq(false), anyLong(), - anyLong()) + anyLong() + ) } private fun enableSplitShade() { - context.getOrCreateTestableResources().addOverride( - R.bool.config_use_split_notification_shade, true - ) + context + .getOrCreateTestableResources() + .addOverride(R.bool.config_use_split_notification_shade, true) configurationController.notifyConfigurationChanged() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt index 9ea8ae9c9bba..32b822d798f8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt @@ -35,13 +35,10 @@ import org.mockito.junit.MockitoJUnit @RunWith(AndroidTestingRunner::class) public class MediaPlayerDataTest : SysuiTestCase() { - @Mock - private lateinit var playerIsPlaying: MediaControlPanel + @Mock private lateinit var playerIsPlaying: MediaControlPanel private var systemClock: FakeSystemClock = FakeSystemClock() - @JvmField - @Rule - val mockito = MockitoJUnit.rule() + @JvmField @Rule val mockito = MockitoJUnit.rule() companion object { val LOCAL = MediaData.PLAYBACK_LOCAL @@ -63,10 +60,20 @@ public class MediaPlayerDataTest : SysuiTestCase() { val playerIsRemote = mock(MediaControlPanel::class.java) val dataIsRemote = createMediaData("app2", PLAYING, REMOTE, !RESUMPTION) - MediaPlayerData.addMediaPlayer("2", dataIsRemote, playerIsRemote, systemClock, - isSsReactivated = false) - MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying, systemClock, - isSsReactivated = false) + MediaPlayerData.addMediaPlayer( + "2", + dataIsRemote, + playerIsRemote, + systemClock, + isSsReactivated = false + ) + MediaPlayerData.addMediaPlayer( + "1", + dataIsPlaying, + playerIsPlaying, + systemClock, + isSsReactivated = false + ) val players = MediaPlayerData.players() assertThat(players).hasSize(2) @@ -81,22 +88,42 @@ public class MediaPlayerDataTest : SysuiTestCase() { val playerIsPlaying2 = mock(MediaControlPanel::class.java) var dataIsPlaying2 = createMediaData("app2", !PLAYING, LOCAL, !RESUMPTION) - MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1, systemClock, - isSsReactivated = false) + MediaPlayerData.addMediaPlayer( + "1", + dataIsPlaying1, + playerIsPlaying1, + systemClock, + isSsReactivated = false + ) systemClock.advanceTime(1) - MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2, systemClock, - isSsReactivated = false) + MediaPlayerData.addMediaPlayer( + "2", + dataIsPlaying2, + playerIsPlaying2, + systemClock, + isSsReactivated = false + ) systemClock.advanceTime(1) dataIsPlaying1 = createMediaData("app1", !PLAYING, LOCAL, !RESUMPTION) dataIsPlaying2 = createMediaData("app2", PLAYING, LOCAL, !RESUMPTION) - MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1, systemClock, - isSsReactivated = false) + MediaPlayerData.addMediaPlayer( + "1", + dataIsPlaying1, + playerIsPlaying1, + systemClock, + isSsReactivated = false + ) systemClock.advanceTime(1) - MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2, systemClock, - isSsReactivated = false) + MediaPlayerData.addMediaPlayer( + "2", + dataIsPlaying2, + playerIsPlaying2, + systemClock, + isSsReactivated = false + ) systemClock.advanceTime(1) val players = MediaPlayerData.players() @@ -124,26 +151,60 @@ public class MediaPlayerDataTest : SysuiTestCase() { val dataUndetermined = createMediaData("app6", UNDETERMINED, LOCAL, RESUMPTION) MediaPlayerData.addMediaPlayer( - "3", dataIsStoppedAndLocal, playerIsStoppedAndLocal, systemClock, - isSsReactivated = false) + "3", + dataIsStoppedAndLocal, + playerIsStoppedAndLocal, + systemClock, + isSsReactivated = false + ) MediaPlayerData.addMediaPlayer( - "5", dataIsStoppedAndRemote, playerIsStoppedAndRemote, systemClock, - isSsReactivated = false) - MediaPlayerData.addMediaPlayer("4", dataCanResume, playerCanResume, systemClock, - isSsReactivated = false) - MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying, systemClock, - isSsReactivated = false) + "5", + dataIsStoppedAndRemote, + playerIsStoppedAndRemote, + systemClock, + isSsReactivated = false + ) MediaPlayerData.addMediaPlayer( - "2", dataIsPlayingAndRemote, playerIsPlayingAndRemote, systemClock, - isSsReactivated = false) - MediaPlayerData.addMediaPlayer("6", dataUndetermined, playerUndetermined, systemClock, - isSsReactivated = false) + "4", + dataCanResume, + playerCanResume, + systemClock, + isSsReactivated = false + ) + MediaPlayerData.addMediaPlayer( + "1", + dataIsPlaying, + playerIsPlaying, + systemClock, + isSsReactivated = false + ) + MediaPlayerData.addMediaPlayer( + "2", + dataIsPlayingAndRemote, + playerIsPlayingAndRemote, + systemClock, + isSsReactivated = false + ) + MediaPlayerData.addMediaPlayer( + "6", + dataUndetermined, + playerUndetermined, + systemClock, + isSsReactivated = false + ) val players = MediaPlayerData.players() assertThat(players).hasSize(6) - assertThat(players).containsExactly(playerIsPlaying, playerIsPlayingAndRemote, - playerIsStoppedAndRemote, playerIsStoppedAndLocal, playerUndetermined, - playerCanResume).inOrder() + assertThat(players) + .containsExactly( + playerIsPlaying, + playerIsPlayingAndRemote, + playerIsStoppedAndRemote, + playerIsStoppedAndLocal, + playerUndetermined, + playerCanResume + ) + .inOrder() } @Test @@ -155,13 +216,23 @@ public class MediaPlayerDataTest : SysuiTestCase() { assertThat(MediaPlayerData.players()).hasSize(0) - MediaPlayerData.addMediaPlayer(keyA, data, playerIsPlaying, systemClock, - isSsReactivated = false) + MediaPlayerData.addMediaPlayer( + keyA, + data, + playerIsPlaying, + systemClock, + isSsReactivated = false + ) systemClock.advanceTime(1) assertThat(MediaPlayerData.players()).hasSize(1) - MediaPlayerData.addMediaPlayer(keyB, data, playerIsPlaying, systemClock, - isSsReactivated = false) + MediaPlayerData.addMediaPlayer( + keyB, + data, + playerIsPlaying, + systemClock, + isSsReactivated = false + ) systemClock.advanceTime(1) assertThat(MediaPlayerData.players()).hasSize(2) @@ -179,12 +250,13 @@ public class MediaPlayerDataTest : SysuiTestCase() { isPlaying: Boolean?, location: Int, resumption: Boolean - ) = MediaTestUtils.emptyMediaData.copy( - app = app, - packageName = "package: $app", - playbackLocation = location, - resumption = resumption, - notificationKey = "key: $app", - isPlaying = isPlaying - ) + ) = + MediaTestUtils.emptyMediaData.copy( + app = app, + packageName = "package: $app", + playbackLocation = location, + resumption = resumption, + notificationKey = "key: $app", + isPlaying = isPlaying + ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MetadataAnimationHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MetadataAnimationHandlerTest.kt index aca35e536424..323b7818ed3d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MetadataAnimationHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MetadataAnimationHandlerTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.media.controls.ui -import org.mockito.Mockito.`when` as whenever import android.animation.Animator import android.test.suitebuilder.annotation.SmallTest import android.testing.AndroidTestingRunner @@ -29,10 +28,11 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.verify -import org.mockito.Mockito.times import org.mockito.Mockito.mock import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit @SmallTest @@ -55,8 +55,7 @@ class MetadataAnimationHandlerTest : SysuiTestCase() { handler = MetadataAnimationHandler(exitAnimator, enterAnimator) } - @After - fun tearDown() {} + @After fun tearDown() {} @Test fun firstBind_startsAnimationSet() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/SquigglyProgressTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/SquigglyProgressTest.kt index aff8992e6266..d6cff81c0aaa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/SquigglyProgressTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/SquigglyProgressTest.kt @@ -50,12 +50,9 @@ class SquigglyProgressTest : SysuiTestCase() { private val tint = Color.GREEN lateinit var squigglyProgress: SquigglyProgress - @Mock - lateinit var canvas: Canvas - @Captor - lateinit var paintCaptor: ArgumentCaptor<Paint> - @JvmField @Rule - val mockitoRule = MockitoJUnit.rule() + @Mock lateinit var canvas: Canvas + @Captor lateinit var paintCaptor: ArgumentCaptor<Paint> + @JvmField @Rule val mockitoRule = MockitoJUnit.rule() @Before fun setup() { @@ -126,7 +123,6 @@ class SquigglyProgressTest : SysuiTestCase() { val (wavePaint, linePaint) = paintCaptor.getAllValues() assertThat(wavePaint.color).isEqualTo(tint) - assertThat(linePaint.color).isEqualTo( - ColorUtils.setAlphaComponent(tint, DISABLED_ALPHA)) + assertThat(linePaint.color).isEqualTo(ColorUtils.setAlphaComponent(tint, DISABLED_ALPHA)) } -}
\ No newline at end of file +} |