Format media control files
- Run ktfmt for Kotlin files that were moved, to follow current style guide - Add missing copyright headers Test: build Bug: 241485754 Change-Id: Icc2a9246dfaea8352824a5a181b4c06d3bcb4a46
This commit is contained in:
@@ -189,45 +189,8 @@
|
|||||||
-packages/SystemUI/src/com/android/systemui/log/LogcatEchoTracker.kt
|
-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/LogcatEchoTrackerDebug.kt
|
||||||
-packages/SystemUI/src/com/android/systemui/log/LogcatEchoTrackerProd.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/MediaProjectionAppSelectorActivity.kt
|
||||||
-packages/SystemUI/src/com/android/systemui/media/MediaProjectionCaptureTarget.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/dagger/MediaProjectionModule.kt
|
||||||
-packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
|
-packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
|
||||||
-packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.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/keyguard/KeyguardUnlockAnimationControllerTest.kt
|
||||||
-packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.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/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/muteawait/MediaMuteAwaitConnectionManagerTest.kt
|
||||||
-packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt
|
-packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt
|
||||||
-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
|
-packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
|
||||||
|
|||||||
@@ -98,11 +98,6 @@ class GutsViewHolder constructor(itemView: View) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val ids = setOf(
|
val ids = setOf(R.id.remove_text, R.id.cancel, R.id.dismiss, R.id.settings)
|
||||||
R.id.remove_text,
|
|
||||||
R.id.cancel,
|
|
||||||
R.id.dismiss,
|
|
||||||
R.id.settings
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,69 +27,42 @@ import com.android.systemui.R
|
|||||||
data class MediaData(
|
data class MediaData(
|
||||||
val userId: Int,
|
val userId: Int,
|
||||||
val initialized: Boolean = false,
|
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?,
|
val app: String?,
|
||||||
/**
|
/** App icon shown on player. */
|
||||||
* App icon shown on player.
|
|
||||||
*/
|
|
||||||
val appIcon: Icon?,
|
val appIcon: Icon?,
|
||||||
/**
|
/** Artist name. */
|
||||||
* Artist name.
|
|
||||||
*/
|
|
||||||
val artist: CharSequence?,
|
val artist: CharSequence?,
|
||||||
/**
|
/** Song name. */
|
||||||
* Song name.
|
|
||||||
*/
|
|
||||||
val song: CharSequence?,
|
val song: CharSequence?,
|
||||||
/**
|
/** Album artwork. */
|
||||||
* Album artwork.
|
|
||||||
*/
|
|
||||||
val artwork: Icon?,
|
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>,
|
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>,
|
val actionsToShowInCompact: List<Int>,
|
||||||
/**
|
/**
|
||||||
* Semantic actions buttons, based on the PlaybackState of the media session.
|
* Semantic actions buttons, based on the PlaybackState of the media session. If present, these
|
||||||
* If present, these actions will be preferred in the UI over [actions]
|
* actions will be preferred in the UI over [actions]
|
||||||
*/
|
*/
|
||||||
val semanticActions: MediaButton? = null,
|
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,
|
val packageName: String,
|
||||||
/**
|
/** Unique media session identifier. */
|
||||||
* Unique media session identifier.
|
|
||||||
*/
|
|
||||||
val token: MediaSession.Token?,
|
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?,
|
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?,
|
val device: MediaDeviceData?,
|
||||||
/**
|
/**
|
||||||
* When active, a player will be displayed on keyguard and quick-quick settings.
|
* When active, a player will be displayed on keyguard and quick-quick settings. This is
|
||||||
* This is unrelated to the stream being playing or not, a player will not be active if
|
* unrelated to the stream being playing or not, a player will not be active if timed out, or in
|
||||||
* timed out, or in resumption mode.
|
* resumption mode.
|
||||||
*/
|
*/
|
||||||
var active: Boolean,
|
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?,
|
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,
|
var playbackLocation: Int = PLAYBACK_LOCAL,
|
||||||
/**
|
/**
|
||||||
* Indicates that this player is a resumption player (ie. It only shows a play actions which
|
* 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,
|
val notificationKey: String? = null,
|
||||||
var hasCheckedForResume: Boolean = false,
|
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,
|
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,
|
val isClearable: Boolean = true,
|
||||||
|
|
||||||
/**
|
/** Timestamp when this player was last active. */
|
||||||
* Timestamp when this player was last active.
|
|
||||||
*/
|
|
||||||
var lastActive: Long = 0L,
|
var lastActive: Long = 0L,
|
||||||
|
|
||||||
/**
|
/** Instance ID for logging purposes */
|
||||||
* Instance ID for logging purposes
|
|
||||||
*/
|
|
||||||
val instanceId: InstanceId,
|
val instanceId: InstanceId,
|
||||||
|
|
||||||
/**
|
/** The UID of the app, used for logging */
|
||||||
* The UID of the app, used for logging
|
|
||||||
*/
|
|
||||||
val appUid: Int
|
val appUid: Int
|
||||||
) {
|
) {
|
||||||
companion object {
|
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(
|
data class MediaButton(
|
||||||
/**
|
/** Play/pause button */
|
||||||
* Play/pause button
|
|
||||||
*/
|
|
||||||
val playOrPause: MediaAction? = null,
|
val playOrPause: MediaAction? = null,
|
||||||
/**
|
/** Next button, or custom action */
|
||||||
* Next button, or custom action
|
|
||||||
*/
|
|
||||||
val nextOrCustom: MediaAction? = null,
|
val nextOrCustom: MediaAction? = null,
|
||||||
/**
|
/** Previous button, or custom action */
|
||||||
* Previous button, or custom action
|
|
||||||
*/
|
|
||||||
val prevOrCustom: MediaAction? = null,
|
val prevOrCustom: MediaAction? = null,
|
||||||
/**
|
/** First custom action space */
|
||||||
* First custom action space
|
|
||||||
*/
|
|
||||||
val custom0: MediaAction? = null,
|
val custom0: MediaAction? = null,
|
||||||
/**
|
/** Second custom action space */
|
||||||
* Second custom action space
|
|
||||||
*/
|
|
||||||
val custom1: MediaAction? = null,
|
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,
|
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
|
val reservePrev: Boolean = false
|
||||||
) {
|
) {
|
||||||
fun getActionById(id: Int): MediaAction? {
|
fun getActionById(id: Int): MediaAction? {
|
||||||
@@ -201,7 +148,8 @@ data class MediaAction(
|
|||||||
|
|
||||||
/** State of the media device. */
|
/** State of the media device. */
|
||||||
data class MediaDeviceData
|
data class MediaDeviceData
|
||||||
@JvmOverloads constructor(
|
@JvmOverloads
|
||||||
|
constructor(
|
||||||
/** Whether or not to enable the chip */
|
/** Whether or not to enable the chip */
|
||||||
val enabled: Boolean,
|
val enabled: Boolean,
|
||||||
|
|
||||||
@@ -221,8 +169,8 @@ data class MediaDeviceData
|
|||||||
val showBroadcastButton: Boolean
|
val showBroadcastButton: Boolean
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Check whether [MediaDeviceData] objects are equal in all fields except the icon. The icon
|
* Check whether [MediaDeviceData] objects are equal in all fields except the icon. The icon is
|
||||||
* is ignored because it can change by reference frequently depending on the device type's
|
* 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
|
* implementation, but this is not usually relevant unless other info has changed
|
||||||
*/
|
*/
|
||||||
fun equalsWithoutIcon(other: MediaDeviceData?): Boolean {
|
fun equalsWithoutIcon(other: MediaDeviceData?): Boolean {
|
||||||
|
|||||||
@@ -30,9 +30,7 @@ import com.android.systemui.util.animation.TransitionLayout
|
|||||||
|
|
||||||
private const val TAG = "MediaViewHolder"
|
private const val TAG = "MediaViewHolder"
|
||||||
|
|
||||||
/**
|
/** Holder class for media player view */
|
||||||
* Holder class for media player view
|
|
||||||
*/
|
|
||||||
class MediaViewHolder constructor(itemView: View) {
|
class MediaViewHolder constructor(itemView: View) {
|
||||||
val player = itemView as TransitionLayout
|
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
|
// These views are only shown while the user is actively scrubbing
|
||||||
val scrubbingElapsedTimeView: TextView =
|
val scrubbingElapsedTimeView: TextView =
|
||||||
itemView.requireViewById(R.id.media_scrubbing_elapsed_time)
|
itemView.requireViewById(R.id.media_scrubbing_elapsed_time)
|
||||||
val scrubbingTotalTimeView: TextView =
|
val scrubbingTotalTimeView: TextView = itemView.requireViewById(R.id.media_scrubbing_total_time)
|
||||||
itemView.requireViewById(R.id.media_scrubbing_total_time)
|
|
||||||
|
|
||||||
val gutsViewHolder = GutsViewHolder(itemView)
|
val gutsViewHolder = GutsViewHolder(itemView)
|
||||||
|
|
||||||
@@ -87,15 +84,7 @@ class MediaViewHolder constructor(itemView: View) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getTransparentActionButtons(): List<ImageButton> {
|
fun getTransparentActionButtons(): List<ImageButton> {
|
||||||
return listOf(
|
return listOf(actionNext, actionPrev, action0, action1, action2, action3, action4)
|
||||||
actionNext,
|
|
||||||
actionPrev,
|
|
||||||
action0,
|
|
||||||
action1,
|
|
||||||
action2,
|
|
||||||
action3,
|
|
||||||
action4
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun marquee(start: Boolean, delay: Long) {
|
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 inflater LayoutInflater to use to inflate the layout.
|
||||||
* @param parent Parent of inflated view.
|
* @param parent Parent of inflated view.
|
||||||
*/
|
*/
|
||||||
@JvmStatic fun create(
|
@JvmStatic
|
||||||
inflater: LayoutInflater,
|
fun create(inflater: LayoutInflater, parent: ViewGroup): MediaViewHolder {
|
||||||
parent: ViewGroup
|
|
||||||
): MediaViewHolder {
|
|
||||||
val mediaView = inflater.inflate(R.layout.media_session_view, parent, false)
|
val mediaView = inflater.inflate(R.layout.media_session_view, parent, false)
|
||||||
mediaView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
|
mediaView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
|
||||||
// Because this media view (a TransitionLayout) is used to measure and layout the views
|
// 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.icon,
|
||||||
R.id.app_name,
|
R.id.app_name,
|
||||||
R.id.header_title,
|
R.id.header_title,
|
||||||
@@ -143,27 +131,23 @@ class MediaViewHolder constructor(itemView: View) {
|
|||||||
R.id.icon,
|
R.id.icon,
|
||||||
R.id.media_scrubbing_elapsed_time,
|
R.id.media_scrubbing_elapsed_time,
|
||||||
R.id.media_scrubbing_total_time
|
R.id.media_scrubbing_total_time
|
||||||
)
|
)
|
||||||
|
|
||||||
// Buttons used for notification-based actions
|
// Buttons used for notification-based actions
|
||||||
val genericButtonIds = setOf(
|
val genericButtonIds =
|
||||||
R.id.action0,
|
setOf(R.id.action0, R.id.action1, R.id.action2, R.id.action3, R.id.action4)
|
||||||
R.id.action1,
|
|
||||||
R.id.action2,
|
|
||||||
R.id.action3,
|
|
||||||
R.id.action4
|
|
||||||
)
|
|
||||||
|
|
||||||
val expandedBottomActionIds = setOf(
|
val expandedBottomActionIds =
|
||||||
R.id.actionPrev,
|
setOf(
|
||||||
R.id.actionNext,
|
R.id.actionPrev,
|
||||||
R.id.action0,
|
R.id.actionNext,
|
||||||
R.id.action1,
|
R.id.action0,
|
||||||
R.id.action2,
|
R.id.action1,
|
||||||
R.id.action3,
|
R.id.action2,
|
||||||
R.id.action4,
|
R.id.action3,
|
||||||
R.id.media_scrubbing_elapsed_time,
|
R.id.action4,
|
||||||
R.id.media_scrubbing_total_time
|
R.id.media_scrubbing_elapsed_time,
|
||||||
)
|
R.id.media_scrubbing_total_time
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
* <p>Updates the seek bar views in response to changes to the model.
|
||||||
*/
|
*/
|
||||||
open class SeekBarObserver(
|
open class SeekBarObserver(private val holder: MediaViewHolder) :
|
||||||
private val holder: MediaViewHolder
|
Observer<SeekBarViewModel.Progress> {
|
||||||
) : Observer<SeekBarViewModel.Progress> {
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic val RESET_ANIMATION_DURATION_MS: Int = 750
|
@JvmStatic val RESET_ANIMATION_DURATION_MS: Int = 750
|
||||||
@JvmStatic val RESET_ANIMATION_THRESHOLD_MS: Int = 250
|
@JvmStatic val RESET_ANIMATION_THRESHOLD_MS: Int = 250
|
||||||
}
|
}
|
||||||
|
|
||||||
val seekBarEnabledMaxHeight = holder.seekBar.context.resources
|
val seekBarEnabledMaxHeight =
|
||||||
.getDimensionPixelSize(R.dimen.qs_media_enabled_seekbar_height)
|
holder.seekBar.context.resources.getDimensionPixelSize(
|
||||||
val seekBarDisabledHeight = holder.seekBar.context.resources
|
R.dimen.qs_media_enabled_seekbar_height
|
||||||
.getDimensionPixelSize(R.dimen.qs_media_disabled_seekbar_height)
|
)
|
||||||
val seekBarEnabledVerticalPadding = holder.seekBar.context.resources
|
val seekBarDisabledHeight =
|
||||||
.getDimensionPixelSize(R.dimen.qs_media_session_enabled_seekbar_vertical_padding)
|
holder.seekBar.context.resources.getDimensionPixelSize(
|
||||||
val seekBarDisabledVerticalPadding = holder.seekBar.context.resources
|
R.dimen.qs_media_disabled_seekbar_height
|
||||||
.getDimensionPixelSize(R.dimen.qs_media_session_disabled_seekbar_vertical_padding)
|
)
|
||||||
|
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
|
var seekBarResetAnimator: Animator? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val seekBarProgressWavelength = holder.seekBar.context.resources
|
val seekBarProgressWavelength =
|
||||||
.getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_wavelength).toFloat()
|
holder.seekBar.context.resources
|
||||||
val seekBarProgressAmplitude = holder.seekBar.context.resources
|
.getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_wavelength)
|
||||||
.getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_amplitude).toFloat()
|
.toFloat()
|
||||||
val seekBarProgressPhase = holder.seekBar.context.resources
|
val seekBarProgressAmplitude =
|
||||||
.getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_phase).toFloat()
|
holder.seekBar.context.resources
|
||||||
val seekBarProgressStrokeWidth = holder.seekBar.context.resources
|
.getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_amplitude)
|
||||||
.getDimensionPixelSize(R.dimen.qs_media_seekbar_progress_stroke_width).toFloat()
|
.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
|
val progressDrawable = holder.seekBar.progressDrawable as? SquigglyProgress
|
||||||
progressDrawable?.let {
|
progressDrawable?.let {
|
||||||
it.waveLength = seekBarProgressWavelength
|
it.waveLength = seekBarProgressWavelength
|
||||||
@@ -98,16 +113,18 @@ open class SeekBarObserver(
|
|||||||
}
|
}
|
||||||
|
|
||||||
holder.seekBar.setMax(data.duration)
|
holder.seekBar.setMax(data.duration)
|
||||||
val totalTimeString = DateUtils.formatElapsedTime(
|
val totalTimeString =
|
||||||
data.duration / DateUtils.SECOND_IN_MILLIS)
|
DateUtils.formatElapsedTime(data.duration / DateUtils.SECOND_IN_MILLIS)
|
||||||
if (data.scrubbing) {
|
if (data.scrubbing) {
|
||||||
holder.scrubbingTotalTimeView.text = totalTimeString
|
holder.scrubbingTotalTimeView.text = totalTimeString
|
||||||
}
|
}
|
||||||
|
|
||||||
data.elapsedTime?.let {
|
data.elapsedTime?.let {
|
||||||
if (!data.scrubbing && !(seekBarResetAnimator?.isRunning ?: false)) {
|
if (!data.scrubbing && !(seekBarResetAnimator?.isRunning ?: false)) {
|
||||||
if (it <= RESET_ANIMATION_THRESHOLD_MS &&
|
if (
|
||||||
holder.seekBar.progress > RESET_ANIMATION_THRESHOLD_MS) {
|
it <= RESET_ANIMATION_THRESHOLD_MS &&
|
||||||
|
holder.seekBar.progress > RESET_ANIMATION_THRESHOLD_MS
|
||||||
|
) {
|
||||||
// This animation resets for every additional update to zero.
|
// This animation resets for every additional update to zero.
|
||||||
val animator = buildResetAnimator(it)
|
val animator = buildResetAnimator(it)
|
||||||
animator.start()
|
animator.start()
|
||||||
@@ -117,24 +134,29 @@ open class SeekBarObserver(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val elapsedTimeString = DateUtils.formatElapsedTime(
|
val elapsedTimeString = DateUtils.formatElapsedTime(it / DateUtils.SECOND_IN_MILLIS)
|
||||||
it / DateUtils.SECOND_IN_MILLIS)
|
|
||||||
if (data.scrubbing) {
|
if (data.scrubbing) {
|
||||||
holder.scrubbingElapsedTimeView.text = elapsedTimeString
|
holder.scrubbingElapsedTimeView.text = elapsedTimeString
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.seekBar.contentDescription = holder.seekBar.context.getString(
|
holder.seekBar.contentDescription =
|
||||||
R.string.controls_media_seekbar_description,
|
holder.seekBar.context.getString(
|
||||||
elapsedTimeString,
|
R.string.controls_media_seekbar_description,
|
||||||
totalTimeString
|
elapsedTimeString,
|
||||||
)
|
totalTimeString
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
open fun buildResetAnimator(targetTime: Int): Animator {
|
open fun buildResetAnimator(targetTime: Int): Animator {
|
||||||
val animator = ObjectAnimator.ofInt(holder.seekBar, "progress",
|
val animator =
|
||||||
holder.seekBar.progress, targetTime + RESET_ANIMATION_DURATION_MS)
|
ObjectAnimator.ofInt(
|
||||||
|
holder.seekBar,
|
||||||
|
"progress",
|
||||||
|
holder.seekBar.progress,
|
||||||
|
targetTime + RESET_ANIMATION_DURATION_MS
|
||||||
|
)
|
||||||
animator.setAutoCancel(true)
|
animator.setAutoCancel(true)
|
||||||
animator.duration = RESET_ANIMATION_DURATION_MS.toLong()
|
animator.duration = RESET_ANIMATION_DURATION_MS.toLong()
|
||||||
animator.interpolator = Interpolators.EMPHASIZED
|
animator.interpolator = Interpolators.EMPHASIZED
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ private const val MIN_FLING_VELOCITY_SCALE_FACTOR = 10
|
|||||||
|
|
||||||
private fun PlaybackState.isInMotion(): Boolean {
|
private fun PlaybackState.isInMotion(): Boolean {
|
||||||
return this.state == PlaybackState.STATE_PLAYING ||
|
return this.state == PlaybackState.STATE_PLAYING ||
|
||||||
this.state == PlaybackState.STATE_FAST_FORWARDING ||
|
this.state == PlaybackState.STATE_FAST_FORWARDING ||
|
||||||
this.state == PlaybackState.STATE_REWINDING
|
this.state == PlaybackState.STATE_REWINDING
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,8 +59,8 @@ private fun PlaybackState.computePosition(duration: Long): Long {
|
|||||||
val updateTime = this.getLastPositionUpdateTime()
|
val updateTime = this.getLastPositionUpdateTime()
|
||||||
val currentTime = SystemClock.elapsedRealtime()
|
val currentTime = SystemClock.elapsedRealtime()
|
||||||
if (updateTime > 0) {
|
if (updateTime > 0) {
|
||||||
var position = (this.playbackSpeed * (currentTime - updateTime)).toLong() +
|
var position =
|
||||||
this.getPosition()
|
(this.playbackSpeed * (currentTime - updateTime)).toLong() + this.getPosition()
|
||||||
if (duration >= 0 && position > duration) {
|
if (duration >= 0 && position > duration) {
|
||||||
position = duration.toLong()
|
position = duration.toLong()
|
||||||
} else if (position < 0) {
|
} else if (position < 0) {
|
||||||
@@ -73,7 +73,9 @@ private fun PlaybackState.computePosition(duration: Long): Long {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** ViewModel for seek bar in QS media player. */
|
/** ViewModel for seek bar in QS media player. */
|
||||||
class SeekBarViewModel @Inject constructor(
|
class SeekBarViewModel
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
@Background private val bgExecutor: RepeatableExecutor,
|
@Background private val bgExecutor: RepeatableExecutor,
|
||||||
private val falsingManager: FalsingManager,
|
private val falsingManager: FalsingManager,
|
||||||
) {
|
) {
|
||||||
@@ -86,9 +88,7 @@ class SeekBarViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
_progress.postValue(value)
|
_progress.postValue(value)
|
||||||
}
|
}
|
||||||
private val _progress = MutableLiveData<Progress>().apply {
|
private val _progress = MutableLiveData<Progress>().apply { postValue(_data) }
|
||||||
postValue(_data)
|
|
||||||
}
|
|
||||||
val progress: LiveData<Progress>
|
val progress: LiveData<Progress>
|
||||||
get() = _progress
|
get() = _progress
|
||||||
private var controller: MediaController? = null
|
private var controller: MediaController? = null
|
||||||
@@ -100,20 +100,21 @@ class SeekBarViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
private var playbackState: PlaybackState? = null
|
private var playbackState: PlaybackState? = null
|
||||||
private var callback = object : MediaController.Callback() {
|
private var callback =
|
||||||
override fun onPlaybackStateChanged(state: PlaybackState?) {
|
object : MediaController.Callback() {
|
||||||
playbackState = state
|
override fun onPlaybackStateChanged(state: PlaybackState?) {
|
||||||
if (playbackState == null || PlaybackState.STATE_NONE.equals(playbackState)) {
|
playbackState = state
|
||||||
|
if (playbackState == null || PlaybackState.STATE_NONE.equals(playbackState)) {
|
||||||
|
clearController()
|
||||||
|
} else {
|
||||||
|
checkIfPollingNeeded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSessionDestroyed() {
|
||||||
clearController()
|
clearController()
|
||||||
} else {
|
|
||||||
checkIfPollingNeeded()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSessionDestroyed() {
|
|
||||||
clearController()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private var cancel: Runnable? = null
|
private var cancel: Runnable? = null
|
||||||
|
|
||||||
/** Indicates if the seek interaction is considered a false guesture. */
|
/** 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. */
|
/** Listening state (QS open or closed) is used to control polling of progress. */
|
||||||
var listening = true
|
var listening = true
|
||||||
set(value) = bgExecutor.execute {
|
set(value) =
|
||||||
if (field != value) {
|
bgExecutor.execute {
|
||||||
field = value
|
if (field != value) {
|
||||||
checkIfPollingNeeded()
|
field = value
|
||||||
|
checkIfPollingNeeded()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private var scrubbingChangeListener: ScrubbingChangeListener? = null
|
private var scrubbingChangeListener: ScrubbingChangeListener? = null
|
||||||
private var enabledChangeListener: EnabledChangeListener? = null
|
private var enabledChangeListener: EnabledChangeListener? = null
|
||||||
@@ -144,14 +146,13 @@ class SeekBarViewModel @Inject constructor(
|
|||||||
|
|
||||||
lateinit var logSeek: () -> Unit
|
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
|
@AnyThread
|
||||||
fun onSeekStarting() = bgExecutor.execute {
|
fun onSeekStarting() =
|
||||||
scrubbing = true
|
bgExecutor.execute {
|
||||||
isFalseSeek = false
|
scrubbing = true
|
||||||
}
|
isFalseSeek = false
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event indicating that the user has moved the seek bar.
|
* 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.
|
* @param position Current location in the track.
|
||||||
*/
|
*/
|
||||||
@AnyThread
|
@AnyThread
|
||||||
fun onSeekProgress(position: Long) = bgExecutor.execute {
|
fun onSeekProgress(position: Long) =
|
||||||
if (scrubbing) {
|
bgExecutor.execute {
|
||||||
// The user hasn't yet finished their touch gesture, so only update the data for visual
|
if (scrubbing) {
|
||||||
// feedback and don't update [controller] yet.
|
// The user hasn't yet finished their touch gesture, so only update the data for
|
||||||
_data = _data.copy(elapsedTime = position.toInt())
|
// visual
|
||||||
} else {
|
// feedback and don't update [controller] yet.
|
||||||
// The seek progress came from an a11y action and we should immediately update to the
|
_data = _data.copy(elapsedTime = position.toInt())
|
||||||
// new position. (a11y actions to change the seekbar position don't trigger
|
} else {
|
||||||
// SeekBar.OnSeekBarChangeListener.onStartTrackingTouch or onStopTrackingTouch.)
|
// The seek progress came from an a11y action and we should immediately update to
|
||||||
onSeek(position)
|
// 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
|
@AnyThread
|
||||||
fun onSeekFalse() = bgExecutor.execute {
|
fun onSeekFalse() =
|
||||||
if (scrubbing) {
|
bgExecutor.execute {
|
||||||
isFalseSeek = true
|
if (scrubbing) {
|
||||||
|
isFalseSeek = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle request to change the current position in the media track.
|
* Handle request to change the current position in the media track.
|
||||||
* @param position Place to seek to in the track.
|
* @param position Place to seek to in the track.
|
||||||
*/
|
*/
|
||||||
@AnyThread
|
@AnyThread
|
||||||
fun onSeek(position: Long) = bgExecutor.execute {
|
fun onSeek(position: Long) =
|
||||||
if (isFalseSeek) {
|
bgExecutor.execute {
|
||||||
scrubbing = false
|
if (isFalseSeek) {
|
||||||
checkPlaybackPosition()
|
scrubbing = false
|
||||||
} else {
|
checkPlaybackPosition()
|
||||||
logSeek()
|
} else {
|
||||||
controller?.transportControls?.seekTo(position)
|
logSeek()
|
||||||
// Invalidate the cached playbackState to avoid the thumb jumping back to the previous
|
controller?.transportControls?.seekTo(position)
|
||||||
// position.
|
// Invalidate the cached playbackState to avoid the thumb jumping back to the
|
||||||
playbackState = null
|
// previous
|
||||||
scrubbing = false
|
// position.
|
||||||
|
playbackState = null
|
||||||
|
scrubbing = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates media information.
|
* Updates media information.
|
||||||
@@ -216,11 +221,18 @@ class SeekBarViewModel @Inject constructor(
|
|||||||
val seekAvailable = ((playbackState?.actions ?: 0L) and PlaybackState.ACTION_SEEK_TO) != 0L
|
val seekAvailable = ((playbackState?.actions ?: 0L) and PlaybackState.ACTION_SEEK_TO) != 0L
|
||||||
val position = playbackState?.position?.toInt()
|
val position = playbackState?.position?.toInt()
|
||||||
val duration = mediaMetadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt() ?: 0
|
val duration = mediaMetadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt() ?: 0
|
||||||
val playing = NotificationMediaManager
|
val playing =
|
||||||
.isPlayingState(playbackState?.state ?: PlaybackState.STATE_NONE)
|
NotificationMediaManager.isPlayingState(
|
||||||
val enabled = if (playbackState == null ||
|
playbackState?.state ?: PlaybackState.STATE_NONE
|
||||||
playbackState?.getState() == PlaybackState.STATE_NONE ||
|
)
|
||||||
(duration <= 0)) false else true
|
val enabled =
|
||||||
|
if (
|
||||||
|
playbackState == null ||
|
||||||
|
playbackState?.getState() == PlaybackState.STATE_NONE ||
|
||||||
|
(duration <= 0)
|
||||||
|
)
|
||||||
|
false
|
||||||
|
else true
|
||||||
_data = Progress(enabled, seekAvailable, playing, scrubbing, position, duration)
|
_data = Progress(enabled, seekAvailable, playing, scrubbing, position, duration)
|
||||||
checkIfPollingNeeded()
|
checkIfPollingNeeded()
|
||||||
}
|
}
|
||||||
@@ -231,26 +243,26 @@ class SeekBarViewModel @Inject constructor(
|
|||||||
* This should be called when the media session behind the controller has been destroyed.
|
* This should be called when the media session behind the controller has been destroyed.
|
||||||
*/
|
*/
|
||||||
@AnyThread
|
@AnyThread
|
||||||
fun clearController() = bgExecutor.execute {
|
fun clearController() =
|
||||||
controller = null
|
bgExecutor.execute {
|
||||||
playbackState = null
|
controller = null
|
||||||
cancel?.run()
|
playbackState = null
|
||||||
cancel = null
|
cancel?.run()
|
||||||
_data = _data.copy(enabled = false)
|
cancel = null
|
||||||
}
|
_data = _data.copy(enabled = false)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/** Call to clean up any resources. */
|
||||||
* Call to clean up any resources.
|
|
||||||
*/
|
|
||||||
@AnyThread
|
@AnyThread
|
||||||
fun onDestroy() = bgExecutor.execute {
|
fun onDestroy() =
|
||||||
controller = null
|
bgExecutor.execute {
|
||||||
playbackState = null
|
controller = null
|
||||||
cancel?.run()
|
playbackState = null
|
||||||
cancel = null
|
cancel?.run()
|
||||||
scrubbingChangeListener = null
|
cancel = null
|
||||||
enabledChangeListener = null
|
scrubbingChangeListener = null
|
||||||
}
|
enabledChangeListener = null
|
||||||
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
private fun checkPlaybackPosition() {
|
private fun checkPlaybackPosition() {
|
||||||
@@ -266,8 +278,12 @@ class SeekBarViewModel @Inject constructor(
|
|||||||
val needed = listening && !scrubbing && playbackState?.isInMotion() ?: false
|
val needed = listening && !scrubbing && playbackState?.isInMotion() ?: false
|
||||||
if (needed) {
|
if (needed) {
|
||||||
if (cancel == null) {
|
if (cancel == null) {
|
||||||
cancel = bgExecutor.executeRepeatedly(this::checkPlaybackPosition, 0L,
|
cancel =
|
||||||
POSITION_UPDATE_INTERVAL_MILLIS)
|
bgExecutor.executeRepeatedly(
|
||||||
|
this::checkPlaybackPosition,
|
||||||
|
0L,
|
||||||
|
POSITION_UPDATE_INTERVAL_MILLIS
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
cancel?.run()
|
cancel?.run()
|
||||||
@@ -353,9 +369,10 @@ class SeekBarViewModel @Inject constructor(
|
|||||||
// Gesture detector helps decide which touch events to intercept.
|
// Gesture detector helps decide which touch events to intercept.
|
||||||
private val detector = GestureDetectorCompat(bar.context, this)
|
private val detector = GestureDetectorCompat(bar.context, this)
|
||||||
// Velocity threshold used to decide when a fling is considered a false gesture.
|
// Velocity threshold used to decide when a fling is considered a false gesture.
|
||||||
private val flingVelocity: Int = ViewConfiguration.get(bar.context).run {
|
private val flingVelocity: Int =
|
||||||
getScaledMinimumFlingVelocity() * MIN_FLING_VELOCITY_SCALE_FACTOR
|
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.
|
// Indicates if the gesture should go to the seek bar or if it should be intercepted.
|
||||||
private var shouldGoToSeekBar = false
|
private var shouldGoToSeekBar = false
|
||||||
|
|
||||||
@@ -385,9 +402,9 @@ class SeekBarViewModel @Inject constructor(
|
|||||||
/**
|
/**
|
||||||
* Handle down events that press down on the thumb.
|
* 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
|
* On the down action, determine a target box around the thumb to know when a scroll gesture
|
||||||
* gesture starts by clicking on the thumb. The target box will be used by subsequent
|
* starts by clicking on the thumb. The target box will be used by subsequent onScroll
|
||||||
* onScroll events.
|
* events.
|
||||||
*
|
*
|
||||||
* Returns true when the down event hits within the target box of the thumb.
|
* 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
|
// TODO: account for thumb offset
|
||||||
val progress = bar.getProgress()
|
val progress = bar.getProgress()
|
||||||
val range = bar.max - bar.min
|
val range = bar.max - bar.min
|
||||||
val widthFraction = if (range > 0) {
|
val widthFraction =
|
||||||
(progress - bar.min).toDouble() / range
|
if (range > 0) {
|
||||||
} else {
|
(progress - bar.min).toDouble() / range
|
||||||
0.0
|
} else {
|
||||||
}
|
0.0
|
||||||
|
}
|
||||||
val availableWidth = bar.width - padL - padR
|
val availableWidth = bar.width - padL - padR
|
||||||
val thumbX = if (bar.isLayoutRtl()) {
|
val thumbX =
|
||||||
padL + availableWidth * (1 - widthFraction)
|
if (bar.isLayoutRtl()) {
|
||||||
} else {
|
padL + availableWidth * (1 - widthFraction)
|
||||||
padL + availableWidth * widthFraction
|
} else {
|
||||||
}
|
padL + availableWidth * widthFraction
|
||||||
|
}
|
||||||
// Set the min, max boundaries of the thumb box.
|
// 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.
|
// I'm cheating by using the height of the seek bar as the width of the box.
|
||||||
val halfHeight: Int = bar.height / 2
|
val halfHeight: Int = bar.height / 2
|
||||||
|
|||||||
@@ -35,26 +35,30 @@ class RecommendationViewHolder private constructor(itemView: View) {
|
|||||||
|
|
||||||
// Recommendation screen
|
// Recommendation screen
|
||||||
val cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
|
val cardIcon = itemView.requireViewById<ImageView>(R.id.recommendation_card_icon)
|
||||||
val mediaCoverItems = listOf<ImageView>(
|
val mediaCoverItems =
|
||||||
itemView.requireViewById(R.id.media_cover1),
|
listOf<ImageView>(
|
||||||
itemView.requireViewById(R.id.media_cover2),
|
itemView.requireViewById(R.id.media_cover1),
|
||||||
itemView.requireViewById(R.id.media_cover3)
|
itemView.requireViewById(R.id.media_cover2),
|
||||||
)
|
itemView.requireViewById(R.id.media_cover3)
|
||||||
val mediaCoverContainers = listOf<ViewGroup>(
|
)
|
||||||
itemView.requireViewById(R.id.media_cover1_container),
|
val mediaCoverContainers =
|
||||||
itemView.requireViewById(R.id.media_cover2_container),
|
listOf<ViewGroup>(
|
||||||
itemView.requireViewById(R.id.media_cover3_container)
|
itemView.requireViewById(R.id.media_cover1_container),
|
||||||
)
|
itemView.requireViewById(R.id.media_cover2_container),
|
||||||
val mediaTitles: List<TextView> = listOf(
|
itemView.requireViewById(R.id.media_cover3_container)
|
||||||
itemView.requireViewById(R.id.media_title1),
|
)
|
||||||
itemView.requireViewById(R.id.media_title2),
|
val mediaTitles: List<TextView> =
|
||||||
itemView.requireViewById(R.id.media_title3)
|
listOf(
|
||||||
)
|
itemView.requireViewById(R.id.media_title1),
|
||||||
val mediaSubtitles: List<TextView> = listOf(
|
itemView.requireViewById(R.id.media_title2),
|
||||||
itemView.requireViewById(R.id.media_subtitle1),
|
itemView.requireViewById(R.id.media_title3)
|
||||||
itemView.requireViewById(R.id.media_subtitle2),
|
)
|
||||||
itemView.requireViewById(R.id.media_subtitle3)
|
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)
|
val gutsViewHolder = GutsViewHolder(itemView)
|
||||||
|
|
||||||
@@ -78,13 +82,14 @@ class RecommendationViewHolder private constructor(itemView: View) {
|
|||||||
* @param inflater LayoutInflater to use to inflate the layout.
|
* @param inflater LayoutInflater to use to inflate the layout.
|
||||||
* @param parent Parent of inflated view.
|
* @param parent Parent of inflated view.
|
||||||
*/
|
*/
|
||||||
@JvmStatic fun create(inflater: LayoutInflater, parent: ViewGroup):
|
@JvmStatic
|
||||||
RecommendationViewHolder {
|
fun create(inflater: LayoutInflater, parent: ViewGroup): RecommendationViewHolder {
|
||||||
val itemView =
|
val itemView =
|
||||||
inflater.inflate(
|
inflater.inflate(
|
||||||
R.layout.media_smartspace_recommendations,
|
R.layout.media_smartspace_recommendations,
|
||||||
parent,
|
parent,
|
||||||
false /* attachToRoot */)
|
false /* attachToRoot */
|
||||||
|
)
|
||||||
// Because this media view (a TransitionLayout) is used to measure and layout the views
|
// 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
|
// 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.
|
// 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.
|
// Res Ids for the control components on the recommendation view.
|
||||||
val controlsIds = setOf(
|
val controlsIds =
|
||||||
R.id.recommendation_card_icon,
|
setOf(
|
||||||
R.id.media_cover1,
|
R.id.recommendation_card_icon,
|
||||||
R.id.media_cover2,
|
R.id.media_cover1,
|
||||||
R.id.media_cover3,
|
R.id.media_cover2,
|
||||||
R.id.media_cover1_container,
|
R.id.media_cover3,
|
||||||
R.id.media_cover2_container,
|
R.id.media_cover1_container,
|
||||||
R.id.media_cover3_container,
|
R.id.media_cover2_container,
|
||||||
R.id.media_title1,
|
R.id.media_cover3_container,
|
||||||
R.id.media_title2,
|
R.id.media_title1,
|
||||||
R.id.media_title3,
|
R.id.media_title2,
|
||||||
R.id.media_subtitle1,
|
R.id.media_title3,
|
||||||
R.id.media_subtitle2,
|
R.id.media_subtitle1,
|
||||||
R.id.media_subtitle3
|
R.id.media_subtitle2,
|
||||||
)
|
R.id.media_subtitle3
|
||||||
|
)
|
||||||
|
|
||||||
val mediaTitlesAndSubtitlesIds = setOf(
|
val mediaTitlesAndSubtitlesIds =
|
||||||
R.id.media_title1,
|
setOf(
|
||||||
R.id.media_title2,
|
R.id.media_title1,
|
||||||
R.id.media_title3,
|
R.id.media_title2,
|
||||||
R.id.media_subtitle1,
|
R.id.media_title3,
|
||||||
R.id.media_subtitle2,
|
R.id.media_subtitle1,
|
||||||
R.id.media_subtitle3
|
R.id.media_subtitle2,
|
||||||
)
|
R.id.media_subtitle3
|
||||||
|
)
|
||||||
|
|
||||||
val mediaContainersIds = setOf(
|
val mediaContainersIds =
|
||||||
R.id.media_cover1_container,
|
setOf(
|
||||||
R.id.media_cover2_container,
|
R.id.media_cover1_container,
|
||||||
R.id.media_cover3_container
|
R.id.media_cover2_container,
|
||||||
)
|
R.id.media_cover3_container
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,55 +25,38 @@ import android.util.Log
|
|||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import com.android.internal.logging.InstanceId
|
import com.android.internal.logging.InstanceId
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting const val KEY_SMARTSPACE_APP_NAME = "KEY_SMARTSPACE_APP_NAME"
|
||||||
const val KEY_SMARTSPACE_APP_NAME = "KEY_SMARTSPACE_APP_NAME"
|
|
||||||
|
|
||||||
/** State of a Smartspace media recommendations view. */
|
/** State of a Smartspace media recommendations view. */
|
||||||
data class SmartspaceMediaData(
|
data class SmartspaceMediaData(
|
||||||
/**
|
/** Unique id of a Smartspace media target. */
|
||||||
* Unique id of a Smartspace media target.
|
|
||||||
*/
|
|
||||||
val targetId: String,
|
val targetId: String,
|
||||||
/**
|
/** Indicates if the status is active. */
|
||||||
* Indicates if the status is active.
|
|
||||||
*/
|
|
||||||
val isActive: Boolean,
|
val isActive: Boolean,
|
||||||
/**
|
/** Package name of the media recommendations' provider-app. */
|
||||||
* Package name of the media recommendations' provider-app.
|
|
||||||
*/
|
|
||||||
val packageName: String,
|
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?,
|
val cardAction: SmartspaceAction?,
|
||||||
/**
|
/** List of media recommendations. */
|
||||||
* List of media recommendations.
|
|
||||||
*/
|
|
||||||
val recommendations: List<SmartspaceAction>,
|
val recommendations: List<SmartspaceAction>,
|
||||||
/**
|
/** Intent for the user's initiated dismissal. */
|
||||||
* Intent for the user's initiated dismissal.
|
|
||||||
*/
|
|
||||||
val dismissIntent: Intent?,
|
val dismissIntent: Intent?,
|
||||||
/**
|
/** The timestamp in milliseconds that headphone is connected. */
|
||||||
* The timestamp in milliseconds that headphone is connected.
|
|
||||||
*/
|
|
||||||
val headphoneConnectionTimeMillis: Long,
|
val headphoneConnectionTimeMillis: Long,
|
||||||
/**
|
/** Instance ID for [MediaUiEventLogger] */
|
||||||
* Instance ID for [MediaUiEventLogger]
|
|
||||||
*/
|
|
||||||
val instanceId: InstanceId
|
val instanceId: InstanceId
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Indicates if all the data is valid.
|
* Indicates if all the data is valid.
|
||||||
*
|
*
|
||||||
* TODO(b/230333302): Make MediaControlPanel more flexible so that we can display fewer than
|
* TODO(b/230333302): Make MediaControlPanel more flexible so that we can display fewer than
|
||||||
|
* ```
|
||||||
* [NUM_REQUIRED_RECOMMENDATIONS].
|
* [NUM_REQUIRED_RECOMMENDATIONS].
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
fun isValid() = getValidRecommendations().size >= 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 }
|
fun getValidRecommendations() = recommendations.filter { it.icon != null }
|
||||||
|
|
||||||
/** Returns the upstream app name if available. */
|
/** Returns the upstream app name if available. */
|
||||||
@@ -92,7 +75,8 @@ data class SmartspaceMediaData(
|
|||||||
Log.w(
|
Log.w(
|
||||||
TAG,
|
TAG,
|
||||||
"Package $packageName does not have a main launcher activity. " +
|
"Package $packageName does not have a main launcher activity. " +
|
||||||
"Fallback to full app name")
|
"Fallback to full app name"
|
||||||
|
)
|
||||||
return try {
|
return try {
|
||||||
val applicationInfo = packageManager.getApplicationInfo(packageName, /* flags= */ 0)
|
val applicationInfo = packageManager.getApplicationInfo(packageName, /* flags= */ 0)
|
||||||
packageManager.getApplicationLabel(applicationInfo)
|
packageManager.getApplicationLabel(applicationInfo)
|
||||||
|
|||||||
@@ -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
|
package com.android.systemui.media.controls.models.recommendation
|
||||||
|
|
||||||
import android.app.smartspace.SmartspaceTarget
|
import android.app.smartspace.SmartspaceTarget
|
||||||
@@ -23,7 +39,7 @@ class SmartspaceMediaDataProvider @Inject constructor() : BcSmartspaceDataPlugin
|
|||||||
smartspaceMediaTargetListeners.remove(smartspaceTargetListener)
|
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>) {
|
override fun onTargetsAvailable(targets: List<SmartspaceTarget>) {
|
||||||
// Filter out non-media targets.
|
// Filter out non-media targets.
|
||||||
val mediaTargets = mutableListOf<SmartspaceTarget>()
|
val mediaTargets = mutableListOf<SmartspaceTarget>()
|
||||||
|
|||||||
@@ -17,17 +17,15 @@
|
|||||||
package com.android.systemui.media.controls.pipeline
|
package com.android.systemui.media.controls.pipeline
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|
||||||
import com.android.settingslib.bluetooth.LocalBluetoothManager
|
import com.android.settingslib.bluetooth.LocalBluetoothManager
|
||||||
import com.android.settingslib.media.InfoMediaManager
|
import com.android.settingslib.media.InfoMediaManager
|
||||||
import com.android.settingslib.media.LocalMediaManager
|
import com.android.settingslib.media.LocalMediaManager
|
||||||
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/** Factory to create [LocalMediaManager] objects. */
|
||||||
* Factory to create [LocalMediaManager] objects.
|
class LocalMediaManagerFactory
|
||||||
*/
|
@Inject
|
||||||
class LocalMediaManagerFactory @Inject constructor(
|
constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val localBluetoothManager: LocalBluetoothManager?
|
private val localBluetoothManager: LocalBluetoothManager?
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -21,11 +21,9 @@ import com.android.systemui.media.controls.models.player.MediaDeviceData
|
|||||||
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
|
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/** Combines [MediaDataManager.Listener] events with [MediaDeviceManager.Listener] events. */
|
||||||
* Combines [MediaDataManager.Listener] events with [MediaDeviceManager.Listener] events.
|
class MediaDataCombineLatest @Inject constructor() :
|
||||||
*/
|
MediaDataManager.Listener, MediaDeviceManager.Listener {
|
||||||
class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener,
|
|
||||||
MediaDeviceManager.Listener {
|
|
||||||
|
|
||||||
private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
|
private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
|
||||||
private val entries: MutableMap<String, Pair<MediaData?, MediaDeviceData?>> = mutableMapOf()
|
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) }
|
listeners.toSet().forEach { it.onSmartspaceMediaDataRemoved(key, immediately) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMediaDeviceChanged(
|
override fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?) {
|
||||||
key: String,
|
|
||||||
oldKey: String?,
|
|
||||||
data: MediaDeviceData?
|
|
||||||
) {
|
|
||||||
if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
|
if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
|
||||||
entries[key] = entries.remove(oldKey)?.first to data
|
entries[key] = entries.remove(oldKey)?.first to data
|
||||||
update(key, oldKey)
|
update(key, oldKey)
|
||||||
@@ -86,9 +80,7 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener,
|
|||||||
*/
|
*/
|
||||||
fun addListener(listener: MediaDataManager.Listener) = listeners.add(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)
|
fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)
|
||||||
|
|
||||||
private fun update(key: String, oldKey: String?) {
|
private fun update(key: String, oldKey: String?) {
|
||||||
@@ -96,18 +88,14 @@ class MediaDataCombineLatest @Inject constructor() : MediaDataManager.Listener,
|
|||||||
if (entry != null && device != null) {
|
if (entry != null && device != null) {
|
||||||
val data = entry.copy(device = device)
|
val data = entry.copy(device = device)
|
||||||
val listenersCopy = listeners.toSet()
|
val listenersCopy = listeners.toSet()
|
||||||
listenersCopy.forEach {
|
listenersCopy.forEach { it.onMediaDataLoaded(key, oldKey, data) }
|
||||||
it.onMediaDataLoaded(key, oldKey, data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun remove(key: String) {
|
private fun remove(key: String) {
|
||||||
entries.remove(key)?.let {
|
entries.remove(key)?.let {
|
||||||
val listenersCopy = listeners.toSet()
|
val listenersCopy = listeners.toSet()
|
||||||
listenersCopy.forEach {
|
listenersCopy.forEach { it.onMediaDataRemoved(key) }
|
||||||
it.onMediaDataRemoved(key)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ import com.android.internal.annotations.VisibleForTesting
|
|||||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||||
import com.android.systemui.broadcast.BroadcastSender
|
import com.android.systemui.broadcast.BroadcastSender
|
||||||
import com.android.systemui.dagger.qualifiers.Main
|
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.player.MediaData
|
||||||
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
|
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.settings.CurrentUserTracker
|
||||||
import com.android.systemui.statusbar.NotificationLockscreenUserManager
|
import com.android.systemui.statusbar.NotificationLockscreenUserManager
|
||||||
import com.android.systemui.util.time.SystemClock
|
import com.android.systemui.util.time.SystemClock
|
||||||
@@ -37,7 +37,8 @@ import kotlin.collections.LinkedHashMap
|
|||||||
|
|
||||||
private const val TAG = "MediaDataFilter"
|
private const val TAG = "MediaDataFilter"
|
||||||
private const val DEBUG = true
|
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")
|
".android.apps.gsa.staticplugins.opa.smartspace.ExportedSmartspaceTrampolineActivity")
|
||||||
private const val RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY = "resumable_media_max_age_seconds"
|
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.
|
* available within this time window, smartspace recommendations will be shown instead.
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
internal val SMARTSPACE_MAX_AGE = SystemProperties
|
internal val SMARTSPACE_MAX_AGE =
|
||||||
.getLong("debug.sysui.smartspace_max_age", TimeUnit.MINUTES.toMillis(30))
|
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
|
* 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
|
* This is added at the end of the pipeline since we may still need to handle callbacks from
|
||||||
* background users (e.g. timeouts).
|
* background users (e.g. timeouts).
|
||||||
*/
|
*/
|
||||||
class MediaDataFilter @Inject constructor(
|
class MediaDataFilter
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val broadcastDispatcher: BroadcastDispatcher,
|
private val broadcastDispatcher: BroadcastDispatcher,
|
||||||
private val broadcastSender: BroadcastSender,
|
private val broadcastSender: BroadcastSender,
|
||||||
@@ -79,12 +82,13 @@ class MediaDataFilter @Inject constructor(
|
|||||||
private var reactivatedKey: String? = null
|
private var reactivatedKey: String? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
userTracker = object : CurrentUserTracker(broadcastDispatcher) {
|
userTracker =
|
||||||
override fun onUserSwitched(newUserId: Int) {
|
object : CurrentUserTracker(broadcastDispatcher) {
|
||||||
// Post this so we can be sure lockscreenUserManager already got the broadcast
|
override fun onUserSwitched(newUserId: Int) {
|
||||||
executor.execute { handleUserSwitched(newUserId) }
|
// Post this so we can be sure lockscreenUserManager already got the broadcast
|
||||||
|
executor.execute { handleUserSwitched(newUserId) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
userTracker.startTracking()
|
userTracker.startTracking()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,9 +115,7 @@ class MediaDataFilter @Inject constructor(
|
|||||||
userEntries.put(key, data)
|
userEntries.put(key, data)
|
||||||
|
|
||||||
// Notify listeners
|
// Notify listeners
|
||||||
listeners.forEach {
|
listeners.forEach { it.onMediaDataLoaded(key, oldKey, data) }
|
||||||
it.onMediaDataLoaded(key, oldKey, data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSmartspaceMediaDataLoaded(
|
override fun onSmartspaceMediaDataLoaded(
|
||||||
@@ -131,14 +133,11 @@ class MediaDataFilter @Inject constructor(
|
|||||||
smartspaceMediaData = data
|
smartspaceMediaData = data
|
||||||
|
|
||||||
// Before forwarding the smartspace target, first check if we have recently inactive media
|
// Before forwarding the smartspace target, first check if we have recently inactive media
|
||||||
val sorted = userEntries.toSortedMap(compareBy {
|
val sorted = userEntries.toSortedMap(compareBy { userEntries.get(it)?.lastActive ?: -1 })
|
||||||
userEntries.get(it)?.lastActive ?: -1
|
|
||||||
})
|
|
||||||
val timeSinceActive = timeSinceActiveForMostRecentMedia(sorted)
|
val timeSinceActive = timeSinceActiveForMostRecentMedia(sorted)
|
||||||
var smartspaceMaxAgeMillis = SMARTSPACE_MAX_AGE
|
var smartspaceMaxAgeMillis = SMARTSPACE_MAX_AGE
|
||||||
data.cardAction?.let {
|
data.cardAction?.let {
|
||||||
val smartspaceMaxAgeSeconds =
|
val smartspaceMaxAgeSeconds = it.extras.getLong(RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY, 0)
|
||||||
it.extras.getLong(RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY, 0)
|
|
||||||
if (smartspaceMaxAgeSeconds > 0) {
|
if (smartspaceMaxAgeSeconds > 0) {
|
||||||
smartspaceMaxAgeMillis = TimeUnit.SECONDS.toMillis(smartspaceMaxAgeSeconds)
|
smartspaceMaxAgeMillis = TimeUnit.SECONDS.toMillis(smartspaceMaxAgeSeconds)
|
||||||
}
|
}
|
||||||
@@ -155,13 +154,21 @@ class MediaDataFilter @Inject constructor(
|
|||||||
Log.d(TAG, "reactivating $lastActiveKey instead of smartspace")
|
Log.d(TAG, "reactivating $lastActiveKey instead of smartspace")
|
||||||
reactivatedKey = lastActiveKey
|
reactivatedKey = lastActiveKey
|
||||||
val mediaData = sorted.get(lastActiveKey)!!.copy(active = true)
|
val mediaData = sorted.get(lastActiveKey)!!.copy(active = true)
|
||||||
logger.logRecommendationActivated(mediaData.appUid, mediaData.packageName,
|
logger.logRecommendationActivated(
|
||||||
mediaData.instanceId)
|
mediaData.appUid,
|
||||||
|
mediaData.packageName,
|
||||||
|
mediaData.instanceId
|
||||||
|
)
|
||||||
listeners.forEach {
|
listeners.forEach {
|
||||||
it.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData,
|
it.onMediaDataLoaded(
|
||||||
receivedSmartspaceCardLatency =
|
lastActiveKey,
|
||||||
|
lastActiveKey,
|
||||||
|
mediaData,
|
||||||
|
receivedSmartspaceCardLatency =
|
||||||
(systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis)
|
(systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis)
|
||||||
.toInt(), isSsReactivated = true)
|
.toInt(),
|
||||||
|
isSsReactivated = true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -173,8 +180,10 @@ class MediaDataFilter @Inject constructor(
|
|||||||
Log.d(TAG, "Invalid recommendation data. Skip showing the rec card")
|
Log.d(TAG, "Invalid recommendation data. Skip showing the rec card")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.logRecommendationAdded(smartspaceMediaData.packageName,
|
logger.logRecommendationAdded(
|
||||||
smartspaceMediaData.instanceId)
|
smartspaceMediaData.packageName,
|
||||||
|
smartspaceMediaData.instanceId
|
||||||
|
)
|
||||||
listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
|
listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,9 +191,7 @@ class MediaDataFilter @Inject constructor(
|
|||||||
allEntries.remove(key)
|
allEntries.remove(key)
|
||||||
userEntries.remove(key)?.let {
|
userEntries.remove(key)?.let {
|
||||||
// Only notify listeners if something actually changed
|
// Only notify listeners if something actually changed
|
||||||
listeners.forEach {
|
listeners.forEach { it.onMediaDataRemoved(key) }
|
||||||
it.onMediaDataRemoved(key)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,16 +204,17 @@ class MediaDataFilter @Inject constructor(
|
|||||||
// Notify listeners to update with actual active value
|
// Notify listeners to update with actual active value
|
||||||
userEntries.get(lastActiveKey)?.let { mediaData ->
|
userEntries.get(lastActiveKey)?.let { mediaData ->
|
||||||
listeners.forEach {
|
listeners.forEach {
|
||||||
it.onMediaDataLoaded(
|
it.onMediaDataLoaded(lastActiveKey, lastActiveKey, mediaData, immediately)
|
||||||
lastActiveKey, lastActiveKey, mediaData, immediately)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (smartspaceMediaData.isActive) {
|
if (smartspaceMediaData.isActive) {
|
||||||
smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
|
smartspaceMediaData =
|
||||||
targetId = smartspaceMediaData.targetId,
|
EMPTY_SMARTSPACE_MEDIA_DATA.copy(
|
||||||
instanceId = smartspaceMediaData.instanceId)
|
targetId = smartspaceMediaData.targetId,
|
||||||
|
instanceId = smartspaceMediaData.instanceId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) }
|
listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) }
|
||||||
}
|
}
|
||||||
@@ -221,25 +229,19 @@ class MediaDataFilter @Inject constructor(
|
|||||||
userEntries.clear()
|
userEntries.clear()
|
||||||
keyCopy.forEach {
|
keyCopy.forEach {
|
||||||
if (DEBUG) Log.d(TAG, "Removing $it after user change")
|
if (DEBUG) Log.d(TAG, "Removing $it after user change")
|
||||||
listenersCopy.forEach { listener ->
|
listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) }
|
||||||
listener.onMediaDataRemoved(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
allEntries.forEach { (key, data) ->
|
allEntries.forEach { (key, data) ->
|
||||||
if (lockscreenUserManager.isCurrentProfile(data.userId)) {
|
if (lockscreenUserManager.isCurrentProfile(data.userId)) {
|
||||||
if (DEBUG) Log.d(TAG, "Re-adding $key after user change")
|
if (DEBUG) Log.d(TAG, "Re-adding $key after user change")
|
||||||
userEntries.put(key, data)
|
userEntries.put(key, data)
|
||||||
listenersCopy.forEach { listener ->
|
listenersCopy.forEach { listener -> listener.onMediaDataLoaded(key, null, data) }
|
||||||
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() {
|
fun onSwipeToDismiss() {
|
||||||
if (DEBUG) Log.d(TAG, "Media carousel swiped away")
|
if (DEBUG) Log.d(TAG, "Media carousel swiped away")
|
||||||
val mediaKeys = userEntries.keys.toSet()
|
val mediaKeys = userEntries.keys.toSet()
|
||||||
@@ -250,55 +252,52 @@ class MediaDataFilter @Inject constructor(
|
|||||||
if (smartspaceMediaData.isActive) {
|
if (smartspaceMediaData.isActive) {
|
||||||
val dismissIntent = smartspaceMediaData.dismissIntent
|
val dismissIntent = smartspaceMediaData.dismissIntent
|
||||||
if (dismissIntent == null) {
|
if (dismissIntent == null) {
|
||||||
Log.w(TAG, "Cannot create dismiss action click action: " +
|
Log.w(
|
||||||
"extras missing dismiss_intent.")
|
TAG,
|
||||||
} else if (dismissIntent.getComponent() != null &&
|
"Cannot create dismiss action click action: " + "extras missing dismiss_intent."
|
||||||
dismissIntent.getComponent().getClassName()
|
)
|
||||||
== EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME) {
|
} else if (
|
||||||
|
dismissIntent.getComponent() != null &&
|
||||||
|
dismissIntent.getComponent().getClassName() ==
|
||||||
|
EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME
|
||||||
|
) {
|
||||||
// Dismiss the card Smartspace data through Smartspace trampoline activity.
|
// Dismiss the card Smartspace data through Smartspace trampoline activity.
|
||||||
context.startActivity(dismissIntent)
|
context.startActivity(dismissIntent)
|
||||||
} else {
|
} else {
|
||||||
broadcastSender.sendBroadcast(dismissIntent)
|
broadcastSender.sendBroadcast(dismissIntent)
|
||||||
}
|
}
|
||||||
smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
|
smartspaceMediaData =
|
||||||
targetId = smartspaceMediaData.targetId,
|
EMPTY_SMARTSPACE_MEDIA_DATA.copy(
|
||||||
instanceId = smartspaceMediaData.instanceId)
|
targetId = smartspaceMediaData.targetId,
|
||||||
mediaDataManager.dismissSmartspaceRecommendation(smartspaceMediaData.targetId,
|
instanceId = smartspaceMediaData.instanceId
|
||||||
delay = 0L)
|
)
|
||||||
|
mediaDataManager.dismissSmartspaceRecommendation(
|
||||||
|
smartspaceMediaData.targetId,
|
||||||
|
delay = 0L
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Are there any active media entries, including the recommendation? */
|
||||||
* Are there any active media entries, including the recommendation?
|
fun hasActiveMediaOrRecommendation() =
|
||||||
*/
|
userEntries.any { it.value.active } ||
|
||||||
fun hasActiveMediaOrRecommendation() = userEntries.any { it.value.active } ||
|
|
||||||
(smartspaceMediaData.isActive &&
|
(smartspaceMediaData.isActive &&
|
||||||
(smartspaceMediaData.isValid() || reactivatedKey != null))
|
(smartspaceMediaData.isValid() || reactivatedKey != null))
|
||||||
|
|
||||||
/**
|
/** Are there any media entries we should display? */
|
||||||
* Are there any media entries we should display?
|
fun hasAnyMediaOrRecommendation() =
|
||||||
*/
|
userEntries.isNotEmpty() || (smartspaceMediaData.isActive && smartspaceMediaData.isValid())
|
||||||
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 }
|
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()
|
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)
|
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)
|
fun removeListener(listener: MediaDataManager.Listener) = _listeners.remove(listener)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -318,8 +317,6 @@ class MediaDataFilter @Inject constructor(
|
|||||||
|
|
||||||
val now = systemClock.elapsedRealtime()
|
val now = systemClock.elapsedRealtime()
|
||||||
val lastActiveKey = sortedEntries.lastKey() // most recently active
|
val lastActiveKey = sortedEntries.lastKey() // most recently active
|
||||||
return sortedEntries.get(lastActiveKey)?.let {
|
return sortedEntries.get(lastActiveKey)?.let { now - it.lastActive } ?: Long.MAX_VALUE
|
||||||
now - it.lastActive
|
|
||||||
} ?: Long.MAX_VALUE
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -36,10 +36,10 @@ import com.android.systemui.R
|
|||||||
import com.android.systemui.dagger.qualifiers.Background
|
import com.android.systemui.dagger.qualifiers.Background
|
||||||
import com.android.systemui.dagger.qualifiers.Main
|
import com.android.systemui.dagger.qualifiers.Main
|
||||||
import com.android.systemui.dump.DumpManager
|
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.MediaData
|
||||||
import com.android.systemui.media.controls.models.player.MediaDeviceData
|
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.MediaMuteAwaitConnectionManager
|
||||||
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
|
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
|
||||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
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 TAG = "MediaDeviceManager"
|
||||||
private const val DEBUG = true
|
private const val DEBUG = true
|
||||||
|
|
||||||
/**
|
/** Provides information about the route (ie. device) where playback is occurring. */
|
||||||
* Provides information about the route (ie. device) where playback is occurring.
|
class MediaDeviceManager
|
||||||
*/
|
@Inject
|
||||||
class MediaDeviceManager @Inject constructor(
|
constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val controllerFactory: MediaControllerFactory,
|
private val controllerFactory: MediaControllerFactory,
|
||||||
private val localMediaManagerFactory: LocalMediaManagerFactory,
|
private val localMediaManagerFactory: LocalMediaManagerFactory,
|
||||||
@@ -74,14 +74,10 @@ class MediaDeviceManager @Inject constructor(
|
|||||||
dumpManager.registerDumpable(javaClass.name, this)
|
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)
|
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)
|
fun removeListener(listener: Listener) = listeners.remove(listener)
|
||||||
|
|
||||||
override fun onMediaDataLoaded(
|
override fun onMediaDataLoaded(
|
||||||
@@ -105,19 +101,11 @@ class MediaDeviceManager @Inject constructor(
|
|||||||
processDevice(key, oldKey, data.device)
|
processDevice(key, oldKey, data.device)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val controller = data.token?.let {
|
val controller = data.token?.let { controllerFactory.create(it) }
|
||||||
controllerFactory.create(it)
|
|
||||||
}
|
|
||||||
val localMediaManager = localMediaManagerFactory.create(data.packageName)
|
val localMediaManager = localMediaManagerFactory.create(data.packageName)
|
||||||
val muteAwaitConnectionManager =
|
val muteAwaitConnectionManager =
|
||||||
muteAwaitConnectionManagerFactory.create(localMediaManager)
|
muteAwaitConnectionManagerFactory.create(localMediaManager)
|
||||||
entry = Entry(
|
entry = Entry(key, oldKey, controller, localMediaManager, muteAwaitConnectionManager)
|
||||||
key,
|
|
||||||
oldKey,
|
|
||||||
controller,
|
|
||||||
localMediaManager,
|
|
||||||
muteAwaitConnectionManager
|
|
||||||
)
|
|
||||||
entries[key] = entry
|
entries[key] = entry
|
||||||
entry.start()
|
entry.start()
|
||||||
}
|
}
|
||||||
@@ -126,11 +114,7 @@ class MediaDeviceManager @Inject constructor(
|
|||||||
override fun onMediaDataRemoved(key: String) {
|
override fun onMediaDataRemoved(key: String) {
|
||||||
val token = entries.remove(key)
|
val token = entries.remove(key)
|
||||||
token?.stop()
|
token?.stop()
|
||||||
token?.let {
|
token?.let { listeners.forEach { it.onKeyRemoved(key) } }
|
||||||
listeners.forEach {
|
|
||||||
it.onKeyRemoved(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dump(pw: PrintWriter, args: Array<String>) {
|
override fun dump(pw: PrintWriter, args: Array<String>) {
|
||||||
@@ -145,9 +129,7 @@ class MediaDeviceManager @Inject constructor(
|
|||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
private fun processDevice(key: String, oldKey: String?, device: MediaDeviceData?) {
|
private fun processDevice(key: String, oldKey: String?, device: MediaDeviceData?) {
|
||||||
listeners.forEach {
|
listeners.forEach { it.onMediaDeviceChanged(key, oldKey, device) }
|
||||||
it.onMediaDeviceChanged(key, oldKey, device)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Listener {
|
interface Listener {
|
||||||
@@ -163,8 +145,10 @@ class MediaDeviceManager @Inject constructor(
|
|||||||
val controller: MediaController?,
|
val controller: MediaController?,
|
||||||
val localMediaManager: LocalMediaManager,
|
val localMediaManager: LocalMediaManager,
|
||||||
val muteAwaitConnectionManager: MediaMuteAwaitConnectionManager?
|
val muteAwaitConnectionManager: MediaMuteAwaitConnectionManager?
|
||||||
) : LocalMediaManager.DeviceCallback, MediaController.Callback(),
|
) :
|
||||||
BluetoothLeBroadcast.Callback {
|
LocalMediaManager.DeviceCallback,
|
||||||
|
MediaController.Callback(),
|
||||||
|
BluetoothLeBroadcast.Callback {
|
||||||
|
|
||||||
val token
|
val token
|
||||||
get() = controller?.sessionToken
|
get() = controller?.sessionToken
|
||||||
@@ -175,54 +159,52 @@ class MediaDeviceManager @Inject constructor(
|
|||||||
val sameWithoutIcon = value != null && value.equalsWithoutIcon(field)
|
val sameWithoutIcon = value != null && value.equalsWithoutIcon(field)
|
||||||
if (!started || !sameWithoutIcon) {
|
if (!started || !sameWithoutIcon) {
|
||||||
field = value
|
field = value
|
||||||
fgExecutor.execute {
|
fgExecutor.execute { processDevice(key, oldKey, value) }
|
||||||
processDevice(key, oldKey, value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// A device that is not yet connected but is expected to connect imminently. Because it's
|
// 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.
|
// expected to connect imminently, it should be displayed as the current device.
|
||||||
private var aboutToConnectDeviceOverride: AboutToConnectDevice? = null
|
private var aboutToConnectDeviceOverride: AboutToConnectDevice? = null
|
||||||
private var broadcastDescription: String? = null
|
private var broadcastDescription: String? = null
|
||||||
private val configListener = object : ConfigurationController.ConfigurationListener {
|
private val configListener =
|
||||||
override fun onLocaleListChanged() {
|
object : ConfigurationController.ConfigurationListener {
|
||||||
updateCurrent()
|
override fun onLocaleListChanged() {
|
||||||
|
updateCurrent()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@AnyThread
|
@AnyThread
|
||||||
fun start() = bgExecutor.execute {
|
fun start() =
|
||||||
if (!started) {
|
bgExecutor.execute {
|
||||||
localMediaManager.registerCallback(this)
|
if (!started) {
|
||||||
localMediaManager.startScan()
|
localMediaManager.registerCallback(this)
|
||||||
muteAwaitConnectionManager?.startListening()
|
localMediaManager.startScan()
|
||||||
playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
|
muteAwaitConnectionManager?.startListening()
|
||||||
controller?.registerCallback(this)
|
playbackType = controller?.playbackInfo?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
|
||||||
updateCurrent()
|
controller?.registerCallback(this)
|
||||||
started = true
|
updateCurrent()
|
||||||
configurationController.addCallback(configListener)
|
started = true
|
||||||
|
configurationController.addCallback(configListener)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@AnyThread
|
@AnyThread
|
||||||
fun stop() = bgExecutor.execute {
|
fun stop() =
|
||||||
if (started) {
|
bgExecutor.execute {
|
||||||
started = false
|
if (started) {
|
||||||
controller?.unregisterCallback(this)
|
started = false
|
||||||
localMediaManager.stopScan()
|
controller?.unregisterCallback(this)
|
||||||
localMediaManager.unregisterCallback(this)
|
localMediaManager.stopScan()
|
||||||
muteAwaitConnectionManager?.stopListening()
|
localMediaManager.unregisterCallback(this)
|
||||||
configurationController.removeCallback(configListener)
|
muteAwaitConnectionManager?.stopListening()
|
||||||
|
configurationController.removeCallback(configListener)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun dump(pw: PrintWriter) {
|
fun dump(pw: PrintWriter) {
|
||||||
val routingSession = controller?.let {
|
val routingSession =
|
||||||
mr2manager.getRoutingSessionForMediaController(it)
|
controller?.let { mr2manager.getRoutingSessionForMediaController(it) }
|
||||||
}
|
val selectedRoutes = routingSession?.let { mr2manager.getSelectedRoutes(it) }
|
||||||
val selectedRoutes = routingSession?.let {
|
|
||||||
mr2manager.getSelectedRoutes(it)
|
|
||||||
}
|
|
||||||
with(pw) {
|
with(pw) {
|
||||||
println(" current device is ${current?.name}")
|
println(" current device is ${current?.name}")
|
||||||
val type = controller?.playbackInfo?.playbackType
|
val type = controller?.playbackInfo?.playbackType
|
||||||
@@ -242,14 +224,11 @@ class MediaDeviceManager @Inject constructor(
|
|||||||
updateCurrent()
|
updateCurrent()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDeviceListUpdate(devices: List<MediaDevice>?) = bgExecutor.execute {
|
override fun onDeviceListUpdate(devices: List<MediaDevice>?) =
|
||||||
updateCurrent()
|
bgExecutor.execute { updateCurrent() }
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSelectedDeviceStateChanged(device: MediaDevice, state: Int) {
|
override fun onSelectedDeviceStateChanged(device: MediaDevice, state: Int) {
|
||||||
bgExecutor.execute {
|
bgExecutor.execute { updateCurrent() }
|
||||||
updateCurrent()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAboutToConnectDeviceAdded(
|
override fun onAboutToConnectDeviceAdded(
|
||||||
@@ -257,14 +236,17 @@ class MediaDeviceManager @Inject constructor(
|
|||||||
deviceName: String,
|
deviceName: String,
|
||||||
deviceIcon: Drawable?
|
deviceIcon: Drawable?
|
||||||
) {
|
) {
|
||||||
aboutToConnectDeviceOverride = AboutToConnectDevice(
|
aboutToConnectDeviceOverride =
|
||||||
fullMediaDevice = localMediaManager.getMediaDeviceById(deviceAddress),
|
AboutToConnectDevice(
|
||||||
backupMediaDeviceData = MediaDeviceData(
|
fullMediaDevice = localMediaManager.getMediaDeviceById(deviceAddress),
|
||||||
/* enabled */ enabled = true,
|
backupMediaDeviceData =
|
||||||
/* icon */ deviceIcon,
|
MediaDeviceData(
|
||||||
/* name */ deviceName,
|
/* enabled */ enabled = true,
|
||||||
/* showBroadcastButton */ showBroadcastButton = false)
|
/* icon */ deviceIcon,
|
||||||
)
|
/* name */ deviceName,
|
||||||
|
/* showBroadcastButton */ showBroadcastButton = false
|
||||||
|
)
|
||||||
|
)
|
||||||
updateCurrent()
|
updateCurrent()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,8 +273,11 @@ class MediaDeviceManager @Inject constructor(
|
|||||||
metadata: BluetoothLeBroadcastMetadata
|
metadata: BluetoothLeBroadcastMetadata
|
||||||
) {
|
) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = $broadcastId , " +
|
Log.d(
|
||||||
"metadata = $metadata")
|
TAG,
|
||||||
|
"onBroadcastMetadataChanged(), broadcastId = $broadcastId , " +
|
||||||
|
"metadata = $metadata"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
updateCurrent()
|
updateCurrent()
|
||||||
}
|
}
|
||||||
@@ -319,8 +304,10 @@ class MediaDeviceManager @Inject constructor(
|
|||||||
|
|
||||||
override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {
|
override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onBroadcastUpdateFailed(), reason = $reason , " +
|
Log.d(
|
||||||
"broadcastId = $broadcastId")
|
TAG,
|
||||||
|
"onBroadcastUpdateFailed(), reason = $reason , " + "broadcastId = $broadcastId"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,34 +318,45 @@ class MediaDeviceManager @Inject constructor(
|
|||||||
@WorkerThread
|
@WorkerThread
|
||||||
private fun updateCurrent() {
|
private fun updateCurrent() {
|
||||||
if (isLeAudioBroadcastEnabled()) {
|
if (isLeAudioBroadcastEnabled()) {
|
||||||
current = MediaDeviceData(
|
current =
|
||||||
|
MediaDeviceData(
|
||||||
/* enabled */ true,
|
/* enabled */ true,
|
||||||
/* icon */ context.getDrawable(R.drawable.settings_input_antenna),
|
/* icon */ context.getDrawable(R.drawable.settings_input_antenna),
|
||||||
/* name */ broadcastDescription,
|
/* name */ broadcastDescription,
|
||||||
/* intent */ null,
|
/* intent */ null,
|
||||||
/* showBroadcastButton */ showBroadcastButton = true)
|
/* showBroadcastButton */ showBroadcastButton = true
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
val aboutToConnect = aboutToConnectDeviceOverride
|
val aboutToConnect = aboutToConnectDeviceOverride
|
||||||
if (aboutToConnect != null &&
|
if (
|
||||||
|
aboutToConnect != null &&
|
||||||
aboutToConnect.fullMediaDevice == null &&
|
aboutToConnect.fullMediaDevice == null &&
|
||||||
aboutToConnect.backupMediaDeviceData != null) {
|
aboutToConnect.backupMediaDeviceData != null
|
||||||
|
) {
|
||||||
// Only use [backupMediaDeviceData] when we don't have [fullMediaDevice].
|
// Only use [backupMediaDeviceData] when we don't have [fullMediaDevice].
|
||||||
current = aboutToConnect.backupMediaDeviceData
|
current = aboutToConnect.backupMediaDeviceData
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val device = aboutToConnect?.fullMediaDevice
|
val device =
|
||||||
?: localMediaManager.currentConnectedDevice
|
aboutToConnect?.fullMediaDevice ?: localMediaManager.currentConnectedDevice
|
||||||
val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) }
|
val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) }
|
||||||
|
|
||||||
// If we have a controller but get a null route, then don't trust the device
|
// 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 enabled = device != null && (controller == null || route != null)
|
||||||
val name = if (controller == null || route != null) {
|
val name =
|
||||||
route?.name?.toString() ?: device?.name
|
if (controller == null || route != null) {
|
||||||
} else {
|
route?.name?.toString() ?: device?.name
|
||||||
null
|
} else {
|
||||||
}
|
null
|
||||||
current = MediaDeviceData(enabled, device?.iconWithoutBackground, name,
|
}
|
||||||
id = device?.id, showBroadcastButton = false)
|
current =
|
||||||
|
MediaDeviceData(
|
||||||
|
enabled,
|
||||||
|
device?.iconWithoutBackground,
|
||||||
|
name,
|
||||||
|
id = device?.id,
|
||||||
|
showBroadcastButton = false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,13 +386,16 @@ class MediaDeviceManager @Inject constructor(
|
|||||||
// unexpected result.
|
// unexpected result.
|
||||||
// Check the current media app's name is the same with current broadcast app's name
|
// Check the current media app's name is the same with current broadcast app's name
|
||||||
// or not.
|
// or not.
|
||||||
var mediaApp = MediaDataUtils.getAppLabel(
|
var mediaApp =
|
||||||
context, localMediaManager.packageName,
|
MediaDataUtils.getAppLabel(
|
||||||
context.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name))
|
context,
|
||||||
|
localMediaManager.packageName,
|
||||||
|
context.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name)
|
||||||
|
)
|
||||||
var isCurrentBroadcastedApp = TextUtils.equals(mediaApp, currentBroadcastedApp)
|
var isCurrentBroadcastedApp = TextUtils.equals(mediaApp, currentBroadcastedApp)
|
||||||
if (isCurrentBroadcastedApp) {
|
if (isCurrentBroadcastedApp) {
|
||||||
broadcastDescription = context.getString(
|
broadcastDescription =
|
||||||
R.string.broadcasting_description_is_broadcasting)
|
context.getString(R.string.broadcasting_description_is_broadcasting)
|
||||||
} else {
|
} else {
|
||||||
broadcastDescription = currentBroadcastedApp
|
broadcastDescription = currentBroadcastedApp
|
||||||
}
|
}
|
||||||
@@ -407,9 +408,9 @@ class MediaDeviceManager @Inject constructor(
|
|||||||
* [LocalMediaManager.DeviceCallback.onAboutToConnectDeviceAdded] for more information.
|
* [LocalMediaManager.DeviceCallback.onAboutToConnectDeviceAdded] for more information.
|
||||||
*
|
*
|
||||||
* @property fullMediaDevice a full-fledged [MediaDevice] object representing the device. If
|
* @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
|
* @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(
|
private data class AboutToConnectDevice(
|
||||||
val fullMediaDevice: MediaDevice? = null,
|
val fullMediaDevice: MediaDevice? = null,
|
||||||
|
|||||||
@@ -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
|
* 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.
|
* achieve this, update events for the local session need to be filtered.
|
||||||
*/
|
*/
|
||||||
class MediaSessionBasedFilter @Inject constructor(
|
class MediaSessionBasedFilter
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
private val sessionManager: MediaSessionManager,
|
private val sessionManager: MediaSessionManager,
|
||||||
@Main private val foregroundExecutor: Executor,
|
@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
|
// Keep track of MediaControllers for a given package to check if an app is casting and it
|
||||||
// filter loaded events for local sessions.
|
// filter loaded events for local sessions.
|
||||||
private val packageControllers: LinkedHashMap<String, MutableList<MediaController>> =
|
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
|
// 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.
|
// 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.
|
// Keep track of which media session tokens have associated notifications.
|
||||||
private val tokensWithNotifications: MutableSet<MediaSession.Token> = mutableSetOf()
|
private val tokensWithNotifications: MutableSet<MediaSession.Token> = mutableSetOf()
|
||||||
|
|
||||||
private val sessionListener = object : MediaSessionManager.OnActiveSessionsChangedListener {
|
private val sessionListener =
|
||||||
override fun onActiveSessionsChanged(controllers: List<MediaController>) {
|
object : MediaSessionManager.OnActiveSessionsChangedListener {
|
||||||
handleControllersChanged(controllers)
|
override fun onActiveSessionsChanged(controllers: List<MediaController>) {
|
||||||
|
handleControllersChanged(controllers)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
backgroundExecutor.execute {
|
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)
|
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)
|
fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -102,31 +101,32 @@ class MediaSessionBasedFilter @Inject constructor(
|
|||||||
isSsReactivated: Boolean
|
isSsReactivated: Boolean
|
||||||
) {
|
) {
|
||||||
backgroundExecutor.execute {
|
backgroundExecutor.execute {
|
||||||
data.token?.let {
|
data.token?.let { tokensWithNotifications.add(it) }
|
||||||
tokensWithNotifications.add(it)
|
|
||||||
}
|
|
||||||
val isMigration = oldKey != null && key != oldKey
|
val isMigration = oldKey != null && key != oldKey
|
||||||
if (isMigration) {
|
if (isMigration) {
|
||||||
keyedTokens.remove(oldKey)?.let { removed -> keyedTokens.put(key, removed) }
|
keyedTokens.remove(oldKey)?.let { removed -> keyedTokens.put(key, removed) }
|
||||||
}
|
}
|
||||||
if (data.token != null) {
|
if (data.token != null) {
|
||||||
keyedTokens.get(key)?.let {
|
keyedTokens.get(key)?.let { tokens -> tokens.add(data.token) }
|
||||||
tokens ->
|
?: run {
|
||||||
tokens.add(data.token)
|
val tokens = mutableSetOf(data.token)
|
||||||
} ?: run {
|
keyedTokens.put(key, tokens)
|
||||||
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
|
// Determine if an app is casting by checking if it has a session with playback type
|
||||||
// PLAYBACK_TYPE_REMOTE.
|
// PLAYBACK_TYPE_REMOTE.
|
||||||
val remoteControllers = packageControllers.get(data.packageName)?.filter {
|
val remoteControllers =
|
||||||
it.playbackInfo?.playbackType == PlaybackInfo.PLAYBACK_TYPE_REMOTE
|
packageControllers.get(data.packageName)?.filter {
|
||||||
}
|
it.playbackInfo?.playbackType == PlaybackInfo.PLAYBACK_TYPE_REMOTE
|
||||||
|
}
|
||||||
// Limiting search to only apps with a single remote session.
|
// Limiting search to only apps with a single remote session.
|
||||||
val remote = if (remoteControllers?.size == 1) remoteControllers.firstOrNull() else null
|
val remote = if (remoteControllers?.size == 1) remoteControllers.firstOrNull() else null
|
||||||
if (isMigration || remote == null || remote.sessionToken == data.token ||
|
if (
|
||||||
!tokensWithNotifications.contains(remote.sessionToken)) {
|
isMigration ||
|
||||||
|
remote == null ||
|
||||||
|
remote.sessionToken == data.token ||
|
||||||
|
!tokensWithNotifications.contains(remote.sessionToken)
|
||||||
|
) {
|
||||||
// Not filtering in this case. Passing the event along to listeners.
|
// Not filtering in this case. Passing the event along to listeners.
|
||||||
dispatchMediaDataLoaded(key, oldKey, data, immediately)
|
dispatchMediaDataLoaded(key, oldKey, data, immediately)
|
||||||
} else {
|
} else {
|
||||||
@@ -148,9 +148,7 @@ class MediaSessionBasedFilter @Inject constructor(
|
|||||||
data: SmartspaceMediaData,
|
data: SmartspaceMediaData,
|
||||||
shouldPrioritize: Boolean
|
shouldPrioritize: Boolean
|
||||||
) {
|
) {
|
||||||
backgroundExecutor.execute {
|
backgroundExecutor.execute { dispatchSmartspaceMediaDataLoaded(key, data) }
|
||||||
dispatchSmartspaceMediaDataLoaded(key, data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMediaDataRemoved(key: String) {
|
override fun onMediaDataRemoved(key: String) {
|
||||||
@@ -162,9 +160,7 @@ class MediaSessionBasedFilter @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
|
override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
|
||||||
backgroundExecutor.execute {
|
backgroundExecutor.execute { dispatchSmartspaceMediaDataRemoved(key, immediately) }
|
||||||
dispatchSmartspaceMediaDataRemoved(key, immediately)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun dispatchMediaDataLoaded(
|
private fun dispatchMediaDataLoaded(
|
||||||
@@ -179,9 +175,7 @@ class MediaSessionBasedFilter @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun dispatchMediaDataRemoved(key: String) {
|
private fun dispatchMediaDataRemoved(key: String) {
|
||||||
foregroundExecutor.execute {
|
foregroundExecutor.execute { listeners.toSet().forEach { it.onMediaDataRemoved(key) } }
|
||||||
listeners.toSet().forEach { it.onMediaDataRemoved(key) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun dispatchSmartspaceMediaDataLoaded(key: String, info: SmartspaceMediaData) {
|
private fun dispatchSmartspaceMediaDataLoaded(key: String, info: SmartspaceMediaData) {
|
||||||
@@ -198,15 +192,12 @@ class MediaSessionBasedFilter @Inject constructor(
|
|||||||
|
|
||||||
private fun handleControllersChanged(controllers: List<MediaController>) {
|
private fun handleControllersChanged(controllers: List<MediaController>) {
|
||||||
packageControllers.clear()
|
packageControllers.clear()
|
||||||
controllers.forEach {
|
controllers.forEach { controller ->
|
||||||
controller ->
|
packageControllers.get(controller.packageName)?.let { tokens -> tokens.add(controller) }
|
||||||
packageControllers.get(controller.packageName)?.let {
|
?: run {
|
||||||
tokens ->
|
val tokens = mutableListOf(controller)
|
||||||
tokens.add(controller)
|
packageControllers.put(controller.packageName, tokens)
|
||||||
} ?: run {
|
}
|
||||||
val tokens = mutableListOf(controller)
|
|
||||||
packageControllers.put(controller.packageName, tokens)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
tokensWithNotifications.retainAll(controllers.map { it.sessionToken })
|
tokensWithNotifications.retainAll(controllers.map { it.sessionToken })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ import android.os.SystemProperties
|
|||||||
import com.android.internal.annotations.VisibleForTesting
|
import com.android.internal.annotations.VisibleForTesting
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.dagger.qualifiers.Main
|
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.models.player.MediaData
|
||||||
|
import com.android.systemui.media.controls.util.MediaControllerFactory
|
||||||
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
||||||
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
|
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
|
||||||
import com.android.systemui.statusbar.SysuiStatusBarStateController
|
import com.android.systemui.statusbar.SysuiStatusBarStateController
|
||||||
@@ -33,18 +33,18 @@ import java.util.concurrent.TimeUnit
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
val PAUSED_MEDIA_TIMEOUT = SystemProperties
|
val PAUSED_MEDIA_TIMEOUT =
|
||||||
.getLong("debug.sysui.media_timeout", TimeUnit.MINUTES.toMillis(10))
|
SystemProperties.getLong("debug.sysui.media_timeout", TimeUnit.MINUTES.toMillis(10))
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
val RESUME_MEDIA_TIMEOUT = SystemProperties
|
val RESUME_MEDIA_TIMEOUT =
|
||||||
.getLong("debug.sysui.media_timeout_resume", TimeUnit.DAYS.toMillis(3))
|
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
|
@SysUISingleton
|
||||||
class MediaTimeoutListener @Inject constructor(
|
class MediaTimeoutListener
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
private val mediaControllerFactory: MediaControllerFactory,
|
private val mediaControllerFactory: MediaControllerFactory,
|
||||||
@Main private val mainExecutor: DelayableExecutor,
|
@Main private val mainExecutor: DelayableExecutor,
|
||||||
private val logger: MediaTimeoutLogger,
|
private val logger: MediaTimeoutLogger,
|
||||||
@@ -58,7 +58,9 @@ class MediaTimeoutListener @Inject constructor(
|
|||||||
* Callback representing that a media object is now expired:
|
* Callback representing that a media object is now expired:
|
||||||
* @param key Media control unique identifier
|
* @param key Media control unique identifier
|
||||||
* @param timedOut True when expired for {@code PAUSED_MEDIA_TIMEOUT} for active media,
|
* @param timedOut True when expired for {@code PAUSED_MEDIA_TIMEOUT} for active media,
|
||||||
|
* ```
|
||||||
* or {@code RESUME_MEDIA_TIMEOUT} for resume media
|
* or {@code RESUME_MEDIA_TIMEOUT} for resume media
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
lateinit var timeoutCallback: (String, Boolean) -> Unit
|
lateinit var timeoutCallback: (String, Boolean) -> Unit
|
||||||
|
|
||||||
@@ -70,21 +72,25 @@ class MediaTimeoutListener @Inject constructor(
|
|||||||
lateinit var stateCallback: (String, PlaybackState) -> Unit
|
lateinit var stateCallback: (String, PlaybackState) -> Unit
|
||||||
|
|
||||||
init {
|
init {
|
||||||
statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
|
statusBarStateController.addCallback(
|
||||||
override fun onDozingChanged(isDozing: Boolean) {
|
object : StatusBarStateController.StateListener {
|
||||||
if (!isDozing) {
|
override fun onDozingChanged(isDozing: Boolean) {
|
||||||
// Check whether any timeouts should have expired
|
if (!isDozing) {
|
||||||
mediaListeners.forEach { (key, listener) ->
|
// Check whether any timeouts should have expired
|
||||||
if (listener.cancellation != null &&
|
mediaListeners.forEach { (key, listener) ->
|
||||||
listener.expiration <= systemClock.elapsedRealtime()) {
|
if (
|
||||||
// We dozed too long - timeout now, and cancel the pending one
|
listener.cancellation != null &&
|
||||||
listener.expireMediaTimeout(key, "timeout happened while dozing")
|
listener.expiration <= systemClock.elapsedRealtime()
|
||||||
listener.doTimeout()
|
) {
|
||||||
|
// We dozed too long - timeout now, and cancel the pending one
|
||||||
|
listener.expireMediaTimeout(key, "timeout happened while dozing")
|
||||||
|
listener.doTimeout()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMediaDataLoaded(
|
override fun onMediaDataLoaded(
|
||||||
@@ -147,10 +153,8 @@ class MediaTimeoutListener @Inject constructor(
|
|||||||
return mediaListeners[key]?.timedOut ?: false
|
return mediaListeners[key]?.timedOut ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class PlaybackStateListener(
|
private inner class PlaybackStateListener(var key: String, data: MediaData) :
|
||||||
var key: String,
|
MediaController.Callback() {
|
||||||
data: MediaData
|
|
||||||
) : MediaController.Callback() {
|
|
||||||
|
|
||||||
var timedOut = false
|
var timedOut = false
|
||||||
var lastState: PlaybackState? = null
|
var lastState: PlaybackState? = null
|
||||||
@@ -164,11 +168,12 @@ class MediaTimeoutListener @Inject constructor(
|
|||||||
mediaController?.unregisterCallback(this)
|
mediaController?.unregisterCallback(this)
|
||||||
field = value
|
field = value
|
||||||
val token = field.token
|
val token = field.token
|
||||||
mediaController = if (token != null) {
|
mediaController =
|
||||||
mediaControllerFactory.create(token)
|
if (token != null) {
|
||||||
} else {
|
mediaControllerFactory.create(token)
|
||||||
null
|
} else {
|
||||||
}
|
null
|
||||||
|
}
|
||||||
mediaController?.registerCallback(this)
|
mediaController?.registerCallback(this)
|
||||||
// Let's register the cancellations, but not dispatch events now.
|
// Let's register the cancellations, but not dispatch events now.
|
||||||
// Timeouts didn't happen yet and reentrant events are troublesome.
|
// Timeouts didn't happen yet and reentrant events are troublesome.
|
||||||
@@ -214,7 +219,8 @@ class MediaTimeoutListener @Inject constructor(
|
|||||||
logger.logPlaybackState(key, state)
|
logger.logPlaybackState(key, state)
|
||||||
|
|
||||||
val playingStateSame = (state?.state?.isPlaying() == isPlaying())
|
val playingStateSame = (state?.state?.isPlaying() == isPlaying())
|
||||||
val actionsSame = (lastState?.actions == state?.actions) &&
|
val actionsSame =
|
||||||
|
(lastState?.actions == state?.actions) &&
|
||||||
areCustomActionListsEqual(lastState?.customActions, state?.customActions)
|
areCustomActionListsEqual(lastState?.customActions, state?.customActions)
|
||||||
val resumptionChanged = resumption != mediaData.resumption
|
val resumptionChanged = resumption != mediaData.resumption
|
||||||
|
|
||||||
@@ -239,15 +245,14 @@ class MediaTimeoutListener @Inject constructor(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state, $resumption")
|
expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state, $resumption")
|
||||||
val timeout = if (mediaData.resumption) {
|
val timeout =
|
||||||
RESUME_MEDIA_TIMEOUT
|
if (mediaData.resumption) {
|
||||||
} else {
|
RESUME_MEDIA_TIMEOUT
|
||||||
PAUSED_MEDIA_TIMEOUT
|
} else {
|
||||||
}
|
PAUSED_MEDIA_TIMEOUT
|
||||||
|
}
|
||||||
expiration = systemClock.elapsedRealtime() + timeout
|
expiration = systemClock.elapsedRealtime() + timeout
|
||||||
cancellation = mainExecutor.executeDelayed({
|
cancellation = mainExecutor.executeDelayed({ doTimeout() }, timeout)
|
||||||
doTimeout()
|
|
||||||
}, timeout)
|
|
||||||
} else {
|
} else {
|
||||||
expireMediaTimeout(key, "playback started - $state, $key")
|
expireMediaTimeout(key, "playback started - $state, $key")
|
||||||
timedOut = false
|
timedOut = false
|
||||||
@@ -303,9 +308,11 @@ class MediaTimeoutListener @Inject constructor(
|
|||||||
firstAction: PlaybackState.CustomAction,
|
firstAction: PlaybackState.CustomAction,
|
||||||
secondAction: PlaybackState.CustomAction
|
secondAction: PlaybackState.CustomAction
|
||||||
): Boolean {
|
): Boolean {
|
||||||
if (firstAction.action != secondAction.action ||
|
if (
|
||||||
|
firstAction.action != secondAction.action ||
|
||||||
firstAction.name != secondAction.name ||
|
firstAction.name != secondAction.name ||
|
||||||
firstAction.icon != secondAction.icon) {
|
firstAction.icon != secondAction.icon
|
||||||
|
) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,140 +22,91 @@ import com.android.systemui.log.dagger.MediaTimeoutListenerLog
|
|||||||
import com.android.systemui.plugins.log.LogBuffer
|
import com.android.systemui.plugins.log.LogBuffer
|
||||||
import com.android.systemui.plugins.log.LogLevel
|
import com.android.systemui.plugins.log.LogLevel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private const val TAG = "MediaTimeout"
|
private const val TAG = "MediaTimeout"
|
||||||
|
|
||||||
/**
|
/** A buffered log for [MediaTimeoutListener] events */
|
||||||
* A buffered log for [MediaTimeoutListener] events
|
|
||||||
*/
|
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
class MediaTimeoutLogger @Inject constructor(
|
class MediaTimeoutLogger
|
||||||
@MediaTimeoutListenerLog private val buffer: LogBuffer
|
@Inject
|
||||||
) {
|
constructor(@MediaTimeoutListenerLog private val buffer: LogBuffer) {
|
||||||
fun logReuseListener(key: String) = buffer.log(
|
fun logReuseListener(key: String) =
|
||||||
TAG,
|
buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "reuse listener: $str1" })
|
||||||
LogLevel.DEBUG,
|
|
||||||
{
|
|
||||||
str1 = key
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"reuse listener: $str1"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
fun logMigrateListener(oldKey: String?, newKey: String?, hadListener: Boolean) = buffer.log(
|
fun logMigrateListener(oldKey: String?, newKey: String?, hadListener: Boolean) =
|
||||||
TAG,
|
buffer.log(
|
||||||
LogLevel.DEBUG,
|
TAG,
|
||||||
{
|
LogLevel.DEBUG,
|
||||||
str1 = oldKey
|
{
|
||||||
str2 = newKey
|
str1 = oldKey
|
||||||
bool1 = hadListener
|
str2 = newKey
|
||||||
},
|
bool1 = hadListener
|
||||||
{
|
},
|
||||||
"migrate from $str1 to $str2, had listener? $bool1"
|
{ "migrate from $str1 to $str2, had listener? $bool1" }
|
||||||
}
|
)
|
||||||
)
|
|
||||||
|
|
||||||
fun logUpdateListener(key: String, wasPlaying: Boolean) = buffer.log(
|
fun logUpdateListener(key: String, wasPlaying: Boolean) =
|
||||||
TAG,
|
buffer.log(
|
||||||
LogLevel.DEBUG,
|
TAG,
|
||||||
{
|
LogLevel.DEBUG,
|
||||||
str1 = key
|
{
|
||||||
bool1 = wasPlaying
|
str1 = key
|
||||||
},
|
bool1 = wasPlaying
|
||||||
{
|
},
|
||||||
"updating $str1, was playing? $bool1"
|
{ "updating $str1, was playing? $bool1" }
|
||||||
}
|
)
|
||||||
)
|
|
||||||
|
|
||||||
fun logDelayedUpdate(key: String) = buffer.log(
|
fun logDelayedUpdate(key: String) =
|
||||||
TAG,
|
buffer.log(
|
||||||
LogLevel.DEBUG,
|
TAG,
|
||||||
{
|
LogLevel.DEBUG,
|
||||||
str1 = key
|
{ str1 = key },
|
||||||
},
|
{ "deliver delayed playback state for $str1" }
|
||||||
{
|
)
|
||||||
"deliver delayed playback state for $str1"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
fun logSessionDestroyed(key: String) = buffer.log(
|
fun logSessionDestroyed(key: String) =
|
||||||
TAG,
|
buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "session destroyed $str1" })
|
||||||
LogLevel.DEBUG,
|
|
||||||
{
|
|
||||||
str1 = key
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"session destroyed $str1"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
fun logPlaybackState(key: String, state: PlaybackState?) = buffer.log(
|
fun logPlaybackState(key: String, state: PlaybackState?) =
|
||||||
TAG,
|
buffer.log(
|
||||||
LogLevel.VERBOSE,
|
|
||||||
{
|
|
||||||
str1 = key
|
|
||||||
str2 = state?.toString()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"state update: key=$str1 state=$str2"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
fun logStateCallback(key: String) = buffer.log(
|
|
||||||
TAG,
|
TAG,
|
||||||
LogLevel.VERBOSE,
|
LogLevel.VERBOSE,
|
||||||
{
|
{
|
||||||
str1 = key
|
str1 = key
|
||||||
|
str2 = state?.toString()
|
||||||
},
|
},
|
||||||
|
{ "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,
|
||||||
{
|
{
|
||||||
"dispatching state update for $key"
|
str1 = key
|
||||||
}
|
bool1 = playing
|
||||||
)
|
bool2 = resumption
|
||||||
|
},
|
||||||
|
{ "schedule timeout $str1, playing=$bool1 resumption=$bool2" }
|
||||||
|
)
|
||||||
|
|
||||||
fun logScheduleTimeout(key: String, playing: Boolean, resumption: Boolean) = buffer.log(
|
fun logCancelIgnored(key: String) =
|
||||||
TAG,
|
buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "cancellation already exists for $str1" })
|
||||||
LogLevel.DEBUG,
|
|
||||||
{
|
|
||||||
str1 = key
|
|
||||||
bool1 = playing
|
|
||||||
bool2 = resumption
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"schedule timeout $str1, playing=$bool1 resumption=$bool2"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
fun logCancelIgnored(key: String) = buffer.log(
|
fun logTimeout(key: String) =
|
||||||
TAG,
|
buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "execute timeout for $str1" })
|
||||||
LogLevel.DEBUG,
|
|
||||||
{
|
|
||||||
str1 = key
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cancellation already exists for $str1"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
fun logTimeout(key: String) = buffer.log(
|
fun logTimeoutCancelled(key: String, reason: String) =
|
||||||
TAG,
|
buffer.log(
|
||||||
LogLevel.DEBUG,
|
TAG,
|
||||||
{
|
LogLevel.VERBOSE,
|
||||||
str1 = key
|
{
|
||||||
},
|
str1 = key
|
||||||
{
|
str2 = reason
|
||||||
"execute timeout for $str1"
|
},
|
||||||
}
|
{ "media timeout cancelled for $str1, reason: $str2" }
|
||||||
)
|
)
|
||||||
|
}
|
||||||
fun logTimeoutCancelled(key: String, reason: String) = buffer.log(
|
|
||||||
TAG,
|
|
||||||
LogLevel.VERBOSE,
|
|
||||||
{
|
|
||||||
str1 = key
|
|
||||||
str2 = reason
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"media timeout cancelled for $str1, reason: $str2"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -50,7 +50,9 @@ private const val MEDIA_PREFERENCES = "media_control_prefs"
|
|||||||
private const val MEDIA_PREFERENCE_KEY = "browser_components_"
|
private const val MEDIA_PREFERENCE_KEY = "browser_components_"
|
||||||
|
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
class MediaResumeListener @Inject constructor(
|
class MediaResumeListener
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val broadcastDispatcher: BroadcastDispatcher,
|
private val broadcastDispatcher: BroadcastDispatcher,
|
||||||
@Background private val backgroundExecutor: Executor,
|
@Background private val backgroundExecutor: Executor,
|
||||||
@@ -62,7 +64,7 @@ class MediaResumeListener @Inject constructor(
|
|||||||
|
|
||||||
private var useMediaResumption: Boolean = Utils.useMediaResumption(context)
|
private var useMediaResumption: Boolean = Utils.useMediaResumption(context)
|
||||||
private val resumeComponents: ConcurrentLinkedQueue<Pair<ComponentName, Long>> =
|
private val resumeComponents: ConcurrentLinkedQueue<Pair<ComponentName, Long>> =
|
||||||
ConcurrentLinkedQueue()
|
ConcurrentLinkedQueue()
|
||||||
|
|
||||||
private lateinit var mediaDataManager: MediaDataManager
|
private lateinit var mediaDataManager: MediaDataManager
|
||||||
|
|
||||||
@@ -75,40 +77,49 @@ class MediaResumeListener @Inject constructor(
|
|||||||
private var currentUserId: Int = context.userId
|
private var currentUserId: Int = context.userId
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
val userChangeReceiver = object : BroadcastReceiver() {
|
val userChangeReceiver =
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
object : BroadcastReceiver() {
|
||||||
if (Intent.ACTION_USER_UNLOCKED == intent.action) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
loadMediaResumptionControls()
|
if (Intent.ACTION_USER_UNLOCKED == intent.action) {
|
||||||
} else if (Intent.ACTION_USER_SWITCHED == intent.action) {
|
loadMediaResumptionControls()
|
||||||
currentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)
|
} else if (Intent.ACTION_USER_SWITCHED == intent.action) {
|
||||||
loadSavedComponents()
|
currentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)
|
||||||
|
loadSavedComponents()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private val mediaBrowserCallback = object : ResumeMediaBrowser.Callback() {
|
private val mediaBrowserCallback =
|
||||||
override fun addTrack(
|
object : ResumeMediaBrowser.Callback() {
|
||||||
desc: MediaDescription,
|
override fun addTrack(
|
||||||
component: ComponentName,
|
desc: MediaDescription,
|
||||||
browser: ResumeMediaBrowser
|
component: ComponentName,
|
||||||
) {
|
browser: ResumeMediaBrowser
|
||||||
val token = browser.token
|
) {
|
||||||
val appIntent = browser.appIntent
|
val token = browser.token
|
||||||
val pm = context.getPackageManager()
|
val appIntent = browser.appIntent
|
||||||
var appName: CharSequence = component.packageName
|
val pm = context.getPackageManager()
|
||||||
val resumeAction = getResumeAction(component)
|
var appName: CharSequence = component.packageName
|
||||||
try {
|
val resumeAction = getResumeAction(component)
|
||||||
appName = pm.getApplicationLabel(
|
try {
|
||||||
pm.getApplicationInfo(component.packageName, 0))
|
appName =
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
pm.getApplicationLabel(pm.getApplicationInfo(component.packageName, 0))
|
||||||
Log.e(TAG, "Error getting package information", e)
|
} 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 {
|
init {
|
||||||
if (useMediaResumption) {
|
if (useMediaResumption) {
|
||||||
@@ -116,8 +127,12 @@ class MediaResumeListener @Inject constructor(
|
|||||||
val unlockFilter = IntentFilter()
|
val unlockFilter = IntentFilter()
|
||||||
unlockFilter.addAction(Intent.ACTION_USER_UNLOCKED)
|
unlockFilter.addAction(Intent.ACTION_USER_UNLOCKED)
|
||||||
unlockFilter.addAction(Intent.ACTION_USER_SWITCHED)
|
unlockFilter.addAction(Intent.ACTION_USER_SWITCHED)
|
||||||
broadcastDispatcher.registerReceiver(userChangeReceiver, unlockFilter, null,
|
broadcastDispatcher.registerReceiver(
|
||||||
UserHandle.ALL)
|
userChangeReceiver,
|
||||||
|
unlockFilter,
|
||||||
|
null,
|
||||||
|
UserHandle.ALL
|
||||||
|
)
|
||||||
loadSavedComponents()
|
loadSavedComponents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,12 +141,15 @@ class MediaResumeListener @Inject constructor(
|
|||||||
mediaDataManager = manager
|
mediaDataManager = manager
|
||||||
|
|
||||||
// Add listener for resumption setting changes
|
// Add listener for resumption setting changes
|
||||||
tunerService.addTunable(object : TunerService.Tunable {
|
tunerService.addTunable(
|
||||||
override fun onTuningChanged(key: String?, newValue: String?) {
|
object : TunerService.Tunable {
|
||||||
useMediaResumption = Utils.useMediaResumption(context)
|
override fun onTuningChanged(key: String?, newValue: String?) {
|
||||||
mediaDataManager.setMediaResumptionEnabled(useMediaResumption)
|
useMediaResumption = Utils.useMediaResumption(context)
|
||||||
}
|
mediaDataManager.setMediaResumptionEnabled(useMediaResumption)
|
||||||
}, Settings.Secure.MEDIA_CONTROLS_RESUME)
|
}
|
||||||
|
},
|
||||||
|
Settings.Secure.MEDIA_CONTROLS_RESUME
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadSavedComponents() {
|
private fun loadSavedComponents() {
|
||||||
@@ -139,8 +157,10 @@ class MediaResumeListener @Inject constructor(
|
|||||||
resumeComponents.clear()
|
resumeComponents.clear()
|
||||||
val prefs = context.getSharedPreferences(MEDIA_PREFERENCES, Context.MODE_PRIVATE)
|
val prefs = context.getSharedPreferences(MEDIA_PREFERENCES, Context.MODE_PRIVATE)
|
||||||
val listString = prefs.getString(MEDIA_PREFERENCE_KEY + currentUserId, null)
|
val listString = prefs.getString(MEDIA_PREFERENCE_KEY + currentUserId, null)
|
||||||
val components = listString?.split(ResumeMediaBrowser.DELIMITER.toRegex())
|
val components =
|
||||||
?.dropLastWhile { it.isEmpty() }
|
listString?.split(ResumeMediaBrowser.DELIMITER.toRegex())?.dropLastWhile {
|
||||||
|
it.isEmpty()
|
||||||
|
}
|
||||||
var needsUpdate = false
|
var needsUpdate = false
|
||||||
components?.forEach {
|
components?.forEach {
|
||||||
val info = it.split("/")
|
val info = it.split("/")
|
||||||
@@ -148,17 +168,18 @@ class MediaResumeListener @Inject constructor(
|
|||||||
val className = info[1]
|
val className = info[1]
|
||||||
val component = ComponentName(packageName, className)
|
val component = ComponentName(packageName, className)
|
||||||
|
|
||||||
val lastPlayed = if (info.size == 3) {
|
val lastPlayed =
|
||||||
try {
|
if (info.size == 3) {
|
||||||
info[2].toLong()
|
try {
|
||||||
} catch (e: NumberFormatException) {
|
info[2].toLong()
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
needsUpdate = true
|
||||||
|
systemClock.currentTimeMillis()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
needsUpdate = true
|
needsUpdate = true
|
||||||
systemClock.currentTimeMillis()
|
systemClock.currentTimeMillis()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
needsUpdate = true
|
|
||||||
systemClock.currentTimeMillis()
|
|
||||||
}
|
|
||||||
resumeComponents.add(component to lastPlayed)
|
resumeComponents.add(component to lastPlayed)
|
||||||
}
|
}
|
||||||
Log.d(TAG, "loaded resume components ${resumeComponents.toArray().contentToString()}")
|
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() {
|
private fun loadMediaResumptionControls() {
|
||||||
if (!useMediaResumption) {
|
if (!useMediaResumption) {
|
||||||
return
|
return
|
||||||
@@ -207,9 +226,7 @@ class MediaResumeListener @Inject constructor(
|
|||||||
val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE)
|
val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE)
|
||||||
val resumeInfo = pm.queryIntentServices(serviceIntent, 0)
|
val resumeInfo = pm.queryIntentServices(serviceIntent, 0)
|
||||||
|
|
||||||
val inf = resumeInfo?.filter {
|
val inf = resumeInfo?.filter { it.serviceInfo.packageName == data.packageName }
|
||||||
it.serviceInfo.packageName == data.packageName
|
|
||||||
}
|
|
||||||
if (inf != null && inf.size > 0) {
|
if (inf != null && inf.size > 0) {
|
||||||
backgroundExecutor.execute {
|
backgroundExecutor.execute {
|
||||||
tryUpdateResumptionList(key, inf!!.get(0).componentInfo.componentName)
|
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")
|
Log.d(TAG, "Testing if we can connect to $componentName")
|
||||||
// Set null action to prevent additional attempts to connect
|
// Set null action to prevent additional attempts to connect
|
||||||
mediaDataManager.setResumeAction(key, null)
|
mediaDataManager.setResumeAction(key, null)
|
||||||
mediaBrowser = mediaBrowserFactory.create(
|
mediaBrowser =
|
||||||
|
mediaBrowserFactory.create(
|
||||||
object : ResumeMediaBrowser.Callback() {
|
object : ResumeMediaBrowser.Callback() {
|
||||||
override fun onConnected() {
|
override fun onConnected() {
|
||||||
Log.d(TAG, "Connected to $componentName")
|
Log.d(TAG, "Connected to $componentName")
|
||||||
@@ -253,7 +271,8 @@ class MediaResumeListener @Inject constructor(
|
|||||||
mediaBrowser = null
|
mediaBrowser = null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
componentName)
|
componentName
|
||||||
|
)
|
||||||
mediaBrowser?.testConnection()
|
mediaBrowser?.testConnection()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,9 +307,7 @@ class MediaResumeListener @Inject constructor(
|
|||||||
prefs.edit().putString(MEDIA_PREFERENCE_KEY + currentUserId, sb.toString()).apply()
|
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 {
|
private fun getResumeAction(componentName: ComponentName): Runnable {
|
||||||
return Runnable {
|
return Runnable {
|
||||||
mediaBrowser = mediaBrowserFactory.create(null, componentName)
|
mediaBrowser = mediaBrowserFactory.create(null, componentName)
|
||||||
@@ -299,8 +316,6 @@ class MediaResumeListener @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun dump(pw: PrintWriter, args: Array<out String>) {
|
override fun dump(pw: PrintWriter, args: Array<out String>) {
|
||||||
pw.apply {
|
pw.apply { println("resumeComponents: $resumeComponents") }
|
||||||
println("resumeComponents: $resumeComponents")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,50 +25,49 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
/** A logger for events in [ResumeMediaBrowser]. */
|
/** A logger for events in [ResumeMediaBrowser]. */
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
class ResumeMediaBrowserLogger @Inject constructor(
|
class ResumeMediaBrowserLogger @Inject constructor(@MediaBrowserLog private val buffer: LogBuffer) {
|
||||||
@MediaBrowserLog private val buffer: LogBuffer
|
|
||||||
) {
|
|
||||||
/** Logs that we've initiated a connection to a [android.media.browse.MediaBrowser]. */
|
/** Logs that we've initiated a connection to a [android.media.browse.MediaBrowser]. */
|
||||||
fun logConnection(componentName: ComponentName, reason: String) = buffer.log(
|
fun logConnection(componentName: ComponentName, reason: String) =
|
||||||
TAG,
|
buffer.log(
|
||||||
LogLevel.DEBUG,
|
TAG,
|
||||||
{
|
LogLevel.DEBUG,
|
||||||
str1 = componentName.toShortString()
|
{
|
||||||
str2 = reason
|
str1 = componentName.toShortString()
|
||||||
},
|
str2 = reason
|
||||||
{ "Connecting browser for component $str1 due to $str2" }
|
},
|
||||||
)
|
{ "Connecting browser for component $str1 due to $str2" }
|
||||||
|
)
|
||||||
|
|
||||||
/** Logs that we've disconnected from a [android.media.browse.MediaBrowser]. */
|
/** Logs that we've disconnected from a [android.media.browse.MediaBrowser]. */
|
||||||
fun logDisconnect(componentName: ComponentName) = buffer.log(
|
fun logDisconnect(componentName: ComponentName) =
|
||||||
TAG,
|
buffer.log(
|
||||||
LogLevel.DEBUG,
|
TAG,
|
||||||
{
|
LogLevel.DEBUG,
|
||||||
str1 = componentName.toShortString()
|
{ str1 = componentName.toShortString() },
|
||||||
},
|
{ "Disconnecting browser for component $str1" }
|
||||||
{ "Disconnecting browser for component $str1" }
|
)
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs that we received a [android.media.session.MediaController.Callback.onSessionDestroyed]
|
* Logs that we received a [android.media.session.MediaController.Callback.onSessionDestroyed]
|
||||||
* event.
|
* event.
|
||||||
*
|
*
|
||||||
* @param isBrowserConnected true if there's a currently connected
|
* @param isBrowserConnected true if there's a currently connected
|
||||||
|
* ```
|
||||||
* [android.media.browse.MediaBrowser] and false otherwise.
|
* [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(
|
fun logSessionDestroyed(isBrowserConnected: Boolean, componentName: ComponentName) =
|
||||||
isBrowserConnected: Boolean,
|
buffer.log(
|
||||||
componentName: ComponentName
|
TAG,
|
||||||
) = buffer.log(
|
LogLevel.DEBUG,
|
||||||
TAG,
|
{
|
||||||
LogLevel.DEBUG,
|
bool1 = isBrowserConnected
|
||||||
{
|
str1 = componentName.toShortString()
|
||||||
bool1 = isBrowserConnected
|
},
|
||||||
str1 = componentName.toShortString()
|
{ "Session destroyed. Active browser = $bool1. Browser component = $str1." }
|
||||||
},
|
)
|
||||||
{ "Session destroyed. Active browser = $bool1. Browser component = $str1." }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val TAG = "MediaBrowser"
|
private const val TAG = "MediaBrowser"
|
||||||
|
|||||||
@@ -20,13 +20,17 @@ import android.graphics.drawable.Animatable2
|
|||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AnimationBindHandler is responsible for tracking the bound animation state and preventing
|
* AnimationBindHandler is responsible for tracking the bound animation state and preventing jank
|
||||||
* jank and conflicts due to media notifications arriving at any time during an animation. It
|
* and conflicts due to media notifications arriving at any time during an animation. It does this
|
||||||
* does this in two parts.
|
* in two parts.
|
||||||
* - Exit animations fired as a result of user input are tracked. When these are running, any
|
* - 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).
|
* 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.
|
* rebind id will be totally ignored to prevent the continuous animation from restarting.
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
internal class AnimationBindHandler : Animatable2.AnimationCallback() {
|
internal class AnimationBindHandler : Animatable2.AnimationCallback() {
|
||||||
private val onAnimationsComplete = mutableListOf<() -> Unit>()
|
private val onAnimationsComplete = mutableListOf<() -> Unit>()
|
||||||
@@ -37,10 +41,10 @@ internal class AnimationBindHandler : Animatable2.AnimationCallback() {
|
|||||||
get() = registrations.any { it.isRunning }
|
get() = registrations.any { it.isRunning }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This check prevents rebinding to the action button if the identifier has not changed. A
|
* This check prevents rebinding to the action button if the identifier has not changed. A null
|
||||||
* null value is always considered to be changed. This is used to prevent the connecting
|
* value is always considered to be changed. This is used to prevent the connecting animation
|
||||||
* animation from rebinding (and restarting) if multiple buffer PlaybackStates are pushed by
|
* from rebinding (and restarting) if multiple buffer PlaybackStates are pushed by an
|
||||||
* an application in a row.
|
* application in a row.
|
||||||
*/
|
*/
|
||||||
fun updateRebindId(newRebindId: Int?): Boolean {
|
fun updateRebindId(newRebindId: Int?): Boolean {
|
||||||
if (rebindId == null || newRebindId == null || rebindId != newRebindId) {
|
if (rebindId == null || newRebindId == null || rebindId != newRebindId) {
|
||||||
@@ -78,4 +82,4 @@ internal class AnimationBindHandler : Animatable2.AnimationCallback() {
|
|||||||
onAnimationsComplete.clear()
|
onAnimationsComplete.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,12 +39,11 @@ interface ColorTransition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [ColorTransition] that animates between two specific colors.
|
* A [ColorTransition] that animates between two specific colors. It uses a ValueAnimator to execute
|
||||||
* It uses a ValueAnimator to execute the animation and interpolate between the source color and
|
* the animation and interpolate between the source color and the target color.
|
||||||
* the target color.
|
|
||||||
*
|
*
|
||||||
* Selection of the target color from the scheme, and application of the interpolated color
|
* Selection of the target color from the scheme, and application of the interpolated color are
|
||||||
* are delegated to callbacks.
|
* delegated to callbacks.
|
||||||
*/
|
*/
|
||||||
open class AnimatingColorTransition(
|
open class AnimatingColorTransition(
|
||||||
private val defaultColor: Int,
|
private val defaultColor: Int,
|
||||||
@@ -59,9 +58,8 @@ open class AnimatingColorTransition(
|
|||||||
var targetColor: Int = defaultColor
|
var targetColor: Int = defaultColor
|
||||||
|
|
||||||
override fun onAnimationUpdate(animation: ValueAnimator) {
|
override fun onAnimationUpdate(animation: ValueAnimator) {
|
||||||
currentColor = argbEvaluator.evaluate(
|
currentColor =
|
||||||
animation.animatedFraction, sourceColor, targetColor
|
argbEvaluator.evaluate(animation.animatedFraction, sourceColor, targetColor) as Int
|
||||||
) as Int
|
|
||||||
applyColor(currentColor)
|
applyColor(currentColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,111 +89,126 @@ open class AnimatingColorTransition(
|
|||||||
}
|
}
|
||||||
|
|
||||||
typealias AnimatingColorTransitionFactory =
|
typealias AnimatingColorTransitionFactory =
|
||||||
(Int, (ColorScheme) -> Int, (Int) -> Unit) -> AnimatingColorTransition
|
(Int, (ColorScheme) -> Int, (Int) -> Unit) -> AnimatingColorTransition
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ColorSchemeTransition constructs a ColorTransition for each color in the scheme
|
* ColorSchemeTransition constructs a ColorTransition for each color in the scheme that needs to be
|
||||||
* that needs to be transitioned when changed. It also sets up the assignment functions for sending
|
* transitioned when changed. It also sets up the assignment functions for sending the sending the
|
||||||
* the sending the interpolated colors to the appropriate views.
|
* interpolated colors to the appropriate views.
|
||||||
*/
|
*/
|
||||||
class ColorSchemeTransition internal constructor(
|
class ColorSchemeTransition
|
||||||
|
internal constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val mediaViewHolder: MediaViewHolder,
|
private val mediaViewHolder: MediaViewHolder,
|
||||||
animatingColorTransitionFactory: AnimatingColorTransitionFactory
|
animatingColorTransitionFactory: AnimatingColorTransitionFactory
|
||||||
) {
|
) {
|
||||||
constructor(context: Context, mediaViewHolder: MediaViewHolder) :
|
constructor(
|
||||||
this(context, mediaViewHolder, ::AnimatingColorTransition)
|
context: Context,
|
||||||
|
mediaViewHolder: MediaViewHolder
|
||||||
|
) : this(context, mediaViewHolder, ::AnimatingColorTransition)
|
||||||
|
|
||||||
val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95)
|
val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95)
|
||||||
val surfaceColor = animatingColorTransitionFactory(
|
val surfaceColor =
|
||||||
bgColor,
|
animatingColorTransitionFactory(bgColor, ::surfaceFromScheme) { surfaceColor ->
|
||||||
::surfaceFromScheme
|
val colorList = ColorStateList.valueOf(surfaceColor)
|
||||||
) { surfaceColor ->
|
mediaViewHolder.seamlessIcon.imageTintList = colorList
|
||||||
val colorList = ColorStateList.valueOf(surfaceColor)
|
mediaViewHolder.seamlessText.setTextColor(surfaceColor)
|
||||||
mediaViewHolder.seamlessIcon.imageTintList = colorList
|
mediaViewHolder.albumView.backgroundTintList = colorList
|
||||||
mediaViewHolder.seamlessText.setTextColor(surfaceColor)
|
mediaViewHolder.gutsViewHolder.setSurfaceColor(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 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 colorSeamless = animatingColorTransitionFactory(
|
val accentPrimary =
|
||||||
loadDefaultColor(R.attr.textColorPrimary),
|
animatingColorTransitionFactory(
|
||||||
{ colorScheme: ColorScheme ->
|
loadDefaultColor(R.attr.textColorPrimary),
|
||||||
// A1-100 dark in dark theme, A1-200 in light theme
|
::accentPrimaryFromScheme
|
||||||
if (context.resources.configuration.uiMode and
|
) { accentPrimary ->
|
||||||
Configuration.UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES)
|
val accentColorList = ColorStateList.valueOf(accentPrimary)
|
||||||
colorScheme.accent1[2]
|
mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList
|
||||||
else colorScheme.accent1[3]
|
mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary)
|
||||||
}, { 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 textPrimaryInverse = animatingColorTransitionFactory(
|
val accentSecondary =
|
||||||
loadDefaultColor(R.attr.textColorPrimaryInverse),
|
animatingColorTransitionFactory(
|
||||||
::textPrimaryInverseFromScheme
|
loadDefaultColor(R.attr.textColorPrimary),
|
||||||
) { textPrimaryInverse ->
|
::accentSecondaryFromScheme
|
||||||
mediaViewHolder.actionPlayPause.imageTintList = ColorStateList.valueOf(textPrimaryInverse)
|
) { accentSecondary ->
|
||||||
}
|
val colorList = ColorStateList.valueOf(accentSecondary)
|
||||||
|
(mediaViewHolder.seamlessButton.background as? RippleDrawable)?.let {
|
||||||
|
it.setColor(colorList)
|
||||||
|
it.effectColor = colorList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val textSecondary = animatingColorTransitionFactory(
|
val colorSeamless =
|
||||||
loadDefaultColor(R.attr.textColorSecondary),
|
animatingColorTransitionFactory(
|
||||||
::textSecondaryFromScheme
|
loadDefaultColor(R.attr.textColorPrimary),
|
||||||
) { textSecondary -> mediaViewHolder.artistText.setTextColor(textSecondary) }
|
{ 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 textTertiary = animatingColorTransitionFactory(
|
val textPrimary =
|
||||||
loadDefaultColor(R.attr.textColorTertiary),
|
animatingColorTransitionFactory(
|
||||||
::textTertiaryFromScheme
|
loadDefaultColor(R.attr.textColorPrimary),
|
||||||
) { textTertiary ->
|
::textPrimaryFromScheme
|
||||||
mediaViewHolder.seekBar.progressBackgroundTintList = ColorStateList.valueOf(textTertiary)
|
) { 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 colorTransitions = arrayOf(
|
val textPrimaryInverse =
|
||||||
surfaceColor,
|
animatingColorTransitionFactory(
|
||||||
colorSeamless,
|
loadDefaultColor(R.attr.textColorPrimaryInverse),
|
||||||
accentPrimary,
|
::textPrimaryInverseFromScheme
|
||||||
accentSecondary,
|
) { textPrimaryInverse ->
|
||||||
textPrimary,
|
mediaViewHolder.actionPlayPause.imageTintList =
|
||||||
textPrimaryInverse,
|
ColorStateList.valueOf(textPrimaryInverse)
|
||||||
textSecondary,
|
}
|
||||||
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,
|
||||||
|
)
|
||||||
|
|
||||||
private fun loadDefaultColor(id: Int): Int {
|
private fun loadDefaultColor(id: Int): Int {
|
||||||
return Utils.getColorAttr(context, id).defaultColor
|
return Utils.getColorAttr(context, id).defaultColor
|
||||||
|
|||||||
@@ -42,22 +42,20 @@ import org.xmlpull.v1.XmlPullParser
|
|||||||
|
|
||||||
private const val BACKGROUND_ANIM_DURATION = 370L
|
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
|
@Keep
|
||||||
class IlluminationDrawable : Drawable() {
|
class IlluminationDrawable : Drawable() {
|
||||||
|
|
||||||
private var themeAttrs: IntArray? = null
|
private var themeAttrs: IntArray? = null
|
||||||
private var cornerRadiusOverride = -1f
|
private var cornerRadiusOverride = -1f
|
||||||
var cornerRadius = 0f
|
var cornerRadius = 0f
|
||||||
get() {
|
get() {
|
||||||
return if (cornerRadiusOverride >= 0) {
|
return if (cornerRadiusOverride >= 0) {
|
||||||
cornerRadiusOverride
|
cornerRadiusOverride
|
||||||
} else {
|
} else {
|
||||||
field
|
field
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
private var highlightColor = Color.TRANSPARENT
|
private var highlightColor = Color.TRANSPARENT
|
||||||
private var tmpHsl = floatArrayOf(0f, 0f, 0f)
|
private var tmpHsl = floatArrayOf(0f, 0f, 0f)
|
||||||
private var paint = Paint()
|
private var paint = Paint()
|
||||||
@@ -65,22 +63,27 @@ class IlluminationDrawable : Drawable() {
|
|||||||
private val lightSources = arrayListOf<LightSourceDrawable>()
|
private val lightSources = arrayListOf<LightSourceDrawable>()
|
||||||
|
|
||||||
private var backgroundColor = Color.TRANSPARENT
|
private var backgroundColor = Color.TRANSPARENT
|
||||||
set(value) {
|
set(value) {
|
||||||
if (value == field) {
|
if (value == field) {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
field = value
|
||||||
|
animateBackground()
|
||||||
}
|
}
|
||||||
field = value
|
|
||||||
animateBackground()
|
|
||||||
}
|
|
||||||
|
|
||||||
private var backgroundAnimation: ValueAnimator? = null
|
private var backgroundAnimation: ValueAnimator? = null
|
||||||
|
|
||||||
/**
|
/** Draw background and gradient. */
|
||||||
* Draw background and gradient.
|
|
||||||
*/
|
|
||||||
override fun draw(canvas: Canvas) {
|
override fun draw(canvas: Canvas) {
|
||||||
canvas.drawRoundRect(0f, 0f, bounds.width().toFloat(), bounds.height().toFloat(),
|
canvas.drawRoundRect(
|
||||||
cornerRadius, cornerRadius, paint)
|
0f,
|
||||||
|
0f,
|
||||||
|
bounds.width().toFloat(),
|
||||||
|
bounds.height().toFloat(),
|
||||||
|
cornerRadius,
|
||||||
|
cornerRadius,
|
||||||
|
paint
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getOutline(outline: Outline) {
|
override fun getOutline(outline: Outline) {
|
||||||
@@ -105,12 +108,11 @@ class IlluminationDrawable : Drawable() {
|
|||||||
|
|
||||||
private fun updateStateFromTypedArray(a: TypedArray) {
|
private fun updateStateFromTypedArray(a: TypedArray) {
|
||||||
if (a.hasValue(R.styleable.IlluminationDrawable_cornerRadius)) {
|
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)) {
|
if (a.hasValue(R.styleable.IlluminationDrawable_highlight)) {
|
||||||
highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) /
|
highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) / 100f
|
||||||
100f
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,34 +165,42 @@ class IlluminationDrawable : Drawable() {
|
|||||||
private fun animateBackground() {
|
private fun animateBackground() {
|
||||||
ColorUtils.colorToHSL(backgroundColor, tmpHsl)
|
ColorUtils.colorToHSL(backgroundColor, tmpHsl)
|
||||||
val L = tmpHsl[2]
|
val L = tmpHsl[2]
|
||||||
tmpHsl[2] = MathUtils.constrain(if (L < 1f - highlight) {
|
tmpHsl[2] =
|
||||||
L + highlight
|
MathUtils.constrain(
|
||||||
} else {
|
if (L < 1f - highlight) {
|
||||||
L - highlight
|
L + highlight
|
||||||
}, 0f, 1f)
|
} else {
|
||||||
|
L - highlight
|
||||||
|
},
|
||||||
|
0f,
|
||||||
|
1f
|
||||||
|
)
|
||||||
|
|
||||||
val initialBackground = paint.color
|
val initialBackground = paint.color
|
||||||
val initialHighlight = highlightColor
|
val initialHighlight = highlightColor
|
||||||
val finalHighlight = ColorUtils.HSLToColor(tmpHsl)
|
val finalHighlight = ColorUtils.HSLToColor(tmpHsl)
|
||||||
|
|
||||||
backgroundAnimation?.cancel()
|
backgroundAnimation?.cancel()
|
||||||
backgroundAnimation = ValueAnimator.ofFloat(0f, 1f).apply {
|
backgroundAnimation =
|
||||||
duration = BACKGROUND_ANIM_DURATION
|
ValueAnimator.ofFloat(0f, 1f).apply {
|
||||||
interpolator = Interpolators.FAST_OUT_LINEAR_IN
|
duration = BACKGROUND_ANIM_DURATION
|
||||||
addUpdateListener {
|
interpolator = Interpolators.FAST_OUT_LINEAR_IN
|
||||||
val progress = it.animatedValue as Float
|
addUpdateListener {
|
||||||
paint.color = blendARGB(initialBackground, backgroundColor, progress)
|
val progress = it.animatedValue as Float
|
||||||
highlightColor = blendARGB(initialHighlight, finalHighlight, progress)
|
paint.color = blendARGB(initialBackground, backgroundColor, progress)
|
||||||
lightSources.forEach { it.highlightColor = highlightColor }
|
highlightColor = blendARGB(initialHighlight, finalHighlight, progress)
|
||||||
invalidateSelf()
|
lightSources.forEach { it.highlightColor = highlightColor }
|
||||||
}
|
invalidateSelf()
|
||||||
addListener(object : AnimatorListenerAdapter() {
|
|
||||||
override fun onAnimationEnd(animation: Animator?) {
|
|
||||||
backgroundAnimation = null
|
|
||||||
}
|
}
|
||||||
})
|
addListener(
|
||||||
start()
|
object : AnimatorListenerAdapter() {
|
||||||
}
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
|
backgroundAnimation = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setTintList(tint: ColorStateList?) {
|
override fun setTintList(tint: ColorStateList?) {
|
||||||
@@ -215,4 +225,4 @@ class IlluminationDrawable : Drawable() {
|
|||||||
fun setCornerRadiusOverride(cornerRadius: Float?) {
|
fun setCornerRadiusOverride(cornerRadius: Float?) {
|
||||||
cornerRadiusOverride = cornerRadius ?: -1f
|
cornerRadiusOverride = cornerRadius ?: -1f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ import javax.inject.Named
|
|||||||
* switches media player positioning between split pane container vs single pane container
|
* switches media player positioning between split pane container vs single pane container
|
||||||
*/
|
*/
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
class KeyguardMediaController @Inject constructor(
|
class KeyguardMediaController
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
@param:Named(KEYGUARD) private val mediaHost: MediaHost,
|
@param:Named(KEYGUARD) private val mediaHost: MediaHost,
|
||||||
private val bypassController: KeyguardBypassController,
|
private val bypassController: KeyguardBypassController,
|
||||||
private val statusBarStateController: SysuiStatusBarStateController,
|
private val statusBarStateController: SysuiStatusBarStateController,
|
||||||
@@ -56,34 +58,40 @@ class KeyguardMediaController @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
|
statusBarStateController.addCallback(
|
||||||
override fun onStateChanged(newState: Int) {
|
object : StatusBarStateController.StateListener {
|
||||||
refreshMediaPosition()
|
override fun onStateChanged(newState: Int) {
|
||||||
}
|
|
||||||
})
|
|
||||||
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 =
|
|
||||||
secureSettings.getBoolForUser(
|
|
||||||
Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
|
|
||||||
true,
|
|
||||||
UserHandle.USER_CURRENT
|
|
||||||
)
|
|
||||||
refreshMediaPosition()
|
refreshMediaPosition()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
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 =
|
||||||
|
secureSettings.getBoolForUser(
|
||||||
|
Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
|
||||||
|
true,
|
||||||
|
UserHandle.USER_CURRENT
|
||||||
|
)
|
||||||
|
refreshMediaPosition()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
secureSettings.registerContentObserverForUser(
|
secureSettings.registerContentObserverForUser(
|
||||||
Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
|
Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN,
|
||||||
settingsObserver,
|
settingsObserver,
|
||||||
UserHandle.USER_ALL)
|
UserHandle.USER_ALL
|
||||||
|
)
|
||||||
|
|
||||||
// First let's set the desired state that we want for this host
|
// First let's set the desired state that we want for this host
|
||||||
mediaHost.expansion = MediaHostState.EXPANDED
|
mediaHost.expansion = MediaHostState.EXPANDED
|
||||||
@@ -110,27 +118,21 @@ class KeyguardMediaController @Inject constructor(
|
|||||||
refreshMediaPosition()
|
refreshMediaPosition()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Is the media player visible? */
|
||||||
* Is the media player visible?
|
|
||||||
*/
|
|
||||||
var visible = false
|
var visible = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var visibilityChangedListener: ((Boolean) -> Unit)? = null
|
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
|
var singlePaneContainer: MediaContainerView? = null
|
||||||
private set
|
private set
|
||||||
private var splitShadeContainer: ViewGroup? = null
|
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 var allowMediaPlayerOnLockScreen: Boolean = true
|
||||||
private val lockScreenMediaPlayerUri =
|
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
|
* 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)
|
onMediaHostVisibilityChanged(mediaHost.visible)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Called whenever the media hosts visibility changes */
|
||||||
* Called whenever the media hosts visibility changes
|
|
||||||
*/
|
|
||||||
private fun onMediaHostVisibilityChanged(visible: Boolean) {
|
private fun onMediaHostVisibilityChanged(visible: Boolean) {
|
||||||
refreshMediaPosition()
|
refreshMediaPosition()
|
||||||
if (visible) {
|
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) {
|
fun attachSplitShadeContainer(container: ViewGroup) {
|
||||||
splitShadeContainer = container
|
splitShadeContainer = container
|
||||||
reattachHostView()
|
reattachHostView()
|
||||||
@@ -183,9 +181,7 @@ class KeyguardMediaController @Inject constructor(
|
|||||||
}
|
}
|
||||||
if (activeContainer?.childCount == 0) {
|
if (activeContainer?.childCount == 0) {
|
||||||
// Detach the hostView from its parent view if exists
|
// Detach the hostView from its parent view if exists
|
||||||
mediaHost.hostView.parent?.let {
|
mediaHost.hostView.parent?.let { (it as? ViewGroup)?.removeView(mediaHost.hostView) }
|
||||||
(it as? ViewGroup)?.removeView(mediaHost.hostView)
|
|
||||||
}
|
|
||||||
activeContainer.addView(mediaHost.hostView)
|
activeContainer.addView(mediaHost.hostView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,7 +189,8 @@ class KeyguardMediaController @Inject constructor(
|
|||||||
fun refreshMediaPosition() {
|
fun refreshMediaPosition() {
|
||||||
val keyguardOrUserSwitcher = (statusBarStateController.state == StatusBarState.KEYGUARD)
|
val keyguardOrUserSwitcher = (statusBarStateController.state == StatusBarState.KEYGUARD)
|
||||||
// mediaHost.visible required for proper animations handling
|
// mediaHost.visible required for proper animations handling
|
||||||
visible = mediaHost.visible &&
|
visible =
|
||||||
|
mediaHost.visible &&
|
||||||
!bypassController.bypassEnabled &&
|
!bypassController.bypassEnabled &&
|
||||||
keyguardOrUserSwitcher &&
|
keyguardOrUserSwitcher &&
|
||||||
allowMediaPlayerOnLockScreen
|
allowMediaPlayerOnLockScreen
|
||||||
|
|||||||
@@ -55,9 +55,7 @@ private data class RippleData(
|
|||||||
var highlight: Float
|
var highlight: Float
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/** Drawable that can draw an animated gradient when tapped. */
|
||||||
* Drawable that can draw an animated gradient when tapped.
|
|
||||||
*/
|
|
||||||
@Keep
|
@Keep
|
||||||
class LightSourceDrawable : Drawable() {
|
class LightSourceDrawable : Drawable() {
|
||||||
|
|
||||||
@@ -67,17 +65,15 @@ class LightSourceDrawable : Drawable() {
|
|||||||
private var paint = Paint()
|
private var paint = Paint()
|
||||||
|
|
||||||
var highlightColor = Color.WHITE
|
var highlightColor = Color.WHITE
|
||||||
set(value) {
|
set(value) {
|
||||||
if (field == value) {
|
if (field == value) {
|
||||||
return
|
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
|
private var active: Boolean = false
|
||||||
set(value) {
|
set(value) {
|
||||||
if (value == field) {
|
if (value == field) {
|
||||||
@@ -91,46 +87,54 @@ class LightSourceDrawable : Drawable() {
|
|||||||
rippleData.progress = RIPPLE_DOWN_PROGRESS
|
rippleData.progress = RIPPLE_DOWN_PROGRESS
|
||||||
} else {
|
} else {
|
||||||
rippleAnimation?.cancel()
|
rippleAnimation?.cancel()
|
||||||
rippleAnimation = ValueAnimator.ofFloat(rippleData.alpha, 0f).apply {
|
rippleAnimation =
|
||||||
duration = RIPPLE_CANCEL_DURATION
|
ValueAnimator.ofFloat(rippleData.alpha, 0f).apply {
|
||||||
interpolator = Interpolators.LINEAR_OUT_SLOW_IN
|
duration = RIPPLE_CANCEL_DURATION
|
||||||
addUpdateListener {
|
interpolator = Interpolators.LINEAR_OUT_SLOW_IN
|
||||||
rippleData.alpha = it.animatedValue as Float
|
addUpdateListener {
|
||||||
invalidateSelf()
|
rippleData.alpha = it.animatedValue as Float
|
||||||
}
|
|
||||||
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()
|
invalidateSelf()
|
||||||
}
|
}
|
||||||
})
|
addListener(
|
||||||
start()
|
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()
|
invalidateSelf()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var rippleAnimation: Animator? = null
|
private var rippleAnimation: Animator? = null
|
||||||
|
|
||||||
/**
|
/** Draw background and gradient. */
|
||||||
* Draw background and gradient.
|
|
||||||
*/
|
|
||||||
override fun draw(canvas: Canvas) {
|
override fun draw(canvas: Canvas) {
|
||||||
val radius = lerp(rippleData.minSize, rippleData.maxSize, rippleData.progress)
|
val radius = lerp(rippleData.minSize, rippleData.maxSize, rippleData.progress)
|
||||||
val centerColor =
|
val centerColor =
|
||||||
ColorUtils.setAlphaComponent(highlightColor, (rippleData.alpha * 255).toInt())
|
ColorUtils.setAlphaComponent(highlightColor, (rippleData.alpha * 255).toInt())
|
||||||
paint.shader = RadialGradient(rippleData.x, rippleData.y, radius,
|
paint.shader =
|
||||||
intArrayOf(centerColor, Color.TRANSPARENT), GRADIENT_STOPS, Shader.TileMode.CLAMP)
|
RadialGradient(
|
||||||
|
rippleData.x,
|
||||||
|
rippleData.y,
|
||||||
|
radius,
|
||||||
|
intArrayOf(centerColor, Color.TRANSPARENT),
|
||||||
|
GRADIENT_STOPS,
|
||||||
|
Shader.TileMode.CLAMP
|
||||||
|
)
|
||||||
canvas.drawCircle(rippleData.x, rippleData.y, radius, paint)
|
canvas.drawCircle(rippleData.x, rippleData.y, radius, paint)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,8 +166,8 @@ class LightSourceDrawable : Drawable() {
|
|||||||
rippleData.maxSize = a.getDimension(R.styleable.IlluminationDrawable_rippleMaxSize, 0f)
|
rippleData.maxSize = a.getDimension(R.styleable.IlluminationDrawable_rippleMaxSize, 0f)
|
||||||
}
|
}
|
||||||
if (a.hasValue(R.styleable.IlluminationDrawable_highlight)) {
|
if (a.hasValue(R.styleable.IlluminationDrawable_highlight)) {
|
||||||
rippleData.highlight = a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) /
|
rippleData.highlight =
|
||||||
100f
|
a.getInteger(R.styleable.IlluminationDrawable_highlight, 0) / 100f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,40 +197,44 @@ class LightSourceDrawable : Drawable() {
|
|||||||
invalidateSelf()
|
invalidateSelf()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Draws an animated ripple that expands fading away. */
|
||||||
* Draws an animated ripple that expands fading away.
|
|
||||||
*/
|
|
||||||
private fun illuminate() {
|
private fun illuminate() {
|
||||||
rippleData.alpha = 1f
|
rippleData.alpha = 1f
|
||||||
invalidateSelf()
|
invalidateSelf()
|
||||||
|
|
||||||
rippleAnimation?.cancel()
|
rippleAnimation?.cancel()
|
||||||
rippleAnimation = AnimatorSet().apply {
|
rippleAnimation =
|
||||||
playTogether(ValueAnimator.ofFloat(1f, 0f).apply {
|
AnimatorSet().apply {
|
||||||
startDelay = 133
|
playTogether(
|
||||||
duration = RIPPLE_ANIM_DURATION - startDelay
|
ValueAnimator.ofFloat(1f, 0f).apply {
|
||||||
interpolator = Interpolators.LINEAR_OUT_SLOW_IN
|
startDelay = 133
|
||||||
addUpdateListener {
|
duration = RIPPLE_ANIM_DURATION - startDelay
|
||||||
rippleData.alpha = it.animatedValue as Float
|
interpolator = Interpolators.LINEAR_OUT_SLOW_IN
|
||||||
invalidateSelf()
|
addUpdateListener {
|
||||||
}
|
rippleData.alpha = it.animatedValue as Float
|
||||||
}, ValueAnimator.ofFloat(rippleData.progress, 1f).apply {
|
invalidateSelf()
|
||||||
duration = RIPPLE_ANIM_DURATION
|
}
|
||||||
interpolator = Interpolators.LINEAR_OUT_SLOW_IN
|
},
|
||||||
addUpdateListener {
|
ValueAnimator.ofFloat(rippleData.progress, 1f).apply {
|
||||||
rippleData.progress = it.animatedValue as Float
|
duration = RIPPLE_ANIM_DURATION
|
||||||
invalidateSelf()
|
interpolator = Interpolators.LINEAR_OUT_SLOW_IN
|
||||||
}
|
addUpdateListener {
|
||||||
})
|
rippleData.progress = it.animatedValue as Float
|
||||||
addListener(object : AnimatorListenerAdapter() {
|
invalidateSelf()
|
||||||
override fun onAnimationEnd(animation: Animator?) {
|
}
|
||||||
rippleData.progress = 0f
|
}
|
||||||
rippleAnimation = null
|
)
|
||||||
invalidateSelf()
|
addListener(
|
||||||
}
|
object : AnimatorListenerAdapter() {
|
||||||
})
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
start()
|
rippleData.progress = 0f
|
||||||
}
|
rippleAnimation = null
|
||||||
|
invalidateSelf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setHotspot(x: Float, y: Float) {
|
override fun setHotspot(x: Float, y: Float) {
|
||||||
@@ -251,8 +259,13 @@ class LightSourceDrawable : Drawable() {
|
|||||||
|
|
||||||
override fun getDirtyBounds(): Rect {
|
override fun getDirtyBounds(): Rect {
|
||||||
val radius = lerp(rippleData.minSize, rippleData.maxSize, rippleData.progress)
|
val radius = lerp(rippleData.minSize, rippleData.maxSize, rippleData.progress)
|
||||||
val bounds = Rect((rippleData.x - radius).toInt(), (rippleData.y - radius).toInt(),
|
val bounds =
|
||||||
(rippleData.x + radius).toInt(), (rippleData.y + radius).toInt())
|
Rect(
|
||||||
|
(rippleData.x - radius).toInt(),
|
||||||
|
(rippleData.y - radius).toInt(),
|
||||||
|
(rippleData.x + radius).toInt(),
|
||||||
|
(rippleData.y + radius).toInt()
|
||||||
|
)
|
||||||
bounds.union(super.getDirtyBounds())
|
bounds.union(super.getDirtyBounds())
|
||||||
return bounds
|
return bounds
|
||||||
}
|
}
|
||||||
@@ -293,4 +306,4 @@ class LightSourceDrawable : Drawable() {
|
|||||||
|
|
||||||
return changed
|
return changed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -24,53 +24,43 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
/** A debug logger for [MediaCarouselController]. */
|
/** A debug logger for [MediaCarouselController]. */
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
class MediaCarouselControllerLogger @Inject constructor(
|
class MediaCarouselControllerLogger
|
||||||
@MediaCarouselControllerLog private val buffer: LogBuffer
|
@Inject
|
||||||
) {
|
constructor(@MediaCarouselControllerLog private val buffer: LogBuffer) {
|
||||||
/**
|
/**
|
||||||
* Log that there might be a potential memory leak for the [MediaControlPanel] and/or
|
* Log that there might be a potential memory leak for the [MediaControlPanel] and/or
|
||||||
* [MediaViewController] related to [key].
|
* [MediaViewController] related to [key].
|
||||||
*/
|
*/
|
||||||
fun logPotentialMemoryLeak(key: String) = buffer.log(
|
fun logPotentialMemoryLeak(key: String) =
|
||||||
TAG,
|
buffer.log(
|
||||||
LogLevel.DEBUG,
|
TAG,
|
||||||
{ str1 = key },
|
LogLevel.DEBUG,
|
||||||
{
|
{ str1 = key },
|
||||||
"Potential memory leak: " +
|
{
|
||||||
|
"Potential memory leak: " +
|
||||||
"Removing control panel for $str1 from map without calling #onDestroy"
|
"Removing control panel for $str1 from map without calling #onDestroy"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
fun logMediaLoaded(key: String) = buffer.log(
|
fun logMediaLoaded(key: String) =
|
||||||
TAG,
|
buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "add player $str1" })
|
||||||
LogLevel.DEBUG,
|
|
||||||
{ str1 = key },
|
|
||||||
{ "add player $str1" }
|
|
||||||
)
|
|
||||||
|
|
||||||
fun logMediaRemoved(key: String) = buffer.log(
|
fun logMediaRemoved(key: String) =
|
||||||
TAG,
|
buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "removing player $str1" })
|
||||||
LogLevel.DEBUG,
|
|
||||||
{ str1 = key },
|
|
||||||
{ "removing player $str1" }
|
|
||||||
)
|
|
||||||
|
|
||||||
fun logRecommendationLoaded(key: String) = buffer.log(
|
fun logRecommendationLoaded(key: String) =
|
||||||
TAG,
|
buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "add recommendation $str1" })
|
||||||
LogLevel.DEBUG,
|
|
||||||
{ str1 = key },
|
|
||||||
{ "add recommendation $str1" }
|
|
||||||
)
|
|
||||||
|
|
||||||
fun logRecommendationRemoved(key: String, immediately: Boolean) = buffer.log(
|
fun logRecommendationRemoved(key: String, immediately: Boolean) =
|
||||||
TAG,
|
buffer.log(
|
||||||
LogLevel.DEBUG,
|
TAG,
|
||||||
{
|
LogLevel.DEBUG,
|
||||||
str1 = key
|
{
|
||||||
bool1 = immediately
|
str1 = key
|
||||||
},
|
bool1 = immediately
|
||||||
{ "removing recommendation $str1, immediate=$bool1" }
|
},
|
||||||
)
|
{ "removing recommendation $str1, immediate=$bool1" }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val TAG = "MediaCarouselCtlrLog"
|
private const val TAG = "MediaCarouselCtlrLog"
|
||||||
|
|||||||
@@ -44,16 +44,13 @@ private const val RUBBERBAND_FACTOR = 0.2f
|
|||||||
private const val SETTINGS_BUTTON_TRANSLATION_FRACTION = 0.3f
|
private const val SETTINGS_BUTTON_TRANSLATION_FRACTION = 0.3f
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default spring configuration to use for animations where stiffness and/or damping ratio
|
* Default spring configuration to use for animations where stiffness and/or damping ratio were not
|
||||||
* were not provided, and a default spring was not set via [PhysicsAnimator.setDefaultSpringConfig].
|
* provided, and a default spring was not set via [PhysicsAnimator.setDefaultSpringConfig].
|
||||||
*/
|
*/
|
||||||
private val translationConfig = PhysicsAnimator.SpringConfig(
|
private val translationConfig =
|
||||||
SpringForce.STIFFNESS_LOW,
|
PhysicsAnimator.SpringConfig(SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY)
|
||||||
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(
|
class MediaCarouselScrollHandler(
|
||||||
private val scrollView: MediaScrollView,
|
private val scrollView: MediaScrollView,
|
||||||
private val pageIndicator: PageIndicator,
|
private val pageIndicator: PageIndicator,
|
||||||
@@ -66,57 +63,36 @@ class MediaCarouselScrollHandler(
|
|||||||
private val logSmartspaceImpression: (Boolean) -> Unit,
|
private val logSmartspaceImpression: (Boolean) -> Unit,
|
||||||
private val logger: MediaUiEventLogger
|
private val logger: MediaUiEventLogger
|
||||||
) {
|
) {
|
||||||
/**
|
/** Is the view in RTL */
|
||||||
* Is the view in RTL
|
val isRtl: Boolean
|
||||||
*/
|
get() = scrollView.isLayoutRtl
|
||||||
val isRtl: Boolean get() = scrollView.isLayoutRtl
|
/** Do we need falsing protection? */
|
||||||
/**
|
|
||||||
* Do we need falsing protection?
|
|
||||||
*/
|
|
||||||
var falsingProtectionNeeded: Boolean = false
|
var falsingProtectionNeeded: Boolean = false
|
||||||
/**
|
/** The width of the carousel */
|
||||||
* The width of the carousel
|
|
||||||
*/
|
|
||||||
private var carouselWidth: Int = 0
|
private var carouselWidth: Int = 0
|
||||||
|
|
||||||
/**
|
/** The height of the carousel */
|
||||||
* The height of the carousel
|
|
||||||
*/
|
|
||||||
private var carouselHeight: Int = 0
|
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
|
private var cornerRadius: Int = 0
|
||||||
|
|
||||||
/**
|
/** The content where the players are added */
|
||||||
* The content where the players are added
|
|
||||||
*/
|
|
||||||
private var mediaContent: ViewGroup
|
private var mediaContent: ViewGroup
|
||||||
/**
|
/** The gesture detector to detect touch gestures */
|
||||||
* The gesture detector to detect touch gestures
|
|
||||||
*/
|
|
||||||
private val gestureDetector: GestureDetectorCompat
|
private val gestureDetector: GestureDetectorCompat
|
||||||
|
|
||||||
/**
|
/** The settings button view */
|
||||||
* The settings button view
|
|
||||||
*/
|
|
||||||
private lateinit var settingsButton: View
|
private lateinit var settingsButton: View
|
||||||
|
|
||||||
/**
|
/** What's the currently visible player index? */
|
||||||
* What's the currently visible player index?
|
|
||||||
*/
|
|
||||||
var visibleMediaIndex: Int = 0
|
var visibleMediaIndex: Int = 0
|
||||||
private set
|
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
|
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
|
var contentTranslation = 0.0f
|
||||||
private set(value) {
|
private set(value) {
|
||||||
field = value
|
field = value
|
||||||
@@ -126,9 +102,7 @@ class MediaCarouselScrollHandler(
|
|||||||
updateClipToOutline()
|
updateClipToOutline()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** The width of a player including padding */
|
||||||
* The width of a player including padding
|
|
||||||
*/
|
|
||||||
var playerWidthPlusPadding: Int = 0
|
var playerWidthPlusPadding: Int = 0
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
@@ -136,82 +110,75 @@ class MediaCarouselScrollHandler(
|
|||||||
// it's still at the same place
|
// it's still at the same place
|
||||||
var newRelativeScroll = visibleMediaIndex * playerWidthPlusPadding
|
var newRelativeScroll = visibleMediaIndex * playerWidthPlusPadding
|
||||||
if (scrollIntoCurrentMedia > playerWidthPlusPadding) {
|
if (scrollIntoCurrentMedia > playerWidthPlusPadding) {
|
||||||
newRelativeScroll += playerWidthPlusPadding -
|
newRelativeScroll +=
|
||||||
(scrollIntoCurrentMedia - playerWidthPlusPadding)
|
playerWidthPlusPadding - (scrollIntoCurrentMedia - playerWidthPlusPadding)
|
||||||
} else {
|
} else {
|
||||||
newRelativeScroll += scrollIntoCurrentMedia
|
newRelativeScroll += scrollIntoCurrentMedia
|
||||||
}
|
}
|
||||||
scrollView.relativeScrollX = newRelativeScroll
|
scrollView.relativeScrollX = newRelativeScroll
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Does the dismiss currently show the setting cog? */
|
||||||
* Does the dismiss currently show the setting cog?
|
|
||||||
*/
|
|
||||||
var showsSettingsButton: Boolean = false
|
var showsSettingsButton: Boolean = false
|
||||||
|
|
||||||
/**
|
/** A utility to detect gestures, used in the touch listener */
|
||||||
* A utility to detect gestures, used in the touch listener
|
private val gestureListener =
|
||||||
*/
|
object : GestureDetector.SimpleOnGestureListener() {
|
||||||
private val gestureListener = object : GestureDetector.SimpleOnGestureListener() {
|
override fun onFling(
|
||||||
override fun onFling(
|
eStart: MotionEvent?,
|
||||||
eStart: MotionEvent?,
|
eCurrent: MotionEvent?,
|
||||||
eCurrent: MotionEvent?,
|
vX: Float,
|
||||||
vX: Float,
|
vY: Float
|
||||||
vY: Float
|
) = onFling(vX, vY)
|
||||||
) = onFling(vX, vY)
|
|
||||||
|
|
||||||
override fun onScroll(
|
override fun onScroll(
|
||||||
down: MotionEvent?,
|
down: MotionEvent?,
|
||||||
lastMotion: MotionEvent?,
|
lastMotion: MotionEvent?,
|
||||||
distanceX: Float,
|
distanceX: Float,
|
||||||
distanceY: Float
|
distanceY: Float
|
||||||
) = onScroll(down!!, lastMotion!!, distanceX)
|
) = onScroll(down!!, lastMotion!!, distanceX)
|
||||||
|
|
||||||
override fun onDown(e: MotionEvent?): Boolean {
|
override fun onDown(e: MotionEvent?): Boolean {
|
||||||
if (falsingProtectionNeeded) {
|
if (falsingProtectionNeeded) {
|
||||||
falsingCollector.onNotificationStartDismissing()
|
falsingCollector.onNotificationStartDismissing()
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/** The touch listener for the scroll view */
|
||||||
* The touch listener for the scroll view
|
private val touchListener =
|
||||||
*/
|
object : Gefingerpoken {
|
||||||
private val touchListener = object : Gefingerpoken {
|
override fun onTouchEvent(motionEvent: MotionEvent?) = onTouch(motionEvent!!)
|
||||||
override fun onTouchEvent(motionEvent: MotionEvent?) = onTouch(motionEvent!!)
|
override fun onInterceptTouchEvent(ev: MotionEvent?) = onInterceptTouch(ev!!)
|
||||||
override fun onInterceptTouchEvent(ev: MotionEvent?) = onInterceptTouch(ev!!)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/** A listener that is invoked when the scrolling changes to update player visibilities */
|
||||||
* A listener that is invoked when the scrolling changes to update player visibilities
|
private val scrollChangedListener =
|
||||||
*/
|
object : View.OnScrollChangeListener {
|
||||||
private val scrollChangedListener = object : View.OnScrollChangeListener {
|
override fun onScrollChange(
|
||||||
override fun onScrollChange(
|
v: View?,
|
||||||
v: View?,
|
scrollX: Int,
|
||||||
scrollX: Int,
|
scrollY: Int,
|
||||||
scrollY: Int,
|
oldScrollX: Int,
|
||||||
oldScrollX: Int,
|
oldScrollY: Int
|
||||||
oldScrollY: Int
|
) {
|
||||||
) {
|
if (playerWidthPlusPadding == 0) {
|
||||||
if (playerWidthPlusPadding == 0) {
|
return
|
||||||
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
|
var visibleToUser: Boolean = false
|
||||||
|
|
||||||
/**
|
/** Whether the quick setting is expanded or not */
|
||||||
* Whether the quick setting is expanded or not
|
|
||||||
*/
|
|
||||||
var qsExpanded: Boolean = false
|
var qsExpanded: Boolean = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -220,47 +187,61 @@ class MediaCarouselScrollHandler(
|
|||||||
scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER)
|
scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER)
|
||||||
mediaContent = scrollView.contentContainer
|
mediaContent = scrollView.contentContainer
|
||||||
scrollView.setOnScrollChangeListener(scrollChangedListener)
|
scrollView.setOnScrollChangeListener(scrollChangedListener)
|
||||||
scrollView.outlineProvider = object : ViewOutlineProvider() {
|
scrollView.outlineProvider =
|
||||||
override fun getOutline(view: View?, outline: Outline?) {
|
object : ViewOutlineProvider() {
|
||||||
outline?.setRoundRect(0, 0, carouselWidth, carouselHeight, cornerRadius.toFloat())
|
override fun getOutline(view: View?, outline: Outline?) {
|
||||||
|
outline?.setRoundRect(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
carouselWidth,
|
||||||
|
carouselHeight,
|
||||||
|
cornerRadius.toFloat()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSettingsButtonUpdated(button: View) {
|
fun onSettingsButtonUpdated(button: View) {
|
||||||
settingsButton = button
|
settingsButton = button
|
||||||
// We don't have a context to resolve, lets use the settingsbuttons one since that is
|
// We don't have a context to resolve, lets use the settingsbuttons one since that is
|
||||||
// reinflated appropriately
|
// reinflated appropriately
|
||||||
cornerRadius = settingsButton.resources.getDimensionPixelSize(
|
cornerRadius =
|
||||||
Utils.getThemeAttr(settingsButton.context, android.R.attr.dialogCornerRadius))
|
settingsButton.resources.getDimensionPixelSize(
|
||||||
|
Utils.getThemeAttr(settingsButton.context, android.R.attr.dialogCornerRadius)
|
||||||
|
)
|
||||||
updateSettingsPresentation()
|
updateSettingsPresentation()
|
||||||
scrollView.invalidateOutline()
|
scrollView.invalidateOutline()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSettingsPresentation() {
|
private fun updateSettingsPresentation() {
|
||||||
if (showsSettingsButton && settingsButton.width > 0) {
|
if (showsSettingsButton && settingsButton.width > 0) {
|
||||||
val settingsOffset = MathUtils.map(
|
val settingsOffset =
|
||||||
|
MathUtils.map(
|
||||||
0.0f,
|
0.0f,
|
||||||
getMaxTranslation().toFloat(),
|
getMaxTranslation().toFloat(),
|
||||||
0.0f,
|
0.0f,
|
||||||
1.0f,
|
1.0f,
|
||||||
Math.abs(contentTranslation))
|
Math.abs(contentTranslation)
|
||||||
val settingsTranslation = (1.0f - settingsOffset) * -settingsButton.width *
|
)
|
||||||
|
val settingsTranslation =
|
||||||
|
(1.0f - settingsOffset) *
|
||||||
|
-settingsButton.width *
|
||||||
SETTINGS_BUTTON_TRANSLATION_FRACTION
|
SETTINGS_BUTTON_TRANSLATION_FRACTION
|
||||||
val newTranslationX = if (isRtl) {
|
val newTranslationX =
|
||||||
// In RTL, the 0-placement is on the right side of the view, not the left...
|
if (isRtl) {
|
||||||
if (contentTranslation > 0) {
|
// In RTL, the 0-placement is on the right side of the view, not the left...
|
||||||
-(scrollView.width - settingsTranslation - settingsButton.width)
|
if (contentTranslation > 0) {
|
||||||
|
-(scrollView.width - settingsTranslation - settingsButton.width)
|
||||||
|
} else {
|
||||||
|
-settingsTranslation
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
-settingsTranslation
|
if (contentTranslation > 0) {
|
||||||
|
settingsTranslation
|
||||||
|
} else {
|
||||||
|
scrollView.width - settingsTranslation - settingsButton.width
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (contentTranslation > 0) {
|
|
||||||
settingsTranslation
|
|
||||||
} else {
|
|
||||||
scrollView.width - settingsTranslation - settingsButton.width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val rotation = (1.0f - settingsOffset) * 50
|
val rotation = (1.0f - settingsOffset) * 50
|
||||||
settingsButton.rotation = rotation * -Math.signum(contentTranslation)
|
settingsButton.rotation = rotation * -Math.signum(contentTranslation)
|
||||||
val alpha = MathUtils.saturate(MathUtils.map(0.5f, 1.0f, 0.0f, 1.0f, settingsOffset))
|
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
|
val newScrollX = scrollView.relativeScrollX + dx
|
||||||
// Delay the scrolling since scrollView calls springback which cancels
|
// Delay the scrolling since scrollView calls springback which cancels
|
||||||
// the animation again..
|
// the animation again..
|
||||||
mainExecutor.execute {
|
mainExecutor.execute { scrollView.smoothScrollTo(newScrollX, scrollView.scrollY) }
|
||||||
scrollView.smoothScrollTo(newScrollX, scrollView.scrollY)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
val currentTranslation = scrollView.getContentTranslation()
|
val currentTranslation = scrollView.getContentTranslation()
|
||||||
if (currentTranslation != 0.0f) {
|
if (currentTranslation != 0.0f) {
|
||||||
// We started a Swipe but didn't end up with a fling. Let's either go to the
|
// We started a Swipe but didn't end up with a fling. Let's either go to the
|
||||||
// dismissed position or go back.
|
// dismissed position or go back.
|
||||||
val springBack = Math.abs(currentTranslation) < getMaxTranslation() / 2 ||
|
val springBack =
|
||||||
isFalseTouch()
|
Math.abs(currentTranslation) < getMaxTranslation() / 2 || isFalseTouch()
|
||||||
val newTranslation: Float
|
val newTranslation: Float
|
||||||
if (springBack) {
|
if (springBack) {
|
||||||
newTranslation = 0.0f
|
newTranslation = 0.0f
|
||||||
@@ -325,13 +304,17 @@ class MediaCarouselScrollHandler(
|
|||||||
if (!showsSettingsButton) {
|
if (!showsSettingsButton) {
|
||||||
// Delay the dismiss a bit to avoid too much overlap. Waiting until the
|
// Delay the dismiss a bit to avoid too much overlap. Waiting until the
|
||||||
// animation has finished also feels a bit too slow here.
|
// animation has finished also feels a bit too slow here.
|
||||||
mainExecutor.executeDelayed({
|
mainExecutor.executeDelayed({ dismissCallback.invoke() }, DISMISS_DELAY)
|
||||||
dismissCallback.invoke()
|
|
||||||
}, DISMISS_DELAY)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PhysicsAnimator.getInstance(this).spring(CONTENT_TRANSLATION,
|
PhysicsAnimator.getInstance(this)
|
||||||
newTranslation, startVelocity = 0.0f, config = translationConfig).start()
|
.spring(
|
||||||
|
CONTENT_TRANSLATION,
|
||||||
|
newTranslation,
|
||||||
|
startVelocity = 0.0f,
|
||||||
|
config = translationConfig
|
||||||
|
)
|
||||||
|
.start()
|
||||||
scrollView.animationTargetX = newTranslation
|
scrollView.animationTargetX = newTranslation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -339,10 +322,11 @@ class MediaCarouselScrollHandler(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isFalseTouch() = falsingProtectionNeeded &&
|
private fun isFalseTouch() =
|
||||||
falsingManager.isFalseTouch(NOTIFICATION_DISMISS)
|
falsingProtectionNeeded && falsingManager.isFalseTouch(NOTIFICATION_DISMISS)
|
||||||
|
|
||||||
private fun getMaxTranslation() = if (showsSettingsButton) {
|
private fun getMaxTranslation() =
|
||||||
|
if (showsSettingsButton) {
|
||||||
settingsButton.width
|
settingsButton.width
|
||||||
} else {
|
} else {
|
||||||
playerWidthPlusPadding
|
playerWidthPlusPadding
|
||||||
@@ -352,15 +336,10 @@ class MediaCarouselScrollHandler(
|
|||||||
return gestureDetector.onTouchEvent(motionEvent)
|
return gestureDetector.onTouchEvent(motionEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onScroll(
|
fun onScroll(down: MotionEvent, lastMotion: MotionEvent, distanceX: Float): Boolean {
|
||||||
down: MotionEvent,
|
|
||||||
lastMotion: MotionEvent,
|
|
||||||
distanceX: Float
|
|
||||||
): Boolean {
|
|
||||||
val totalX = lastMotion.x - down.x
|
val totalX = lastMotion.x - down.x
|
||||||
val currentTranslation = scrollView.getContentTranslation()
|
val currentTranslation = scrollView.getContentTranslation()
|
||||||
if (currentTranslation != 0.0f ||
|
if (currentTranslation != 0.0f || !scrollView.canScrollHorizontally((-totalX).toInt())) {
|
||||||
!scrollView.canScrollHorizontally((-totalX).toInt())) {
|
|
||||||
var newTranslation = currentTranslation - distanceX
|
var newTranslation = currentTranslation - distanceX
|
||||||
val absTranslation = Math.abs(newTranslation)
|
val absTranslation = Math.abs(newTranslation)
|
||||||
if (absTranslation > getMaxTranslation()) {
|
if (absTranslation > getMaxTranslation()) {
|
||||||
@@ -374,14 +353,18 @@ class MediaCarouselScrollHandler(
|
|||||||
newTranslation = currentTranslation - distanceX * RUBBERBAND_FACTOR
|
newTranslation = currentTranslation - distanceX * RUBBERBAND_FACTOR
|
||||||
} else {
|
} else {
|
||||||
// We just crossed the boundary, let's rubberband it all
|
// We just crossed the boundary, let's rubberband it all
|
||||||
newTranslation = Math.signum(newTranslation) * (getMaxTranslation() +
|
newTranslation =
|
||||||
(absTranslation - getMaxTranslation()) * RUBBERBAND_FACTOR)
|
Math.signum(newTranslation) *
|
||||||
|
(getMaxTranslation() +
|
||||||
|
(absTranslation - getMaxTranslation()) * RUBBERBAND_FACTOR)
|
||||||
}
|
}
|
||||||
} // Otherwise we don't have do do anything, and will remove the unrubberbanded
|
} // Otherwise we don't have do do anything, and will remove the unrubberbanded
|
||||||
// translation
|
// translation
|
||||||
}
|
}
|
||||||
if (Math.signum(newTranslation) != Math.signum(currentTranslation) &&
|
if (
|
||||||
currentTranslation != 0.0f) {
|
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
|
// We crossed the 0.0 threshold of the translation. Let's see if we're allowed
|
||||||
// to scroll into the new direction
|
// to scroll into the new direction
|
||||||
if (scrollView.canScrollHorizontally(-newTranslation.toInt())) {
|
if (scrollView.canScrollHorizontally(-newTranslation.toInt())) {
|
||||||
@@ -392,8 +375,14 @@ class MediaCarouselScrollHandler(
|
|||||||
}
|
}
|
||||||
val physicsAnimator = PhysicsAnimator.getInstance(this)
|
val physicsAnimator = PhysicsAnimator.getInstance(this)
|
||||||
if (physicsAnimator.isRunning()) {
|
if (physicsAnimator.isRunning()) {
|
||||||
physicsAnimator.spring(CONTENT_TRANSLATION,
|
physicsAnimator
|
||||||
newTranslation, startVelocity = 0.0f, config = translationConfig).start()
|
.spring(
|
||||||
|
CONTENT_TRANSLATION,
|
||||||
|
newTranslation,
|
||||||
|
startVelocity = 0.0f,
|
||||||
|
config = translationConfig
|
||||||
|
)
|
||||||
|
.start()
|
||||||
} else {
|
} else {
|
||||||
contentTranslation = newTranslation
|
contentTranslation = newTranslation
|
||||||
}
|
}
|
||||||
@@ -403,10 +392,7 @@ class MediaCarouselScrollHandler(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onFling(
|
private fun onFling(vX: Float, vY: Float): Boolean {
|
||||||
vX: Float,
|
|
||||||
vY: Float
|
|
||||||
): Boolean {
|
|
||||||
if (vX * vX < 0.5 * vY * vY) {
|
if (vX * vX < 0.5 * vY * vY) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -425,13 +411,17 @@ class MediaCarouselScrollHandler(
|
|||||||
// Delay the dismiss a bit to avoid too much overlap. Waiting until the animation
|
// Delay the dismiss a bit to avoid too much overlap. Waiting until the animation
|
||||||
// has finished also feels a bit too slow here.
|
// has finished also feels a bit too slow here.
|
||||||
if (!showsSettingsButton) {
|
if (!showsSettingsButton) {
|
||||||
mainExecutor.executeDelayed({
|
mainExecutor.executeDelayed({ dismissCallback.invoke() }, DISMISS_DELAY)
|
||||||
dismissCallback.invoke()
|
|
||||||
}, DISMISS_DELAY)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PhysicsAnimator.getInstance(this).spring(CONTENT_TRANSLATION,
|
PhysicsAnimator.getInstance(this)
|
||||||
newTranslation, startVelocity = vX, config = translationConfig).start()
|
.spring(
|
||||||
|
CONTENT_TRANSLATION,
|
||||||
|
newTranslation,
|
||||||
|
startVelocity = vX,
|
||||||
|
config = translationConfig
|
||||||
|
)
|
||||||
|
.start()
|
||||||
scrollView.animationTargetX = newTranslation
|
scrollView.animationTargetX = newTranslation
|
||||||
} else {
|
} else {
|
||||||
// We're flinging the player! Let's go either to the previous or to the next player
|
// 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)
|
val view = mediaContent.getChildAt(destIndex)
|
||||||
// We need to post this since we're dispatching a touch to the underlying view to cancel
|
// We need to post this since we're dispatching a touch to the underlying view to cancel
|
||||||
// but canceling will actually abort the animation.
|
// but canceling will actually abort the animation.
|
||||||
mainExecutor.execute {
|
mainExecutor.execute { scrollView.smoothScrollTo(view.left, scrollView.scrollY) }
|
||||||
scrollView.smoothScrollTo(view.left, scrollView.scrollY)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Reset the translation of the players when swiped */
|
||||||
* Reset the translation of the players when swiped
|
|
||||||
*/
|
|
||||||
fun resetTranslation(animate: Boolean = false) {
|
fun resetTranslation(animate: Boolean = false) {
|
||||||
if (scrollView.getContentTranslation() != 0.0f) {
|
if (scrollView.getContentTranslation() != 0.0f) {
|
||||||
if (animate) {
|
if (animate) {
|
||||||
PhysicsAnimator.getInstance(this).spring(CONTENT_TRANSLATION,
|
PhysicsAnimator.getInstance(this)
|
||||||
0.0f, config = translationConfig).start()
|
.spring(CONTENT_TRANSLATION, 0.0f, config = translationConfig)
|
||||||
|
.start()
|
||||||
scrollView.animationTargetX = 0.0f
|
scrollView.animationTargetX = 0.0f
|
||||||
} else {
|
} else {
|
||||||
PhysicsAnimator.getInstance(this).cancel()
|
PhysicsAnimator.getInstance(this).cancel()
|
||||||
@@ -486,21 +473,22 @@ class MediaCarouselScrollHandler(
|
|||||||
closeGuts(false)
|
closeGuts(false)
|
||||||
updatePlayerVisibilities()
|
updatePlayerVisibilities()
|
||||||
}
|
}
|
||||||
val relativeLocation = visibleMediaIndex.toFloat() + if (playerWidthPlusPadding > 0)
|
val relativeLocation =
|
||||||
scrollInAmount.toFloat() / playerWidthPlusPadding else 0f
|
visibleMediaIndex.toFloat() +
|
||||||
|
if (playerWidthPlusPadding > 0) scrollInAmount.toFloat() / playerWidthPlusPadding
|
||||||
|
else 0f
|
||||||
// Fix the location, because PageIndicator does not handle RTL internally
|
// Fix the location, because PageIndicator does not handle RTL internally
|
||||||
val location = if (isRtl) {
|
val location =
|
||||||
mediaContent.childCount - relativeLocation - 1
|
if (isRtl) {
|
||||||
} else {
|
mediaContent.childCount - relativeLocation - 1
|
||||||
relativeLocation
|
} else {
|
||||||
}
|
relativeLocation
|
||||||
|
}
|
||||||
pageIndicator.setLocation(location)
|
pageIndicator.setLocation(location)
|
||||||
updateClipToOutline()
|
updateClipToOutline()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Notified whenever the players or their order has changed */
|
||||||
* Notified whenever the players or their order has changed
|
|
||||||
*/
|
|
||||||
fun onPlayersChanged() {
|
fun onPlayersChanged() {
|
||||||
updatePlayerVisibilities()
|
updatePlayerVisibilities()
|
||||||
updateMediaPaddings()
|
updateMediaPaddings()
|
||||||
@@ -530,8 +518,8 @@ class MediaCarouselScrollHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify that a player will be removed right away. This gives us the opporunity to look
|
* Notify that a player will be removed right away. This gives us the opporunity to look where
|
||||||
* where it was and update our scroll position.
|
* it was and update our scroll position.
|
||||||
*/
|
*/
|
||||||
fun onPrePlayerRemoved(removed: MediaControlPanel) {
|
fun onPrePlayerRemoved(removed: MediaControlPanel) {
|
||||||
val removedIndex = mediaContent.indexOfChild(removed.mediaViewHolder?.player)
|
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) {
|
fun setCarouselBounds(currentCarouselWidth: Int, currentCarouselHeight: Int) {
|
||||||
if (currentCarouselHeight != carouselHeight || currentCarouselWidth != carouselHeight) {
|
if (currentCarouselHeight != carouselHeight || currentCarouselWidth != carouselHeight) {
|
||||||
carouselWidth = currentCarouselWidth
|
carouselWidth = currentCarouselWidth
|
||||||
@@ -562,9 +548,7 @@ class MediaCarouselScrollHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Reset the MediaScrollView to the start. */
|
||||||
* Reset the MediaScrollView to the start.
|
|
||||||
*/
|
|
||||||
fun scrollToStart() {
|
fun scrollToStart() {
|
||||||
scrollView.relativeScrollX = 0
|
scrollView.relativeScrollX = 0
|
||||||
}
|
}
|
||||||
@@ -582,21 +566,22 @@ class MediaCarouselScrollHandler(
|
|||||||
val destIndex = Math.min(mediaContent.getChildCount() - 1, destIndex)
|
val destIndex = Math.min(mediaContent.getChildCount() - 1, destIndex)
|
||||||
val view = mediaContent.getChildAt(destIndex)
|
val view = mediaContent.getChildAt(destIndex)
|
||||||
// We need to post this to wait for the active player becomes visible.
|
// We need to post this to wait for the active player becomes visible.
|
||||||
mainExecutor.executeDelayed({
|
mainExecutor.executeDelayed(
|
||||||
scrollView.smoothScrollTo(view.left, scrollView.scrollY)
|
{ scrollView.smoothScrollTo(view.left, scrollView.scrollY) },
|
||||||
}, SCROLL_DELAY)
|
SCROLL_DELAY
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val CONTENT_TRANSLATION = object : FloatPropertyCompat<MediaCarouselScrollHandler>(
|
private val CONTENT_TRANSLATION =
|
||||||
"contentTranslation") {
|
object : FloatPropertyCompat<MediaCarouselScrollHandler>("contentTranslation") {
|
||||||
override fun getValue(handler: MediaCarouselScrollHandler): Float {
|
override fun getValue(handler: MediaCarouselScrollHandler): Float {
|
||||||
return handler.contentTranslation
|
return handler.contentTranslation
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setValue(handler: MediaCarouselScrollHandler, value: Float) {
|
override fun setValue(handler: MediaCarouselScrollHandler, value: Float) {
|
||||||
handler.contentTranslation = value
|
handler.contentTranslation = value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -30,7 +30,8 @@ import com.android.systemui.util.animation.UniqueObjectHostView
|
|||||||
import java.util.Objects
|
import java.util.Objects
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class MediaHost constructor(
|
class MediaHost
|
||||||
|
constructor(
|
||||||
private val state: MediaHostStateHolder,
|
private val state: MediaHostStateHolder,
|
||||||
private val mediaHierarchyManager: MediaHierarchyManager,
|
private val mediaHierarchyManager: MediaHierarchyManager,
|
||||||
private val mediaDataManager: MediaDataManager,
|
private val mediaDataManager: MediaDataManager,
|
||||||
@@ -45,14 +46,10 @@ class MediaHost constructor(
|
|||||||
|
|
||||||
private var inited: Boolean = false
|
private var inited: Boolean = false
|
||||||
|
|
||||||
/**
|
/** Are we listening to media data changes? */
|
||||||
* Are we listening to media data changes?
|
|
||||||
*/
|
|
||||||
private var listeningToMediaData = false
|
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()
|
val currentBounds: Rect = Rect()
|
||||||
get() {
|
get() {
|
||||||
hostView.getLocationOnScreen(tmpLocationOnScreen)
|
hostView.getLocationOnScreen(tmpLocationOnScreen)
|
||||||
@@ -81,38 +78,39 @@ class MediaHost constructor(
|
|||||||
*/
|
*/
|
||||||
val currentClipping = Rect()
|
val currentClipping = Rect()
|
||||||
|
|
||||||
private val listener = object : MediaDataManager.Listener {
|
private val listener =
|
||||||
override fun onMediaDataLoaded(
|
object : MediaDataManager.Listener {
|
||||||
key: String,
|
override fun onMediaDataLoaded(
|
||||||
oldKey: String?,
|
key: String,
|
||||||
data: MediaData,
|
oldKey: String?,
|
||||||
immediately: Boolean,
|
data: MediaData,
|
||||||
receivedSmartspaceCardLatency: Int,
|
immediately: Boolean,
|
||||||
isSsReactivated: Boolean
|
receivedSmartspaceCardLatency: Int,
|
||||||
) {
|
isSsReactivated: Boolean
|
||||||
if (immediately) {
|
) {
|
||||||
|
if (immediately) {
|
||||||
|
updateViewVisibility()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSmartspaceMediaDataLoaded(
|
||||||
|
key: String,
|
||||||
|
data: SmartspaceMediaData,
|
||||||
|
shouldPrioritize: Boolean
|
||||||
|
) {
|
||||||
updateViewVisibility()
|
updateViewVisibility()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSmartspaceMediaDataLoaded(
|
override fun onMediaDataRemoved(key: String) {
|
||||||
key: String,
|
|
||||||
data: SmartspaceMediaData,
|
|
||||||
shouldPrioritize: Boolean
|
|
||||||
) {
|
|
||||||
updateViewVisibility()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMediaDataRemoved(key: String) {
|
|
||||||
updateViewVisibility()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
|
|
||||||
if (immediately) {
|
|
||||||
updateViewVisibility()
|
updateViewVisibility()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
|
||||||
|
if (immediately) {
|
||||||
|
updateViewVisibility()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun addVisibilityChangeListener(listener: (Boolean) -> Unit) {
|
fun addVisibilityChangeListener(listener: (Boolean) -> Unit) {
|
||||||
visibleChangedListeners.add(listener)
|
visibleChangedListeners.add(listener)
|
||||||
@@ -123,12 +121,14 @@ class MediaHost constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize this MediaObject and create a host view.
|
* Initialize this MediaObject and create a host view. All state should already be set on this
|
||||||
* All state should already be set on this host before calling this method in order to avoid
|
* host before calling this method in order to avoid unnecessary state changes which lead to
|
||||||
* unnecessary state changes which lead to remeasurings later on.
|
* remeasurings later on.
|
||||||
*
|
*
|
||||||
* @param location the location this host name has. Used to identify the host during
|
* @param location the location this host name has. Used to identify the host during
|
||||||
|
* ```
|
||||||
* transitions.
|
* transitions.
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
fun init(@MediaLocation location: Int) {
|
fun init(@MediaLocation location: Int) {
|
||||||
if (inited) {
|
if (inited) {
|
||||||
@@ -141,36 +141,42 @@ class MediaHost constructor(
|
|||||||
// Listen by default, as the host might not be attached by our clients, until
|
// 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!
|
// they get a visibility change. We still want to stay up to date in that case!
|
||||||
setListeningToMediaData(true)
|
setListeningToMediaData(true)
|
||||||
hostView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
|
hostView.addOnAttachStateChangeListener(
|
||||||
override fun onViewAttachedToWindow(v: View?) {
|
object : OnAttachStateChangeListener {
|
||||||
setListeningToMediaData(true)
|
override fun onViewAttachedToWindow(v: View?) {
|
||||||
updateViewVisibility()
|
setListeningToMediaData(true)
|
||||||
}
|
updateViewVisibility()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onViewDetachedFromWindow(v: View?) {
|
override fun onViewDetachedFromWindow(v: View?) {
|
||||||
setListeningToMediaData(false)
|
setListeningToMediaData(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
// Listen to measurement updates and update our state with it
|
// Listen to measurement updates and update our state with it
|
||||||
hostView.measurementManager = object : UniqueObjectHostView.MeasurementManager {
|
hostView.measurementManager =
|
||||||
override fun onMeasure(input: MeasurementInput): MeasurementOutput {
|
object : UniqueObjectHostView.MeasurementManager {
|
||||||
// Modify the measurement to exactly match the dimensions
|
override fun onMeasure(input: MeasurementInput): MeasurementOutput {
|
||||||
if (View.MeasureSpec.getMode(input.widthMeasureSpec) == View.MeasureSpec.AT_MOST) {
|
// Modify the measurement to exactly match the dimensions
|
||||||
input.widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(
|
if (
|
||||||
View.MeasureSpec.getSize(input.widthMeasureSpec),
|
View.MeasureSpec.getMode(input.widthMeasureSpec) == View.MeasureSpec.AT_MOST
|
||||||
View.MeasureSpec.EXACTLY)
|
) {
|
||||||
|
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
|
// Whenever the state changes, let our state manager know
|
||||||
state.changedListener = {
|
state.changedListener = { mediaHostStatesManager.updateHostState(location, state) }
|
||||||
mediaHostStatesManager.updateHostState(location, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
updateViewVisibility()
|
updateViewVisibility()
|
||||||
}
|
}
|
||||||
@@ -191,17 +197,16 @@ class MediaHost constructor(
|
|||||||
* the visibility has changed
|
* the visibility has changed
|
||||||
*/
|
*/
|
||||||
fun updateViewVisibility() {
|
fun updateViewVisibility() {
|
||||||
state.visible = if (showsOnlyActiveMedia) {
|
state.visible =
|
||||||
mediaDataManager.hasActiveMediaOrRecommendation()
|
if (showsOnlyActiveMedia) {
|
||||||
} else {
|
mediaDataManager.hasActiveMediaOrRecommendation()
|
||||||
mediaDataManager.hasAnyMediaOrRecommendation()
|
} else {
|
||||||
}
|
mediaDataManager.hasAnyMediaOrRecommendation()
|
||||||
|
}
|
||||||
val newVisibility = if (visible) View.VISIBLE else View.GONE
|
val newVisibility = if (visible) View.VISIBLE else View.GONE
|
||||||
if (newVisibility != hostView.visibility) {
|
if (newVisibility != hostView.visibility) {
|
||||||
hostView.visibility = newVisibility
|
hostView.visibility = newVisibility
|
||||||
visibleChangedListeners.forEach {
|
visibleChangedListeners.forEach { it.invoke(visible) }
|
||||||
it.invoke(visible)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,14 +274,10 @@ class MediaHost constructor(
|
|||||||
|
|
||||||
private var lastDisappearHash = disappearParameters.hashCode()
|
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
|
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 {
|
override fun copy(): MediaHostState {
|
||||||
val mediaHostState = MediaHostStateHolder()
|
val mediaHostState = MediaHostStateHolder()
|
||||||
mediaHostState.expansion = expansion
|
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
|
* A description of a media host state that describes the behavior whenever the media carousel is
|
||||||
* is hosted. The HostState notifies the media players of changes to their properties, who
|
* hosted. The HostState notifies the media players of changes to their properties, who in turn will
|
||||||
* in turn will create view states from it.
|
* create view states from it. When adding a new property to this, make sure to update the listener
|
||||||
* When adding a new property to this, make sure to update the listener and notify them
|
* and notify them about the changes. In case you need to have a different rendering based on the
|
||||||
* about the changes.
|
* state, you can add a new constraintState to the [MediaViewController]. Otherwise, similar host
|
||||||
* In case you need to have a different rendering based on the state, you can add a new
|
* states will resolve to the same viewstate, a behavior that is described in [CacheKey]. Make sure
|
||||||
* constraintState to the [MediaViewController]. Otherwise, similar host states will resolve
|
* to only update that key if the underlying view needs to have a different measurement.
|
||||||
* 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 {
|
interface MediaHostState {
|
||||||
|
|
||||||
@@ -349,46 +348,36 @@ interface MediaHostState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The last measurement input that this state was measured with. Infers width and height of
|
* The last measurement input that this state was measured with. Infers width and height of the
|
||||||
* the players.
|
* players.
|
||||||
*/
|
*/
|
||||||
var measurementInput: MeasurementInput?
|
var measurementInput: MeasurementInput?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The expansion of the player, [COLLAPSED] for fully collapsed (up to 3 actions),
|
* The expansion of the player, [COLLAPSED] for fully collapsed (up to 3 actions), [EXPANDED]
|
||||||
* [EXPANDED] for fully expanded (up to 5 actions).
|
* for fully expanded (up to 5 actions).
|
||||||
*/
|
*/
|
||||||
var expansion: Float
|
var expansion: Float
|
||||||
|
|
||||||
/**
|
/** Fraction of the height animation. */
|
||||||
* Fraction of the height animation.
|
|
||||||
*/
|
|
||||||
var squishFraction: Float
|
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
|
var showsOnlyActiveMedia: Boolean
|
||||||
|
|
||||||
/**
|
/** If the view should be VISIBLE or GONE. */
|
||||||
* If the view should be VISIBLE or GONE.
|
|
||||||
*/
|
|
||||||
val visible: Boolean
|
val visible: Boolean
|
||||||
|
|
||||||
/**
|
/** Does this host need any falsing protection? */
|
||||||
* Does this host need any falsing protection?
|
|
||||||
*/
|
|
||||||
var falsingProtectionNeeded: Boolean
|
var falsingProtectionNeeded: Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The parameters how the view disappears from this location when going to a host that's not
|
* 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
|
* visible. If modified, make sure to set this value again on the host to ensure the values are
|
||||||
* are propagated
|
* propagated
|
||||||
*/
|
*/
|
||||||
var disappearParameters: DisappearParameters
|
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
|
fun copy(): MediaHostState
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,85 +38,76 @@ class MediaHostStatesManager @Inject constructor() {
|
|||||||
*/
|
*/
|
||||||
val carouselSizes: MutableMap<Int, MeasurementOutput> = mutableMapOf()
|
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()
|
val mediaHostStates: MutableMap<Int, MediaHostState> = mutableMapOf()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify that a media state for a given location has changed. Should only be called from
|
* Notify that a media state for a given location has changed. Should only be called from Media
|
||||||
* Media hosts themselves.
|
* hosts themselves.
|
||||||
*/
|
*/
|
||||||
fun updateHostState(
|
fun updateHostState(@MediaLocation location: Int, hostState: MediaHostState) =
|
||||||
@MediaLocation location: Int,
|
traceSection("MediaHostStatesManager#updateHostState") {
|
||||||
hostState: MediaHostState
|
val currentState = mediaHostStates.get(location)
|
||||||
) = traceSection("MediaHostStatesManager#updateHostState") {
|
if (!hostState.equals(currentState)) {
|
||||||
val currentState = mediaHostStates.get(location)
|
val newState = hostState.copy()
|
||||||
if (!hostState.equals(currentState)) {
|
mediaHostStates.put(location, newState)
|
||||||
val newState = hostState.copy()
|
updateCarouselDimensions(location, hostState)
|
||||||
mediaHostStates.put(location, newState)
|
// First update all the controllers to ensure they get the chance to measure
|
||||||
updateCarouselDimensions(location, hostState)
|
for (controller in controllers) {
|
||||||
// First update all the controllers to ensure they get the chance to measure
|
controller.stateCallback.onHostStateChanged(location, newState)
|
||||||
for (controller in controllers) {
|
}
|
||||||
controller.stateCallback.onHostStateChanged(location, newState)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then update all other callbacks which may depend on the controllers above
|
// Then update all other callbacks which may depend on the controllers above
|
||||||
for (callback in callbacks) {
|
for (callback in callbacks) {
|
||||||
callback.onHostStateChanged(location, newState)
|
callback.onHostStateChanged(location, newState)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the dimensions of all players combined, which determines the overall height of the
|
* Get the dimensions of all players combined, which determines the overall height of the media
|
||||||
* media carousel and the media hosts.
|
* carousel and the media hosts.
|
||||||
*/
|
*/
|
||||||
fun updateCarouselDimensions(
|
fun updateCarouselDimensions(
|
||||||
@MediaLocation location: Int,
|
@MediaLocation location: Int,
|
||||||
hostState: MediaHostState
|
hostState: MediaHostState
|
||||||
): MeasurementOutput = traceSection("MediaHostStatesManager#updateCarouselDimensions") {
|
): MeasurementOutput =
|
||||||
val result = MeasurementOutput(0, 0)
|
traceSection("MediaHostStatesManager#updateCarouselDimensions") {
|
||||||
for (controller in controllers) {
|
val result = MeasurementOutput(0, 0)
|
||||||
val measurement = controller.getMeasurementsForState(hostState)
|
for (controller in controllers) {
|
||||||
measurement?.let {
|
val measurement = controller.getMeasurementsForState(hostState)
|
||||||
if (it.measuredHeight > result.measuredHeight) {
|
measurement?.let {
|
||||||
result.measuredHeight = it.measuredHeight
|
if (it.measuredHeight > result.measuredHeight) {
|
||||||
}
|
result.measuredHeight = it.measuredHeight
|
||||||
if (it.measuredWidth > result.measuredWidth) {
|
}
|
||||||
result.measuredWidth = it.measuredWidth
|
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) {
|
fun addCallback(callback: Callback) {
|
||||||
callbacks.add(callback)
|
callbacks.add(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Remove a callback that listens to media states */
|
||||||
* Remove a callback that listens to media states
|
|
||||||
*/
|
|
||||||
fun removeCallback(callback: Callback) {
|
fun removeCallback(callback: Callback) {
|
||||||
callbacks.remove(callback)
|
callbacks.remove(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a controller that listens to media states and is used to determine the size of
|
* Register a controller that listens to media states and is used to determine the size of the
|
||||||
* the media carousel
|
* media carousel
|
||||||
*/
|
*/
|
||||||
fun addController(controller: MediaViewController) {
|
fun addController(controller: MediaViewController) {
|
||||||
controllers.add(controller)
|
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) {
|
fun removeController(controller: MediaViewController) {
|
||||||
controllers.remove(controller)
|
controllers.remove(controller)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
package com.android.systemui.media.controls.ui
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -11,14 +27,13 @@ import com.android.systemui.Gefingerpoken
|
|||||||
import com.android.wm.shell.animation.physicsAnimator
|
import com.android.wm.shell.animation.physicsAnimator
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A ScrollView used in Media that doesn't limit itself to the childs bounds. This is useful
|
* A ScrollView used in Media that doesn't limit itself to the childs bounds. This is useful when
|
||||||
* when only measuring children but not the parent, when trying to apply a new scroll position
|
* only measuring children but not the parent, when trying to apply a new scroll position
|
||||||
*/
|
*/
|
||||||
class MediaScrollView @JvmOverloads constructor(
|
class MediaScrollView
|
||||||
context: Context,
|
@JvmOverloads
|
||||||
attrs: AttributeSet? = null,
|
constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
|
||||||
defStyleAttr: Int = 0
|
HorizontalScrollView(context, attrs, defStyleAttr) {
|
||||||
) : HorizontalScrollView(context, attrs, defStyleAttr) {
|
|
||||||
|
|
||||||
lateinit var contentContainer: ViewGroup
|
lateinit var contentContainer: ViewGroup
|
||||||
private set
|
private set
|
||||||
@@ -33,35 +48,33 @@ class MediaScrollView @JvmOverloads constructor(
|
|||||||
* Get the current content translation. This is usually the normal translationX of the content,
|
* Get the current content translation. This is usually the normal translationX of the content,
|
||||||
* but when animating, it might differ
|
* but when animating, it might differ
|
||||||
*/
|
*/
|
||||||
fun getContentTranslation() = if (contentContainer.physicsAnimator.isRunning()) {
|
fun getContentTranslation() =
|
||||||
animationTargetX
|
if (contentContainer.physicsAnimator.isRunning()) {
|
||||||
} else {
|
animationTargetX
|
||||||
contentContainer.translationX
|
} else {
|
||||||
}
|
contentContainer.translationX
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert between the absolute (left-to-right) and relative (start-to-end) scrollX of the media
|
* 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
|
* carousel. The player indices are always relative (start-to-end) and the scrollView.scrollX is
|
||||||
* is always absolute. This function is its own inverse.
|
* always absolute. This function is its own inverse.
|
||||||
*/
|
*/
|
||||||
private fun transformScrollX(scrollX: Int): Int = if (isLayoutRtl) {
|
private fun transformScrollX(scrollX: Int): Int =
|
||||||
contentContainer.width - width - scrollX
|
if (isLayoutRtl) {
|
||||||
} else {
|
contentContainer.width - width - scrollX
|
||||||
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
|
var relativeScrollX: Int
|
||||||
get() = transformScrollX(scrollX)
|
get() = transformScrollX(scrollX)
|
||||||
set(value) {
|
set(value) {
|
||||||
scrollX = transformScrollX(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) {
|
override fun scrollTo(x: Int, y: Int) {
|
||||||
if (mScrollX != x || mScrollY != y) {
|
if (mScrollX != x || mScrollY != y) {
|
||||||
val oldX: Int = mScrollX
|
val oldX: Int = mScrollX
|
||||||
@@ -78,17 +91,13 @@ class MediaScrollView @JvmOverloads constructor(
|
|||||||
|
|
||||||
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
|
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
|
||||||
var intercept = false
|
var intercept = false
|
||||||
touchListener?.let {
|
touchListener?.let { intercept = it.onInterceptTouchEvent(ev) }
|
||||||
intercept = it.onInterceptTouchEvent(ev)
|
|
||||||
}
|
|
||||||
return super.onInterceptTouchEvent(ev) || intercept
|
return super.onInterceptTouchEvent(ev) || intercept
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTouchEvent(ev: MotionEvent?): Boolean {
|
override fun onTouchEvent(ev: MotionEvent?): Boolean {
|
||||||
var touch = false
|
var touch = false
|
||||||
touchListener?.let {
|
touchListener?.let { touch = it.onTouchEvent(ev) }
|
||||||
touch = it.onTouchEvent(ev)
|
|
||||||
}
|
|
||||||
return super.onTouchEvent(ev) || touch
|
return super.onTouchEvent(ev) || touch
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,19 +121,25 @@ class MediaScrollView @JvmOverloads constructor(
|
|||||||
// When we're dismissing we ignore all the scrolling
|
// When we're dismissing we ignore all the scrolling
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
|
return super.overScrollBy(
|
||||||
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent)
|
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() {
|
fun cancelCurrentScroll() {
|
||||||
val now = SystemClock.uptimeMillis()
|
val now = SystemClock.uptimeMillis()
|
||||||
val event = MotionEvent.obtain(now, now,
|
val event = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0)
|
||||||
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0)
|
|
||||||
event.source = InputDevice.SOURCE_TOUCHSCREEN
|
event.source = InputDevice.SOURCE_TOUCHSCREEN
|
||||||
super.onTouchEvent(event)
|
super.onTouchEvent(event)
|
||||||
event.recycle()
|
event.recycle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ import javax.inject.Inject
|
|||||||
* A class responsible for controlling a single instance of a media player handling interactions
|
* 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.
|
* 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 context: Context,
|
||||||
private val configurationController: ConfigurationController,
|
private val configurationController: ConfigurationController,
|
||||||
private val mediaHostStatesManager: MediaHostStatesManager,
|
private val mediaHostStatesManager: MediaHostStatesManager,
|
||||||
@@ -50,17 +52,18 @@ class MediaViewController @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicating that the media view controller is for a notification-based player,
|
* Indicating that the media view controller is for a notification-based player, session-based
|
||||||
* session-based player, or recommendation
|
* player, or recommendation
|
||||||
*/
|
*/
|
||||||
enum class TYPE {
|
enum class TYPE {
|
||||||
PLAYER, RECOMMENDATION
|
PLAYER,
|
||||||
|
RECOMMENDATION
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmField
|
@JvmField val GUTS_ANIMATION_DURATION = 500L
|
||||||
val GUTS_ANIMATION_DURATION = 500L
|
val controlIds =
|
||||||
val controlIds = setOf(
|
setOf(
|
||||||
R.id.media_progress_bar,
|
R.id.media_progress_bar,
|
||||||
R.id.actionNext,
|
R.id.actionNext,
|
||||||
R.id.actionPrev,
|
R.id.actionPrev,
|
||||||
@@ -71,22 +74,20 @@ class MediaViewController @Inject constructor(
|
|||||||
R.id.action4,
|
R.id.action4,
|
||||||
R.id.media_scrubbing_elapsed_time,
|
R.id.media_scrubbing_elapsed_time,
|
||||||
R.id.media_scrubbing_total_time
|
R.id.media_scrubbing_total_time
|
||||||
)
|
)
|
||||||
|
|
||||||
val detailIds = setOf(
|
val detailIds =
|
||||||
|
setOf(
|
||||||
R.id.header_title,
|
R.id.header_title,
|
||||||
R.id.header_artist,
|
R.id.header_artist,
|
||||||
R.id.actionPlayPause,
|
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
|
lateinit var sizeChangedListener: () -> Unit
|
||||||
private var firstRefresh: Boolean = true
|
private var firstRefresh: Boolean = true
|
||||||
@VisibleForTesting
|
@VisibleForTesting private var transitionLayout: TransitionLayout? = null
|
||||||
private var transitionLayout: TransitionLayout? = null
|
|
||||||
private val layoutController = TransitionLayoutController()
|
private val layoutController = TransitionLayoutController()
|
||||||
private var animationDelay: Long = 0
|
private var animationDelay: Long = 0
|
||||||
private var animationDuration: Long = 0
|
private var animationDuration: Long = 0
|
||||||
@@ -94,116 +95,98 @@ class MediaViewController @Inject constructor(
|
|||||||
private val measurement = MeasurementOutput(0, 0)
|
private val measurement = MeasurementOutput(0, 0)
|
||||||
private var type: TYPE = TYPE.PLAYER
|
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()
|
private val viewStates: MutableMap<CacheKey, TransitionViewState?> = mutableMapOf()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ending location of the view where it ends when all animations and transitions have
|
* The ending location of the view where it ends when all animations and transitions have
|
||||||
* finished
|
* finished
|
||||||
*/
|
*/
|
||||||
@MediaLocation
|
@MediaLocation var currentEndLocation: Int = -1
|
||||||
var currentEndLocation: Int = -1
|
|
||||||
|
|
||||||
/**
|
/** The starting location of the view where it starts for all animations and transitions */
|
||||||
* The starting location of the view where it starts for all animations and transitions
|
@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
|
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()
|
private val tmpState = TransitionViewState()
|
||||||
|
|
||||||
/**
|
/** A temporary state used to store intermediate measurements. */
|
||||||
* A temporary state used to store intermediate measurements.
|
|
||||||
*/
|
|
||||||
private val tmpState2 = TransitionViewState()
|
private val tmpState2 = TransitionViewState()
|
||||||
|
|
||||||
/**
|
/** A temporary state used to store intermediate measurements. */
|
||||||
* A temporary state used to store intermediate measurements.
|
|
||||||
*/
|
|
||||||
private val tmpState3 = TransitionViewState()
|
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()
|
private val tmpKey = CacheKey()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current width of the player. This might not factor in case the player is animating
|
* The current width of the player. This might not factor in case the player is animating to the
|
||||||
* to the current state, but represents the end state
|
* current state, but represents the end state
|
||||||
*/
|
*/
|
||||||
var currentWidth: Int = 0
|
var currentWidth: Int = 0
|
||||||
/**
|
/**
|
||||||
* The current height of the player. This might not factor in case the player is animating
|
* The current height of the player. This might not factor in case the player is animating to
|
||||||
* to the current state, but represents the end state
|
* the current state, but represents the end state
|
||||||
*/
|
*/
|
||||||
var currentHeight: Int = 0
|
var currentHeight: Int = 0
|
||||||
|
|
||||||
/**
|
/** Get the translationX of the layout */
|
||||||
* Get the translationX of the layout
|
|
||||||
*/
|
|
||||||
var translationX: Float = 0.0f
|
var translationX: Float = 0.0f
|
||||||
private set
|
private set
|
||||||
get() {
|
get() {
|
||||||
return transitionLayout?.translationX ?: 0.0f
|
return transitionLayout?.translationX ?: 0.0f
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Get the translationY of the layout */
|
||||||
* Get the translationY of the layout
|
|
||||||
*/
|
|
||||||
var translationY: Float = 0.0f
|
var translationY: Float = 0.0f
|
||||||
private set
|
private set
|
||||||
get() {
|
get() {
|
||||||
return transitionLayout?.translationY ?: 0.0f
|
return transitionLayout?.translationY ?: 0.0f
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** A callback for RTL config changes */
|
||||||
* A callback for RTL config changes
|
private val configurationListener =
|
||||||
*/
|
object : ConfigurationController.ConfigurationListener {
|
||||||
private val configurationListener = object : ConfigurationController.ConfigurationListener {
|
override fun onConfigChanged(newConfig: Configuration?) {
|
||||||
override fun onConfigChanged(newConfig: Configuration?) {
|
// Because the TransitionLayout is not always attached (and calculates/caches layout
|
||||||
// Because the TransitionLayout is not always attached (and calculates/caches layout
|
// results regardless of attach state), we have to force the layoutDirection of the
|
||||||
// results regardless of attach state), we have to force the layoutDirection of the view
|
// view
|
||||||
// to the correct value for the user's current locale to ensure correct recalculation
|
// to the correct value for the user's current locale to ensure correct
|
||||||
// when/after calling refreshState()
|
// recalculation
|
||||||
newConfig?.apply {
|
// when/after calling refreshState()
|
||||||
if (transitionLayout?.rawLayoutDirection != layoutDirection) {
|
newConfig?.apply {
|
||||||
transitionLayout?.layoutDirection = layoutDirection
|
if (transitionLayout?.rawLayoutDirection != layoutDirection) {
|
||||||
refreshState()
|
transitionLayout?.layoutDirection = layoutDirection
|
||||||
|
refreshState()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/** A callback for media state changes */
|
||||||
* A callback for media state changes
|
val stateCallback =
|
||||||
*/
|
object : MediaHostStatesManager.Callback {
|
||||||
val stateCallback = object : MediaHostStatesManager.Callback {
|
override fun onHostStateChanged(
|
||||||
override fun onHostStateChanged(
|
@MediaLocation location: Int,
|
||||||
@MediaLocation location: Int,
|
mediaHostState: MediaHostState
|
||||||
mediaHostState: MediaHostState
|
) {
|
||||||
) {
|
if (location == currentEndLocation || location == currentStartLocation) {
|
||||||
if (location == currentEndLocation || location == currentStartLocation) {
|
setCurrentState(
|
||||||
setCurrentState(currentStartLocation,
|
currentStartLocation,
|
||||||
currentEndLocation,
|
currentEndLocation,
|
||||||
currentTransitionProgress,
|
currentTransitionProgress,
|
||||||
applyImmediately = false)
|
applyImmediately = false
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The expanded constraint set used to render a expanded player. If it is modified, make sure
|
* The expanded constraint set used to render a expanded player. If it is modified, make sure to
|
||||||
* to call [refreshState]
|
* call [refreshState]
|
||||||
*/
|
*/
|
||||||
val collapsedLayout = ConstraintSet()
|
val collapsedLayout = ConstraintSet()
|
||||||
|
|
||||||
@@ -213,9 +196,7 @@ class MediaViewController @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
val expandedLayout = ConstraintSet()
|
val expandedLayout = ConstraintSet()
|
||||||
|
|
||||||
/**
|
/** Whether the guts are visible for the associated player. */
|
||||||
* Whether the guts are visible for the associated player.
|
|
||||||
*/
|
|
||||||
var isGutsVisible = false
|
var isGutsVisible = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
@@ -237,17 +218,17 @@ class MediaViewController @Inject constructor(
|
|||||||
configurationController.removeCallback(configurationListener)
|
configurationController.removeCallback(configurationListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Show guts with an animated transition. */
|
||||||
* Show guts with an animated transition.
|
|
||||||
*/
|
|
||||||
fun openGuts() {
|
fun openGuts() {
|
||||||
if (isGutsVisible) return
|
if (isGutsVisible) return
|
||||||
isGutsVisible = true
|
isGutsVisible = true
|
||||||
animatePendingStateChange(GUTS_ANIMATION_DURATION, 0L)
|
animatePendingStateChange(GUTS_ANIMATION_DURATION, 0L)
|
||||||
setCurrentState(currentStartLocation,
|
setCurrentState(
|
||||||
currentEndLocation,
|
currentStartLocation,
|
||||||
currentTransitionProgress,
|
currentEndLocation,
|
||||||
applyImmediately = false)
|
currentTransitionProgress,
|
||||||
|
applyImmediately = false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -262,10 +243,12 @@ class MediaViewController @Inject constructor(
|
|||||||
if (!immediate) {
|
if (!immediate) {
|
||||||
animatePendingStateChange(GUTS_ANIMATION_DURATION, 0L)
|
animatePendingStateChange(GUTS_ANIMATION_DURATION, 0L)
|
||||||
}
|
}
|
||||||
setCurrentState(currentStartLocation,
|
setCurrentState(
|
||||||
currentEndLocation,
|
currentStartLocation,
|
||||||
currentTransitionProgress,
|
currentEndLocation,
|
||||||
applyImmediately = immediate)
|
currentTransitionProgress,
|
||||||
|
applyImmediately = immediate
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ensureAllMeasurements() {
|
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 =
|
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
|
* Set the views to be showing/hidden based on the [isGutsVisible] for a given
|
||||||
* [TransitionViewState].
|
* [TransitionViewState].
|
||||||
*/
|
*/
|
||||||
private fun setGutsViewState(viewState: TransitionViewState) {
|
private fun setGutsViewState(viewState: TransitionViewState) {
|
||||||
val controlsIds = when (type) {
|
val controlsIds =
|
||||||
TYPE.PLAYER -> MediaViewHolder.controlsIds
|
when (type) {
|
||||||
TYPE.RECOMMENDATION -> RecommendationViewHolder.controlsIds
|
TYPE.PLAYER -> MediaViewHolder.controlsIds
|
||||||
}
|
TYPE.RECOMMENDATION -> RecommendationViewHolder.controlsIds
|
||||||
|
}
|
||||||
val gutsIds = GutsViewHolder.ids
|
val gutsIds = GutsViewHolder.ids
|
||||||
controlsIds.forEach { id ->
|
controlsIds.forEach { id ->
|
||||||
viewState.widgetStates.get(id)?.let { state ->
|
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(
|
internal fun squishViewState(
|
||||||
viewState: TransitionViewState,
|
viewState: TransitionViewState,
|
||||||
squishFraction: Float
|
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
|
* 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.
|
* it's not available, it will recreate one by measuring, which may be expensive.
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
fun obtainViewState(state: MediaHostState?): TransitionViewState? {
|
fun obtainViewState(state: MediaHostState?): TransitionViewState? {
|
||||||
if (state == null || state.measurementInput == null) {
|
if (state == null || state.measurementInput == null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -371,10 +351,12 @@ class MediaViewController @Inject constructor(
|
|||||||
}
|
}
|
||||||
// Let's create a new measurement
|
// Let's create a new measurement
|
||||||
if (state.expansion == 0.0f || state.expansion == 1.0f) {
|
if (state.expansion == 0.0f || state.expansion == 1.0f) {
|
||||||
result = transitionLayout!!.calculateViewState(
|
result =
|
||||||
|
transitionLayout!!.calculateViewState(
|
||||||
state.measurementInput!!,
|
state.measurementInput!!,
|
||||||
constraintSetForExpansion(state.expansion),
|
constraintSetForExpansion(state.expansion),
|
||||||
TransitionViewState())
|
TransitionViewState()
|
||||||
|
)
|
||||||
|
|
||||||
setGutsViewState(result)
|
setGutsViewState(result)
|
||||||
// We don't want to cache interpolated or null states as this could quickly fill up
|
// 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 startViewState = obtainViewState(startState) as TransitionViewState
|
||||||
val endState = state.copy().also { it.expansion = 1.0f }
|
val endState = state.copy().also { it.expansion = 1.0f }
|
||||||
val endViewState = obtainViewState(endState) as TransitionViewState
|
val endViewState = obtainViewState(endState) as TransitionViewState
|
||||||
result = layoutController.getInterpolatedState(
|
result =
|
||||||
startViewState,
|
layoutController.getInterpolatedState(startViewState, endViewState, state.expansion)
|
||||||
endViewState,
|
|
||||||
state.expansion)
|
|
||||||
}
|
}
|
||||||
if (state.squishFraction <= 1f) {
|
if (state.squishFraction <= 1f) {
|
||||||
return squishViewState(result, state.squishFraction)
|
return squishViewState(result, state.squishFraction)
|
||||||
@@ -401,11 +381,7 @@ class MediaViewController @Inject constructor(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getKey(
|
private fun getKey(state: MediaHostState, guts: Boolean, result: CacheKey): CacheKey {
|
||||||
state: MediaHostState,
|
|
||||||
guts: Boolean,
|
|
||||||
result: CacheKey
|
|
||||||
): CacheKey {
|
|
||||||
result.apply {
|
result.apply {
|
||||||
heightMeasureSpec = state.measurementInput?.heightMeasureSpec ?: 0
|
heightMeasureSpec = state.measurementInput?.heightMeasureSpec ?: 0
|
||||||
widthMeasureSpec = state.measurementInput?.widthMeasureSpec ?: 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
|
* Attach a view to this controller. This may perform measurements if it's not available yet and
|
||||||
* and should therefore be done carefully.
|
* should therefore be done carefully.
|
||||||
*/
|
*/
|
||||||
fun attach(
|
fun attach(transitionLayout: TransitionLayout, type: TYPE) =
|
||||||
transitionLayout: TransitionLayout,
|
traceSection("MediaViewController#attach") {
|
||||||
type: TYPE
|
updateMediaViewControllerType(type)
|
||||||
) = traceSection("MediaViewController#attach") {
|
logger.logMediaLocation("attach $type", currentStartLocation, currentEndLocation)
|
||||||
updateMediaViewControllerType(type)
|
this.transitionLayout = transitionLayout
|
||||||
logger.logMediaLocation("attach $type", currentStartLocation, currentEndLocation)
|
layoutController.attach(transitionLayout)
|
||||||
this.transitionLayout = transitionLayout
|
if (currentEndLocation == -1) {
|
||||||
layoutController.attach(transitionLayout)
|
return
|
||||||
if (currentEndLocation == -1) {
|
}
|
||||||
return
|
// Set the previously set state immediately to the view, now that it's finally attached
|
||||||
}
|
setCurrentState(
|
||||||
// Set the previously set state immediately to the view, now that it's finally attached
|
|
||||||
setCurrentState(
|
|
||||||
startLocation = currentStartLocation,
|
startLocation = currentStartLocation,
|
||||||
endLocation = currentEndLocation,
|
endLocation = currentEndLocation,
|
||||||
transitionProgress = currentTransitionProgress,
|
transitionProgress = currentTransitionProgress,
|
||||||
applyImmediately = true)
|
applyImmediately = true
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtain a measurement for a given location. This makes sure that the state is up to date
|
* Obtain a measurement for a given location. This makes sure that the state is up to date and
|
||||||
* and all widgets know their location. Calling this method may create a measurement if we
|
* all widgets know their location. Calling this method may create a measurement if we don't
|
||||||
* don't have a cached value available already.
|
* have a cached value available already.
|
||||||
*/
|
*/
|
||||||
fun getMeasurementsForState(
|
fun getMeasurementsForState(hostState: MediaHostState): MeasurementOutput? =
|
||||||
hostState: MediaHostState
|
traceSection("MediaViewController#getMeasurementsForState") {
|
||||||
): MeasurementOutput? = traceSection("MediaViewController#getMeasurementsForState") {
|
val viewState = obtainViewState(hostState) ?: return null
|
||||||
val viewState = obtainViewState(hostState) ?: return null
|
measurement.measuredWidth = viewState.width
|
||||||
measurement.measuredWidth = viewState.width
|
measurement.measuredHeight = viewState.height
|
||||||
measurement.measuredHeight = viewState.height
|
return measurement
|
||||||
return measurement
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a new state for the controlled view which can be an interpolation between multiple
|
* 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,
|
@MediaLocation endLocation: Int,
|
||||||
transitionProgress: Float,
|
transitionProgress: Float,
|
||||||
applyImmediately: Boolean
|
applyImmediately: Boolean
|
||||||
) = traceSection("MediaViewController#setCurrentState") {
|
) =
|
||||||
currentEndLocation = endLocation
|
traceSection("MediaViewController#setCurrentState") {
|
||||||
currentStartLocation = startLocation
|
currentEndLocation = endLocation
|
||||||
currentTransitionProgress = transitionProgress
|
currentStartLocation = startLocation
|
||||||
logger.logMediaLocation("setCurrentState", startLocation, endLocation)
|
currentTransitionProgress = transitionProgress
|
||||||
|
logger.logMediaLocation("setCurrentState", startLocation, endLocation)
|
||||||
|
|
||||||
val shouldAnimate = animateNextStateChange && !applyImmediately
|
val shouldAnimate = animateNextStateChange && !applyImmediately
|
||||||
|
|
||||||
val endHostState = mediaHostStatesManager.mediaHostStates[endLocation] ?: return
|
val endHostState = mediaHostStatesManager.mediaHostStates[endLocation] ?: return
|
||||||
val startHostState = mediaHostStatesManager.mediaHostStates[startLocation]
|
val startHostState = mediaHostStatesManager.mediaHostStates[startLocation]
|
||||||
|
|
||||||
// Obtain the view state that we'd want to be at the end
|
// 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
|
// 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
|
// reset once the state is fully available
|
||||||
var endViewState = obtainViewState(endHostState) ?: return
|
var endViewState = obtainViewState(endHostState) ?: return
|
||||||
endViewState = updateViewStateToCarouselSize(endViewState, endLocation, tmpState2)!!
|
endViewState = updateViewStateToCarouselSize(endViewState, endLocation, tmpState2)!!
|
||||||
layoutController.setMeasureState(endViewState)
|
layoutController.setMeasureState(endViewState)
|
||||||
|
|
||||||
// If the view isn't bound, we can drop the animation, otherwise we'll execute it
|
// If the view isn't bound, we can drop the animation, otherwise we'll execute it
|
||||||
animateNextStateChange = false
|
animateNextStateChange = false
|
||||||
if (transitionLayout == null) {
|
if (transitionLayout == null) {
|
||||||
return
|
return
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
val result: TransitionViewState
|
||||||
// Let's get presentation from the endState
|
var startViewState = obtainViewState(startHostState)
|
||||||
result = layoutController.getGoneState(endViewState, endHostState.disappearParameters,
|
startViewState = updateViewStateToCarouselSize(startViewState, startLocation, tmpState3)
|
||||||
1.0f - transitionProgress,
|
|
||||||
tmpState)
|
if (!endHostState.visible) {
|
||||||
} else if (transitionProgress == 1.0f || startViewState == null) {
|
// Let's handle the case where the end is gone first. In this case we take the
|
||||||
// We're at the end. Let's use that state
|
// start viewState and will make it gone
|
||||||
result = endViewState
|
if (startViewState == null || startHostState == null || !startHostState.visible) {
|
||||||
} else if (transitionProgress == 0.0f) {
|
// the start isn't a valid state, let's use the endstate directly
|
||||||
// We're at the start. Let's use that state
|
result = endViewState
|
||||||
result = startViewState
|
} else {
|
||||||
} else {
|
// Let's get the gone presentation from the start state
|
||||||
result = layoutController.getInterpolatedState(startViewState, endViewState,
|
result =
|
||||||
transitionProgress, tmpState)
|
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 {
|
||||||
|
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(
|
private fun updateViewStateToCarouselSize(
|
||||||
viewState: TransitionViewState?,
|
viewState: TransitionViewState?,
|
||||||
@@ -558,8 +550,8 @@ class MediaViewController @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the [TransitionViewState] and [MediaHostState] of a [@MediaLocation].
|
* Retrieves the [TransitionViewState] and [MediaHostState] of a [@MediaLocation]. In the event
|
||||||
* In the event of [location] not being visible, [locationWhenHidden] will be used instead.
|
* of [location] not being visible, [locationWhenHidden] will be used instead.
|
||||||
*
|
*
|
||||||
* @param location Target
|
* @param location Target
|
||||||
* @param locationWhenHidden Location that will be used when the target is not
|
* @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.
|
* This updates the width the view will me measured with.
|
||||||
*/
|
*/
|
||||||
fun onLocationPreChange(@MediaLocation newLocation: Int) {
|
fun onLocationPreChange(@MediaLocation newLocation: Int) {
|
||||||
obtainViewStateForLocation(newLocation)?.let {
|
obtainViewStateForLocation(newLocation)?.let { layoutController.setMeasureState(it) }
|
||||||
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) {
|
fun animatePendingStateChange(duration: Long, delay: Long) {
|
||||||
animateNextStateChange = true
|
animateNextStateChange = true
|
||||||
animationDuration = duration
|
animationDuration = duration
|
||||||
animationDelay = delay
|
animationDelay = delay
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Clear all existing measurements and refresh the state to match the view. */
|
||||||
* Clear all existing measurements and refresh the state to match the view.
|
fun refreshState() =
|
||||||
*/
|
traceSection("MediaViewController#refreshState") {
|
||||||
fun refreshState() = traceSection("MediaViewController#refreshState") {
|
// Let's clear all of our measurements and recreate them!
|
||||||
// Let's clear all of our measurements and recreate them!
|
viewStates.clear()
|
||||||
viewStates.clear()
|
if (firstRefresh) {
|
||||||
if (firstRefresh) {
|
// This is the first bind, let's ensure we pre-cache all measurements. Otherwise
|
||||||
// This is the first bind, let's ensure we pre-cache all measurements. Otherwise
|
// We'll just load these on demand.
|
||||||
// We'll just load these on demand.
|
ensureAllMeasurements()
|
||||||
ensureAllMeasurements()
|
firstRefresh = false
|
||||||
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(
|
private data class CacheKey(
|
||||||
var widthMeasureSpec: Int = -1,
|
var widthMeasureSpec: Int = -1,
|
||||||
var heightMeasureSpec: Int = -1,
|
var heightMeasureSpec: Int = -1,
|
||||||
|
|||||||
@@ -24,40 +24,32 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
private const val TAG = "MediaView"
|
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
|
@SysUISingleton
|
||||||
class MediaViewLogger @Inject constructor(
|
class MediaViewLogger @Inject constructor(@MediaViewLog private val buffer: LogBuffer) {
|
||||||
@MediaViewLog private val buffer: LogBuffer
|
|
||||||
) {
|
|
||||||
fun logMediaSize(reason: String, width: Int, height: Int) {
|
fun logMediaSize(reason: String, width: Int, height: Int) {
|
||||||
buffer.log(
|
buffer.log(
|
||||||
TAG,
|
TAG,
|
||||||
LogLevel.DEBUG,
|
LogLevel.DEBUG,
|
||||||
{
|
{
|
||||||
str1 = reason
|
str1 = reason
|
||||||
int1 = width
|
int1 = width
|
||||||
int2 = height
|
int2 = height
|
||||||
},
|
},
|
||||||
{
|
{ "size ($str1): $int1 x $int2" }
|
||||||
"size ($str1): $int1 x $int2"
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun logMediaLocation(reason: String, startLocation: Int, endLocation: Int) {
|
fun logMediaLocation(reason: String, startLocation: Int, endLocation: Int) {
|
||||||
buffer.log(
|
buffer.log(
|
||||||
TAG,
|
TAG,
|
||||||
LogLevel.DEBUG,
|
LogLevel.DEBUG,
|
||||||
{
|
{
|
||||||
str1 = reason
|
str1 = reason
|
||||||
int1 = startLocation
|
int1 = startLocation
|
||||||
int2 = endLocation
|
int2 = endLocation
|
||||||
},
|
},
|
||||||
{
|
{ "location ($str1): $int1 -> $int2" }
|
||||||
"location ($str1): $int1 -> $int2"
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,4 +73,4 @@ internal open class MetadataAnimationHandler(
|
|||||||
exitAnimator.addListener(this)
|
exitAnimator.addListener(this)
|
||||||
enterAnimator.addListener(this)
|
enterAnimator.addListener(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
package com.android.systemui.media.controls.ui
|
||||||
|
|
||||||
import android.animation.Animator
|
import android.animation.Animator
|
||||||
@@ -23,8 +39,7 @@ import kotlin.math.cos
|
|||||||
private const val TAG = "Squiggly"
|
private const val TAG = "Squiggly"
|
||||||
|
|
||||||
private const val TWO_PI = (Math.PI * 2f).toFloat()
|
private const val TWO_PI = (Math.PI * 2f).toFloat()
|
||||||
@VisibleForTesting
|
@VisibleForTesting internal const val DISABLED_ALPHA = 77
|
||||||
internal const val DISABLED_ALPHA = 77
|
|
||||||
|
|
||||||
class SquigglyProgress : Drawable() {
|
class SquigglyProgress : Drawable() {
|
||||||
|
|
||||||
@@ -86,26 +101,29 @@ class SquigglyProgress : Drawable() {
|
|||||||
lastFrameTime = SystemClock.uptimeMillis()
|
lastFrameTime = SystemClock.uptimeMillis()
|
||||||
}
|
}
|
||||||
heightAnimator?.cancel()
|
heightAnimator?.cancel()
|
||||||
heightAnimator = ValueAnimator.ofFloat(heightFraction, if (animate) 1f else 0f).apply {
|
heightAnimator =
|
||||||
if (animate) {
|
ValueAnimator.ofFloat(heightFraction, if (animate) 1f else 0f).apply {
|
||||||
startDelay = 60
|
if (animate) {
|
||||||
duration = 800
|
startDelay = 60
|
||||||
interpolator = Interpolators.EMPHASIZED_DECELERATE
|
duration = 800
|
||||||
} else {
|
interpolator = Interpolators.EMPHASIZED_DECELERATE
|
||||||
duration = 550
|
} else {
|
||||||
interpolator = Interpolators.STANDARD_DECELERATE
|
duration = 550
|
||||||
}
|
interpolator = Interpolators.STANDARD_DECELERATE
|
||||||
addUpdateListener {
|
|
||||||
heightFraction = it.animatedValue as Float
|
|
||||||
invalidateSelf()
|
|
||||||
}
|
|
||||||
addListener(object : AnimatorListenerAdapter() {
|
|
||||||
override fun onAnimationEnd(animation: Animator?) {
|
|
||||||
heightAnimator = null
|
|
||||||
}
|
}
|
||||||
})
|
addUpdateListener {
|
||||||
start()
|
heightFraction = it.animatedValue as Float
|
||||||
}
|
invalidateSelf()
|
||||||
|
}
|
||||||
|
addListener(
|
||||||
|
object : AnimatorListenerAdapter() {
|
||||||
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
|
heightAnimator = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
override fun draw(canvas: Canvas) {
|
||||||
@@ -120,9 +138,15 @@ class SquigglyProgress : Drawable() {
|
|||||||
val progress = level / 10_000f
|
val progress = level / 10_000f
|
||||||
val totalWidth = bounds.width().toFloat()
|
val totalWidth = bounds.width().toFloat()
|
||||||
val totalProgressPx = totalWidth * progress
|
val totalProgressPx = totalWidth * progress
|
||||||
val waveProgressPx = totalWidth * (
|
val waveProgressPx =
|
||||||
if (!transitionEnabled || progress > matchedWaveEndpoint) progress else
|
totalWidth *
|
||||||
lerp(minWaveEndpoint, matchedWaveEndpoint, lerpInv(0f, matchedWaveEndpoint, progress)))
|
(if (!transitionEnabled || progress > matchedWaveEndpoint) progress
|
||||||
|
else
|
||||||
|
lerp(
|
||||||
|
minWaveEndpoint,
|
||||||
|
matchedWaveEndpoint,
|
||||||
|
lerpInv(0f, matchedWaveEndpoint, progress)
|
||||||
|
))
|
||||||
|
|
||||||
// Build Wiggly Path
|
// Build Wiggly Path
|
||||||
val waveStart = -phaseOffset - waveLength / 2f
|
val waveStart = -phaseOffset - waveLength / 2f
|
||||||
@@ -132,10 +156,8 @@ class SquigglyProgress : Drawable() {
|
|||||||
val computeAmplitude: (Float, Float) -> Float = { x, sign ->
|
val computeAmplitude: (Float, Float) -> Float = { x, sign ->
|
||||||
if (transitionEnabled) {
|
if (transitionEnabled) {
|
||||||
val length = transitionPeriods * waveLength
|
val length = transitionPeriods * waveLength
|
||||||
val coeff = lerpInvSat(
|
val coeff =
|
||||||
waveProgressPx + length / 2f,
|
lerpInvSat(waveProgressPx + length / 2f, waveProgressPx - length / 2f, x)
|
||||||
waveProgressPx - length / 2f,
|
|
||||||
x)
|
|
||||||
sign * heightFraction * lineAmplitude * coeff
|
sign * heightFraction * lineAmplitude * coeff
|
||||||
} else {
|
} else {
|
||||||
sign * heightFraction * lineAmplitude
|
sign * heightFraction * lineAmplitude
|
||||||
@@ -156,10 +178,7 @@ class SquigglyProgress : Drawable() {
|
|||||||
val nextX = currentX + dist
|
val nextX = currentX + dist
|
||||||
val midX = currentX + dist / 2
|
val midX = currentX + dist / 2
|
||||||
val nextAmp = computeAmplitude(nextX, waveSign)
|
val nextAmp = computeAmplitude(nextX, waveSign)
|
||||||
path.cubicTo(
|
path.cubicTo(midX, currentAmp, midX, nextAmp, nextX, nextAmp)
|
||||||
midX, currentAmp,
|
|
||||||
midX, nextAmp,
|
|
||||||
nextX, nextAmp)
|
|
||||||
currentAmp = nextAmp
|
currentAmp = nextAmp
|
||||||
currentX = nextX
|
currentX = nextX
|
||||||
}
|
}
|
||||||
@@ -229,7 +248,7 @@ class SquigglyProgress : Drawable() {
|
|||||||
|
|
||||||
private fun updateColors(tintColor: Int, alpha: Int) {
|
private fun updateColors(tintColor: Int, alpha: Int) {
|
||||||
wavePaint.color = ColorUtils.setAlphaComponent(tintColor, alpha)
|
wavePaint.color = ColorUtils.setAlphaComponent(tintColor, alpha)
|
||||||
linePaint.color = ColorUtils.setAlphaComponent(tintColor,
|
linePaint.color =
|
||||||
(DISABLED_ALPHA * (alpha / 255f)).toInt())
|
ColorUtils.setAlphaComponent(tintColor, (DISABLED_ALPHA * (alpha / 255f)).toInt())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,9 +20,7 @@ import android.content.Context
|
|||||||
import com.android.systemui.util.Utils
|
import com.android.systemui.util.Utils
|
||||||
import javax.inject.Inject
|
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) {
|
class MediaFeatureFlag @Inject constructor(private val context: Context) {
|
||||||
val enabled
|
val enabled
|
||||||
get() = Utils.useQsMediaPlayer(context)
|
get() = Utils.useQsMediaPlayer(context)
|
||||||
|
|||||||
@@ -34,9 +34,7 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) {
|
|||||||
return enabled || featureFlags.isEnabled(Flags.MEDIA_SESSION_ACTIONS)
|
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)
|
fun areMuteAwaitConnectionsEnabled() = featureFlags.isEnabled(Flags.MEDIA_MUTE_AWAIT)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -30,17 +30,13 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
private const val INSTANCE_ID_MAX = 1 shl 20
|
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
|
@SysUISingleton
|
||||||
class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger) {
|
class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger) {
|
||||||
|
|
||||||
private val instanceIdSequence = InstanceIdSequence(INSTANCE_ID_MAX)
|
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 {
|
fun getNewInstanceId(): InstanceId {
|
||||||
return instanceIdSequence.newInstanceId()
|
return instanceIdSequence.newInstanceId()
|
||||||
}
|
}
|
||||||
@@ -51,12 +47,13 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger)
|
|||||||
instanceId: InstanceId,
|
instanceId: InstanceId,
|
||||||
playbackLocation: Int
|
playbackLocation: Int
|
||||||
) {
|
) {
|
||||||
val event = when (playbackLocation) {
|
val event =
|
||||||
MediaData.PLAYBACK_LOCAL -> MediaUiEvent.LOCAL_MEDIA_ADDED
|
when (playbackLocation) {
|
||||||
MediaData.PLAYBACK_CAST_LOCAL -> MediaUiEvent.CAST_MEDIA_ADDED
|
MediaData.PLAYBACK_LOCAL -> MediaUiEvent.LOCAL_MEDIA_ADDED
|
||||||
MediaData.PLAYBACK_CAST_REMOTE -> MediaUiEvent.REMOTE_MEDIA_ADDED
|
MediaData.PLAYBACK_CAST_LOCAL -> MediaUiEvent.CAST_MEDIA_ADDED
|
||||||
else -> throw IllegalArgumentException("Unknown playback location")
|
MediaData.PLAYBACK_CAST_REMOTE -> MediaUiEvent.REMOTE_MEDIA_ADDED
|
||||||
}
|
else -> throw IllegalArgumentException("Unknown playback location")
|
||||||
|
}
|
||||||
logger.logWithInstanceId(event, uid, packageName, instanceId)
|
logger.logWithInstanceId(event, uid, packageName, instanceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,12 +63,13 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger)
|
|||||||
instanceId: InstanceId,
|
instanceId: InstanceId,
|
||||||
playbackLocation: Int
|
playbackLocation: Int
|
||||||
) {
|
) {
|
||||||
val event = when (playbackLocation) {
|
val event =
|
||||||
MediaData.PLAYBACK_LOCAL -> MediaUiEvent.TRANSFER_TO_LOCAL
|
when (playbackLocation) {
|
||||||
MediaData.PLAYBACK_CAST_LOCAL -> MediaUiEvent.TRANSFER_TO_CAST
|
MediaData.PLAYBACK_LOCAL -> MediaUiEvent.TRANSFER_TO_LOCAL
|
||||||
MediaData.PLAYBACK_CAST_REMOTE -> MediaUiEvent.TRANSFER_TO_REMOTE
|
MediaData.PLAYBACK_CAST_LOCAL -> MediaUiEvent.TRANSFER_TO_CAST
|
||||||
else -> throw IllegalArgumentException("Unknown playback location")
|
MediaData.PLAYBACK_CAST_REMOTE -> MediaUiEvent.TRANSFER_TO_REMOTE
|
||||||
}
|
else -> throw IllegalArgumentException("Unknown playback location")
|
||||||
|
}
|
||||||
logger.logWithInstanceId(event, uid, packageName, instanceId)
|
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) {
|
fun logLongPressSettings(uid: Int, packageName: String, instanceId: InstanceId) {
|
||||||
logger.logWithInstanceId(MediaUiEvent.OPEN_SETTINGS_LONG_PRESS, uid, packageName,
|
logger.logWithInstanceId(
|
||||||
instanceId)
|
MediaUiEvent.OPEN_SETTINGS_LONG_PRESS,
|
||||||
|
uid,
|
||||||
|
packageName,
|
||||||
|
instanceId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun logCarouselSettings() {
|
fun logCarouselSettings() {
|
||||||
@@ -120,12 +122,13 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger)
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun logTapAction(buttonId: Int, uid: Int, packageName: String, instanceId: InstanceId) {
|
fun logTapAction(buttonId: Int, uid: Int, packageName: String, instanceId: InstanceId) {
|
||||||
val event = when (buttonId) {
|
val event =
|
||||||
R.id.actionPlayPause -> MediaUiEvent.TAP_ACTION_PLAY_PAUSE
|
when (buttonId) {
|
||||||
R.id.actionPrev -> MediaUiEvent.TAP_ACTION_PREV
|
R.id.actionPlayPause -> MediaUiEvent.TAP_ACTION_PLAY_PAUSE
|
||||||
R.id.actionNext -> MediaUiEvent.TAP_ACTION_NEXT
|
R.id.actionPrev -> MediaUiEvent.TAP_ACTION_PREV
|
||||||
else -> MediaUiEvent.TAP_ACTION_OTHER
|
R.id.actionNext -> MediaUiEvent.TAP_ACTION_NEXT
|
||||||
}
|
else -> MediaUiEvent.TAP_ACTION_OTHER
|
||||||
|
}
|
||||||
|
|
||||||
logger.logWithInstanceId(event, uid, packageName, instanceId)
|
logger.logWithInstanceId(event, uid, packageName, instanceId)
|
||||||
}
|
}
|
||||||
@@ -143,148 +146,130 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger)
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun logCarouselPosition(@MediaLocation location: Int) {
|
fun logCarouselPosition(@MediaLocation location: Int) {
|
||||||
val event = when (location) {
|
val event =
|
||||||
MediaHierarchyManager.LOCATION_QQS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QQS
|
when (location) {
|
||||||
MediaHierarchyManager.LOCATION_QS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QS
|
MediaHierarchyManager.LOCATION_QQS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QQS
|
||||||
MediaHierarchyManager.LOCATION_LOCKSCREEN ->
|
MediaHierarchyManager.LOCATION_QS -> MediaUiEvent.MEDIA_CAROUSEL_LOCATION_QS
|
||||||
MediaUiEvent.MEDIA_CAROUSEL_LOCATION_LOCKSCREEN
|
MediaHierarchyManager.LOCATION_LOCKSCREEN ->
|
||||||
MediaHierarchyManager.LOCATION_DREAM_OVERLAY ->
|
MediaUiEvent.MEDIA_CAROUSEL_LOCATION_LOCKSCREEN
|
||||||
MediaUiEvent.MEDIA_CAROUSEL_LOCATION_DREAM
|
MediaHierarchyManager.LOCATION_DREAM_OVERLAY ->
|
||||||
else -> throw IllegalArgumentException("Unknown media carousel location $location")
|
MediaUiEvent.MEDIA_CAROUSEL_LOCATION_DREAM
|
||||||
}
|
else -> throw IllegalArgumentException("Unknown media carousel location $location")
|
||||||
|
}
|
||||||
logger.log(event)
|
logger.log(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun logRecommendationAdded(packageName: String, instanceId: InstanceId) {
|
fun logRecommendationAdded(packageName: String, instanceId: InstanceId) {
|
||||||
logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_ADDED, 0, packageName,
|
logger.logWithInstanceId(
|
||||||
instanceId)
|
MediaUiEvent.MEDIA_RECOMMENDATION_ADDED,
|
||||||
|
0,
|
||||||
|
packageName,
|
||||||
|
instanceId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun logRecommendationRemoved(packageName: String, instanceId: InstanceId) {
|
fun logRecommendationRemoved(packageName: String, instanceId: InstanceId) {
|
||||||
logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_REMOVED, 0, packageName,
|
logger.logWithInstanceId(
|
||||||
instanceId)
|
MediaUiEvent.MEDIA_RECOMMENDATION_REMOVED,
|
||||||
|
0,
|
||||||
|
packageName,
|
||||||
|
instanceId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun logRecommendationActivated(uid: Int, packageName: String, instanceId: InstanceId) {
|
fun logRecommendationActivated(uid: Int, packageName: String, instanceId: InstanceId) {
|
||||||
logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_ACTIVATED, uid, packageName,
|
logger.logWithInstanceId(
|
||||||
instanceId)
|
MediaUiEvent.MEDIA_RECOMMENDATION_ACTIVATED,
|
||||||
|
uid,
|
||||||
|
packageName,
|
||||||
|
instanceId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun logRecommendationItemTap(packageName: String, instanceId: InstanceId, position: Int) {
|
fun logRecommendationItemTap(packageName: String, instanceId: InstanceId, position: Int) {
|
||||||
logger.logWithInstanceIdAndPosition(MediaUiEvent.MEDIA_RECOMMENDATION_ITEM_TAP, 0,
|
logger.logWithInstanceIdAndPosition(
|
||||||
packageName, instanceId, position)
|
MediaUiEvent.MEDIA_RECOMMENDATION_ITEM_TAP,
|
||||||
|
0,
|
||||||
|
packageName,
|
||||||
|
instanceId,
|
||||||
|
position
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun logRecommendationCardTap(packageName: String, instanceId: InstanceId) {
|
fun logRecommendationCardTap(packageName: String, instanceId: InstanceId) {
|
||||||
logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_CARD_TAP, 0, packageName,
|
logger.logWithInstanceId(
|
||||||
instanceId)
|
MediaUiEvent.MEDIA_RECOMMENDATION_CARD_TAP,
|
||||||
|
0,
|
||||||
|
packageName,
|
||||||
|
instanceId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun logOpenBroadcastDialog(uid: Int, packageName: String, instanceId: InstanceId) {
|
fun logOpenBroadcastDialog(uid: Int, packageName: String, instanceId: InstanceId) {
|
||||||
logger.logWithInstanceId(MediaUiEvent.MEDIA_OPEN_BROADCAST_DIALOG, uid, packageName,
|
logger.logWithInstanceId(
|
||||||
instanceId)
|
MediaUiEvent.MEDIA_OPEN_BROADCAST_DIALOG,
|
||||||
|
uid,
|
||||||
|
packageName,
|
||||||
|
instanceId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
|
enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
|
||||||
@UiEvent(doc = "A new media control was added for media playing locally on the device")
|
@UiEvent(doc = "A new media control was added for media playing locally on the device")
|
||||||
LOCAL_MEDIA_ADDED(1029),
|
LOCAL_MEDIA_ADDED(1029),
|
||||||
|
|
||||||
@UiEvent(doc = "A new media control was added for media cast from the device")
|
@UiEvent(doc = "A new media control was added for media cast from the device")
|
||||||
CAST_MEDIA_ADDED(1030),
|
CAST_MEDIA_ADDED(1030),
|
||||||
|
|
||||||
@UiEvent(doc = "A new media control was added for media playing remotely")
|
@UiEvent(doc = "A new media control was added for media playing remotely")
|
||||||
REMOTE_MEDIA_ADDED(1031),
|
REMOTE_MEDIA_ADDED(1031),
|
||||||
|
|
||||||
@UiEvent(doc = "The media for an existing control was transferred to local playback")
|
@UiEvent(doc = "The media for an existing control was transferred to local playback")
|
||||||
TRANSFER_TO_LOCAL(1032),
|
TRANSFER_TO_LOCAL(1032),
|
||||||
|
|
||||||
@UiEvent(doc = "The media for an existing control was transferred to a cast device")
|
@UiEvent(doc = "The media for an existing control was transferred to a cast device")
|
||||||
TRANSFER_TO_CAST(1033),
|
TRANSFER_TO_CAST(1033),
|
||||||
|
|
||||||
@UiEvent(doc = "The media for an existing control was transferred to a remote device")
|
@UiEvent(doc = "The media for an existing control was transferred to a remote device")
|
||||||
TRANSFER_TO_REMOTE(1034),
|
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")
|
@UiEvent(doc = "An existing active media control was converted into resumable media")
|
||||||
ACTIVE_TO_RESUME(1014),
|
ACTIVE_TO_RESUME(1014),
|
||||||
|
@UiEvent(doc = "A media control timed out") MEDIA_TIMEOUT(1015),
|
||||||
@UiEvent(doc = "A media control timed out")
|
@UiEvent(doc = "A media control was removed from the carousel") MEDIA_REMOVED(1016),
|
||||||
MEDIA_TIMEOUT(1015),
|
@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 = "A media control was removed from the carousel")
|
@UiEvent(doc = "The user long pressed on a media control") OPEN_LONG_PRESS(1019),
|
||||||
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")
|
@UiEvent(doc = "The user dismissed a media control via its long press menu")
|
||||||
DISMISS_LONG_PRESS(1020),
|
DISMISS_LONG_PRESS(1020),
|
||||||
|
|
||||||
@UiEvent(doc = "The user opened media settings from a media control's long press menu")
|
@UiEvent(doc = "The user opened media settings from a media control's long press menu")
|
||||||
OPEN_SETTINGS_LONG_PRESS(1021),
|
OPEN_SETTINGS_LONG_PRESS(1021),
|
||||||
|
|
||||||
@UiEvent(doc = "The user opened media settings from the media carousel")
|
@UiEvent(doc = "The user opened media settings from the media carousel")
|
||||||
OPEN_SETTINGS_CAROUSEL(1022),
|
OPEN_SETTINGS_CAROUSEL(1022),
|
||||||
|
|
||||||
@UiEvent(doc = "The play/pause button on a media control was tapped")
|
@UiEvent(doc = "The play/pause button on a media control was tapped")
|
||||||
TAP_ACTION_PLAY_PAUSE(1023),
|
TAP_ACTION_PLAY_PAUSE(1023),
|
||||||
|
@UiEvent(doc = "The previous button on a media control was tapped") TAP_ACTION_PREV(1024),
|
||||||
@UiEvent(doc = "The previous button on a media control was tapped")
|
@UiEvent(doc = "The next button on a media control was tapped") TAP_ACTION_NEXT(1025),
|
||||||
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")
|
@UiEvent(doc = "A custom or generic action button on a media control was tapped")
|
||||||
TAP_ACTION_OTHER(1026),
|
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")
|
@UiEvent(doc = "The user opened the output switcher from a media control")
|
||||||
OPEN_OUTPUT_SWITCHER(1028),
|
OPEN_OUTPUT_SWITCHER(1028),
|
||||||
|
@UiEvent(doc = "The user tapped on a media control view") MEDIA_TAP_CONTENT_VIEW(1036),
|
||||||
@UiEvent(doc = "The user tapped on a media control view")
|
@UiEvent(doc = "The media carousel moved to QQS") MEDIA_CAROUSEL_LOCATION_QQS(1037),
|
||||||
MEDIA_TAP_CONTENT_VIEW(1036),
|
@UiEvent(doc = "THe media carousel moved to QS") MEDIA_CAROUSEL_LOCATION_QS(1038),
|
||||||
|
|
||||||
@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")
|
@UiEvent(doc = "The media carousel moved to the lockscreen")
|
||||||
MEDIA_CAROUSEL_LOCATION_LOCKSCREEN(1039),
|
MEDIA_CAROUSEL_LOCATION_LOCKSCREEN(1039),
|
||||||
|
|
||||||
@UiEvent(doc = "The media carousel moved to the dream state")
|
@UiEvent(doc = "The media carousel moved to the dream state")
|
||||||
MEDIA_CAROUSEL_LOCATION_DREAM(1040),
|
MEDIA_CAROUSEL_LOCATION_DREAM(1040),
|
||||||
|
|
||||||
@UiEvent(doc = "A media recommendation card was added to the media carousel")
|
@UiEvent(doc = "A media recommendation card was added to the media carousel")
|
||||||
MEDIA_RECOMMENDATION_ADDED(1041),
|
MEDIA_RECOMMENDATION_ADDED(1041),
|
||||||
|
|
||||||
@UiEvent(doc = "A media recommendation card was removed from the media carousel")
|
@UiEvent(doc = "A media recommendation card was removed from the media carousel")
|
||||||
MEDIA_RECOMMENDATION_REMOVED(1042),
|
MEDIA_RECOMMENDATION_REMOVED(1042),
|
||||||
|
|
||||||
@UiEvent(doc = "An existing media control was made active as a recommendation")
|
@UiEvent(doc = "An existing media control was made active as a recommendation")
|
||||||
MEDIA_RECOMMENDATION_ACTIVATED(1043),
|
MEDIA_RECOMMENDATION_ACTIVATED(1043),
|
||||||
|
|
||||||
@UiEvent(doc = "User tapped on an item in a media recommendation card")
|
@UiEvent(doc = "User tapped on an item in a media recommendation card")
|
||||||
MEDIA_RECOMMENDATION_ITEM_TAP(1044),
|
MEDIA_RECOMMENDATION_ITEM_TAP(1044),
|
||||||
|
|
||||||
@UiEvent(doc = "User tapped on a media recommendation card")
|
@UiEvent(doc = "User tapped on a media recommendation card")
|
||||||
MEDIA_RECOMMENDATION_CARD_TAP(1045),
|
MEDIA_RECOMMENDATION_CARD_TAP(1045),
|
||||||
|
|
||||||
@UiEvent(doc = "User opened the broadcast dialog from a media control")
|
@UiEvent(doc = "User opened the broadcast dialog from a media control")
|
||||||
MEDIA_OPEN_BROADCAST_DIALOG(1079);
|
MEDIA_OPEN_BROADCAST_DIALOG(1079);
|
||||||
|
|
||||||
override fun getId() = metricId
|
override fun getId() = metricId
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
package com.android.systemui.media.controls
|
||||||
|
|
||||||
import com.android.internal.logging.InstanceId
|
import com.android.internal.logging.InstanceId
|
||||||
@@ -5,24 +21,26 @@ import com.android.systemui.media.controls.models.player.MediaData
|
|||||||
|
|
||||||
class MediaTestUtils {
|
class MediaTestUtils {
|
||||||
companion object {
|
companion object {
|
||||||
val emptyMediaData = MediaData(
|
val emptyMediaData =
|
||||||
userId = 0,
|
MediaData(
|
||||||
initialized = true,
|
userId = 0,
|
||||||
app = null,
|
initialized = true,
|
||||||
appIcon = null,
|
app = null,
|
||||||
artist = null,
|
appIcon = null,
|
||||||
song = null,
|
artist = null,
|
||||||
artwork = null,
|
song = null,
|
||||||
actions = emptyList(),
|
artwork = null,
|
||||||
actionsToShowInCompact = emptyList(),
|
actions = emptyList(),
|
||||||
packageName = "",
|
actionsToShowInCompact = emptyList(),
|
||||||
token = null,
|
packageName = "",
|
||||||
clickIntent = null,
|
token = null,
|
||||||
device = null,
|
clickIntent = null,
|
||||||
active = true,
|
device = null,
|
||||||
resumeAction = null,
|
active = true,
|
||||||
isPlaying = false,
|
resumeAction = null,
|
||||||
instanceId = InstanceId.fakeInstanceId(-1),
|
isPlaying = false,
|
||||||
appUid = -1)
|
instanceId = InstanceId.fakeInstanceId(-1),
|
||||||
|
appUid = -1
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
package com.android.systemui.media.controls.models.player
|
||||||
|
|
||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
@@ -21,4 +37,4 @@ class MediaViewHolderTest : SysuiTestCase() {
|
|||||||
|
|
||||||
MediaViewHolder.create(inflater, parent)
|
MediaViewHolder.create(inflater, parent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ import org.junit.Test
|
|||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.junit.MockitoJUnit
|
|
||||||
import org.mockito.Mockito.`when` as whenever
|
import org.mockito.Mockito.`when` as whenever
|
||||||
|
import org.mockito.junit.MockitoJUnit
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@RunWith(AndroidTestingRunner::class)
|
@RunWith(AndroidTestingRunner::class)
|
||||||
@@ -57,10 +57,14 @@ class SeekBarObserverTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
context.orCreateTestableResources
|
context.orCreateTestableResources.addOverride(
|
||||||
.addOverride(R.dimen.qs_media_enabled_seekbar_height, enabledHeight)
|
R.dimen.qs_media_enabled_seekbar_height,
|
||||||
context.orCreateTestableResources
|
enabledHeight
|
||||||
.addOverride(R.dimen.qs_media_disabled_seekbar_height, disabledHeight)
|
)
|
||||||
|
context.orCreateTestableResources.addOverride(
|
||||||
|
R.dimen.qs_media_disabled_seekbar_height,
|
||||||
|
disabledHeight
|
||||||
|
)
|
||||||
|
|
||||||
seekBarView = SeekBar(context)
|
seekBarView = SeekBar(context)
|
||||||
seekBarView.progressDrawable = mockSquigglyProgress
|
seekBarView.progressDrawable = mockSquigglyProgress
|
||||||
@@ -70,11 +74,12 @@ class SeekBarObserverTest : SysuiTestCase() {
|
|||||||
whenever(mockHolder.scrubbingElapsedTimeView).thenReturn(scrubbingElapsedTimeView)
|
whenever(mockHolder.scrubbingElapsedTimeView).thenReturn(scrubbingElapsedTimeView)
|
||||||
whenever(mockHolder.scrubbingTotalTimeView).thenReturn(scrubbingTotalTimeView)
|
whenever(mockHolder.scrubbingTotalTimeView).thenReturn(scrubbingTotalTimeView)
|
||||||
|
|
||||||
observer = object : SeekBarObserver(mockHolder) {
|
observer =
|
||||||
override fun buildResetAnimator(targetTime: Int): Animator {
|
object : SeekBarObserver(mockHolder) {
|
||||||
return mockSeekbarAnimator
|
override fun buildResetAnimator(targetTime: Int): Animator {
|
||||||
|
return mockSeekbarAnimator
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -57,17 +57,18 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
|
|
||||||
private lateinit var viewModel: SeekBarViewModel
|
private lateinit var viewModel: SeekBarViewModel
|
||||||
private lateinit var fakeExecutor: FakeExecutor
|
private lateinit var fakeExecutor: FakeExecutor
|
||||||
private val taskExecutor: TaskExecutor = object : TaskExecutor() {
|
private val taskExecutor: TaskExecutor =
|
||||||
override fun executeOnDiskIO(runnable: Runnable) {
|
object : TaskExecutor() {
|
||||||
runnable.run()
|
override fun executeOnDiskIO(runnable: Runnable) {
|
||||||
|
runnable.run()
|
||||||
|
}
|
||||||
|
override fun postToMainThread(runnable: Runnable) {
|
||||||
|
runnable.run()
|
||||||
|
}
|
||||||
|
override fun isMainThread(): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
override fun postToMainThread(runnable: Runnable) {
|
|
||||||
runnable.run()
|
|
||||||
}
|
|
||||||
override fun isMainThread(): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Mock private lateinit var mockController: MediaController
|
@Mock private lateinit var mockController: MediaController
|
||||||
@Mock private lateinit var mockTransport: MediaController.TransportControls
|
@Mock private lateinit var mockTransport: MediaController.TransportControls
|
||||||
@Mock private lateinit var falsingManager: FalsingManager
|
@Mock private lateinit var falsingManager: FalsingManager
|
||||||
@@ -81,7 +82,7 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
fun setUp() {
|
fun setUp() {
|
||||||
fakeExecutor = FakeExecutor(FakeSystemClock())
|
fakeExecutor = FakeExecutor(FakeSystemClock())
|
||||||
viewModel = SeekBarViewModel(FakeRepeatableExecutor(fakeExecutor), falsingManager)
|
viewModel = SeekBarViewModel(FakeRepeatableExecutor(fakeExecutor), falsingManager)
|
||||||
viewModel.logSeek = { }
|
viewModel.logSeek = {}
|
||||||
whenever(mockController.sessionToken).thenReturn(token1)
|
whenever(mockController.sessionToken).thenReturn(token1)
|
||||||
whenever(mockBar.context).thenReturn(context)
|
whenever(mockBar.context).thenReturn(context)
|
||||||
|
|
||||||
@@ -135,16 +136,18 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
fun updateDurationWithPlayback() {
|
fun updateDurationWithPlayback() {
|
||||||
// GIVEN that the duration is contained within the metadata
|
// GIVEN that the duration is contained within the metadata
|
||||||
val duration = 12000L
|
val duration = 12000L
|
||||||
val metadata = MediaMetadata.Builder().run {
|
val metadata =
|
||||||
putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
|
MediaMetadata.Builder().run {
|
||||||
build()
|
putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getMetadata()).thenReturn(metadata)
|
whenever(mockController.getMetadata()).thenReturn(metadata)
|
||||||
// AND a valid playback state (ie. media session is not destroyed)
|
// AND a valid playback state (ie. media session is not destroyed)
|
||||||
val state = PlaybackState.Builder().run {
|
val state =
|
||||||
setState(PlaybackState.STATE_PLAYING, 200L, 1f)
|
PlaybackState.Builder().run {
|
||||||
build()
|
setState(PlaybackState.STATE_PLAYING, 200L, 1f)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getPlaybackState()).thenReturn(state)
|
whenever(mockController.getPlaybackState()).thenReturn(state)
|
||||||
// WHEN the controller is updated
|
// WHEN the controller is updated
|
||||||
viewModel.updateController(mockController)
|
viewModel.updateController(mockController)
|
||||||
@@ -158,10 +161,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
fun updateDurationWithoutPlayback() {
|
fun updateDurationWithoutPlayback() {
|
||||||
// GIVEN that the duration is contained within the metadata
|
// GIVEN that the duration is contained within the metadata
|
||||||
val duration = 12000L
|
val duration = 12000L
|
||||||
val metadata = MediaMetadata.Builder().run {
|
val metadata =
|
||||||
putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
|
MediaMetadata.Builder().run {
|
||||||
build()
|
putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getMetadata()).thenReturn(metadata)
|
whenever(mockController.getMetadata()).thenReturn(metadata)
|
||||||
// WHEN the controller is updated
|
// WHEN the controller is updated
|
||||||
viewModel.updateController(mockController)
|
viewModel.updateController(mockController)
|
||||||
@@ -174,16 +178,18 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
fun updateDurationNegative() {
|
fun updateDurationNegative() {
|
||||||
// GIVEN that the duration is negative
|
// GIVEN that the duration is negative
|
||||||
val duration = -1L
|
val duration = -1L
|
||||||
val metadata = MediaMetadata.Builder().run {
|
val metadata =
|
||||||
putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
|
MediaMetadata.Builder().run {
|
||||||
build()
|
putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getMetadata()).thenReturn(metadata)
|
whenever(mockController.getMetadata()).thenReturn(metadata)
|
||||||
// AND a valid playback state (ie. media session is not destroyed)
|
// AND a valid playback state (ie. media session is not destroyed)
|
||||||
val state = PlaybackState.Builder().run {
|
val state =
|
||||||
setState(PlaybackState.STATE_PLAYING, 200L, 1f)
|
PlaybackState.Builder().run {
|
||||||
build()
|
setState(PlaybackState.STATE_PLAYING, 200L, 1f)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getPlaybackState()).thenReturn(state)
|
whenever(mockController.getPlaybackState()).thenReturn(state)
|
||||||
// WHEN the controller is updated
|
// WHEN the controller is updated
|
||||||
viewModel.updateController(mockController)
|
viewModel.updateController(mockController)
|
||||||
@@ -195,16 +201,18 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
fun updateDurationZero() {
|
fun updateDurationZero() {
|
||||||
// GIVEN that the duration is zero
|
// GIVEN that the duration is zero
|
||||||
val duration = 0L
|
val duration = 0L
|
||||||
val metadata = MediaMetadata.Builder().run {
|
val metadata =
|
||||||
putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
|
MediaMetadata.Builder().run {
|
||||||
build()
|
putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getMetadata()).thenReturn(metadata)
|
whenever(mockController.getMetadata()).thenReturn(metadata)
|
||||||
// AND a valid playback state (ie. media session is not destroyed)
|
// AND a valid playback state (ie. media session is not destroyed)
|
||||||
val state = PlaybackState.Builder().run {
|
val state =
|
||||||
setState(PlaybackState.STATE_PLAYING, 200L, 1f)
|
PlaybackState.Builder().run {
|
||||||
build()
|
setState(PlaybackState.STATE_PLAYING, 200L, 1f)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getPlaybackState()).thenReturn(state)
|
whenever(mockController.getPlaybackState()).thenReturn(state)
|
||||||
// WHEN the controller is updated
|
// WHEN the controller is updated
|
||||||
viewModel.updateController(mockController)
|
viewModel.updateController(mockController)
|
||||||
@@ -218,10 +226,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
// GIVEN that the metadata is null
|
// GIVEN that the metadata is null
|
||||||
whenever(mockController.getMetadata()).thenReturn(null)
|
whenever(mockController.getMetadata()).thenReturn(null)
|
||||||
// AND a valid playback state (ie. media session is not destroyed)
|
// AND a valid playback state (ie. media session is not destroyed)
|
||||||
val state = PlaybackState.Builder().run {
|
val state =
|
||||||
setState(PlaybackState.STATE_PLAYING, 200L, 1f)
|
PlaybackState.Builder().run {
|
||||||
build()
|
setState(PlaybackState.STATE_PLAYING, 200L, 1f)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getPlaybackState()).thenReturn(state)
|
whenever(mockController.getPlaybackState()).thenReturn(state)
|
||||||
// WHEN the controller is updated
|
// WHEN the controller is updated
|
||||||
viewModel.updateController(mockController)
|
viewModel.updateController(mockController)
|
||||||
@@ -233,10 +242,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
fun updateElapsedTime() {
|
fun updateElapsedTime() {
|
||||||
// GIVEN that the PlaybackState contains the current position
|
// GIVEN that the PlaybackState contains the current position
|
||||||
val position = 200L
|
val position = 200L
|
||||||
val state = PlaybackState.Builder().run {
|
val state =
|
||||||
setState(PlaybackState.STATE_PLAYING, position, 1f)
|
PlaybackState.Builder().run {
|
||||||
build()
|
setState(PlaybackState.STATE_PLAYING, position, 1f)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getPlaybackState()).thenReturn(state)
|
whenever(mockController.getPlaybackState()).thenReturn(state)
|
||||||
// WHEN the controller is updated
|
// WHEN the controller is updated
|
||||||
viewModel.updateController(mockController)
|
viewModel.updateController(mockController)
|
||||||
@@ -248,10 +258,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
@Ignore
|
@Ignore
|
||||||
fun updateSeekAvailable() {
|
fun updateSeekAvailable() {
|
||||||
// GIVEN that seek is included in actions
|
// GIVEN that seek is included in actions
|
||||||
val state = PlaybackState.Builder().run {
|
val state =
|
||||||
setActions(PlaybackState.ACTION_SEEK_TO)
|
PlaybackState.Builder().run {
|
||||||
build()
|
setActions(PlaybackState.ACTION_SEEK_TO)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getPlaybackState()).thenReturn(state)
|
whenever(mockController.getPlaybackState()).thenReturn(state)
|
||||||
// WHEN the controller is updated
|
// WHEN the controller is updated
|
||||||
viewModel.updateController(mockController)
|
viewModel.updateController(mockController)
|
||||||
@@ -263,10 +274,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
@Ignore
|
@Ignore
|
||||||
fun updateSeekNotAvailable() {
|
fun updateSeekNotAvailable() {
|
||||||
// GIVEN that seek is not included in actions
|
// GIVEN that seek is not included in actions
|
||||||
val state = PlaybackState.Builder().run {
|
val state =
|
||||||
setActions(PlaybackState.ACTION_PLAY)
|
PlaybackState.Builder().run {
|
||||||
build()
|
setActions(PlaybackState.ACTION_PLAY)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getPlaybackState()).thenReturn(state)
|
whenever(mockController.getPlaybackState()).thenReturn(state)
|
||||||
// WHEN the controller is updated
|
// WHEN the controller is updated
|
||||||
viewModel.updateController(mockController)
|
viewModel.updateController(mockController)
|
||||||
@@ -318,9 +330,7 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
@Ignore
|
@Ignore
|
||||||
fun onSeekProgressWithSeekStarting() {
|
fun onSeekProgressWithSeekStarting() {
|
||||||
val pos = 42L
|
val pos = 42L
|
||||||
with(viewModel) {
|
with(viewModel) { onSeekProgress(pos) }
|
||||||
onSeekProgress(pos)
|
|
||||||
}
|
|
||||||
fakeExecutor.runAllReady()
|
fakeExecutor.runAllReady()
|
||||||
// THEN then elapsed time should not be updated
|
// THEN then elapsed time should not be updated
|
||||||
assertThat(viewModel.progress.value!!.elapsedTime).isNull()
|
assertThat(viewModel.progress.value!!.elapsedTime).isNull()
|
||||||
@@ -329,11 +339,12 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun seekStarted_listenerNotified() {
|
fun seekStarted_listenerNotified() {
|
||||||
var isScrubbing: Boolean? = null
|
var isScrubbing: Boolean? = null
|
||||||
val listener = object : SeekBarViewModel.ScrubbingChangeListener {
|
val listener =
|
||||||
override fun onScrubbingChanged(scrubbing: Boolean) {
|
object : SeekBarViewModel.ScrubbingChangeListener {
|
||||||
isScrubbing = scrubbing
|
override fun onScrubbingChanged(scrubbing: Boolean) {
|
||||||
|
isScrubbing = scrubbing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
viewModel.setScrubbingChangeListener(listener)
|
viewModel.setScrubbingChangeListener(listener)
|
||||||
|
|
||||||
viewModel.onSeekStarting()
|
viewModel.onSeekStarting()
|
||||||
@@ -345,11 +356,12 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun seekEnded_listenerNotified() {
|
fun seekEnded_listenerNotified() {
|
||||||
var isScrubbing: Boolean? = null
|
var isScrubbing: Boolean? = null
|
||||||
val listener = object : SeekBarViewModel.ScrubbingChangeListener {
|
val listener =
|
||||||
override fun onScrubbingChanged(scrubbing: Boolean) {
|
object : SeekBarViewModel.ScrubbingChangeListener {
|
||||||
isScrubbing = scrubbing
|
override fun onScrubbingChanged(scrubbing: Boolean) {
|
||||||
|
isScrubbing = scrubbing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
viewModel.setScrubbingChangeListener(listener)
|
viewModel.setScrubbingChangeListener(listener)
|
||||||
|
|
||||||
// Start seeking
|
// Start seeking
|
||||||
@@ -385,9 +397,7 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
val bar = SeekBar(context)
|
val bar = SeekBar(context)
|
||||||
|
|
||||||
// WHEN we get an onProgressChanged event without an onStartTrackingTouch event
|
// WHEN we get an onProgressChanged event without an onStartTrackingTouch event
|
||||||
with(viewModel.seekBarListener) {
|
with(viewModel.seekBarListener) { onProgressChanged(bar, pos, true) }
|
||||||
onProgressChanged(bar, pos, true)
|
|
||||||
}
|
|
||||||
fakeExecutor.runAllReady()
|
fakeExecutor.runAllReady()
|
||||||
|
|
||||||
// THEN we immediately update the transport
|
// THEN we immediately update the transport
|
||||||
@@ -412,9 +422,7 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
viewModel.updateController(mockController)
|
viewModel.updateController(mockController)
|
||||||
// WHEN user starts dragging the seek bar
|
// WHEN user starts dragging the seek bar
|
||||||
val pos = 42
|
val pos = 42
|
||||||
val bar = SeekBar(context).apply {
|
val bar = SeekBar(context).apply { progress = pos }
|
||||||
progress = pos
|
|
||||||
}
|
|
||||||
viewModel.seekBarListener.onStartTrackingTouch(bar)
|
viewModel.seekBarListener.onStartTrackingTouch(bar)
|
||||||
fakeExecutor.runAllReady()
|
fakeExecutor.runAllReady()
|
||||||
// THEN transport controls should be used
|
// THEN transport controls should be used
|
||||||
@@ -427,9 +435,7 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
viewModel.updateController(mockController)
|
viewModel.updateController(mockController)
|
||||||
// WHEN user ends drag
|
// WHEN user ends drag
|
||||||
val pos = 42
|
val pos = 42
|
||||||
val bar = SeekBar(context).apply {
|
val bar = SeekBar(context).apply { progress = pos }
|
||||||
progress = pos
|
|
||||||
}
|
|
||||||
viewModel.seekBarListener.onStopTrackingTouch(bar)
|
viewModel.seekBarListener.onStopTrackingTouch(bar)
|
||||||
fakeExecutor.runAllReady()
|
fakeExecutor.runAllReady()
|
||||||
// THEN transport controls should be used
|
// THEN transport controls should be used
|
||||||
@@ -443,9 +449,7 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
// WHEN user starts dragging the seek bar
|
// WHEN user starts dragging the seek bar
|
||||||
val pos = 42
|
val pos = 42
|
||||||
val progPos = 84
|
val progPos = 84
|
||||||
val bar = SeekBar(context).apply {
|
val bar = SeekBar(context).apply { progress = pos }
|
||||||
progress = pos
|
|
||||||
}
|
|
||||||
with(viewModel.seekBarListener) {
|
with(viewModel.seekBarListener) {
|
||||||
onStartTrackingTouch(bar)
|
onStartTrackingTouch(bar)
|
||||||
onProgressChanged(bar, progPos, true)
|
onProgressChanged(bar, progPos, true)
|
||||||
@@ -478,10 +482,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun queuePollTaskWhenPlaying() {
|
fun queuePollTaskWhenPlaying() {
|
||||||
// GIVEN that the track is playing
|
// GIVEN that the track is playing
|
||||||
val state = PlaybackState.Builder().run {
|
val state =
|
||||||
setState(PlaybackState.STATE_PLAYING, 100L, 1f)
|
PlaybackState.Builder().run {
|
||||||
build()
|
setState(PlaybackState.STATE_PLAYING, 100L, 1f)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getPlaybackState()).thenReturn(state)
|
whenever(mockController.getPlaybackState()).thenReturn(state)
|
||||||
// WHEN the controller is updated
|
// WHEN the controller is updated
|
||||||
viewModel.updateController(mockController)
|
viewModel.updateController(mockController)
|
||||||
@@ -492,10 +497,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun noQueuePollTaskWhenStopped() {
|
fun noQueuePollTaskWhenStopped() {
|
||||||
// GIVEN that the playback state is stopped
|
// GIVEN that the playback state is stopped
|
||||||
val state = PlaybackState.Builder().run {
|
val state =
|
||||||
setState(PlaybackState.STATE_STOPPED, 200L, 1f)
|
PlaybackState.Builder().run {
|
||||||
build()
|
setState(PlaybackState.STATE_STOPPED, 200L, 1f)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getPlaybackState()).thenReturn(state)
|
whenever(mockController.getPlaybackState()).thenReturn(state)
|
||||||
// WHEN updated
|
// WHEN updated
|
||||||
viewModel.updateController(mockController)
|
viewModel.updateController(mockController)
|
||||||
@@ -512,10 +518,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
runAllReady()
|
runAllReady()
|
||||||
}
|
}
|
||||||
// AND the playback state is playing
|
// AND the playback state is playing
|
||||||
val state = PlaybackState.Builder().run {
|
val state =
|
||||||
setState(PlaybackState.STATE_PLAYING, 200L, 1f)
|
PlaybackState.Builder().run {
|
||||||
build()
|
setState(PlaybackState.STATE_PLAYING, 200L, 1f)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getPlaybackState()).thenReturn(state)
|
whenever(mockController.getPlaybackState()).thenReturn(state)
|
||||||
// WHEN updated
|
// WHEN updated
|
||||||
viewModel.updateController(mockController)
|
viewModel.updateController(mockController)
|
||||||
@@ -532,10 +539,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
runAllReady()
|
runAllReady()
|
||||||
}
|
}
|
||||||
// AND the playback state is playing
|
// AND the playback state is playing
|
||||||
val state = PlaybackState.Builder().run {
|
val state =
|
||||||
setState(PlaybackState.STATE_PLAYING, 200L, 1f)
|
PlaybackState.Builder().run {
|
||||||
build()
|
setState(PlaybackState.STATE_PLAYING, 200L, 1f)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getPlaybackState()).thenReturn(state)
|
whenever(mockController.getPlaybackState()).thenReturn(state)
|
||||||
// WHEN updated
|
// WHEN updated
|
||||||
viewModel.updateController(mockController)
|
viewModel.updateController(mockController)
|
||||||
@@ -546,10 +554,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun pollTaskQueuesAnotherPollTaskWhenPlaying() {
|
fun pollTaskQueuesAnotherPollTaskWhenPlaying() {
|
||||||
// GIVEN that the track is playing
|
// GIVEN that the track is playing
|
||||||
val state = PlaybackState.Builder().run {
|
val state =
|
||||||
setState(PlaybackState.STATE_PLAYING, 100L, 1f)
|
PlaybackState.Builder().run {
|
||||||
build()
|
setState(PlaybackState.STATE_PLAYING, 100L, 1f)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getPlaybackState()).thenReturn(state)
|
whenever(mockController.getPlaybackState()).thenReturn(state)
|
||||||
viewModel.updateController(mockController)
|
viewModel.updateController(mockController)
|
||||||
// WHEN the next task runs
|
// WHEN the next task runs
|
||||||
@@ -566,10 +575,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
// GIVEN listening
|
// GIVEN listening
|
||||||
viewModel.listening = true
|
viewModel.listening = true
|
||||||
// AND the playback state is playing
|
// AND the playback state is playing
|
||||||
val state = PlaybackState.Builder().run {
|
val state =
|
||||||
setState(PlaybackState.STATE_PLAYING, 200L, 1f)
|
PlaybackState.Builder().run {
|
||||||
build()
|
setState(PlaybackState.STATE_PLAYING, 200L, 1f)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getPlaybackState()).thenReturn(state)
|
whenever(mockController.getPlaybackState()).thenReturn(state)
|
||||||
viewModel.updateController(mockController)
|
viewModel.updateController(mockController)
|
||||||
with(fakeExecutor) {
|
with(fakeExecutor) {
|
||||||
@@ -592,10 +602,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
// GIVEN listening
|
// GIVEN listening
|
||||||
viewModel.listening = true
|
viewModel.listening = true
|
||||||
// AND the playback state is playing
|
// AND the playback state is playing
|
||||||
val state = PlaybackState.Builder().run {
|
val state =
|
||||||
setState(PlaybackState.STATE_PLAYING, 200L, 1f)
|
PlaybackState.Builder().run {
|
||||||
build()
|
setState(PlaybackState.STATE_PLAYING, 200L, 1f)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getPlaybackState()).thenReturn(state)
|
whenever(mockController.getPlaybackState()).thenReturn(state)
|
||||||
viewModel.updateController(mockController)
|
viewModel.updateController(mockController)
|
||||||
with(fakeExecutor) {
|
with(fakeExecutor) {
|
||||||
@@ -621,10 +632,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
// GIVEN listening
|
// GIVEN listening
|
||||||
viewModel.listening = true
|
viewModel.listening = true
|
||||||
// AND the playback state is playing
|
// AND the playback state is playing
|
||||||
val state = PlaybackState.Builder().run {
|
val state =
|
||||||
setState(PlaybackState.STATE_PLAYING, 200L, 1f)
|
PlaybackState.Builder().run {
|
||||||
build()
|
setState(PlaybackState.STATE_PLAYING, 200L, 1f)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getPlaybackState()).thenReturn(state)
|
whenever(mockController.getPlaybackState()).thenReturn(state)
|
||||||
viewModel.updateController(mockController)
|
viewModel.updateController(mockController)
|
||||||
with(fakeExecutor) {
|
with(fakeExecutor) {
|
||||||
@@ -654,10 +666,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
runAllReady()
|
runAllReady()
|
||||||
}
|
}
|
||||||
// AND the playback state is playing
|
// AND the playback state is playing
|
||||||
val state = PlaybackState.Builder().run {
|
val state =
|
||||||
setState(PlaybackState.STATE_STOPPED, 200L, 1f)
|
PlaybackState.Builder().run {
|
||||||
build()
|
setState(PlaybackState.STATE_STOPPED, 200L, 1f)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getPlaybackState()).thenReturn(state)
|
whenever(mockController.getPlaybackState()).thenReturn(state)
|
||||||
viewModel.updateController(mockController)
|
viewModel.updateController(mockController)
|
||||||
// WHEN start listening
|
// WHEN start listening
|
||||||
@@ -673,10 +686,11 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
verify(mockController).registerCallback(captor.capture())
|
verify(mockController).registerCallback(captor.capture())
|
||||||
val callback = captor.value
|
val callback = captor.value
|
||||||
// WHEN the callback receives an new state
|
// WHEN the callback receives an new state
|
||||||
val state = PlaybackState.Builder().run {
|
val state =
|
||||||
setState(PlaybackState.STATE_PLAYING, 100L, 1f)
|
PlaybackState.Builder().run {
|
||||||
build()
|
setState(PlaybackState.STATE_PLAYING, 100L, 1f)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
callback.onPlaybackStateChanged(state)
|
callback.onPlaybackStateChanged(state)
|
||||||
with(fakeExecutor) {
|
with(fakeExecutor) {
|
||||||
advanceClockToNext()
|
advanceClockToNext()
|
||||||
@@ -690,16 +704,18 @@ public class SeekBarViewModelTest : SysuiTestCase() {
|
|||||||
@Ignore
|
@Ignore
|
||||||
fun clearSeekBar() {
|
fun clearSeekBar() {
|
||||||
// GIVEN that the duration is contained within the metadata
|
// GIVEN that the duration is contained within the metadata
|
||||||
val metadata = MediaMetadata.Builder().run {
|
val metadata =
|
||||||
putLong(MediaMetadata.METADATA_KEY_DURATION, 12000L)
|
MediaMetadata.Builder().run {
|
||||||
build()
|
putLong(MediaMetadata.METADATA_KEY_DURATION, 12000L)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getMetadata()).thenReturn(metadata)
|
whenever(mockController.getMetadata()).thenReturn(metadata)
|
||||||
// AND a valid playback state (ie. media session is not destroyed)
|
// AND a valid playback state (ie. media session is not destroyed)
|
||||||
val state = PlaybackState.Builder().run {
|
val state =
|
||||||
setState(PlaybackState.STATE_PLAYING, 200L, 1f)
|
PlaybackState.Builder().run {
|
||||||
build()
|
setState(PlaybackState.STATE_PLAYING, 200L, 1f)
|
||||||
}
|
build()
|
||||||
|
}
|
||||||
whenever(mockController.getPlaybackState()).thenReturn(state)
|
whenever(mockController.getPlaybackState()).thenReturn(state)
|
||||||
// AND the controller has been updated
|
// AND the controller has been updated
|
||||||
viewModel.updateController(mockController)
|
viewModel.updateController(mockController)
|
||||||
|
|||||||
@@ -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
|
package com.android.systemui.media.controls.models.recommendation
|
||||||
|
|
||||||
import android.app.smartspace.SmartspaceAction
|
import android.app.smartspace.SmartspaceAction
|
||||||
@@ -36,11 +52,11 @@ class SmartspaceMediaDataTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun isValid_tooFewRecs_returnsFalse() {
|
fun isValid_tooFewRecs_returnsFalse() {
|
||||||
val data = DEFAULT_DATA.copy(
|
val data =
|
||||||
recommendations = listOf(
|
DEFAULT_DATA.copy(
|
||||||
SmartspaceAction.Builder("id", "title").setIcon(icon).build()
|
recommendations =
|
||||||
|
listOf(SmartspaceAction.Builder("id", "title").setIcon(icon).build())
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
assertThat(data.isValid()).isFalse()
|
assertThat(data.isValid()).isFalse()
|
||||||
}
|
}
|
||||||
@@ -50,14 +66,10 @@ class SmartspaceMediaDataTest : SysuiTestCase() {
|
|||||||
val recommendations = mutableListOf<SmartspaceAction>()
|
val recommendations = mutableListOf<SmartspaceAction>()
|
||||||
// Add one fewer recommendation w/ icon than the number required
|
// Add one fewer recommendation w/ icon than the number required
|
||||||
for (i in 1 until NUM_REQUIRED_RECOMMENDATIONS) {
|
for (i in 1 until NUM_REQUIRED_RECOMMENDATIONS) {
|
||||||
recommendations.add(
|
recommendations.add(SmartspaceAction.Builder("id", "title").setIcon(icon).build())
|
||||||
SmartspaceAction.Builder("id", "title").setIcon(icon).build()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
for (i in 1 until 3) {
|
for (i in 1 until 3) {
|
||||||
recommendations.add(
|
recommendations.add(SmartspaceAction.Builder("id", "title").setIcon(null).build())
|
||||||
SmartspaceAction.Builder("id", "title").setIcon(null).build()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val data = DEFAULT_DATA.copy(recommendations = recommendations)
|
val data = DEFAULT_DATA.copy(recommendations = recommendations)
|
||||||
@@ -70,9 +82,7 @@ class SmartspaceMediaDataTest : SysuiTestCase() {
|
|||||||
val recommendations = mutableListOf<SmartspaceAction>()
|
val recommendations = mutableListOf<SmartspaceAction>()
|
||||||
// Add the number of required recommendations
|
// Add the number of required recommendations
|
||||||
for (i in 0 until NUM_REQUIRED_RECOMMENDATIONS) {
|
for (i in 0 until NUM_REQUIRED_RECOMMENDATIONS) {
|
||||||
recommendations.add(
|
recommendations.add(SmartspaceAction.Builder("id", "title").setIcon(icon).build())
|
||||||
SmartspaceAction.Builder("id", "title").setIcon(icon).build()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val data = DEFAULT_DATA.copy(recommendations = recommendations)
|
val data = DEFAULT_DATA.copy(recommendations = recommendations)
|
||||||
@@ -85,9 +95,7 @@ class SmartspaceMediaDataTest : SysuiTestCase() {
|
|||||||
val recommendations = mutableListOf<SmartspaceAction>()
|
val recommendations = mutableListOf<SmartspaceAction>()
|
||||||
// Add more than enough recommendations
|
// Add more than enough recommendations
|
||||||
for (i in 0 until NUM_REQUIRED_RECOMMENDATIONS + 3) {
|
for (i in 0 until NUM_REQUIRED_RECOMMENDATIONS + 3) {
|
||||||
recommendations.add(
|
recommendations.add(SmartspaceAction.Builder("id", "title").setIcon(icon).build())
|
||||||
SmartspaceAction.Builder("id", "title").setIcon(icon).build()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val data = DEFAULT_DATA.copy(recommendations = recommendations)
|
val data = DEFAULT_DATA.copy(recommendations = recommendations)
|
||||||
@@ -96,13 +104,14 @@ class SmartspaceMediaDataTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val DEFAULT_DATA = SmartspaceMediaData(
|
private val DEFAULT_DATA =
|
||||||
targetId = "INVALID",
|
SmartspaceMediaData(
|
||||||
isActive = false,
|
targetId = "INVALID",
|
||||||
packageName = "INVALID",
|
isActive = false,
|
||||||
cardAction = null,
|
packageName = "INVALID",
|
||||||
recommendations = emptyList(),
|
cardAction = null,
|
||||||
dismissIntent = null,
|
recommendations = emptyList(),
|
||||||
headphoneConnectionTimeMillis = 0,
|
dismissIntent = null,
|
||||||
instanceId = InstanceId.fakeInstanceId(-1)
|
headphoneConnectionTimeMillis = 0,
|
||||||
)
|
instanceId = InstanceId.fakeInstanceId(-1)
|
||||||
|
)
|
||||||
|
|||||||
@@ -63,24 +63,15 @@ private val SMARTSPACE_INSTANCE_ID = InstanceId.fakeInstanceId(456)!!
|
|||||||
@TestableLooper.RunWithLooper
|
@TestableLooper.RunWithLooper
|
||||||
class MediaDataFilterTest : SysuiTestCase() {
|
class MediaDataFilterTest : SysuiTestCase() {
|
||||||
|
|
||||||
@Mock
|
@Mock private lateinit var listener: MediaDataManager.Listener
|
||||||
private lateinit var listener: MediaDataManager.Listener
|
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
|
||||||
@Mock
|
@Mock private lateinit var broadcastSender: BroadcastSender
|
||||||
private lateinit var broadcastDispatcher: BroadcastDispatcher
|
@Mock private lateinit var mediaDataManager: MediaDataManager
|
||||||
@Mock
|
@Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
|
||||||
private lateinit var broadcastSender: BroadcastSender
|
@Mock private lateinit var executor: Executor
|
||||||
@Mock
|
@Mock private lateinit var smartspaceData: SmartspaceMediaData
|
||||||
private lateinit var mediaDataManager: MediaDataManager
|
@Mock private lateinit var smartspaceMediaRecommendationItem: SmartspaceAction
|
||||||
@Mock
|
@Mock private lateinit var logger: MediaUiEventLogger
|
||||||
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 mediaDataFilter: MediaDataFilter
|
||||||
private lateinit var dataMain: MediaData
|
private lateinit var dataMain: MediaData
|
||||||
@@ -91,14 +82,16 @@ class MediaDataFilterTest : SysuiTestCase() {
|
|||||||
fun setup() {
|
fun setup() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
MediaPlayerData.clear()
|
MediaPlayerData.clear()
|
||||||
mediaDataFilter = MediaDataFilter(
|
mediaDataFilter =
|
||||||
context,
|
MediaDataFilter(
|
||||||
broadcastDispatcher,
|
context,
|
||||||
broadcastSender,
|
broadcastDispatcher,
|
||||||
lockscreenUserManager,
|
broadcastSender,
|
||||||
executor,
|
lockscreenUserManager,
|
||||||
clock,
|
executor,
|
||||||
logger)
|
clock,
|
||||||
|
logger
|
||||||
|
)
|
||||||
mediaDataFilter.mediaDataManager = mediaDataManager
|
mediaDataFilter.mediaDataManager = mediaDataManager
|
||||||
mediaDataFilter.addListener(listener)
|
mediaDataFilter.addListener(listener)
|
||||||
|
|
||||||
@@ -106,11 +99,13 @@ class MediaDataFilterTest : SysuiTestCase() {
|
|||||||
setUser(USER_MAIN)
|
setUser(USER_MAIN)
|
||||||
|
|
||||||
// Set up test media data
|
// Set up test media data
|
||||||
dataMain = MediaTestUtils.emptyMediaData.copy(
|
dataMain =
|
||||||
|
MediaTestUtils.emptyMediaData.copy(
|
||||||
userId = USER_MAIN,
|
userId = USER_MAIN,
|
||||||
packageName = PACKAGE,
|
packageName = PACKAGE,
|
||||||
instanceId = INSTANCE_ID,
|
instanceId = INSTANCE_ID,
|
||||||
appUid = APP_UID)
|
appUid = APP_UID
|
||||||
|
)
|
||||||
dataGuest = dataMain.copy(userId = USER_GUEST)
|
dataGuest = dataMain.copy(userId = USER_GUEST)
|
||||||
|
|
||||||
`when`(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY)
|
`when`(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY)
|
||||||
@@ -118,8 +113,8 @@ class MediaDataFilterTest : SysuiTestCase() {
|
|||||||
`when`(smartspaceData.isValid()).thenReturn(true)
|
`when`(smartspaceData.isValid()).thenReturn(true)
|
||||||
`when`(smartspaceData.packageName).thenReturn(SMARTSPACE_PACKAGE)
|
`when`(smartspaceData.packageName).thenReturn(SMARTSPACE_PACKAGE)
|
||||||
`when`(smartspaceData.recommendations).thenReturn(listOf(smartspaceMediaRecommendationItem))
|
`when`(smartspaceData.recommendations).thenReturn(listOf(smartspaceMediaRecommendationItem))
|
||||||
`when`(smartspaceData.headphoneConnectionTimeMillis).thenReturn(
|
`when`(smartspaceData.headphoneConnectionTimeMillis)
|
||||||
clock.currentTimeMillis() - 100)
|
.thenReturn(clock.currentTimeMillis() - 100)
|
||||||
`when`(smartspaceData.instanceId).thenReturn(SMARTSPACE_INSTANCE_ID)
|
`when`(smartspaceData.instanceId).thenReturn(SMARTSPACE_INSTANCE_ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,8 +130,8 @@ class MediaDataFilterTest : SysuiTestCase() {
|
|||||||
mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
|
mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
|
||||||
|
|
||||||
// THEN we should tell the listener
|
// THEN we should tell the listener
|
||||||
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true),
|
verify(listener)
|
||||||
eq(0), eq(false))
|
.onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true), eq(0), eq(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -145,8 +140,8 @@ class MediaDataFilterTest : SysuiTestCase() {
|
|||||||
mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
|
mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
|
||||||
|
|
||||||
// THEN we should NOT tell the listener
|
// THEN we should NOT tell the listener
|
||||||
verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean(),
|
verify(listener, never())
|
||||||
anyInt(), anyBoolean())
|
.onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -192,12 +187,12 @@ class MediaDataFilterTest : SysuiTestCase() {
|
|||||||
setUser(USER_GUEST)
|
setUser(USER_GUEST)
|
||||||
|
|
||||||
// THEN we should add back the guest user media
|
// THEN we should add back the guest user media
|
||||||
verify(listener).onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest), eq(true),
|
verify(listener)
|
||||||
eq(0), eq(false))
|
.onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest), eq(true), eq(0), eq(false))
|
||||||
|
|
||||||
// but not the main user's
|
// but not the main user's
|
||||||
verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), eq(dataMain), anyBoolean(),
|
verify(listener, never())
|
||||||
anyInt(), anyBoolean())
|
.onMediaDataLoaded(eq(KEY), any(), eq(dataMain), anyBoolean(), anyInt(), anyBoolean())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -345,7 +340,7 @@ class MediaDataFilterTest : SysuiTestCase() {
|
|||||||
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
|
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
|
||||||
|
|
||||||
verify(listener)
|
verify(listener)
|
||||||
.onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
|
.onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
|
||||||
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
|
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
|
||||||
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
|
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
|
||||||
verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
|
verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
|
||||||
@@ -358,8 +353,8 @@ class MediaDataFilterTest : SysuiTestCase() {
|
|||||||
|
|
||||||
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
|
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
|
||||||
|
|
||||||
verify(listener, never()).onMediaDataLoaded(any(), any(), any(), anyBoolean(),
|
verify(listener, never())
|
||||||
anyInt(), anyBoolean())
|
.onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
|
||||||
verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
|
verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
|
||||||
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
|
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
|
||||||
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
|
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
|
||||||
@@ -375,7 +370,7 @@ class MediaDataFilterTest : SysuiTestCase() {
|
|||||||
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
|
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
|
||||||
|
|
||||||
verify(listener)
|
verify(listener)
|
||||||
.onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
|
.onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
|
||||||
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
|
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
|
||||||
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
|
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
|
||||||
verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
|
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
|
// WHEN we have media that was recently played, but not currently active
|
||||||
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
|
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
|
||||||
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
|
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
|
||||||
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true),
|
verify(listener)
|
||||||
eq(0), eq(false))
|
.onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
|
||||||
|
|
||||||
// AND we get a smartspace signal
|
// AND we get a smartspace signal
|
||||||
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
|
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
|
||||||
|
|
||||||
// THEN we should tell listeners to treat the media as not active instead
|
// THEN we should tell listeners to treat the media as not active instead
|
||||||
verify(listener, never()).onMediaDataLoaded(eq(KEY), eq(KEY), any(), anyBoolean(),
|
verify(listener, never())
|
||||||
anyInt(), anyBoolean())
|
.onMediaDataLoaded(eq(KEY), eq(KEY), any(), anyBoolean(), anyInt(), anyBoolean())
|
||||||
verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
|
verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
|
||||||
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
|
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse()
|
||||||
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
|
assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
|
||||||
@@ -428,16 +423,23 @@ class MediaDataFilterTest : SysuiTestCase() {
|
|||||||
// WHEN we have media that was recently played, but not currently active
|
// WHEN we have media that was recently played, but not currently active
|
||||||
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
|
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
|
||||||
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
|
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
|
||||||
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true),
|
verify(listener)
|
||||||
eq(0), eq(false))
|
.onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
|
||||||
|
|
||||||
// AND we get a smartspace signal
|
// AND we get a smartspace signal
|
||||||
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
|
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
|
||||||
|
|
||||||
// THEN we should tell listeners to treat the media as active instead
|
// THEN we should tell listeners to treat the media as active instead
|
||||||
val dataCurrentAndActive = dataCurrent.copy(active = true)
|
val dataCurrentAndActive = dataCurrent.copy(active = true)
|
||||||
verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true),
|
verify(listener)
|
||||||
eq(100), eq(true))
|
.onMediaDataLoaded(
|
||||||
|
eq(KEY),
|
||||||
|
eq(KEY),
|
||||||
|
eq(dataCurrentAndActive),
|
||||||
|
eq(true),
|
||||||
|
eq(100),
|
||||||
|
eq(true)
|
||||||
|
)
|
||||||
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
|
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
|
||||||
// Smartspace update shouldn't be propagated for the empty rec list.
|
// Smartspace update shouldn't be propagated for the empty rec list.
|
||||||
verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
|
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
|
// WHEN we have media that was recently played, but not currently active
|
||||||
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
|
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
|
||||||
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
|
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
|
||||||
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true),
|
verify(listener)
|
||||||
eq(0), eq(false))
|
.onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
|
||||||
|
|
||||||
// AND we get a smartspace signal
|
// AND we get a smartspace signal
|
||||||
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
|
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
|
||||||
|
|
||||||
// THEN we should tell listeners to treat the media as active instead
|
// THEN we should tell listeners to treat the media as active instead
|
||||||
val dataCurrentAndActive = dataCurrent.copy(active = true)
|
val dataCurrentAndActive = dataCurrent.copy(active = true)
|
||||||
verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true),
|
verify(listener)
|
||||||
eq(100), eq(true))
|
.onMediaDataLoaded(
|
||||||
|
eq(KEY),
|
||||||
|
eq(KEY),
|
||||||
|
eq(dataCurrentAndActive),
|
||||||
|
eq(true),
|
||||||
|
eq(100),
|
||||||
|
eq(true)
|
||||||
|
)
|
||||||
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
|
assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue()
|
||||||
// Smartspace update should also be propagated but not prioritized.
|
// Smartspace update should also be propagated but not prioritized.
|
||||||
verify(listener)
|
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).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
|
||||||
verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID))
|
verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID))
|
||||||
}
|
}
|
||||||
@@ -482,14 +491,21 @@ class MediaDataFilterTest : SysuiTestCase() {
|
|||||||
fun testOnSmartspaceMediaDataRemoved_usedMediaAndSmartspace_clearsBoth() {
|
fun testOnSmartspaceMediaDataRemoved_usedMediaAndSmartspace_clearsBoth() {
|
||||||
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
|
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
|
||||||
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
|
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
|
||||||
verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true),
|
verify(listener)
|
||||||
eq(0), eq(false))
|
.onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
|
||||||
|
|
||||||
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
|
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
|
||||||
|
|
||||||
val dataCurrentAndActive = dataCurrent.copy(active = true)
|
val dataCurrentAndActive = dataCurrent.copy(active = true)
|
||||||
verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true),
|
verify(listener)
|
||||||
eq(100), eq(true))
|
.onMediaDataLoaded(
|
||||||
|
eq(KEY),
|
||||||
|
eq(KEY),
|
||||||
|
eq(dataCurrentAndActive),
|
||||||
|
eq(true),
|
||||||
|
eq(100),
|
||||||
|
eq(true)
|
||||||
|
)
|
||||||
|
|
||||||
mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
|
mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -113,7 +113,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
|
|||||||
fakeFgExecutor = FakeExecutor(FakeSystemClock())
|
fakeFgExecutor = FakeExecutor(FakeSystemClock())
|
||||||
fakeBgExecutor = FakeExecutor(FakeSystemClock())
|
fakeBgExecutor = FakeExecutor(FakeSystemClock())
|
||||||
localBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager::class.java)
|
localBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager::class.java)
|
||||||
manager = MediaDeviceManager(
|
manager =
|
||||||
|
MediaDeviceManager(
|
||||||
context,
|
context,
|
||||||
controllerFactory,
|
controllerFactory,
|
||||||
lmmFactory,
|
lmmFactory,
|
||||||
@@ -124,7 +125,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
|
|||||||
fakeFgExecutor,
|
fakeFgExecutor,
|
||||||
fakeBgExecutor,
|
fakeBgExecutor,
|
||||||
dumpster
|
dumpster
|
||||||
)
|
)
|
||||||
manager.addListener(listener)
|
manager.addListener(listener)
|
||||||
|
|
||||||
// Configure mocks.
|
// Configure mocks.
|
||||||
@@ -138,11 +139,9 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
|
|||||||
// Create a media sesssion and notification for testing.
|
// Create a media sesssion and notification for testing.
|
||||||
session = MediaSession(context, SESSION_KEY)
|
session = MediaSession(context, SESSION_KEY)
|
||||||
|
|
||||||
mediaData = MediaTestUtils.emptyMediaData.copy(
|
mediaData =
|
||||||
packageName = PACKAGE,
|
MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, token = session.sessionToken)
|
||||||
token = session.sessionToken)
|
whenever(controllerFactory.create(session.sessionToken)).thenReturn(controller)
|
||||||
whenever(controllerFactory.create(session.sessionToken))
|
|
||||||
.thenReturn(controller)
|
|
||||||
setupLeAudioConfiguration(false)
|
setupLeAudioConfiguration(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,7 +357,9 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
|
|||||||
val deviceCallback = captureCallback()
|
val deviceCallback = captureCallback()
|
||||||
// First set a non-null about-to-connect device
|
// First set a non-null about-to-connect device
|
||||||
deviceCallback.onAboutToConnectDeviceAdded(
|
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.
|
// Run and reset the executors and listeners so we only focus on new events.
|
||||||
fakeBgExecutor.runAllReady()
|
fakeBgExecutor.runAllReady()
|
||||||
@@ -587,8 +588,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testRemotePlaybackDeviceOverride() {
|
fun testRemotePlaybackDeviceOverride() {
|
||||||
whenever(route.name).thenReturn(DEVICE_NAME)
|
whenever(route.name).thenReturn(DEVICE_NAME)
|
||||||
val deviceData = MediaDeviceData(false, null, REMOTE_DEVICE_NAME, null,
|
val deviceData =
|
||||||
showBroadcastButton = false)
|
MediaDeviceData(false, null, REMOTE_DEVICE_NAME, null, showBroadcastButton = false)
|
||||||
val mediaDataWithDevice = mediaData.copy(device = deviceData)
|
val mediaDataWithDevice = mediaData.copy(device = deviceData)
|
||||||
|
|
||||||
// GIVEN media data that already has a device set
|
// GIVEN media data that already has a device set
|
||||||
@@ -617,8 +618,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
|
|||||||
val data = captureDeviceData(KEY)
|
val data = captureDeviceData(KEY)
|
||||||
assertThat(data.showBroadcastButton).isTrue()
|
assertThat(data.showBroadcastButton).isTrue()
|
||||||
assertThat(data.enabled).isTrue()
|
assertThat(data.enabled).isTrue()
|
||||||
assertThat(data.name).isEqualTo(context.getString(
|
assertThat(data.name)
|
||||||
R.string.broadcasting_description_is_broadcasting))
|
.isEqualTo(context.getString(R.string.broadcasting_description_is_broadcasting))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -659,20 +660,21 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback {
|
fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback {
|
||||||
val callback: BluetoothLeBroadcast.Callback = object : BluetoothLeBroadcast.Callback {
|
val callback: BluetoothLeBroadcast.Callback =
|
||||||
override fun onBroadcastStarted(reason: Int, broadcastId: Int) {}
|
object : BluetoothLeBroadcast.Callback {
|
||||||
override fun onBroadcastStartFailed(reason: Int) {}
|
override fun onBroadcastStarted(reason: Int, broadcastId: Int) {}
|
||||||
override fun onBroadcastStopped(reason: Int, broadcastId: Int) {}
|
override fun onBroadcastStartFailed(reason: Int) {}
|
||||||
override fun onBroadcastStopFailed(reason: Int) {}
|
override fun onBroadcastStopped(reason: Int, broadcastId: Int) {}
|
||||||
override fun onPlaybackStarted(reason: Int, broadcastId: Int) {}
|
override fun onBroadcastStopFailed(reason: Int) {}
|
||||||
override fun onPlaybackStopped(reason: Int, broadcastId: Int) {}
|
override fun onPlaybackStarted(reason: Int, broadcastId: Int) {}
|
||||||
override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {}
|
override fun onPlaybackStopped(reason: Int, broadcastId: Int) {}
|
||||||
override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {}
|
override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {}
|
||||||
override fun onBroadcastMetadataChanged(
|
override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {}
|
||||||
broadcastId: Int,
|
override fun onBroadcastMetadataChanged(
|
||||||
metadata: BluetoothLeBroadcastMetadata
|
broadcastId: Int,
|
||||||
) {}
|
metadata: BluetoothLeBroadcastMetadata
|
||||||
}
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
bluetoothLeBroadcast.registerCallback(fakeFgExecutor, callback)
|
bluetoothLeBroadcast.registerCallback(fakeFgExecutor, callback)
|
||||||
return callback
|
return callback
|
||||||
@@ -681,7 +683,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
|
|||||||
fun setupLeAudioConfiguration(isLeAudio: Boolean) {
|
fun setupLeAudioConfiguration(isLeAudio: Boolean) {
|
||||||
whenever(localBluetoothManager.profileManager).thenReturn(localBluetoothProfileManager)
|
whenever(localBluetoothManager.profileManager).thenReturn(localBluetoothProfileManager)
|
||||||
whenever(localBluetoothProfileManager.leAudioBroadcastProfile)
|
whenever(localBluetoothProfileManager.leAudioBroadcastProfile)
|
||||||
.thenReturn(localBluetoothLeBroadcast)
|
.thenReturn(localBluetoothLeBroadcast)
|
||||||
whenever(localBluetoothLeBroadcast.isEnabled(any())).thenReturn(isLeAudio)
|
whenever(localBluetoothLeBroadcast.isEnabled(any())).thenReturn(isLeAudio)
|
||||||
whenever(localBluetoothLeBroadcast.appSourceName).thenReturn(BROADCAST_APP_NAME)
|
whenever(localBluetoothLeBroadcast.appSourceName).thenReturn(BROADCAST_APP_NAME)
|
||||||
}
|
}
|
||||||
@@ -689,7 +691,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
|
|||||||
fun setupBroadcastPackage(currentName: String) {
|
fun setupBroadcastPackage(currentName: String) {
|
||||||
whenever(lmm.packageName).thenReturn(PACKAGE)
|
whenever(lmm.packageName).thenReturn(PACKAGE)
|
||||||
whenever(packageManager.getApplicationInfo(eq(PACKAGE), anyInt()))
|
whenever(packageManager.getApplicationInfo(eq(PACKAGE), anyInt()))
|
||||||
.thenReturn(applicationInfo)
|
.thenReturn(applicationInfo)
|
||||||
whenever(packageManager.getApplicationLabel(applicationInfo)).thenReturn(currentName)
|
whenever(packageManager.getApplicationLabel(applicationInfo)).thenReturn(currentName)
|
||||||
context.setMockPackageManager(packageManager)
|
context.setMockPackageManager(packageManager)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,14 +23,12 @@ import android.media.session.MediaSessionManager
|
|||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
import android.testing.TestableLooper
|
import android.testing.TestableLooper
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
|
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.media.controls.MediaTestUtils
|
import com.android.systemui.media.controls.MediaTestUtils
|
||||||
import com.android.systemui.media.controls.models.player.MediaData
|
import com.android.systemui.media.controls.models.player.MediaData
|
||||||
import com.android.systemui.util.concurrency.FakeExecutor
|
import com.android.systemui.util.concurrency.FakeExecutor
|
||||||
import com.android.systemui.util.mockito.eq
|
import com.android.systemui.util.mockito.eq
|
||||||
import com.android.systemui.util.time.FakeSystemClock
|
import com.android.systemui.util.time.FakeSystemClock
|
||||||
|
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
@@ -44,17 +42,15 @@ import org.mockito.Mockito.any
|
|||||||
import org.mockito.Mockito.never
|
import org.mockito.Mockito.never
|
||||||
import org.mockito.Mockito.reset
|
import org.mockito.Mockito.reset
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.junit.MockitoJUnit
|
|
||||||
import org.mockito.Mockito.`when` as whenever
|
import org.mockito.Mockito.`when` as whenever
|
||||||
|
import org.mockito.junit.MockitoJUnit
|
||||||
|
|
||||||
private const val PACKAGE = "PKG"
|
private const val PACKAGE = "PKG"
|
||||||
private const val KEY = "TEST_KEY"
|
private const val KEY = "TEST_KEY"
|
||||||
private const val NOTIF_KEY = "TEST_KEY"
|
private const val NOTIF_KEY = "TEST_KEY"
|
||||||
|
|
||||||
private val info = MediaTestUtils.emptyMediaData.copy(
|
private val info =
|
||||||
packageName = PACKAGE,
|
MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, notificationKey = NOTIF_KEY)
|
||||||
notificationKey = NOTIF_KEY
|
|
||||||
)
|
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@RunWith(AndroidTestingRunner::class)
|
@RunWith(AndroidTestingRunner::class)
|
||||||
@@ -141,10 +137,10 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
|
|||||||
|
|
||||||
// Capture listener
|
// Capture listener
|
||||||
bgExecutor.runAllReady()
|
bgExecutor.runAllReady()
|
||||||
val listenerCaptor = ArgumentCaptor.forClass(
|
val listenerCaptor =
|
||||||
MediaSessionManager.OnActiveSessionsChangedListener::class.java)
|
ArgumentCaptor.forClass(MediaSessionManager.OnActiveSessionsChangedListener::class.java)
|
||||||
verify(mediaSessionManager).addOnActiveSessionsChangedListener(
|
verify(mediaSessionManager)
|
||||||
listenerCaptor.capture(), any())
|
.addOnActiveSessionsChangedListener(listenerCaptor.capture(), any())
|
||||||
sessionListener = listenerCaptor.value
|
sessionListener = listenerCaptor.value
|
||||||
|
|
||||||
filter.addListener(mediaListener)
|
filter.addListener(mediaListener)
|
||||||
@@ -163,8 +159,8 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
|
|||||||
filter.onMediaDataLoaded(KEY, null, mediaData1)
|
filter.onMediaDataLoaded(KEY, null, mediaData1)
|
||||||
bgExecutor.runAllReady()
|
bgExecutor.runAllReady()
|
||||||
fgExecutor.runAllReady()
|
fgExecutor.runAllReady()
|
||||||
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
|
verify(mediaListener)
|
||||||
eq(0), eq(false))
|
.onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -186,8 +182,8 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
|
|||||||
bgExecutor.runAllReady()
|
bgExecutor.runAllReady()
|
||||||
fgExecutor.runAllReady()
|
fgExecutor.runAllReady()
|
||||||
// THEN the event is not filtered
|
// THEN the event is not filtered
|
||||||
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
|
verify(mediaListener)
|
||||||
eq(0), eq(false))
|
.onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -216,8 +212,8 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
|
|||||||
bgExecutor.runAllReady()
|
bgExecutor.runAllReady()
|
||||||
fgExecutor.runAllReady()
|
fgExecutor.runAllReady()
|
||||||
// THEN the event is not filtered
|
// THEN the event is not filtered
|
||||||
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
|
verify(mediaListener)
|
||||||
eq(0), eq(false))
|
.onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -232,15 +228,22 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
|
|||||||
bgExecutor.runAllReady()
|
bgExecutor.runAllReady()
|
||||||
fgExecutor.runAllReady()
|
fgExecutor.runAllReady()
|
||||||
// THEN the event is not filtered
|
// THEN the event is not filtered
|
||||||
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
|
verify(mediaListener)
|
||||||
eq(0), eq(false))
|
.onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
|
||||||
// WHEN a loaded event is received that matches the local session
|
// WHEN a loaded event is received that matches the local session
|
||||||
filter.onMediaDataLoaded(KEY, null, mediaData2)
|
filter.onMediaDataLoaded(KEY, null, mediaData2)
|
||||||
bgExecutor.runAllReady()
|
bgExecutor.runAllReady()
|
||||||
fgExecutor.runAllReady()
|
fgExecutor.runAllReady()
|
||||||
// THEN the event is filtered
|
// THEN the event is filtered
|
||||||
verify(mediaListener, never()).onMediaDataLoaded(
|
verify(mediaListener, never())
|
||||||
eq(KEY), eq(null), eq(mediaData2), anyBoolean(), anyInt(), anyBoolean())
|
.onMediaDataLoaded(
|
||||||
|
eq(KEY),
|
||||||
|
eq(null),
|
||||||
|
eq(mediaData2),
|
||||||
|
anyBoolean(),
|
||||||
|
anyInt(),
|
||||||
|
anyBoolean()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -256,8 +259,8 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
|
|||||||
fgExecutor.runAllReady()
|
fgExecutor.runAllReady()
|
||||||
// THEN the event is not filtered because there isn't a notification for the remote
|
// THEN the event is not filtered because there isn't a notification for the remote
|
||||||
// session.
|
// session.
|
||||||
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
|
verify(mediaListener)
|
||||||
eq(0), eq(false))
|
.onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -274,16 +277,22 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
|
|||||||
bgExecutor.runAllReady()
|
bgExecutor.runAllReady()
|
||||||
fgExecutor.runAllReady()
|
fgExecutor.runAllReady()
|
||||||
// THEN the event is not filtered
|
// THEN the event is not filtered
|
||||||
verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true),
|
verify(mediaListener)
|
||||||
eq(0), eq(false))
|
.onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
|
||||||
// WHEN a loaded event is received that matches the local session
|
// WHEN a loaded event is received that matches the local session
|
||||||
filter.onMediaDataLoaded(key2, null, mediaData2)
|
filter.onMediaDataLoaded(key2, null, mediaData2)
|
||||||
bgExecutor.runAllReady()
|
bgExecutor.runAllReady()
|
||||||
fgExecutor.runAllReady()
|
fgExecutor.runAllReady()
|
||||||
// THEN the event is filtered
|
// THEN the event is filtered
|
||||||
verify(mediaListener, never())
|
verify(mediaListener, never())
|
||||||
.onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), anyBoolean(),
|
.onMediaDataLoaded(
|
||||||
anyInt(), anyBoolean())
|
eq(key2),
|
||||||
|
eq(null),
|
||||||
|
eq(mediaData2),
|
||||||
|
anyBoolean(),
|
||||||
|
anyInt(),
|
||||||
|
anyBoolean()
|
||||||
|
)
|
||||||
// AND there should be a removed event for key2
|
// AND there should be a removed event for key2
|
||||||
verify(mediaListener).onMediaDataRemoved(eq(key2))
|
verify(mediaListener).onMediaDataRemoved(eq(key2))
|
||||||
}
|
}
|
||||||
@@ -302,15 +311,15 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
|
|||||||
bgExecutor.runAllReady()
|
bgExecutor.runAllReady()
|
||||||
fgExecutor.runAllReady()
|
fgExecutor.runAllReady()
|
||||||
// THEN the event is not filtered
|
// THEN the event is not filtered
|
||||||
verify(mediaListener).onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true),
|
verify(mediaListener)
|
||||||
eq(0), eq(false))
|
.onMediaDataLoaded(eq(key1), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
|
||||||
// WHEN a loaded event is received that matches the remote session
|
// WHEN a loaded event is received that matches the remote session
|
||||||
filter.onMediaDataLoaded(key2, null, mediaData2)
|
filter.onMediaDataLoaded(key2, null, mediaData2)
|
||||||
bgExecutor.runAllReady()
|
bgExecutor.runAllReady()
|
||||||
fgExecutor.runAllReady()
|
fgExecutor.runAllReady()
|
||||||
// THEN the event is not filtered
|
// THEN the event is not filtered
|
||||||
verify(mediaListener).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), eq(true),
|
verify(mediaListener)
|
||||||
eq(0), eq(false))
|
.onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), eq(true), eq(0), eq(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -326,15 +335,15 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
|
|||||||
bgExecutor.runAllReady()
|
bgExecutor.runAllReady()
|
||||||
fgExecutor.runAllReady()
|
fgExecutor.runAllReady()
|
||||||
// THEN the event is not filtered
|
// THEN the event is not filtered
|
||||||
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
|
verify(mediaListener)
|
||||||
eq(0), eq(false))
|
.onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
|
||||||
// WHEN a loaded event is received that matches the local session
|
// WHEN a loaded event is received that matches the local session
|
||||||
filter.onMediaDataLoaded(KEY, null, mediaData2)
|
filter.onMediaDataLoaded(KEY, null, mediaData2)
|
||||||
bgExecutor.runAllReady()
|
bgExecutor.runAllReady()
|
||||||
fgExecutor.runAllReady()
|
fgExecutor.runAllReady()
|
||||||
// THEN the event is not filtered
|
// THEN the event is not filtered
|
||||||
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2), eq(true),
|
verify(mediaListener)
|
||||||
eq(0), eq(false))
|
.onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData2), eq(true), eq(0), eq(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -352,8 +361,8 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
|
|||||||
bgExecutor.runAllReady()
|
bgExecutor.runAllReady()
|
||||||
fgExecutor.runAllReady()
|
fgExecutor.runAllReady()
|
||||||
// THEN the event is not filtered
|
// THEN the event is not filtered
|
||||||
verify(mediaListener).onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true),
|
verify(mediaListener)
|
||||||
eq(0), eq(false))
|
.onMediaDataLoaded(eq(KEY), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -375,8 +384,8 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
|
|||||||
bgExecutor.runAllReady()
|
bgExecutor.runAllReady()
|
||||||
fgExecutor.runAllReady()
|
fgExecutor.runAllReady()
|
||||||
// THEN the key migration event is fired
|
// THEN the key migration event is fired
|
||||||
verify(mediaListener).onMediaDataLoaded(eq(key2), eq(key1), eq(mediaData2), eq(true),
|
verify(mediaListener)
|
||||||
eq(0), eq(false))
|
.onMediaDataLoaded(eq(key2), eq(key1), eq(mediaData2), eq(true), eq(0), eq(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -406,14 +415,20 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() {
|
|||||||
fgExecutor.runAllReady()
|
fgExecutor.runAllReady()
|
||||||
// THEN the key migration event is filtered
|
// THEN the key migration event is filtered
|
||||||
verify(mediaListener, never())
|
verify(mediaListener, never())
|
||||||
.onMediaDataLoaded(eq(key2), eq(null), eq(mediaData2), anyBoolean(),
|
.onMediaDataLoaded(
|
||||||
anyInt(), anyBoolean())
|
eq(key2),
|
||||||
|
eq(null),
|
||||||
|
eq(mediaData2),
|
||||||
|
anyBoolean(),
|
||||||
|
anyInt(),
|
||||||
|
anyBoolean()
|
||||||
|
)
|
||||||
// WHEN a loaded event is received that matches the remote session
|
// WHEN a loaded event is received that matches the remote session
|
||||||
filter.onMediaDataLoaded(key2, null, mediaData1)
|
filter.onMediaDataLoaded(key2, null, mediaData1)
|
||||||
bgExecutor.runAllReady()
|
bgExecutor.runAllReady()
|
||||||
fgExecutor.runAllReady()
|
fgExecutor.runAllReady()
|
||||||
// THEN the key migration event is fired
|
// THEN the key migration event is fired
|
||||||
verify(mediaListener).onMediaDataLoaded(eq(key2), eq(null), eq(mediaData1), eq(true),
|
verify(mediaListener)
|
||||||
eq(0), eq(false))
|
.onMediaDataLoaded(eq(key2), eq(null), eq(mediaData1), eq(true), eq(0), eq(false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,11 +44,11 @@ import org.mockito.ArgumentMatchers.anyString
|
|||||||
import org.mockito.Captor
|
import org.mockito.Captor
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
import org.mockito.Mockito.`when`
|
|
||||||
import org.mockito.Mockito.clearInvocations
|
import org.mockito.Mockito.clearInvocations
|
||||||
import org.mockito.Mockito.mock
|
import org.mockito.Mockito.mock
|
||||||
import org.mockito.Mockito.never
|
import org.mockito.Mockito.never
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
|
import org.mockito.Mockito.`when`
|
||||||
import org.mockito.junit.MockitoJUnit
|
import org.mockito.junit.MockitoJUnit
|
||||||
|
|
||||||
private const val KEY = "KEY"
|
private const val KEY = "KEY"
|
||||||
@@ -73,7 +73,8 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
|
|||||||
@Mock private lateinit var timeoutCallback: (String, Boolean) -> Unit
|
@Mock private lateinit var timeoutCallback: (String, Boolean) -> Unit
|
||||||
@Mock private lateinit var stateCallback: (String, PlaybackState) -> Unit
|
@Mock private lateinit var stateCallback: (String, PlaybackState) -> Unit
|
||||||
@Captor private lateinit var mediaCallbackCaptor: ArgumentCaptor<MediaController.Callback>
|
@Captor private lateinit var mediaCallbackCaptor: ArgumentCaptor<MediaController.Callback>
|
||||||
@Captor private lateinit var dozingCallbackCaptor:
|
@Captor
|
||||||
|
private lateinit var dozingCallbackCaptor:
|
||||||
ArgumentCaptor<StatusBarStateController.StateListener>
|
ArgumentCaptor<StatusBarStateController.StateListener>
|
||||||
@JvmField @Rule val mockito = MockitoJUnit.rule()
|
@JvmField @Rule val mockito = MockitoJUnit.rule()
|
||||||
private lateinit var metadataBuilder: MediaMetadata.Builder
|
private lateinit var metadataBuilder: MediaMetadata.Builder
|
||||||
@@ -88,36 +89,41 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
|
|||||||
fun setup() {
|
fun setup() {
|
||||||
`when`(mediaControllerFactory.create(any())).thenReturn(mediaController)
|
`when`(mediaControllerFactory.create(any())).thenReturn(mediaController)
|
||||||
executor = FakeExecutor(clock)
|
executor = FakeExecutor(clock)
|
||||||
mediaTimeoutListener = MediaTimeoutListener(
|
mediaTimeoutListener =
|
||||||
mediaControllerFactory,
|
MediaTimeoutListener(
|
||||||
executor,
|
mediaControllerFactory,
|
||||||
logger,
|
executor,
|
||||||
statusBarStateController,
|
logger,
|
||||||
clock
|
statusBarStateController,
|
||||||
)
|
clock
|
||||||
|
)
|
||||||
mediaTimeoutListener.timeoutCallback = timeoutCallback
|
mediaTimeoutListener.timeoutCallback = timeoutCallback
|
||||||
mediaTimeoutListener.stateCallback = stateCallback
|
mediaTimeoutListener.stateCallback = stateCallback
|
||||||
|
|
||||||
// Create a media session and notification for testing.
|
// Create a media session and notification for testing.
|
||||||
metadataBuilder = MediaMetadata.Builder().apply {
|
metadataBuilder =
|
||||||
putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
|
MediaMetadata.Builder().apply {
|
||||||
putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
|
putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
|
||||||
}
|
putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
|
||||||
playbackBuilder = PlaybackState.Builder().apply {
|
}
|
||||||
setState(PlaybackState.STATE_PAUSED, 6000L, 1f)
|
playbackBuilder =
|
||||||
setActions(PlaybackState.ACTION_PLAY)
|
PlaybackState.Builder().apply {
|
||||||
}
|
setState(PlaybackState.STATE_PAUSED, 6000L, 1f)
|
||||||
session = MediaSession(context, SESSION_KEY).apply {
|
setActions(PlaybackState.ACTION_PLAY)
|
||||||
setMetadata(metadataBuilder.build())
|
}
|
||||||
setPlaybackState(playbackBuilder.build())
|
session =
|
||||||
}
|
MediaSession(context, SESSION_KEY).apply {
|
||||||
|
setMetadata(metadataBuilder.build())
|
||||||
|
setPlaybackState(playbackBuilder.build())
|
||||||
|
}
|
||||||
session.setActive(true)
|
session.setActive(true)
|
||||||
|
|
||||||
mediaData = MediaTestUtils.emptyMediaData.copy(
|
mediaData =
|
||||||
app = PACKAGE,
|
MediaTestUtils.emptyMediaData.copy(
|
||||||
packageName = PACKAGE,
|
app = PACKAGE,
|
||||||
token = session.sessionToken
|
packageName = PACKAGE,
|
||||||
)
|
token = session.sessionToken
|
||||||
|
)
|
||||||
|
|
||||||
resumeData = mediaData.copy(token = null, active = false, resumption = true)
|
resumeData = mediaData.copy(token = null, active = false, resumption = true)
|
||||||
}
|
}
|
||||||
@@ -215,8 +221,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
|
|||||||
// Assuming we're registered
|
// Assuming we're registered
|
||||||
testOnMediaDataLoaded_registersPlaybackListener()
|
testOnMediaDataLoaded_registersPlaybackListener()
|
||||||
|
|
||||||
mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
|
mediaCallbackCaptor.value.onPlaybackStateChanged(
|
||||||
.setState(PlaybackState.STATE_PAUSED, 0L, 0f).build())
|
PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
|
||||||
|
)
|
||||||
assertThat(executor.numPending()).isEqualTo(1)
|
assertThat(executor.numPending()).isEqualTo(1)
|
||||||
assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT)
|
assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT)
|
||||||
}
|
}
|
||||||
@@ -226,8 +233,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
|
|||||||
// Assuming we have a pending timeout
|
// Assuming we have a pending timeout
|
||||||
testOnPlaybackStateChanged_schedulesTimeout_whenPaused()
|
testOnPlaybackStateChanged_schedulesTimeout_whenPaused()
|
||||||
|
|
||||||
mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
|
mediaCallbackCaptor.value.onPlaybackStateChanged(
|
||||||
.setState(PlaybackState.STATE_PLAYING, 0L, 0f).build())
|
PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 0f).build()
|
||||||
|
)
|
||||||
assertThat(executor.numPending()).isEqualTo(0)
|
assertThat(executor.numPending()).isEqualTo(0)
|
||||||
verify(logger).logTimeoutCancelled(eq(KEY), any())
|
verify(logger).logTimeoutCancelled(eq(KEY), any())
|
||||||
}
|
}
|
||||||
@@ -237,8 +245,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
|
|||||||
// Assuming we have a pending timeout
|
// Assuming we have a pending timeout
|
||||||
testOnPlaybackStateChanged_schedulesTimeout_whenPaused()
|
testOnPlaybackStateChanged_schedulesTimeout_whenPaused()
|
||||||
|
|
||||||
mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
|
mediaCallbackCaptor.value.onPlaybackStateChanged(
|
||||||
.setState(PlaybackState.STATE_STOPPED, 0L, 0f).build())
|
PlaybackState.Builder().setState(PlaybackState.STATE_STOPPED, 0L, 0f).build()
|
||||||
|
)
|
||||||
assertThat(executor.numPending()).isEqualTo(1)
|
assertThat(executor.numPending()).isEqualTo(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,9 +341,8 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testOnMediaDataLoaded_pausedToResume_updatesTimeout() {
|
fun testOnMediaDataLoaded_pausedToResume_updatesTimeout() {
|
||||||
// WHEN regular media is paused
|
// WHEN regular media is paused
|
||||||
val pausedState = PlaybackState.Builder()
|
val pausedState =
|
||||||
.setState(PlaybackState.STATE_PAUSED, 0L, 0f)
|
PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
|
||||||
.build()
|
|
||||||
`when`(mediaController.playbackState).thenReturn(pausedState)
|
`when`(mediaController.playbackState).thenReturn(pausedState)
|
||||||
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
|
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
|
||||||
assertThat(executor.numPending()).isEqualTo(1)
|
assertThat(executor.numPending()).isEqualTo(1)
|
||||||
@@ -365,9 +373,8 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
|
|||||||
mediaTimeoutListener.onMediaDataLoaded(PACKAGE, null, resumeData)
|
mediaTimeoutListener.onMediaDataLoaded(PACKAGE, null, resumeData)
|
||||||
|
|
||||||
// AND that media is resumed
|
// AND that media is resumed
|
||||||
val playingState = PlaybackState.Builder()
|
val playingState =
|
||||||
.setState(PlaybackState.STATE_PAUSED, 0L, 0f)
|
PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
|
||||||
.build()
|
|
||||||
`when`(mediaController.playbackState).thenReturn(playingState)
|
`when`(mediaController.playbackState).thenReturn(playingState)
|
||||||
mediaTimeoutListener.onMediaDataLoaded(KEY, PACKAGE, mediaData)
|
mediaTimeoutListener.onMediaDataLoaded(KEY, PACKAGE, mediaData)
|
||||||
|
|
||||||
@@ -389,15 +396,11 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testOnMediaDataLoaded_playbackActionsChanged_noCallback() {
|
fun testOnMediaDataLoaded_playbackActionsChanged_noCallback() {
|
||||||
// Load media data once
|
// Load media data once
|
||||||
val pausedState = PlaybackState.Builder()
|
val pausedState = PlaybackState.Builder().setActions(PlaybackState.ACTION_PAUSE).build()
|
||||||
.setActions(PlaybackState.ACTION_PAUSE)
|
|
||||||
.build()
|
|
||||||
loadMediaDataWithPlaybackState(pausedState)
|
loadMediaDataWithPlaybackState(pausedState)
|
||||||
|
|
||||||
// When media data is loaded again, with different actions
|
// When media data is loaded again, with different actions
|
||||||
val playingState = PlaybackState.Builder()
|
val playingState = PlaybackState.Builder().setActions(PlaybackState.ACTION_PLAY).build()
|
||||||
.setActions(PlaybackState.ACTION_PLAY)
|
|
||||||
.build()
|
|
||||||
loadMediaDataWithPlaybackState(playingState)
|
loadMediaDataWithPlaybackState(playingState)
|
||||||
|
|
||||||
// Then the callback is not invoked
|
// Then the callback is not invoked
|
||||||
@@ -407,15 +410,11 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testOnPlaybackStateChanged_playbackActionsChanged_sendsCallback() {
|
fun testOnPlaybackStateChanged_playbackActionsChanged_sendsCallback() {
|
||||||
// Load media data once
|
// Load media data once
|
||||||
val pausedState = PlaybackState.Builder()
|
val pausedState = PlaybackState.Builder().setActions(PlaybackState.ACTION_PAUSE).build()
|
||||||
.setActions(PlaybackState.ACTION_PAUSE)
|
|
||||||
.build()
|
|
||||||
loadMediaDataWithPlaybackState(pausedState)
|
loadMediaDataWithPlaybackState(pausedState)
|
||||||
|
|
||||||
// When the playback state changes, and has different actions
|
// When the playback state changes, and has different actions
|
||||||
val playingState = PlaybackState.Builder()
|
val playingState = PlaybackState.Builder().setActions(PlaybackState.ACTION_PLAY).build()
|
||||||
.setActions(PlaybackState.ACTION_PLAY)
|
|
||||||
.build()
|
|
||||||
mediaCallbackCaptor.value.onPlaybackStateChanged(playingState)
|
mediaCallbackCaptor.value.onPlaybackStateChanged(playingState)
|
||||||
|
|
||||||
// Then the callback is invoked
|
// Then the callback is invoked
|
||||||
@@ -424,24 +423,30 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testOnPlaybackStateChanged_differentCustomActions_sendsCallback() {
|
fun testOnPlaybackStateChanged_differentCustomActions_sendsCallback() {
|
||||||
val customOne = PlaybackState.CustomAction.Builder(
|
val customOne =
|
||||||
|
PlaybackState.CustomAction.Builder(
|
||||||
"ACTION_1",
|
"ACTION_1",
|
||||||
"custom action 1",
|
"custom action 1",
|
||||||
android.R.drawable.ic_media_ff)
|
android.R.drawable.ic_media_ff
|
||||||
|
)
|
||||||
.build()
|
.build()
|
||||||
val pausedState = PlaybackState.Builder()
|
val pausedState =
|
||||||
|
PlaybackState.Builder()
|
||||||
.setActions(PlaybackState.ACTION_PAUSE)
|
.setActions(PlaybackState.ACTION_PAUSE)
|
||||||
.addCustomAction(customOne)
|
.addCustomAction(customOne)
|
||||||
.build()
|
.build()
|
||||||
loadMediaDataWithPlaybackState(pausedState)
|
loadMediaDataWithPlaybackState(pausedState)
|
||||||
|
|
||||||
// When the playback state actions change
|
// When the playback state actions change
|
||||||
val customTwo = PlaybackState.CustomAction.Builder(
|
val customTwo =
|
||||||
"ACTION_2",
|
PlaybackState.CustomAction.Builder(
|
||||||
"custom action 2",
|
"ACTION_2",
|
||||||
android.R.drawable.ic_media_rew)
|
"custom action 2",
|
||||||
|
android.R.drawable.ic_media_rew
|
||||||
|
)
|
||||||
.build()
|
.build()
|
||||||
val pausedStateTwoActions = PlaybackState.Builder()
|
val pausedStateTwoActions =
|
||||||
|
PlaybackState.Builder()
|
||||||
.setActions(PlaybackState.ACTION_PAUSE)
|
.setActions(PlaybackState.ACTION_PAUSE)
|
||||||
.addCustomAction(customOne)
|
.addCustomAction(customOne)
|
||||||
.addCustomAction(customTwo)
|
.addCustomAction(customTwo)
|
||||||
@@ -454,9 +459,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testOnPlaybackStateChanged_sameActions_noCallback() {
|
fun testOnPlaybackStateChanged_sameActions_noCallback() {
|
||||||
val stateWithActions = PlaybackState.Builder()
|
val stateWithActions = PlaybackState.Builder().setActions(PlaybackState.ACTION_PLAY).build()
|
||||||
.setActions(PlaybackState.ACTION_PLAY)
|
|
||||||
.build()
|
|
||||||
loadMediaDataWithPlaybackState(stateWithActions)
|
loadMediaDataWithPlaybackState(stateWithActions)
|
||||||
|
|
||||||
// When the playback state updates with the same actions
|
// When the playback state updates with the same actions
|
||||||
@@ -470,18 +473,20 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
|
|||||||
fun testOnPlaybackStateChanged_sameCustomActions_noCallback() {
|
fun testOnPlaybackStateChanged_sameCustomActions_noCallback() {
|
||||||
val actionName = "custom action"
|
val actionName = "custom action"
|
||||||
val actionIcon = android.R.drawable.ic_media_ff
|
val actionIcon = android.R.drawable.ic_media_ff
|
||||||
val customOne = PlaybackState.CustomAction.Builder(actionName, actionName, actionIcon)
|
val customOne =
|
||||||
.build()
|
PlaybackState.CustomAction.Builder(actionName, actionName, actionIcon).build()
|
||||||
val stateOne = PlaybackState.Builder()
|
val stateOne =
|
||||||
|
PlaybackState.Builder()
|
||||||
.setActions(PlaybackState.ACTION_PAUSE)
|
.setActions(PlaybackState.ACTION_PAUSE)
|
||||||
.addCustomAction(customOne)
|
.addCustomAction(customOne)
|
||||||
.build()
|
.build()
|
||||||
loadMediaDataWithPlaybackState(stateOne)
|
loadMediaDataWithPlaybackState(stateOne)
|
||||||
|
|
||||||
// When the playback state is updated, but has the same actions
|
// When the playback state is updated, but has the same actions
|
||||||
val customTwo = PlaybackState.CustomAction.Builder(actionName, actionName, actionIcon)
|
val customTwo =
|
||||||
.build()
|
PlaybackState.CustomAction.Builder(actionName, actionName, actionIcon).build()
|
||||||
val stateTwo = PlaybackState.Builder()
|
val stateTwo =
|
||||||
|
PlaybackState.Builder()
|
||||||
.setActions(PlaybackState.ACTION_PAUSE)
|
.setActions(PlaybackState.ACTION_PAUSE)
|
||||||
.addCustomAction(customTwo)
|
.addCustomAction(customTwo)
|
||||||
.build()
|
.build()
|
||||||
@@ -494,15 +499,13 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testOnMediaDataLoaded_isPlayingChanged_noCallback() {
|
fun testOnMediaDataLoaded_isPlayingChanged_noCallback() {
|
||||||
// Load media data in paused state
|
// Load media data in paused state
|
||||||
val pausedState = PlaybackState.Builder()
|
val pausedState =
|
||||||
.setState(PlaybackState.STATE_PAUSED, 0L, 0f)
|
PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
|
||||||
.build()
|
|
||||||
loadMediaDataWithPlaybackState(pausedState)
|
loadMediaDataWithPlaybackState(pausedState)
|
||||||
|
|
||||||
// When media data is loaded again but playing
|
// When media data is loaded again but playing
|
||||||
val playingState = PlaybackState.Builder()
|
val playingState =
|
||||||
.setState(PlaybackState.STATE_PLAYING, 0L, 1f)
|
PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build()
|
||||||
.build()
|
|
||||||
loadMediaDataWithPlaybackState(playingState)
|
loadMediaDataWithPlaybackState(playingState)
|
||||||
|
|
||||||
// Then the callback is not invoked
|
// Then the callback is not invoked
|
||||||
@@ -512,15 +515,13 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testOnPlaybackStateChanged_isPlayingChanged_sendsCallback() {
|
fun testOnPlaybackStateChanged_isPlayingChanged_sendsCallback() {
|
||||||
// Load media data in paused state
|
// Load media data in paused state
|
||||||
val pausedState = PlaybackState.Builder()
|
val pausedState =
|
||||||
.setState(PlaybackState.STATE_PAUSED, 0L, 0f)
|
PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
|
||||||
.build()
|
|
||||||
loadMediaDataWithPlaybackState(pausedState)
|
loadMediaDataWithPlaybackState(pausedState)
|
||||||
|
|
||||||
// When the playback state changes to playing
|
// When the playback state changes to playing
|
||||||
val playingState = PlaybackState.Builder()
|
val playingState =
|
||||||
.setState(PlaybackState.STATE_PLAYING, 0L, 1f)
|
PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build()
|
||||||
.build()
|
|
||||||
mediaCallbackCaptor.value.onPlaybackStateChanged(playingState)
|
mediaCallbackCaptor.value.onPlaybackStateChanged(playingState)
|
||||||
|
|
||||||
// Then the callback is invoked
|
// Then the callback is invoked
|
||||||
@@ -530,15 +531,13 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testOnPlaybackStateChanged_isPlayingSame_noCallback() {
|
fun testOnPlaybackStateChanged_isPlayingSame_noCallback() {
|
||||||
// Load media data in paused state
|
// Load media data in paused state
|
||||||
val pausedState = PlaybackState.Builder()
|
val pausedState =
|
||||||
.setState(PlaybackState.STATE_PAUSED, 0L, 0f)
|
PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
|
||||||
.build()
|
|
||||||
loadMediaDataWithPlaybackState(pausedState)
|
loadMediaDataWithPlaybackState(pausedState)
|
||||||
|
|
||||||
// When the playback state is updated, but still not playing
|
// When the playback state is updated, but still not playing
|
||||||
val playingState = PlaybackState.Builder()
|
val playingState =
|
||||||
.setState(PlaybackState.STATE_STOPPED, 0L, 0f)
|
PlaybackState.Builder().setState(PlaybackState.STATE_STOPPED, 0L, 0f).build()
|
||||||
.build()
|
|
||||||
mediaCallbackCaptor.value.onPlaybackStateChanged(playingState)
|
mediaCallbackCaptor.value.onPlaybackStateChanged(playingState)
|
||||||
|
|
||||||
// Then the callback is not invoked
|
// Then the callback is not invoked
|
||||||
@@ -549,8 +548,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
|
|||||||
fun testTimeoutCallback_dozedPastTimeout_invokedOnWakeup() {
|
fun testTimeoutCallback_dozedPastTimeout_invokedOnWakeup() {
|
||||||
// When paused media is loaded
|
// When paused media is loaded
|
||||||
testOnMediaDataLoaded_registersPlaybackListener()
|
testOnMediaDataLoaded_registersPlaybackListener()
|
||||||
mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
|
mediaCallbackCaptor.value.onPlaybackStateChanged(
|
||||||
.setState(PlaybackState.STATE_PAUSED, 0L, 0f).build())
|
PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
|
||||||
|
)
|
||||||
verify(statusBarStateController).addCallback(capture(dozingCallbackCaptor))
|
verify(statusBarStateController).addCallback(capture(dozingCallbackCaptor))
|
||||||
|
|
||||||
// And we doze past the scheduled timeout
|
// And we doze past the scheduled timeout
|
||||||
@@ -574,8 +574,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
|
|||||||
val time = clock.currentTimeMillis()
|
val time = clock.currentTimeMillis()
|
||||||
clock.setElapsedRealtime(time)
|
clock.setElapsedRealtime(time)
|
||||||
testOnMediaDataLoaded_registersPlaybackListener()
|
testOnMediaDataLoaded_registersPlaybackListener()
|
||||||
mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
|
mediaCallbackCaptor.value.onPlaybackStateChanged(
|
||||||
.setState(PlaybackState.STATE_PAUSED, 0L, 0f).build())
|
PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
|
||||||
|
)
|
||||||
verify(statusBarStateController).addCallback(capture(dozingCallbackCaptor))
|
verify(statusBarStateController).addCallback(capture(dozingCallbackCaptor))
|
||||||
|
|
||||||
// And we doze, but not past the scheduled timeout
|
// And we doze, but not past the scheduled timeout
|
||||||
|
|||||||
@@ -36,13 +36,13 @@ import com.android.systemui.dump.DumpManager
|
|||||||
import com.android.systemui.media.controls.MediaTestUtils
|
import com.android.systemui.media.controls.MediaTestUtils
|
||||||
import com.android.systemui.media.controls.models.player.MediaData
|
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.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.MediaDataManager
|
||||||
|
import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT
|
||||||
import com.android.systemui.tuner.TunerService
|
import com.android.systemui.tuner.TunerService
|
||||||
import com.android.systemui.util.concurrency.FakeExecutor
|
import com.android.systemui.util.concurrency.FakeExecutor
|
||||||
import com.android.systemui.util.time.FakeSystemClock
|
import com.android.systemui.util.time.FakeSystemClock
|
||||||
import org.junit.After
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
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 const val RESUME_COMPONENTS = "package1/class1:package2/class2:package3/class3"
|
||||||
|
|
||||||
private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
|
private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
|
||||||
|
|
||||||
private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
|
private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
|
||||||
|
|
||||||
private fun <T> any(): T = Mockito.any<T>()
|
private fun <T> any(): T = Mockito.any<T>()
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@@ -98,26 +100,32 @@ class MediaResumeListenerTest : SysuiTestCase() {
|
|||||||
private lateinit var resumeListener: MediaResumeListener
|
private lateinit var resumeListener: MediaResumeListener
|
||||||
private val clock = FakeSystemClock()
|
private val clock = FakeSystemClock()
|
||||||
|
|
||||||
private var originalQsSetting = Settings.Global.getInt(context.contentResolver,
|
private var originalQsSetting =
|
||||||
Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1)
|
Settings.Global.getInt(
|
||||||
private var originalResumeSetting = Settings.Secure.getInt(context.contentResolver,
|
context.contentResolver,
|
||||||
Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
|
Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS,
|
||||||
|
1
|
||||||
|
)
|
||||||
|
private var originalResumeSetting =
|
||||||
|
Settings.Secure.getInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
Settings.Global.putInt(context.contentResolver,
|
Settings.Global.putInt(
|
||||||
Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1)
|
context.contentResolver,
|
||||||
Settings.Secure.putInt(context.contentResolver,
|
Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS,
|
||||||
Settings.Secure.MEDIA_CONTROLS_RESUME, 1)
|
1
|
||||||
|
)
|
||||||
|
Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 1)
|
||||||
|
|
||||||
whenever(resumeBrowserFactory.create(capture(callbackCaptor), any()))
|
whenever(resumeBrowserFactory.create(capture(callbackCaptor), any()))
|
||||||
.thenReturn(resumeBrowser)
|
.thenReturn(resumeBrowser)
|
||||||
|
|
||||||
// resume components are stored in sharedpreferences
|
// resume components are stored in sharedpreferences
|
||||||
whenever(mockContext.getSharedPreferences(eq(MEDIA_PREFERENCES), anyInt()))
|
whenever(mockContext.getSharedPreferences(eq(MEDIA_PREFERENCES), anyInt()))
|
||||||
.thenReturn(sharedPrefs)
|
.thenReturn(sharedPrefs)
|
||||||
whenever(sharedPrefs.getString(any(), any())).thenReturn(RESUME_COMPONENTS)
|
whenever(sharedPrefs.getString(any(), any())).thenReturn(RESUME_COMPONENTS)
|
||||||
whenever(sharedPrefs.edit()).thenReturn(sharedPrefsEditor)
|
whenever(sharedPrefs.edit()).thenReturn(sharedPrefsEditor)
|
||||||
whenever(sharedPrefsEditor.putString(any(), any())).thenReturn(sharedPrefsEditor)
|
whenever(sharedPrefsEditor.putString(any(), any())).thenReturn(sharedPrefsEditor)
|
||||||
@@ -125,36 +133,59 @@ class MediaResumeListenerTest : SysuiTestCase() {
|
|||||||
whenever(mockContext.contentResolver).thenReturn(context.contentResolver)
|
whenever(mockContext.contentResolver).thenReturn(context.contentResolver)
|
||||||
|
|
||||||
executor = FakeExecutor(clock)
|
executor = FakeExecutor(clock)
|
||||||
resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
|
resumeListener =
|
||||||
tunerService, resumeBrowserFactory, dumpManager, clock)
|
MediaResumeListener(
|
||||||
|
mockContext,
|
||||||
|
broadcastDispatcher,
|
||||||
|
executor,
|
||||||
|
tunerService,
|
||||||
|
resumeBrowserFactory,
|
||||||
|
dumpManager,
|
||||||
|
clock
|
||||||
|
)
|
||||||
resumeListener.setManager(mediaDataManager)
|
resumeListener.setManager(mediaDataManager)
|
||||||
mediaDataManager.addListener(resumeListener)
|
mediaDataManager.addListener(resumeListener)
|
||||||
|
|
||||||
data = MediaTestUtils.emptyMediaData.copy(
|
data =
|
||||||
|
MediaTestUtils.emptyMediaData.copy(
|
||||||
song = TITLE,
|
song = TITLE,
|
||||||
packageName = PACKAGE_NAME,
|
packageName = PACKAGE_NAME,
|
||||||
token = token)
|
token = token
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
fun tearDown() {
|
fun tearDown() {
|
||||||
Settings.Global.putInt(context.contentResolver,
|
Settings.Global.putInt(
|
||||||
Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, originalQsSetting)
|
context.contentResolver,
|
||||||
Settings.Secure.putInt(context.contentResolver,
|
Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS,
|
||||||
Settings.Secure.MEDIA_CONTROLS_RESUME, originalResumeSetting)
|
originalQsSetting
|
||||||
|
)
|
||||||
|
Settings.Secure.putInt(
|
||||||
|
context.contentResolver,
|
||||||
|
Settings.Secure.MEDIA_CONTROLS_RESUME,
|
||||||
|
originalResumeSetting
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testWhenNoResumption_doesNothing() {
|
fun testWhenNoResumption_doesNothing() {
|
||||||
Settings.Secure.putInt(context.contentResolver,
|
Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
|
||||||
Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
|
|
||||||
|
|
||||||
// When listener is created, we do NOT register a user change listener
|
// When listener is created, we do NOT register a user change listener
|
||||||
val listener = MediaResumeListener(context, broadcastDispatcher, executor, tunerService,
|
val listener =
|
||||||
resumeBrowserFactory, dumpManager, clock)
|
MediaResumeListener(
|
||||||
|
context,
|
||||||
|
broadcastDispatcher,
|
||||||
|
executor,
|
||||||
|
tunerService,
|
||||||
|
resumeBrowserFactory,
|
||||||
|
dumpManager,
|
||||||
|
clock
|
||||||
|
)
|
||||||
listener.setManager(mediaDataManager)
|
listener.setManager(mediaDataManager)
|
||||||
verify(broadcastDispatcher, never()).registerReceiver(eq(listener.userChangeReceiver),
|
verify(broadcastDispatcher, never())
|
||||||
any(), any(), any(), anyInt(), any())
|
.registerReceiver(eq(listener.userChangeReceiver), any(), any(), any(), anyInt(), any())
|
||||||
|
|
||||||
// When data is loaded, we do NOT execute or update anything
|
// When data is loaded, we do NOT execute or update anything
|
||||||
listener.onMediaDataLoaded(KEY, OLD_KEY, data)
|
listener.onMediaDataLoaded(KEY, OLD_KEY, data)
|
||||||
@@ -175,9 +206,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
|
|||||||
fun testOnLoad_checksForResume_badService() {
|
fun testOnLoad_checksForResume_badService() {
|
||||||
setUpMbsWithValidResolveInfo()
|
setUpMbsWithValidResolveInfo()
|
||||||
|
|
||||||
whenever(resumeBrowser.testConnection()).thenAnswer {
|
whenever(resumeBrowser.testConnection()).thenAnswer { callbackCaptor.value.onError() }
|
||||||
callbackCaptor.value.onError()
|
|
||||||
}
|
|
||||||
|
|
||||||
// When media data is loaded that has not been checked yet, and does not have a MBS
|
// When media data is loaded that has not been checked yet, and does not have a MBS
|
||||||
resumeListener.onMediaDataLoaded(KEY, null, data)
|
resumeListener.onMediaDataLoaded(KEY, null, data)
|
||||||
@@ -231,7 +260,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
// But we do not tell it to add new controls
|
// But we do not tell it to add new controls
|
||||||
verify(mediaDataManager, never())
|
verify(mediaDataManager, never())
|
||||||
.addResumptionControls(anyInt(), any(), any(), any(), any(), any(), any())
|
.addResumptionControls(anyInt(), any(), any(), any(), any(), any(), any())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -258,8 +287,15 @@ class MediaResumeListenerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
// Make sure broadcast receiver is registered
|
// Make sure broadcast receiver is registered
|
||||||
resumeListener.setManager(mediaDataManager)
|
resumeListener.setManager(mediaDataManager)
|
||||||
verify(broadcastDispatcher).registerReceiver(eq(resumeListener.userChangeReceiver),
|
verify(broadcastDispatcher)
|
||||||
any(), any(), any(), anyInt(), any())
|
.registerReceiver(
|
||||||
|
eq(resumeListener.userChangeReceiver),
|
||||||
|
any(),
|
||||||
|
any(),
|
||||||
|
any(),
|
||||||
|
anyInt(),
|
||||||
|
any()
|
||||||
|
)
|
||||||
|
|
||||||
// When we get an unlock event
|
// When we get an unlock event
|
||||||
val intent = Intent(Intent.ACTION_USER_UNLOCKED)
|
val intent = Intent(Intent.ACTION_USER_UNLOCKED)
|
||||||
@@ -269,8 +305,8 @@ class MediaResumeListenerTest : SysuiTestCase() {
|
|||||||
verify(resumeBrowser, times(3)).findRecentMedia()
|
verify(resumeBrowser, times(3)).findRecentMedia()
|
||||||
|
|
||||||
// Then since the mock service found media, the manager should be informed
|
// Then since the mock service found media, the manager should be informed
|
||||||
verify(mediaDataManager, times(3)).addResumptionControls(anyInt(),
|
verify(mediaDataManager, times(3))
|
||||||
any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
|
.addResumptionControls(anyInt(), any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -309,12 +345,14 @@ class MediaResumeListenerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
// Then we save an update with the current time
|
// Then we save an update with the current time
|
||||||
verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor)))
|
verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor)))
|
||||||
componentCaptor.value.split(ResumeMediaBrowser.DELIMITER.toRegex())
|
componentCaptor.value
|
||||||
.dropLastWhile { it.isEmpty() }.forEach {
|
.split(ResumeMediaBrowser.DELIMITER.toRegex())
|
||||||
val result = it.split("/")
|
.dropLastWhile { it.isEmpty() }
|
||||||
assertThat(result.size).isEqualTo(3)
|
.forEach {
|
||||||
assertThat(result[2].toLong()).isEqualTo(currentTime)
|
val result = it.split("/")
|
||||||
}
|
assertThat(result.size).isEqualTo(3)
|
||||||
|
assertThat(result[2].toLong()).isEqualTo(currentTime)
|
||||||
|
}
|
||||||
verify(sharedPrefsEditor, times(1)).apply()
|
verify(sharedPrefsEditor, times(1)).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,8 +371,16 @@ class MediaResumeListenerTest : SysuiTestCase() {
|
|||||||
val lastPlayed = clock.currentTimeMillis()
|
val lastPlayed = clock.currentTimeMillis()
|
||||||
val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
|
val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
|
||||||
whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
|
whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
|
||||||
val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
|
val resumeListener =
|
||||||
tunerService, resumeBrowserFactory, dumpManager, clock)
|
MediaResumeListener(
|
||||||
|
mockContext,
|
||||||
|
broadcastDispatcher,
|
||||||
|
executor,
|
||||||
|
tunerService,
|
||||||
|
resumeBrowserFactory,
|
||||||
|
dumpManager,
|
||||||
|
clock
|
||||||
|
)
|
||||||
resumeListener.setManager(mediaDataManager)
|
resumeListener.setManager(mediaDataManager)
|
||||||
mediaDataManager.addListener(resumeListener)
|
mediaDataManager.addListener(resumeListener)
|
||||||
|
|
||||||
@@ -344,8 +390,8 @@ class MediaResumeListenerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
// We add its resume controls
|
// We add its resume controls
|
||||||
verify(resumeBrowser, times(1)).findRecentMedia()
|
verify(resumeBrowser, times(1)).findRecentMedia()
|
||||||
verify(mediaDataManager, times(1)).addResumptionControls(anyInt(),
|
verify(mediaDataManager, times(1))
|
||||||
any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
|
.addResumptionControls(anyInt(), any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -354,8 +400,16 @@ class MediaResumeListenerTest : SysuiTestCase() {
|
|||||||
val lastPlayed = clock.currentTimeMillis() - RESUME_MEDIA_TIMEOUT - 100
|
val lastPlayed = clock.currentTimeMillis() - RESUME_MEDIA_TIMEOUT - 100
|
||||||
val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
|
val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
|
||||||
whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
|
whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
|
||||||
val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
|
val resumeListener =
|
||||||
tunerService, resumeBrowserFactory, dumpManager, clock)
|
MediaResumeListener(
|
||||||
|
mockContext,
|
||||||
|
broadcastDispatcher,
|
||||||
|
executor,
|
||||||
|
tunerService,
|
||||||
|
resumeBrowserFactory,
|
||||||
|
dumpManager,
|
||||||
|
clock
|
||||||
|
)
|
||||||
resumeListener.setManager(mediaDataManager)
|
resumeListener.setManager(mediaDataManager)
|
||||||
mediaDataManager.addListener(resumeListener)
|
mediaDataManager.addListener(resumeListener)
|
||||||
|
|
||||||
@@ -365,8 +419,8 @@ class MediaResumeListenerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
// We do not try to add resume controls
|
// We do not try to add resume controls
|
||||||
verify(resumeBrowser, times(0)).findRecentMedia()
|
verify(resumeBrowser, times(0)).findRecentMedia()
|
||||||
verify(mediaDataManager, times(0)).addResumptionControls(anyInt(),
|
verify(mediaDataManager, times(0))
|
||||||
any(), any(), any(), any(), any(), any())
|
.addResumptionControls(anyInt(), any(), any(), any(), any(), any(), any())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -385,8 +439,16 @@ class MediaResumeListenerTest : SysuiTestCase() {
|
|||||||
val lastPlayed = currentTime - 1000
|
val lastPlayed = currentTime - 1000
|
||||||
val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
|
val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
|
||||||
whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
|
whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
|
||||||
val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
|
val resumeListener =
|
||||||
tunerService, resumeBrowserFactory, dumpManager, clock)
|
MediaResumeListener(
|
||||||
|
mockContext,
|
||||||
|
broadcastDispatcher,
|
||||||
|
executor,
|
||||||
|
tunerService,
|
||||||
|
resumeBrowserFactory,
|
||||||
|
dumpManager,
|
||||||
|
clock
|
||||||
|
)
|
||||||
resumeListener.setManager(mediaDataManager)
|
resumeListener.setManager(mediaDataManager)
|
||||||
mediaDataManager.addListener(resumeListener)
|
mediaDataManager.addListener(resumeListener)
|
||||||
|
|
||||||
@@ -396,12 +458,14 @@ class MediaResumeListenerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
// Then we store the new lastPlayed time
|
// Then we store the new lastPlayed time
|
||||||
verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor)))
|
verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor)))
|
||||||
componentCaptor.value.split(ResumeMediaBrowser.DELIMITER.toRegex())
|
componentCaptor.value
|
||||||
.dropLastWhile { it.isEmpty() }.forEach {
|
.split(ResumeMediaBrowser.DELIMITER.toRegex())
|
||||||
val result = it.split("/")
|
.dropLastWhile { it.isEmpty() }
|
||||||
assertThat(result.size).isEqualTo(3)
|
.forEach {
|
||||||
assertThat(result[2].toLong()).isEqualTo(currentTime)
|
val result = it.split("/")
|
||||||
}
|
assertThat(result.size).isEqualTo(3)
|
||||||
|
assertThat(result[2].toLong()).isEqualTo(currentTime)
|
||||||
|
}
|
||||||
verify(sharedPrefsEditor, times(1)).apply()
|
verify(sharedPrefsEditor, times(1)).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -422,9 +486,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
|
|||||||
setUpMbsWithValidResolveInfo()
|
setUpMbsWithValidResolveInfo()
|
||||||
|
|
||||||
// Set up mocks to return with an error
|
// Set up mocks to return with an error
|
||||||
whenever(resumeBrowser.testConnection()).thenAnswer {
|
whenever(resumeBrowser.testConnection()).thenAnswer { callbackCaptor.value.onError() }
|
||||||
callbackCaptor.value.onError()
|
|
||||||
}
|
|
||||||
|
|
||||||
resumeListener.onMediaDataLoaded(key = KEY, oldKey = null, data)
|
resumeListener.onMediaDataLoaded(key = KEY, oldKey = null, data)
|
||||||
executor.runAllReady()
|
executor.runAllReady()
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ import org.mockito.Mock
|
|||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
import org.mockito.Mockito.reset
|
import org.mockito.Mockito.reset
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.MockitoAnnotations
|
|
||||||
import org.mockito.Mockito.`when` as whenever
|
import org.mockito.Mockito.`when` as whenever
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
private const val PACKAGE_NAME = "package"
|
private const val PACKAGE_NAME = "package"
|
||||||
private const val CLASS_NAME = "class"
|
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 const val ROOT = "media browser root"
|
||||||
|
|
||||||
private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
|
private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
|
||||||
|
|
||||||
private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
|
private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
|
||||||
|
|
||||||
private fun <T> any(): T = Mockito.any<T>()
|
private fun <T> any(): T = Mockito.any<T>()
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@@ -57,10 +59,8 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
|
|||||||
|
|
||||||
private lateinit var resumeBrowser: TestableResumeMediaBrowser
|
private lateinit var resumeBrowser: TestableResumeMediaBrowser
|
||||||
private val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
|
private val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
|
||||||
private val description = MediaDescription.Builder()
|
private val description =
|
||||||
.setTitle(TITLE)
|
MediaDescription.Builder().setTitle(TITLE).setMediaId(MEDIA_ID).build()
|
||||||
.setMediaId(MEDIA_ID)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
@Mock lateinit var callback: ResumeMediaBrowser.Callback
|
@Mock lateinit var callback: ResumeMediaBrowser.Callback
|
||||||
@Mock lateinit var listener: MediaResumeListener
|
@Mock lateinit var listener: MediaResumeListener
|
||||||
@@ -81,19 +81,20 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
|
|||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
whenever(browserFactory.create(any(), capture(connectionCallback), any()))
|
whenever(browserFactory.create(any(), capture(connectionCallback), any()))
|
||||||
.thenReturn(browser)
|
.thenReturn(browser)
|
||||||
|
|
||||||
whenever(mediaController.transportControls).thenReturn(transportControls)
|
whenever(mediaController.transportControls).thenReturn(transportControls)
|
||||||
whenever(mediaController.sessionToken).thenReturn(token)
|
whenever(mediaController.sessionToken).thenReturn(token)
|
||||||
|
|
||||||
resumeBrowser = TestableResumeMediaBrowser(
|
resumeBrowser =
|
||||||
context,
|
TestableResumeMediaBrowser(
|
||||||
callback,
|
context,
|
||||||
component,
|
callback,
|
||||||
browserFactory,
|
component,
|
||||||
logger,
|
browserFactory,
|
||||||
mediaController
|
logger,
|
||||||
)
|
mediaController
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -329,30 +330,20 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
|
|||||||
verify(oldBrowser).disconnect()
|
verify(oldBrowser).disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Helper function to mock a failed connection */
|
||||||
* Helper function to mock a failed connection
|
|
||||||
*/
|
|
||||||
private fun setupBrowserFailed() {
|
private fun setupBrowserFailed() {
|
||||||
whenever(browser.connect()).thenAnswer {
|
whenever(browser.connect()).thenAnswer { connectionCallback.value.onConnectionFailed() }
|
||||||
connectionCallback.value.onConnectionFailed()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Helper function to mock a successful connection only */
|
||||||
* Helper function to mock a successful connection only
|
|
||||||
*/
|
|
||||||
private fun setupBrowserConnection() {
|
private fun setupBrowserConnection() {
|
||||||
whenever(browser.connect()).thenAnswer {
|
whenever(browser.connect()).thenAnswer { connectionCallback.value.onConnected() }
|
||||||
connectionCallback.value.onConnected()
|
|
||||||
}
|
|
||||||
whenever(browser.isConnected()).thenReturn(true)
|
whenever(browser.isConnected()).thenReturn(true)
|
||||||
whenever(browser.getRoot()).thenReturn(ROOT)
|
whenever(browser.getRoot()).thenReturn(ROOT)
|
||||||
whenever(browser.sessionToken).thenReturn(token)
|
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() {
|
private fun setupBrowserConnectionNoResults() {
|
||||||
setupBrowserConnection()
|
setupBrowserConnection()
|
||||||
whenever(browser.subscribe(any(), capture(subscriptionCallback))).thenAnswer {
|
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() {
|
private fun setupBrowserConnectionNotPlayable() {
|
||||||
setupBrowserConnection()
|
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() {
|
private fun setupBrowserConnectionValidMedia() {
|
||||||
setupBrowserConnection()
|
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(
|
private class TestableResumeMediaBrowser(
|
||||||
context: Context,
|
context: Context,
|
||||||
callback: Callback,
|
callback: Callback,
|
||||||
@@ -403,4 +388,4 @@ public class ResumeMediaBrowserTest : SysuiTestCase() {
|
|||||||
return fakeController
|
return fakeController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,24 +16,24 @@
|
|||||||
|
|
||||||
package com.android.systemui.media.controls.ui
|
package com.android.systemui.media.controls.ui
|
||||||
|
|
||||||
import org.mockito.Mockito.`when` as whenever
|
|
||||||
import android.graphics.drawable.Animatable2
|
import android.graphics.drawable.Animatable2
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.test.suitebuilder.annotation.SmallTest
|
import android.test.suitebuilder.annotation.SmallTest
|
||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
import android.testing.TestableLooper
|
import android.testing.TestableLooper
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import junit.framework.Assert.assertTrue
|
|
||||||
import junit.framework.Assert.assertFalse
|
import junit.framework.Assert.assertFalse
|
||||||
|
import junit.framework.Assert.assertTrue
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito.verify
|
|
||||||
import org.mockito.Mockito.times
|
|
||||||
import org.mockito.Mockito.never
|
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
|
import org.mockito.junit.MockitoJUnit
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@@ -56,8 +56,7 @@ class AnimationBindHandlerTest : SysuiTestCase() {
|
|||||||
handler = AnimationBindHandler()
|
handler = AnimationBindHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After fun tearDown() {}
|
||||||
fun tearDown() {}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun registerNoAnimations_executeCallbackImmediately() {
|
fun registerNoAnimations_executeCallbackImmediately() {
|
||||||
|
|||||||
@@ -69,21 +69,18 @@ class ColorSchemeTransitionTest : SysuiTestCase() {
|
|||||||
animatingColorTransitionFactory = { _, _, _ -> mockAnimatingTransition }
|
animatingColorTransitionFactory = { _, _, _ -> mockAnimatingTransition }
|
||||||
whenever(extractColor.invoke(colorScheme)).thenReturn(TARGET_COLOR)
|
whenever(extractColor.invoke(colorScheme)).thenReturn(TARGET_COLOR)
|
||||||
|
|
||||||
colorSchemeTransition = ColorSchemeTransition(
|
colorSchemeTransition =
|
||||||
context, mediaViewHolder, animatingColorTransitionFactory
|
ColorSchemeTransition(context, mediaViewHolder, animatingColorTransitionFactory)
|
||||||
)
|
|
||||||
|
|
||||||
colorTransition = object : AnimatingColorTransition(
|
colorTransition =
|
||||||
DEFAULT_COLOR, extractColor, applyColor
|
object : AnimatingColorTransition(DEFAULT_COLOR, extractColor, applyColor) {
|
||||||
) {
|
override fun buildAnimator(): ValueAnimator {
|
||||||
override fun buildAnimator(): ValueAnimator {
|
return valueAnimator
|
||||||
return valueAnimator
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After fun tearDown() {}
|
||||||
fun tearDown() {}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testColorTransition_nullColorScheme_keepsDefault() {
|
fun testColorTransition_nullColorScheme_keepsDefault() {
|
||||||
|
|||||||
@@ -48,17 +48,12 @@ import org.mockito.junit.MockitoJUnit
|
|||||||
@TestableLooper.RunWithLooper
|
@TestableLooper.RunWithLooper
|
||||||
class KeyguardMediaControllerTest : SysuiTestCase() {
|
class KeyguardMediaControllerTest : SysuiTestCase() {
|
||||||
|
|
||||||
@Mock
|
@Mock private lateinit var mediaHost: MediaHost
|
||||||
private lateinit var mediaHost: MediaHost
|
@Mock private lateinit var bypassController: KeyguardBypassController
|
||||||
@Mock
|
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
|
||||||
private lateinit var bypassController: KeyguardBypassController
|
@Mock private lateinit var configurationController: ConfigurationController
|
||||||
@Mock
|
|
||||||
private lateinit var statusBarStateController: SysuiStatusBarStateController
|
|
||||||
@Mock
|
|
||||||
private lateinit var configurationController: ConfigurationController
|
|
||||||
|
|
||||||
@JvmField @Rule
|
@JvmField @Rule val mockito = MockitoJUnit.rule()
|
||||||
val mockito = MockitoJUnit.rule()
|
|
||||||
|
|
||||||
private val mediaContainerView: MediaContainerView = MediaContainerView(context, null)
|
private val mediaContainerView: MediaContainerView = MediaContainerView(context, null)
|
||||||
private val hostView = UniqueObjectHostView(context)
|
private val hostView = UniqueObjectHostView(context)
|
||||||
@@ -76,15 +71,16 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
|
|||||||
hostView.layoutParams = FrameLayout.LayoutParams(100, 100)
|
hostView.layoutParams = FrameLayout.LayoutParams(100, 100)
|
||||||
testableLooper = TestableLooper.get(this)
|
testableLooper = TestableLooper.get(this)
|
||||||
fakeHandler = FakeHandler(testableLooper.looper)
|
fakeHandler = FakeHandler(testableLooper.looper)
|
||||||
keyguardMediaController = KeyguardMediaController(
|
keyguardMediaController =
|
||||||
mediaHost,
|
KeyguardMediaController(
|
||||||
bypassController,
|
mediaHost,
|
||||||
statusBarStateController,
|
bypassController,
|
||||||
context,
|
statusBarStateController,
|
||||||
settings,
|
context,
|
||||||
fakeHandler,
|
settings,
|
||||||
configurationController,
|
fakeHandler,
|
||||||
)
|
configurationController,
|
||||||
|
)
|
||||||
keyguardMediaController.attachSinglePaneContainer(mediaContainerView)
|
keyguardMediaController.attachSinglePaneContainer(mediaContainerView)
|
||||||
keyguardMediaController.useSplitShade = false
|
keyguardMediaController.useSplitShade = false
|
||||||
}
|
}
|
||||||
@@ -153,8 +149,10 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
|
|||||||
keyguardMediaController.attachSplitShadeContainer(splitShadeContainer)
|
keyguardMediaController.attachSplitShadeContainer(splitShadeContainer)
|
||||||
keyguardMediaController.useSplitShade = true
|
keyguardMediaController.useSplitShade = true
|
||||||
|
|
||||||
assertTrue("HostView wasn't attached to the split pane container",
|
assertTrue(
|
||||||
splitShadeContainer.childCount == 1)
|
"HostView wasn't attached to the split pane container",
|
||||||
|
splitShadeContainer.childCount == 1
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -162,8 +160,10 @@ class KeyguardMediaControllerTest : SysuiTestCase() {
|
|||||||
val splitShadeContainer = FrameLayout(context)
|
val splitShadeContainer = FrameLayout(context)
|
||||||
keyguardMediaController.attachSplitShadeContainer(splitShadeContainer)
|
keyguardMediaController.attachSplitShadeContainer(splitShadeContainer)
|
||||||
|
|
||||||
assertTrue("HostView wasn't attached to the single pane container",
|
assertTrue(
|
||||||
mediaContainerView.childCount == 1)
|
"HostView wasn't attached to the single pane container",
|
||||||
|
mediaContainerView.childCount == 1
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -93,22 +93,23 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
|||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
mediaCarouselController = MediaCarouselController(
|
mediaCarouselController =
|
||||||
context,
|
MediaCarouselController(
|
||||||
mediaControlPanelFactory,
|
context,
|
||||||
visualStabilityProvider,
|
mediaControlPanelFactory,
|
||||||
mediaHostStatesManager,
|
visualStabilityProvider,
|
||||||
activityStarter,
|
mediaHostStatesManager,
|
||||||
clock,
|
activityStarter,
|
||||||
executor,
|
clock,
|
||||||
mediaDataManager,
|
executor,
|
||||||
configurationController,
|
mediaDataManager,
|
||||||
falsingCollector,
|
configurationController,
|
||||||
falsingManager,
|
falsingCollector,
|
||||||
dumpManager,
|
falsingManager,
|
||||||
logger,
|
dumpManager,
|
||||||
debugLogger
|
logger,
|
||||||
)
|
debugLogger
|
||||||
|
)
|
||||||
verify(mediaDataManager).addListener(capture(listener))
|
verify(mediaDataManager).addListener(capture(listener))
|
||||||
verify(visualStabilityProvider)
|
verify(visualStabilityProvider)
|
||||||
.addPersistentReorderingAllowedListener(capture(visualStabilityCallback))
|
.addPersistentReorderingAllowedListener(capture(visualStabilityCallback))
|
||||||
@@ -121,60 +122,139 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testPlayerOrdering() {
|
fun testPlayerOrdering() {
|
||||||
// Test values: key, data, last active time
|
// Test values: key, data, last active time
|
||||||
val playingLocal = Triple("playing local",
|
val playingLocal =
|
||||||
DATA.copy(active = true, isPlaying = true,
|
Triple(
|
||||||
playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false),
|
"playing local",
|
||||||
4500L)
|
DATA.copy(
|
||||||
|
active = true,
|
||||||
|
isPlaying = true,
|
||||||
|
playbackLocation = MediaData.PLAYBACK_LOCAL,
|
||||||
|
resumption = false
|
||||||
|
),
|
||||||
|
4500L
|
||||||
|
)
|
||||||
|
|
||||||
val playingCast = Triple("playing cast",
|
val playingCast =
|
||||||
DATA.copy(active = true, isPlaying = true,
|
Triple(
|
||||||
playbackLocation = MediaData.PLAYBACK_CAST_LOCAL, resumption = false),
|
"playing cast",
|
||||||
5000L)
|
DATA.copy(
|
||||||
|
active = true,
|
||||||
|
isPlaying = true,
|
||||||
|
playbackLocation = MediaData.PLAYBACK_CAST_LOCAL,
|
||||||
|
resumption = false
|
||||||
|
),
|
||||||
|
5000L
|
||||||
|
)
|
||||||
|
|
||||||
val pausedLocal = Triple("paused local",
|
val pausedLocal =
|
||||||
DATA.copy(active = true, isPlaying = false,
|
Triple(
|
||||||
playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false),
|
"paused local",
|
||||||
1000L)
|
DATA.copy(
|
||||||
|
active = true,
|
||||||
|
isPlaying = false,
|
||||||
|
playbackLocation = MediaData.PLAYBACK_LOCAL,
|
||||||
|
resumption = false
|
||||||
|
),
|
||||||
|
1000L
|
||||||
|
)
|
||||||
|
|
||||||
val pausedCast = Triple("paused cast",
|
val pausedCast =
|
||||||
DATA.copy(active = true, isPlaying = false,
|
Triple(
|
||||||
playbackLocation = MediaData.PLAYBACK_CAST_LOCAL, resumption = false),
|
"paused cast",
|
||||||
2000L)
|
DATA.copy(
|
||||||
|
active = true,
|
||||||
|
isPlaying = false,
|
||||||
|
playbackLocation = MediaData.PLAYBACK_CAST_LOCAL,
|
||||||
|
resumption = false
|
||||||
|
),
|
||||||
|
2000L
|
||||||
|
)
|
||||||
|
|
||||||
val playingRcn = Triple("playing RCN",
|
val playingRcn =
|
||||||
DATA.copy(active = true, isPlaying = true,
|
Triple(
|
||||||
playbackLocation = MediaData.PLAYBACK_CAST_REMOTE, resumption = false),
|
"playing RCN",
|
||||||
5000L)
|
DATA.copy(
|
||||||
|
active = true,
|
||||||
|
isPlaying = true,
|
||||||
|
playbackLocation = MediaData.PLAYBACK_CAST_REMOTE,
|
||||||
|
resumption = false
|
||||||
|
),
|
||||||
|
5000L
|
||||||
|
)
|
||||||
|
|
||||||
val pausedRcn = Triple("paused RCN",
|
val pausedRcn =
|
||||||
DATA.copy(active = true, isPlaying = false,
|
Triple(
|
||||||
playbackLocation = MediaData.PLAYBACK_CAST_REMOTE, resumption = false),
|
"paused RCN",
|
||||||
5000L)
|
DATA.copy(
|
||||||
|
active = true,
|
||||||
|
isPlaying = false,
|
||||||
|
playbackLocation = MediaData.PLAYBACK_CAST_REMOTE,
|
||||||
|
resumption = false
|
||||||
|
),
|
||||||
|
5000L
|
||||||
|
)
|
||||||
|
|
||||||
val active = Triple("active",
|
val active =
|
||||||
DATA.copy(active = true, isPlaying = false,
|
Triple(
|
||||||
playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true),
|
"active",
|
||||||
250L)
|
DATA.copy(
|
||||||
|
active = true,
|
||||||
|
isPlaying = false,
|
||||||
|
playbackLocation = MediaData.PLAYBACK_LOCAL,
|
||||||
|
resumption = true
|
||||||
|
),
|
||||||
|
250L
|
||||||
|
)
|
||||||
|
|
||||||
val resume1 = Triple("resume 1",
|
val resume1 =
|
||||||
DATA.copy(active = false, isPlaying = false,
|
Triple(
|
||||||
playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true),
|
"resume 1",
|
||||||
500L)
|
DATA.copy(
|
||||||
|
active = false,
|
||||||
|
isPlaying = false,
|
||||||
|
playbackLocation = MediaData.PLAYBACK_LOCAL,
|
||||||
|
resumption = true
|
||||||
|
),
|
||||||
|
500L
|
||||||
|
)
|
||||||
|
|
||||||
val resume2 = Triple("resume 2",
|
val resume2 =
|
||||||
DATA.copy(active = false, isPlaying = false,
|
Triple(
|
||||||
playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true),
|
"resume 2",
|
||||||
1000L)
|
DATA.copy(
|
||||||
|
active = false,
|
||||||
|
isPlaying = false,
|
||||||
|
playbackLocation = MediaData.PLAYBACK_LOCAL,
|
||||||
|
resumption = true
|
||||||
|
),
|
||||||
|
1000L
|
||||||
|
)
|
||||||
|
|
||||||
val activeMoreRecent = Triple("active more recent",
|
val activeMoreRecent =
|
||||||
DATA.copy(active = false, isPlaying = false,
|
Triple(
|
||||||
playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true, lastActive = 2L),
|
"active more recent",
|
||||||
1000L)
|
DATA.copy(
|
||||||
|
active = false,
|
||||||
|
isPlaying = false,
|
||||||
|
playbackLocation = MediaData.PLAYBACK_LOCAL,
|
||||||
|
resumption = true,
|
||||||
|
lastActive = 2L
|
||||||
|
),
|
||||||
|
1000L
|
||||||
|
)
|
||||||
|
|
||||||
val activeLessRecent = Triple("active less recent",
|
val activeLessRecent =
|
||||||
DATA.copy(active = false, isPlaying = false,
|
Triple(
|
||||||
playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true, lastActive = 1L),
|
"active less recent",
|
||||||
1000L)
|
DATA.copy(
|
||||||
|
active = false,
|
||||||
|
isPlaying = false,
|
||||||
|
playbackLocation = MediaData.PLAYBACK_LOCAL,
|
||||||
|
resumption = true,
|
||||||
|
lastActive = 1L
|
||||||
|
),
|
||||||
|
1000L
|
||||||
|
)
|
||||||
// Expected ordering for media players:
|
// Expected ordering for media players:
|
||||||
// Actively playing local sessions
|
// Actively playing local sessions
|
||||||
// Actively playing cast sessions
|
// Actively playing cast sessions
|
||||||
@@ -182,13 +262,28 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
|||||||
// RCNs
|
// RCNs
|
||||||
// Resume controls, by last active
|
// Resume controls, by last active
|
||||||
|
|
||||||
val expected = listOf(playingLocal, playingCast, pausedCast, pausedLocal, playingRcn,
|
val expected =
|
||||||
pausedRcn, active, resume2, resume1)
|
listOf(
|
||||||
|
playingLocal,
|
||||||
|
playingCast,
|
||||||
|
pausedCast,
|
||||||
|
pausedLocal,
|
||||||
|
playingRcn,
|
||||||
|
pausedRcn,
|
||||||
|
active,
|
||||||
|
resume2,
|
||||||
|
resume1
|
||||||
|
)
|
||||||
|
|
||||||
expected.forEach {
|
expected.forEach {
|
||||||
clock.setCurrentTimeMillis(it.third)
|
clock.setCurrentTimeMillis(it.third)
|
||||||
MediaPlayerData.addMediaPlayer(it.first, it.second.copy(notificationKey = it.first),
|
MediaPlayerData.addMediaPlayer(
|
||||||
panel, clock, isSsReactivated = false)
|
it.first,
|
||||||
|
it.second.copy(notificationKey = it.first),
|
||||||
|
panel,
|
||||||
|
clock,
|
||||||
|
isSsReactivated = false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
for ((index, key) in MediaPlayerData.playerKeys().withIndex()) {
|
for ((index, key) in MediaPlayerData.playerKeys().withIndex()) {
|
||||||
@@ -205,8 +300,13 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
|||||||
testPlayerOrdering()
|
testPlayerOrdering()
|
||||||
|
|
||||||
// If smartspace is prioritized
|
// If smartspace is prioritized
|
||||||
MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
|
MediaPlayerData.addMediaRecommendation(
|
||||||
true, clock)
|
SMARTSPACE_KEY,
|
||||||
|
EMPTY_SMARTSPACE_MEDIA_DATA,
|
||||||
|
panel,
|
||||||
|
true,
|
||||||
|
clock
|
||||||
|
)
|
||||||
|
|
||||||
// Then it should be shown immediately after any actively playing controls
|
// Then it should be shown immediately after any actively playing controls
|
||||||
assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
|
assertTrue(MediaPlayerData.playerKeys().elementAt(2).isSsMediaRec)
|
||||||
@@ -218,9 +318,9 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
// If smartspace is prioritized
|
// If smartspace is prioritized
|
||||||
listener.value.onSmartspaceMediaDataLoaded(
|
listener.value.onSmartspaceMediaDataLoaded(
|
||||||
SMARTSPACE_KEY,
|
SMARTSPACE_KEY,
|
||||||
EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
|
EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|
||||||
// Then it should be shown immediately after any actively playing controls
|
// Then it should be shown immediately after any actively playing controls
|
||||||
@@ -233,8 +333,13 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
|||||||
testPlayerOrdering()
|
testPlayerOrdering()
|
||||||
|
|
||||||
// If smartspace is not prioritized
|
// If smartspace is not prioritized
|
||||||
MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
|
MediaPlayerData.addMediaRecommendation(
|
||||||
false, clock)
|
SMARTSPACE_KEY,
|
||||||
|
EMPTY_SMARTSPACE_MEDIA_DATA,
|
||||||
|
panel,
|
||||||
|
false,
|
||||||
|
clock
|
||||||
|
)
|
||||||
|
|
||||||
// Then it should be shown at the end of the carousel's active entries
|
// Then it should be shown at the end of the carousel's active entries
|
||||||
val idx = MediaPlayerData.playerKeys().count { it.data.active } - 1
|
val idx = MediaPlayerData.playerKeys().count { it.data.active } - 1
|
||||||
@@ -245,25 +350,36 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
|||||||
fun testPlayingExistingMediaPlayerFromCarousel_visibleMediaPlayersNotUpdated() {
|
fun testPlayingExistingMediaPlayerFromCarousel_visibleMediaPlayersNotUpdated() {
|
||||||
testPlayerOrdering()
|
testPlayerOrdering()
|
||||||
// playing paused player
|
// playing paused player
|
||||||
listener.value.onMediaDataLoaded("paused local",
|
listener.value.onMediaDataLoaded(
|
||||||
"paused local",
|
"paused local",
|
||||||
DATA.copy(active = true, isPlaying = true,
|
"paused local",
|
||||||
playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false))
|
DATA.copy(
|
||||||
listener.value.onMediaDataLoaded("playing local",
|
active = true,
|
||||||
"playing local",
|
isPlaying = true,
|
||||||
DATA.copy(active = true, isPlaying = false,
|
playbackLocation = MediaData.PLAYBACK_LOCAL,
|
||||||
playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true)
|
resumption = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
listener.value.onMediaDataLoaded(
|
||||||
|
"playing local",
|
||||||
|
"playing local",
|
||||||
|
DATA.copy(
|
||||||
|
active = true,
|
||||||
|
isPlaying = false,
|
||||||
|
playbackLocation = MediaData.PLAYBACK_LOCAL,
|
||||||
|
resumption = true
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
MediaPlayerData.getMediaPlayerIndex("paused local"),
|
MediaPlayerData.getMediaPlayerIndex("paused local"),
|
||||||
mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
|
mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
|
||||||
)
|
)
|
||||||
// paused player order should stays the same in visibleMediaPLayer map.
|
// paused player order should stays the same in visibleMediaPLayer map.
|
||||||
// paused player order should be first in mediaPlayer map.
|
// paused player order should be first in mediaPlayer map.
|
||||||
assertEquals(
|
assertEquals(
|
||||||
MediaPlayerData.visiblePlayerKeys().elementAt(3),
|
MediaPlayerData.visiblePlayerKeys().elementAt(3),
|
||||||
MediaPlayerData.playerKeys().elementAt(0)
|
MediaPlayerData.playerKeys().elementAt(0)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
@@ -285,7 +401,8 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
|||||||
mediaCarouselController.onDesiredLocationChanged(
|
mediaCarouselController.onDesiredLocationChanged(
|
||||||
MediaHierarchyManager.LOCATION_QS,
|
MediaHierarchyManager.LOCATION_QS,
|
||||||
mediaHostState,
|
mediaHostState,
|
||||||
animate = false)
|
animate = false
|
||||||
|
)
|
||||||
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QS)
|
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QS)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,7 +411,8 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
|||||||
mediaCarouselController.onDesiredLocationChanged(
|
mediaCarouselController.onDesiredLocationChanged(
|
||||||
MediaHierarchyManager.LOCATION_QQS,
|
MediaHierarchyManager.LOCATION_QQS,
|
||||||
mediaHostState,
|
mediaHostState,
|
||||||
animate = false)
|
animate = false
|
||||||
|
)
|
||||||
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS)
|
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,7 +421,8 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
|||||||
mediaCarouselController.onDesiredLocationChanged(
|
mediaCarouselController.onDesiredLocationChanged(
|
||||||
MediaHierarchyManager.LOCATION_LOCKSCREEN,
|
MediaHierarchyManager.LOCATION_LOCKSCREEN,
|
||||||
mediaHostState,
|
mediaHostState,
|
||||||
animate = false)
|
animate = false
|
||||||
|
)
|
||||||
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN)
|
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,7 +431,8 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
|||||||
mediaCarouselController.onDesiredLocationChanged(
|
mediaCarouselController.onDesiredLocationChanged(
|
||||||
MediaHierarchyManager.LOCATION_DREAM_OVERLAY,
|
MediaHierarchyManager.LOCATION_DREAM_OVERLAY,
|
||||||
mediaHostState,
|
mediaHostState,
|
||||||
animate = false)
|
animate = false
|
||||||
|
)
|
||||||
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY)
|
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,10 +441,8 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
|||||||
val packageName = "smartspace package"
|
val packageName = "smartspace package"
|
||||||
val instanceId = InstanceId.fakeInstanceId(123)
|
val instanceId = InstanceId.fakeInstanceId(123)
|
||||||
|
|
||||||
val smartspaceData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
|
val smartspaceData =
|
||||||
packageName = packageName,
|
EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = packageName, instanceId = instanceId)
|
||||||
instanceId = instanceId
|
|
||||||
)
|
|
||||||
MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, smartspaceData, panel, true, clock)
|
MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, smartspaceData, panel, true, clock)
|
||||||
mediaCarouselController.removePlayer(SMARTSPACE_KEY)
|
mediaCarouselController.removePlayer(SMARTSPACE_KEY)
|
||||||
|
|
||||||
@@ -333,64 +451,99 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testMediaLoaded_ScrollToActivePlayer() {
|
fun testMediaLoaded_ScrollToActivePlayer() {
|
||||||
listener.value.onMediaDataLoaded("playing local",
|
listener.value.onMediaDataLoaded(
|
||||||
null,
|
"playing local",
|
||||||
DATA.copy(active = true, isPlaying = true,
|
null,
|
||||||
playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false)
|
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.
|
// adding a media recommendation card.
|
||||||
listener.value.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA,
|
listener.value.onSmartspaceMediaDataLoaded(
|
||||||
false)
|
SMARTSPACE_KEY,
|
||||||
|
EMPTY_SMARTSPACE_MEDIA_DATA,
|
||||||
|
false
|
||||||
|
)
|
||||||
mediaCarouselController.shouldScrollToKey = true
|
mediaCarouselController.shouldScrollToKey = true
|
||||||
// switching between media players.
|
// switching between media players.
|
||||||
listener.value.onMediaDataLoaded("playing local",
|
listener.value.onMediaDataLoaded(
|
||||||
"playing local",
|
"playing local",
|
||||||
DATA.copy(active = true, isPlaying = false,
|
"playing local",
|
||||||
playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true)
|
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(
|
assertEquals(
|
||||||
MediaPlayerData.getMediaPlayerIndex("paused local"),
|
MediaPlayerData.getMediaPlayerIndex("paused local"),
|
||||||
mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
|
mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() {
|
fun testMediaLoadedFromRecommendationCard_ScrollToActivePlayer() {
|
||||||
listener.value.onSmartspaceMediaDataLoaded(
|
listener.value.onSmartspaceMediaDataLoaded(
|
||||||
SMARTSPACE_KEY,
|
SMARTSPACE_KEY,
|
||||||
EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = "PACKAGE_NAME", isActive = true),
|
EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = "PACKAGE_NAME", isActive = true),
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
listener.value.onMediaDataLoaded("playing local",
|
listener.value.onMediaDataLoaded(
|
||||||
null,
|
"playing local",
|
||||||
DATA.copy(active = true, isPlaying = true,
|
null,
|
||||||
playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false)
|
DATA.copy(
|
||||||
|
active = true,
|
||||||
|
isPlaying = true,
|
||||||
|
playbackLocation = MediaData.PLAYBACK_LOCAL,
|
||||||
|
resumption = false
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
var playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local")
|
var playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local")
|
||||||
assertEquals(
|
assertEquals(
|
||||||
playerIndex,
|
playerIndex,
|
||||||
mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
|
mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
|
||||||
)
|
)
|
||||||
assertEquals(playerIndex, 0)
|
assertEquals(playerIndex, 0)
|
||||||
|
|
||||||
// Replaying the same media player one more time.
|
// Replaying the same media player one more time.
|
||||||
// And check that the card stays in its position.
|
// And check that the card stays in its position.
|
||||||
mediaCarouselController.shouldScrollToKey = true
|
mediaCarouselController.shouldScrollToKey = true
|
||||||
listener.value.onMediaDataLoaded("playing local",
|
listener.value.onMediaDataLoaded(
|
||||||
null,
|
"playing local",
|
||||||
DATA.copy(active = true, isPlaying = true,
|
null,
|
||||||
playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false,
|
DATA.copy(
|
||||||
packageName = "PACKAGE_NAME")
|
active = true,
|
||||||
|
isPlaying = true,
|
||||||
|
playbackLocation = MediaData.PLAYBACK_LOCAL,
|
||||||
|
resumption = false,
|
||||||
|
packageName = "PACKAGE_NAME"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local")
|
playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local")
|
||||||
assertEquals(playerIndex, 0)
|
assertEquals(playerIndex, 0)
|
||||||
@@ -423,37 +576,43 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testGetCurrentVisibleMediaContentIntent() {
|
fun testGetCurrentVisibleMediaContentIntent() {
|
||||||
val clickIntent1 = mock(PendingIntent::class.java)
|
val clickIntent1 = mock(PendingIntent::class.java)
|
||||||
val player1 = Triple("player1",
|
val player1 = Triple("player1", DATA.copy(clickIntent = clickIntent1), 1000L)
|
||||||
DATA.copy(clickIntent = clickIntent1),
|
|
||||||
1000L)
|
|
||||||
clock.setCurrentTimeMillis(player1.third)
|
clock.setCurrentTimeMillis(player1.third)
|
||||||
MediaPlayerData.addMediaPlayer(player1.first,
|
MediaPlayerData.addMediaPlayer(
|
||||||
player1.second.copy(notificationKey = player1.first),
|
player1.first,
|
||||||
panel, clock, isSsReactivated = false)
|
player1.second.copy(notificationKey = player1.first),
|
||||||
|
panel,
|
||||||
|
clock,
|
||||||
|
isSsReactivated = false
|
||||||
|
)
|
||||||
|
|
||||||
assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent1)
|
assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent1)
|
||||||
|
|
||||||
val clickIntent2 = mock(PendingIntent::class.java)
|
val clickIntent2 = mock(PendingIntent::class.java)
|
||||||
val player2 = Triple("player2",
|
val player2 = Triple("player2", DATA.copy(clickIntent = clickIntent2), 2000L)
|
||||||
DATA.copy(clickIntent = clickIntent2),
|
|
||||||
2000L)
|
|
||||||
clock.setCurrentTimeMillis(player2.third)
|
clock.setCurrentTimeMillis(player2.third)
|
||||||
MediaPlayerData.addMediaPlayer(player2.first,
|
MediaPlayerData.addMediaPlayer(
|
||||||
player2.second.copy(notificationKey = player2.first),
|
player2.first,
|
||||||
panel, clock, isSsReactivated = false)
|
player2.second.copy(notificationKey = player2.first),
|
||||||
|
panel,
|
||||||
|
clock,
|
||||||
|
isSsReactivated = false
|
||||||
|
)
|
||||||
|
|
||||||
// mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is
|
// mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is
|
||||||
// added to the front because it was active more recently.
|
// added to the front because it was active more recently.
|
||||||
assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
|
assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent2)
|
||||||
|
|
||||||
val clickIntent3 = mock(PendingIntent::class.java)
|
val clickIntent3 = mock(PendingIntent::class.java)
|
||||||
val player3 = Triple("player3",
|
val player3 = Triple("player3", DATA.copy(clickIntent = clickIntent3), 500L)
|
||||||
DATA.copy(clickIntent = clickIntent3),
|
|
||||||
500L)
|
|
||||||
clock.setCurrentTimeMillis(player3.third)
|
clock.setCurrentTimeMillis(player3.third)
|
||||||
MediaPlayerData.addMediaPlayer(player3.first,
|
MediaPlayerData.addMediaPlayer(
|
||||||
player3.second.copy(notificationKey = player3.first),
|
player3.first,
|
||||||
panel, clock, isSsReactivated = false)
|
player3.second.copy(notificationKey = player3.first),
|
||||||
|
panel,
|
||||||
|
clock,
|
||||||
|
isSsReactivated = false
|
||||||
|
)
|
||||||
|
|
||||||
// mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is
|
// mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is
|
||||||
// added to the end because it was active less recently.
|
// added to the end because it was active less recently.
|
||||||
@@ -463,10 +622,14 @@ class MediaCarouselControllerTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() {
|
fun testSetCurrentState_UpdatePageIndicatorAlphaWhenSquish() {
|
||||||
val delta = 0.0001F
|
val delta = 0.0001F
|
||||||
val paginationSquishMiddle = TRANSFORM_BEZIER.getInterpolation(
|
val paginationSquishMiddle =
|
||||||
(PAGINATION_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION)
|
TRANSFORM_BEZIER.getInterpolation(
|
||||||
val paginationSquishEnd = TRANSFORM_BEZIER.getInterpolation(
|
(PAGINATION_DELAY + DURATION / 2) / ANIMATION_BASE_DURATION
|
||||||
(PAGINATION_DELAY + DURATION) / ANIMATION_BASE_DURATION)
|
)
|
||||||
|
val paginationSquishEnd =
|
||||||
|
TRANSFORM_BEZIER.getInterpolation(
|
||||||
|
(PAGINATION_DELAY + DURATION) / ANIMATION_BASE_DURATION
|
||||||
|
)
|
||||||
whenever(mediaHostStatesManager.mediaHostStates)
|
whenever(mediaHostStatesManager.mediaHostStates)
|
||||||
.thenReturn(mutableMapOf(LOCATION_QS to mediaHostState))
|
.thenReturn(mutableMapOf(LOCATION_QS to mediaHostState))
|
||||||
whenever(mediaHostState.visible).thenReturn(true)
|
whenever(mediaHostState.visible).thenReturn(true)
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ import com.android.systemui.SysuiTestCase
|
|||||||
import com.android.systemui.bluetooth.BroadcastDialogController
|
import com.android.systemui.bluetooth.BroadcastDialogController
|
||||||
import com.android.systemui.broadcast.BroadcastSender
|
import com.android.systemui.broadcast.BroadcastSender
|
||||||
import com.android.systemui.media.controls.MediaTestUtils
|
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.GutsViewHolder
|
||||||
import com.android.systemui.media.controls.models.player.MediaAction
|
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.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.MediaDeviceData
|
||||||
import com.android.systemui.media.controls.models.player.MediaViewHolder
|
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.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.RecommendationViewHolder
|
||||||
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
|
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.pipeline.MediaDataManager
|
||||||
import com.android.systemui.media.controls.util.MediaUiEventLogger
|
import com.android.systemui.media.controls.util.MediaUiEventLogger
|
||||||
import com.android.systemui.media.dialog.MediaOutputDialogFactory
|
import com.android.systemui.media.dialog.MediaOutputDialogFactory
|
||||||
@@ -177,8 +177,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
|
|
||||||
private lateinit var session: MediaSession
|
private lateinit var session: MediaSession
|
||||||
private lateinit var device: MediaDeviceData
|
private lateinit var device: MediaDeviceData
|
||||||
private val disabledDevice = MediaDeviceData(false, null, DISABLED_DEVICE_NAME, null,
|
private val disabledDevice =
|
||||||
showBroadcastButton = false)
|
MediaDeviceData(false, null, DISABLED_DEVICE_NAME, null, showBroadcastButton = false)
|
||||||
private lateinit var mediaData: MediaData
|
private lateinit var mediaData: MediaData
|
||||||
private val clock = FakeSystemClock()
|
private val clock = FakeSystemClock()
|
||||||
@Mock private lateinit var logger: MediaUiEventLogger
|
@Mock private lateinit var logger: MediaUiEventLogger
|
||||||
@@ -225,24 +225,27 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE)
|
whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE)
|
||||||
context.setMockPackageManager(packageManager)
|
context.setMockPackageManager(packageManager)
|
||||||
|
|
||||||
player = object : MediaControlPanel(
|
player =
|
||||||
context,
|
object :
|
||||||
bgExecutor,
|
MediaControlPanel(
|
||||||
mainExecutor,
|
context,
|
||||||
activityStarter,
|
bgExecutor,
|
||||||
broadcastSender,
|
mainExecutor,
|
||||||
mediaViewController,
|
activityStarter,
|
||||||
seekBarViewModel,
|
broadcastSender,
|
||||||
Lazy { mediaDataManager },
|
mediaViewController,
|
||||||
mediaOutputDialogFactory,
|
seekBarViewModel,
|
||||||
mediaCarouselController,
|
Lazy { mediaDataManager },
|
||||||
falsingManager,
|
mediaOutputDialogFactory,
|
||||||
clock,
|
mediaCarouselController,
|
||||||
logger,
|
falsingManager,
|
||||||
keyguardStateController,
|
clock,
|
||||||
activityIntentHelper,
|
logger,
|
||||||
lockscreenUserManager,
|
keyguardStateController,
|
||||||
broadcastDialogController) {
|
activityIntentHelper,
|
||||||
|
lockscreenUserManager,
|
||||||
|
broadcastDialogController
|
||||||
|
) {
|
||||||
override fun loadAnimator(
|
override fun loadAnimator(
|
||||||
animId: Int,
|
animId: Int,
|
||||||
otionInterpolator: Interpolator,
|
otionInterpolator: Interpolator,
|
||||||
@@ -263,18 +266,20 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
// Set valid recommendation data
|
// Set valid recommendation data
|
||||||
val extras = Bundle()
|
val extras = Bundle()
|
||||||
extras.putString(KEY_SMARTSPACE_APP_NAME, REC_APP_NAME)
|
extras.putString(KEY_SMARTSPACE_APP_NAME, REC_APP_NAME)
|
||||||
val intent = Intent().apply {
|
val intent =
|
||||||
putExtras(extras)
|
Intent().apply {
|
||||||
setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
putExtras(extras)
|
||||||
}
|
setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
}
|
||||||
whenever(smartspaceAction.intent).thenReturn(intent)
|
whenever(smartspaceAction.intent).thenReturn(intent)
|
||||||
whenever(smartspaceAction.extras).thenReturn(extras)
|
whenever(smartspaceAction.extras).thenReturn(extras)
|
||||||
smartspaceData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
|
smartspaceData =
|
||||||
packageName = PACKAGE,
|
EMPTY_SMARTSPACE_MEDIA_DATA.copy(
|
||||||
instanceId = instanceId,
|
packageName = PACKAGE,
|
||||||
recommendations = listOf(smartspaceAction, smartspaceAction, smartspaceAction),
|
instanceId = instanceId,
|
||||||
cardAction = smartspaceAction
|
recommendations = listOf(smartspaceAction, smartspaceAction, smartspaceAction),
|
||||||
)
|
cardAction = smartspaceAction
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initGutsViewHolderMocks() {
|
private fun initGutsViewHolderMocks() {
|
||||||
@@ -292,36 +297,39 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initDeviceMediaData(shouldShowBroadcastButton: Boolean, name: String) {
|
private fun initDeviceMediaData(shouldShowBroadcastButton: Boolean, name: String) {
|
||||||
device = MediaDeviceData(true, null, name, null,
|
device =
|
||||||
showBroadcastButton = shouldShowBroadcastButton)
|
MediaDeviceData(true, null, name, null, showBroadcastButton = shouldShowBroadcastButton)
|
||||||
|
|
||||||
// Create media session
|
// Create media session
|
||||||
val metadataBuilder = MediaMetadata.Builder().apply {
|
val metadataBuilder =
|
||||||
putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
|
MediaMetadata.Builder().apply {
|
||||||
putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
|
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)
|
val playbackBuilder =
|
||||||
setActions(PlaybackState.ACTION_PLAY)
|
PlaybackState.Builder().apply {
|
||||||
}
|
setState(PlaybackState.STATE_PAUSED, 6000L, 1f)
|
||||||
session = MediaSession(context, SESSION_KEY).apply {
|
setActions(PlaybackState.ACTION_PLAY)
|
||||||
setMetadata(metadataBuilder.build())
|
}
|
||||||
setPlaybackState(playbackBuilder.build())
|
session =
|
||||||
}
|
MediaSession(context, SESSION_KEY).apply {
|
||||||
|
setMetadata(metadataBuilder.build())
|
||||||
|
setPlaybackState(playbackBuilder.build())
|
||||||
|
}
|
||||||
session.setActive(true)
|
session.setActive(true)
|
||||||
|
|
||||||
mediaData = MediaTestUtils.emptyMediaData.copy(
|
mediaData =
|
||||||
|
MediaTestUtils.emptyMediaData.copy(
|
||||||
artist = ARTIST,
|
artist = ARTIST,
|
||||||
song = TITLE,
|
song = TITLE,
|
||||||
packageName = PACKAGE,
|
packageName = PACKAGE,
|
||||||
token = session.sessionToken,
|
token = session.sessionToken,
|
||||||
device = device,
|
device = device,
|
||||||
instanceId = instanceId)
|
instanceId = instanceId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Initialize elements in media view holder */
|
||||||
* Initialize elements in media view holder
|
|
||||||
*/
|
|
||||||
private fun initMediaViewHolderMocks() {
|
private fun initMediaViewHolderMocks() {
|
||||||
whenever(seekBarViewModel.progress).thenReturn(seekBarData)
|
whenever(seekBarViewModel.progress).thenReturn(seekBarData)
|
||||||
|
|
||||||
@@ -362,7 +370,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
action1.id,
|
action1.id,
|
||||||
action2.id,
|
action2.id,
|
||||||
action3.id,
|
action3.id,
|
||||||
action4.id)
|
action4.id
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
whenever(viewHolder.player).thenReturn(view)
|
whenever(viewHolder.player).thenReturn(view)
|
||||||
@@ -407,9 +416,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
whenever(viewHolder.actionsTopBarrier).thenReturn(actionsTopBarrier)
|
whenever(viewHolder.actionsTopBarrier).thenReturn(actionsTopBarrier)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Initialize elements for the recommendation view holder */
|
||||||
* Initialize elements for the recommendation view holder
|
|
||||||
*/
|
|
||||||
private fun initRecommendationViewHolderMocks() {
|
private fun initRecommendationViewHolderMocks() {
|
||||||
recTitle1 = TextView(context)
|
recTitle1 = TextView(context)
|
||||||
recTitle2 = TextView(context)
|
recTitle2 = TextView(context)
|
||||||
@@ -432,9 +439,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
.thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3))
|
.thenReturn(listOf(coverContainer1, coverContainer2, coverContainer3))
|
||||||
whenever(recommendationViewHolder.mediaTitles)
|
whenever(recommendationViewHolder.mediaTitles)
|
||||||
.thenReturn(listOf(recTitle1, recTitle2, recTitle3))
|
.thenReturn(listOf(recTitle1, recTitle2, recTitle3))
|
||||||
whenever(recommendationViewHolder.mediaSubtitles).thenReturn(
|
whenever(recommendationViewHolder.mediaSubtitles)
|
||||||
listOf(recSubtitle1, recSubtitle2, recSubtitle3)
|
.thenReturn(listOf(recSubtitle1, recSubtitle2, recSubtitle3))
|
||||||
)
|
|
||||||
|
|
||||||
whenever(recommendationViewHolder.gutsViewHolder).thenReturn(gutsViewHolder)
|
whenever(recommendationViewHolder.gutsViewHolder).thenReturn(gutsViewHolder)
|
||||||
|
|
||||||
@@ -466,12 +472,13 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
fun bindSemanticActions() {
|
fun bindSemanticActions() {
|
||||||
val icon = context.getDrawable(android.R.drawable.ic_media_play)
|
val icon = context.getDrawable(android.R.drawable.ic_media_play)
|
||||||
val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
|
val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
|
||||||
val semanticActions = MediaButton(
|
val semanticActions =
|
||||||
playOrPause = MediaAction(icon, Runnable {}, "play", bg),
|
MediaButton(
|
||||||
nextOrCustom = MediaAction(icon, Runnable {}, "next", bg),
|
playOrPause = MediaAction(icon, Runnable {}, "play", bg),
|
||||||
custom0 = MediaAction(icon, null, "custom 0", bg),
|
nextOrCustom = MediaAction(icon, Runnable {}, "next", bg),
|
||||||
custom1 = MediaAction(icon, null, "custom 1", bg)
|
custom0 = MediaAction(icon, null, "custom 0", bg),
|
||||||
)
|
custom1 = MediaAction(icon, null, "custom 1", bg)
|
||||||
|
)
|
||||||
val state = mediaData.copy(semanticActions = semanticActions)
|
val state = mediaData.copy(semanticActions = semanticActions)
|
||||||
player.attachPlayer(viewHolder)
|
player.attachPlayer(viewHolder)
|
||||||
player.bindPlayer(state, PACKAGE)
|
player.bindPlayer(state, PACKAGE)
|
||||||
@@ -514,15 +521,16 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
|
val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
|
||||||
|
|
||||||
// Setup button state: no prev or next button and their slots reserved
|
// Setup button state: no prev or next button and their slots reserved
|
||||||
val semanticActions = MediaButton(
|
val semanticActions =
|
||||||
playOrPause = MediaAction(icon, Runnable {}, "play", bg),
|
MediaButton(
|
||||||
nextOrCustom = null,
|
playOrPause = MediaAction(icon, Runnable {}, "play", bg),
|
||||||
prevOrCustom = null,
|
nextOrCustom = null,
|
||||||
custom0 = MediaAction(icon, null, "custom 0", bg),
|
prevOrCustom = null,
|
||||||
custom1 = MediaAction(icon, null, "custom 1", bg),
|
custom0 = MediaAction(icon, null, "custom 0", bg),
|
||||||
false,
|
custom1 = MediaAction(icon, null, "custom 1", bg),
|
||||||
true
|
false,
|
||||||
)
|
true
|
||||||
|
)
|
||||||
val state = mediaData.copy(semanticActions = semanticActions)
|
val state = mediaData.copy(semanticActions = semanticActions)
|
||||||
|
|
||||||
player.attachPlayer(viewHolder)
|
player.attachPlayer(viewHolder)
|
||||||
@@ -543,15 +551,16 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
|
val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
|
||||||
|
|
||||||
// Setup button state: no prev or next button and their slots reserved
|
// Setup button state: no prev or next button and their slots reserved
|
||||||
val semanticActions = MediaButton(
|
val semanticActions =
|
||||||
playOrPause = MediaAction(icon, Runnable {}, "play", bg),
|
MediaButton(
|
||||||
nextOrCustom = null,
|
playOrPause = MediaAction(icon, Runnable {}, "play", bg),
|
||||||
prevOrCustom = null,
|
nextOrCustom = null,
|
||||||
custom0 = MediaAction(icon, null, "custom 0", bg),
|
prevOrCustom = null,
|
||||||
custom1 = MediaAction(icon, null, "custom 1", bg),
|
custom0 = MediaAction(icon, null, "custom 0", bg),
|
||||||
true,
|
custom1 = MediaAction(icon, null, "custom 1", bg),
|
||||||
false
|
true,
|
||||||
)
|
false
|
||||||
|
)
|
||||||
val state = mediaData.copy(semanticActions = semanticActions)
|
val state = mediaData.copy(semanticActions = semanticActions)
|
||||||
|
|
||||||
player.attachPlayer(viewHolder)
|
player.attachPlayer(viewHolder)
|
||||||
@@ -659,10 +668,11 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
useRealConstraintSets()
|
useRealConstraintSets()
|
||||||
|
|
||||||
val icon = context.getDrawable(android.R.drawable.ic_media_play)
|
val icon = context.getDrawable(android.R.drawable.ic_media_play)
|
||||||
val semanticActions = MediaButton(
|
val semanticActions =
|
||||||
playOrPause = MediaAction(icon, Runnable {}, "play", null),
|
MediaButton(
|
||||||
nextOrCustom = MediaAction(icon, Runnable {}, "next", null)
|
playOrPause = MediaAction(icon, Runnable {}, "play", null),
|
||||||
)
|
nextOrCustom = MediaAction(icon, Runnable {}, "next", null)
|
||||||
|
)
|
||||||
val state = mediaData.copy(semanticActions = semanticActions)
|
val state = mediaData.copy(semanticActions = semanticActions)
|
||||||
|
|
||||||
player.attachPlayer(viewHolder)
|
player.attachPlayer(viewHolder)
|
||||||
@@ -732,9 +742,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
useRealConstraintSets()
|
useRealConstraintSets()
|
||||||
|
|
||||||
val icon = context.getDrawable(android.R.drawable.ic_media_play)
|
val icon = context.getDrawable(android.R.drawable.ic_media_play)
|
||||||
val semanticActions = MediaButton(
|
val semanticActions =
|
||||||
nextOrCustom = MediaAction(icon, Runnable {}, "next", null)
|
MediaButton(nextOrCustom = MediaAction(icon, Runnable {}, "next", null))
|
||||||
)
|
|
||||||
val state = mediaData.copy(semanticActions = semanticActions)
|
val state = mediaData.copy(semanticActions = semanticActions)
|
||||||
|
|
||||||
player.attachPlayer(viewHolder)
|
player.attachPlayer(viewHolder)
|
||||||
@@ -749,10 +758,11 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun bind_notScrubbing_scrubbingViewsGone() {
|
fun bind_notScrubbing_scrubbingViewsGone() {
|
||||||
val icon = context.getDrawable(android.R.drawable.ic_media_play)
|
val icon = context.getDrawable(android.R.drawable.ic_media_play)
|
||||||
val semanticActions = MediaButton(
|
val semanticActions =
|
||||||
prevOrCustom = MediaAction(icon, {}, "prev", null),
|
MediaButton(
|
||||||
nextOrCustom = MediaAction(icon, {}, "next", null)
|
prevOrCustom = MediaAction(icon, {}, "prev", null),
|
||||||
)
|
nextOrCustom = MediaAction(icon, {}, "next", null)
|
||||||
|
)
|
||||||
val state = mediaData.copy(semanticActions = semanticActions)
|
val state = mediaData.copy(semanticActions = semanticActions)
|
||||||
|
|
||||||
player.attachPlayer(viewHolder)
|
player.attachPlayer(viewHolder)
|
||||||
@@ -783,10 +793,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun setIsScrubbing_noPrevButton_scrubbingTimesNotShown() {
|
fun setIsScrubbing_noPrevButton_scrubbingTimesNotShown() {
|
||||||
val icon = context.getDrawable(android.R.drawable.ic_media_play)
|
val icon = context.getDrawable(android.R.drawable.ic_media_play)
|
||||||
val semanticActions = MediaButton(
|
val semanticActions =
|
||||||
prevOrCustom = null,
|
MediaButton(prevOrCustom = null, nextOrCustom = MediaAction(icon, {}, "next", null))
|
||||||
nextOrCustom = MediaAction(icon, {}, "next", null)
|
|
||||||
)
|
|
||||||
val state = mediaData.copy(semanticActions = semanticActions)
|
val state = mediaData.copy(semanticActions = semanticActions)
|
||||||
player.attachPlayer(viewHolder)
|
player.attachPlayer(viewHolder)
|
||||||
player.bindPlayer(state, PACKAGE)
|
player.bindPlayer(state, PACKAGE)
|
||||||
@@ -803,10 +811,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun setIsScrubbing_noNextButton_scrubbingTimesNotShown() {
|
fun setIsScrubbing_noNextButton_scrubbingTimesNotShown() {
|
||||||
val icon = context.getDrawable(android.R.drawable.ic_media_play)
|
val icon = context.getDrawable(android.R.drawable.ic_media_play)
|
||||||
val semanticActions = MediaButton(
|
val semanticActions =
|
||||||
prevOrCustom = MediaAction(icon, {}, "prev", null),
|
MediaButton(prevOrCustom = MediaAction(icon, {}, "prev", null), nextOrCustom = null)
|
||||||
nextOrCustom = null
|
|
||||||
)
|
|
||||||
val state = mediaData.copy(semanticActions = semanticActions)
|
val state = mediaData.copy(semanticActions = semanticActions)
|
||||||
player.attachPlayer(viewHolder)
|
player.attachPlayer(viewHolder)
|
||||||
player.bindPlayer(state, PACKAGE)
|
player.bindPlayer(state, PACKAGE)
|
||||||
@@ -823,10 +829,11 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun setIsScrubbing_true_scrubbingViewsShownAndPrevNextHiddenOnlyInExpanded() {
|
fun setIsScrubbing_true_scrubbingViewsShownAndPrevNextHiddenOnlyInExpanded() {
|
||||||
val icon = context.getDrawable(android.R.drawable.ic_media_play)
|
val icon = context.getDrawable(android.R.drawable.ic_media_play)
|
||||||
val semanticActions = MediaButton(
|
val semanticActions =
|
||||||
prevOrCustom = MediaAction(icon, {}, "prev", null),
|
MediaButton(
|
||||||
nextOrCustom = MediaAction(icon, {}, "next", null)
|
prevOrCustom = MediaAction(icon, {}, "prev", null),
|
||||||
)
|
nextOrCustom = MediaAction(icon, {}, "next", null)
|
||||||
|
)
|
||||||
val state = mediaData.copy(semanticActions = semanticActions)
|
val state = mediaData.copy(semanticActions = semanticActions)
|
||||||
player.attachPlayer(viewHolder)
|
player.attachPlayer(viewHolder)
|
||||||
player.bindPlayer(state, PACKAGE)
|
player.bindPlayer(state, PACKAGE)
|
||||||
@@ -845,10 +852,11 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun setIsScrubbing_trueThenFalse_scrubbingTimeGoneAtEnd() {
|
fun setIsScrubbing_trueThenFalse_scrubbingTimeGoneAtEnd() {
|
||||||
val icon = context.getDrawable(android.R.drawable.ic_media_play)
|
val icon = context.getDrawable(android.R.drawable.ic_media_play)
|
||||||
val semanticActions = MediaButton(
|
val semanticActions =
|
||||||
prevOrCustom = MediaAction(icon, {}, "prev", null),
|
MediaButton(
|
||||||
nextOrCustom = MediaAction(icon, {}, "next", null)
|
prevOrCustom = MediaAction(icon, {}, "prev", null),
|
||||||
)
|
nextOrCustom = MediaAction(icon, {}, "next", null)
|
||||||
|
)
|
||||||
val state = mediaData.copy(semanticActions = semanticActions)
|
val state = mediaData.copy(semanticActions = semanticActions)
|
||||||
|
|
||||||
player.attachPlayer(viewHolder)
|
player.attachPlayer(viewHolder)
|
||||||
@@ -872,18 +880,20 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
fun bindNotificationActions() {
|
fun bindNotificationActions() {
|
||||||
val icon = context.getDrawable(android.R.drawable.ic_media_play)
|
val icon = context.getDrawable(android.R.drawable.ic_media_play)
|
||||||
val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
|
val bg = context.getDrawable(R.drawable.qs_media_round_button_background)
|
||||||
val actions = listOf(
|
val actions =
|
||||||
MediaAction(icon, Runnable {}, "previous", bg),
|
listOf(
|
||||||
MediaAction(icon, Runnable {}, "play", bg),
|
MediaAction(icon, Runnable {}, "previous", bg),
|
||||||
MediaAction(icon, null, "next", bg),
|
MediaAction(icon, Runnable {}, "play", bg),
|
||||||
MediaAction(icon, null, "custom 0", bg),
|
MediaAction(icon, null, "next", bg),
|
||||||
MediaAction(icon, Runnable {}, "custom 1", bg)
|
MediaAction(icon, null, "custom 0", bg),
|
||||||
)
|
MediaAction(icon, Runnable {}, "custom 1", bg)
|
||||||
val state = mediaData.copy(
|
)
|
||||||
actions = actions,
|
val state =
|
||||||
actionsToShowInCompact = listOf(1, 2),
|
mediaData.copy(
|
||||||
semanticActions = null
|
actions = actions,
|
||||||
)
|
actionsToShowInCompact = listOf(1, 2),
|
||||||
|
semanticActions = null
|
||||||
|
)
|
||||||
|
|
||||||
player.attachPlayer(viewHolder)
|
player.attachPlayer(viewHolder)
|
||||||
player.bindPlayer(state, PACKAGE)
|
player.bindPlayer(state, PACKAGE)
|
||||||
@@ -931,15 +941,12 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
|
|
||||||
val icon = context.getDrawable(R.drawable.ic_media_play)
|
val icon = context.getDrawable(R.drawable.ic_media_play)
|
||||||
val bg = context.getDrawable(R.drawable.ic_media_play_container)
|
val bg = context.getDrawable(R.drawable.ic_media_play_container)
|
||||||
val semanticActions0 = MediaButton(
|
val semanticActions0 =
|
||||||
playOrPause = MediaAction(mockAvd0, Runnable {}, "play", null)
|
MediaButton(playOrPause = MediaAction(mockAvd0, Runnable {}, "play", null))
|
||||||
)
|
val semanticActions1 =
|
||||||
val semanticActions1 = MediaButton(
|
MediaButton(playOrPause = MediaAction(mockAvd1, Runnable {}, "pause", null))
|
||||||
playOrPause = MediaAction(mockAvd1, Runnable {}, "pause", null)
|
val semanticActions2 =
|
||||||
)
|
MediaButton(playOrPause = MediaAction(mockAvd2, Runnable {}, "loading", null))
|
||||||
val semanticActions2 = MediaButton(
|
|
||||||
playOrPause = MediaAction(mockAvd2, Runnable {}, "loading", null)
|
|
||||||
)
|
|
||||||
val state0 = mediaData.copy(semanticActions = semanticActions0)
|
val state0 = mediaData.copy(semanticActions = semanticActions0)
|
||||||
val state1 = mediaData.copy(semanticActions = semanticActions1)
|
val state1 = mediaData.copy(semanticActions = semanticActions1)
|
||||||
val state2 = mediaData.copy(semanticActions = semanticActions2)
|
val state2 = mediaData.copy(semanticActions = semanticActions2)
|
||||||
@@ -1102,11 +1109,10 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
|
|
||||||
val mockAvd0 = mock(AnimatedVectorDrawable::class.java)
|
val mockAvd0 = mock(AnimatedVectorDrawable::class.java)
|
||||||
whenever(mockAvd0.mutate()).thenReturn(mockAvd0)
|
whenever(mockAvd0.mutate()).thenReturn(mockAvd0)
|
||||||
val semanticActions0 = MediaButton(
|
val semanticActions0 =
|
||||||
playOrPause = MediaAction(mockAvd0, Runnable {}, "play", null)
|
MediaButton(playOrPause = MediaAction(mockAvd0, Runnable {}, "play", null))
|
||||||
)
|
val state =
|
||||||
val state = mediaData.copy(resumption = true, semanticActions = semanticActions0,
|
mediaData.copy(resumption = true, semanticActions = semanticActions0, isPlaying = false)
|
||||||
isPlaying = false)
|
|
||||||
player.attachPlayer(viewHolder)
|
player.attachPlayer(viewHolder)
|
||||||
player.bindPlayer(state, PACKAGE)
|
player.bindPlayer(state, PACKAGE)
|
||||||
assertThat(seamlessText.getText()).isEqualTo(APP_NAME)
|
assertThat(seamlessText.getText()).isEqualTo(APP_NAME)
|
||||||
@@ -1445,9 +1451,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun actionPlayPauseClick_isLogged() {
|
fun actionPlayPauseClick_isLogged() {
|
||||||
val semanticActions = MediaButton(
|
val semanticActions =
|
||||||
playOrPause = MediaAction(null, Runnable {}, "play", null)
|
MediaButton(playOrPause = MediaAction(null, Runnable {}, "play", null))
|
||||||
)
|
|
||||||
val data = mediaData.copy(semanticActions = semanticActions)
|
val data = mediaData.copy(semanticActions = semanticActions)
|
||||||
|
|
||||||
player.attachPlayer(viewHolder)
|
player.attachPlayer(viewHolder)
|
||||||
@@ -1459,9 +1464,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun actionPrevClick_isLogged() {
|
fun actionPrevClick_isLogged() {
|
||||||
val semanticActions = MediaButton(
|
val semanticActions =
|
||||||
prevOrCustom = MediaAction(null, Runnable {}, "previous", null)
|
MediaButton(prevOrCustom = MediaAction(null, Runnable {}, "previous", null))
|
||||||
)
|
|
||||||
val data = mediaData.copy(semanticActions = semanticActions)
|
val data = mediaData.copy(semanticActions = semanticActions)
|
||||||
|
|
||||||
player.attachPlayer(viewHolder)
|
player.attachPlayer(viewHolder)
|
||||||
@@ -1473,9 +1477,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun actionNextClick_isLogged() {
|
fun actionNextClick_isLogged() {
|
||||||
val semanticActions = MediaButton(
|
val semanticActions =
|
||||||
nextOrCustom = MediaAction(null, Runnable {}, "next", null)
|
MediaButton(nextOrCustom = MediaAction(null, Runnable {}, "next", null))
|
||||||
)
|
|
||||||
val data = mediaData.copy(semanticActions = semanticActions)
|
val data = mediaData.copy(semanticActions = semanticActions)
|
||||||
|
|
||||||
player.attachPlayer(viewHolder)
|
player.attachPlayer(viewHolder)
|
||||||
@@ -1487,9 +1490,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun actionCustom0Click_isLogged() {
|
fun actionCustom0Click_isLogged() {
|
||||||
val semanticActions = MediaButton(
|
val semanticActions =
|
||||||
custom0 = MediaAction(null, Runnable {}, "custom 0", null)
|
MediaButton(custom0 = MediaAction(null, Runnable {}, "custom 0", null))
|
||||||
)
|
|
||||||
val data = mediaData.copy(semanticActions = semanticActions)
|
val data = mediaData.copy(semanticActions = semanticActions)
|
||||||
|
|
||||||
player.attachPlayer(viewHolder)
|
player.attachPlayer(viewHolder)
|
||||||
@@ -1501,9 +1503,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun actionCustom1Click_isLogged() {
|
fun actionCustom1Click_isLogged() {
|
||||||
val semanticActions = MediaButton(
|
val semanticActions =
|
||||||
custom1 = MediaAction(null, Runnable {}, "custom 1", null)
|
MediaButton(custom1 = MediaAction(null, Runnable {}, "custom 1", null))
|
||||||
)
|
|
||||||
val data = mediaData.copy(semanticActions = semanticActions)
|
val data = mediaData.copy(semanticActions = semanticActions)
|
||||||
|
|
||||||
player.attachPlayer(viewHolder)
|
player.attachPlayer(viewHolder)
|
||||||
@@ -1515,13 +1516,14 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun actionCustom2Click_isLogged() {
|
fun actionCustom2Click_isLogged() {
|
||||||
val actions = listOf(
|
val actions =
|
||||||
MediaAction(null, Runnable {}, "action 0", null),
|
listOf(
|
||||||
MediaAction(null, Runnable {}, "action 1", null),
|
MediaAction(null, Runnable {}, "action 0", null),
|
||||||
MediaAction(null, Runnable {}, "action 2", null),
|
MediaAction(null, Runnable {}, "action 1", null),
|
||||||
MediaAction(null, Runnable {}, "action 3", null),
|
MediaAction(null, Runnable {}, "action 2", null),
|
||||||
MediaAction(null, Runnable {}, "action 4", null)
|
MediaAction(null, Runnable {}, "action 3", null),
|
||||||
)
|
MediaAction(null, Runnable {}, "action 4", null)
|
||||||
|
)
|
||||||
val data = mediaData.copy(actions = actions)
|
val data = mediaData.copy(actions = actions)
|
||||||
|
|
||||||
player.attachPlayer(viewHolder)
|
player.attachPlayer(viewHolder)
|
||||||
@@ -1533,13 +1535,14 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun actionCustom3Click_isLogged() {
|
fun actionCustom3Click_isLogged() {
|
||||||
val actions = listOf(
|
val actions =
|
||||||
MediaAction(null, Runnable {}, "action 0", null),
|
listOf(
|
||||||
MediaAction(null, Runnable {}, "action 1", null),
|
MediaAction(null, Runnable {}, "action 0", null),
|
||||||
MediaAction(null, Runnable {}, "action 2", null),
|
MediaAction(null, Runnable {}, "action 1", null),
|
||||||
MediaAction(null, Runnable {}, "action 3", null),
|
MediaAction(null, Runnable {}, "action 2", null),
|
||||||
MediaAction(null, Runnable {}, "action 4", null)
|
MediaAction(null, Runnable {}, "action 3", null),
|
||||||
)
|
MediaAction(null, Runnable {}, "action 4", null)
|
||||||
|
)
|
||||||
val data = mediaData.copy(actions = actions)
|
val data = mediaData.copy(actions = actions)
|
||||||
|
|
||||||
player.attachPlayer(viewHolder)
|
player.attachPlayer(viewHolder)
|
||||||
@@ -1551,13 +1554,14 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun actionCustom4Click_isLogged() {
|
fun actionCustom4Click_isLogged() {
|
||||||
val actions = listOf(
|
val actions =
|
||||||
MediaAction(null, Runnable {}, "action 0", null),
|
listOf(
|
||||||
MediaAction(null, Runnable {}, "action 1", null),
|
MediaAction(null, Runnable {}, "action 0", null),
|
||||||
MediaAction(null, Runnable {}, "action 2", null),
|
MediaAction(null, Runnable {}, "action 1", null),
|
||||||
MediaAction(null, Runnable {}, "action 3", null),
|
MediaAction(null, Runnable {}, "action 2", null),
|
||||||
MediaAction(null, Runnable {}, "action 4", null)
|
MediaAction(null, Runnable {}, "action 3", null),
|
||||||
)
|
MediaAction(null, Runnable {}, "action 4", null)
|
||||||
|
)
|
||||||
val data = mediaData.copy(actions = actions)
|
val data = mediaData.copy(actions = actions)
|
||||||
|
|
||||||
player.attachPlayer(viewHolder)
|
player.attachPlayer(viewHolder)
|
||||||
@@ -1621,8 +1625,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
|
|
||||||
// THEN it shows without dismissing keyguard first
|
// THEN it shows without dismissing keyguard first
|
||||||
captor.value.onClick(viewHolder.player)
|
captor.value.onClick(viewHolder.player)
|
||||||
verify(activityStarter).startActivity(eq(clickIntent), eq(true),
|
verify(activityStarter).startActivity(eq(clickIntent), eq(true), nullable(), eq(true))
|
||||||
nullable(), eq(true))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -1710,20 +1713,22 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
fun bindRecommendation_listHasTooFewRecs_notDisplayed() {
|
fun bindRecommendation_listHasTooFewRecs_notDisplayed() {
|
||||||
player.attachRecommendation(recommendationViewHolder)
|
player.attachRecommendation(recommendationViewHolder)
|
||||||
val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
|
val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
|
||||||
val data = smartspaceData.copy(
|
val data =
|
||||||
recommendations = listOf(
|
smartspaceData.copy(
|
||||||
SmartspaceAction.Builder("id1", "title1")
|
recommendations =
|
||||||
.setSubtitle("subtitle1")
|
listOf(
|
||||||
.setIcon(icon)
|
SmartspaceAction.Builder("id1", "title1")
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle("subtitle1")
|
||||||
.build(),
|
.setIcon(icon)
|
||||||
SmartspaceAction.Builder("id2", "title2")
|
.setExtras(Bundle.EMPTY)
|
||||||
.setSubtitle("subtitle2")
|
.build(),
|
||||||
.setIcon(icon)
|
SmartspaceAction.Builder("id2", "title2")
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle("subtitle2")
|
||||||
.build(),
|
.setIcon(icon)
|
||||||
|
.setExtras(Bundle.EMPTY)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
player.bindRecommendation(data)
|
player.bindRecommendation(data)
|
||||||
|
|
||||||
@@ -1735,30 +1740,32 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
fun bindRecommendation_listHasTooFewRecsWithIcons_notDisplayed() {
|
fun bindRecommendation_listHasTooFewRecsWithIcons_notDisplayed() {
|
||||||
player.attachRecommendation(recommendationViewHolder)
|
player.attachRecommendation(recommendationViewHolder)
|
||||||
val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
|
val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
|
||||||
val data = smartspaceData.copy(
|
val data =
|
||||||
recommendations = listOf(
|
smartspaceData.copy(
|
||||||
SmartspaceAction.Builder("id1", "title1")
|
recommendations =
|
||||||
.setSubtitle("subtitle1")
|
listOf(
|
||||||
.setIcon(icon)
|
SmartspaceAction.Builder("id1", "title1")
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle("subtitle1")
|
||||||
.build(),
|
.setIcon(icon)
|
||||||
SmartspaceAction.Builder("id2", "title2")
|
.setExtras(Bundle.EMPTY)
|
||||||
.setSubtitle("subtitle2")
|
.build(),
|
||||||
.setIcon(icon)
|
SmartspaceAction.Builder("id2", "title2")
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle("subtitle2")
|
||||||
.build(),
|
.setIcon(icon)
|
||||||
SmartspaceAction.Builder("id2", "empty icon 1")
|
.setExtras(Bundle.EMPTY)
|
||||||
.setSubtitle("subtitle2")
|
.build(),
|
||||||
.setIcon(null)
|
SmartspaceAction.Builder("id2", "empty icon 1")
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle("subtitle2")
|
||||||
.build(),
|
.setIcon(null)
|
||||||
SmartspaceAction.Builder("id2", "empty icon 2")
|
.setExtras(Bundle.EMPTY)
|
||||||
.setSubtitle("subtitle2")
|
.build(),
|
||||||
.setIcon(null)
|
SmartspaceAction.Builder("id2", "empty icon 2")
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle("subtitle2")
|
||||||
.build(),
|
.setIcon(null)
|
||||||
|
.setExtras(Bundle.EMPTY)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
player.bindRecommendation(data)
|
player.bindRecommendation(data)
|
||||||
|
|
||||||
@@ -1778,25 +1785,27 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
val subtitle3 = "Subtitle3"
|
val subtitle3 = "Subtitle3"
|
||||||
val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
|
val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
|
||||||
|
|
||||||
val data = smartspaceData.copy(
|
val data =
|
||||||
recommendations = listOf(
|
smartspaceData.copy(
|
||||||
SmartspaceAction.Builder("id1", title1)
|
recommendations =
|
||||||
.setSubtitle(subtitle1)
|
listOf(
|
||||||
.setIcon(icon)
|
SmartspaceAction.Builder("id1", title1)
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle(subtitle1)
|
||||||
.build(),
|
.setIcon(icon)
|
||||||
SmartspaceAction.Builder("id2", title2)
|
.setExtras(Bundle.EMPTY)
|
||||||
.setSubtitle(subtitle2)
|
.build(),
|
||||||
.setIcon(icon)
|
SmartspaceAction.Builder("id2", title2)
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle(subtitle2)
|
||||||
.build(),
|
.setIcon(icon)
|
||||||
SmartspaceAction.Builder("id3", title3)
|
.setExtras(Bundle.EMPTY)
|
||||||
.setSubtitle(subtitle3)
|
.build(),
|
||||||
.setIcon(icon)
|
SmartspaceAction.Builder("id3", title3)
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle(subtitle3)
|
||||||
.build()
|
.setIcon(icon)
|
||||||
|
.setExtras(Bundle.EMPTY)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
player.bindRecommendation(data)
|
player.bindRecommendation(data)
|
||||||
|
|
||||||
assertThat(recTitle1.text).isEqualTo(title1)
|
assertThat(recTitle1.text).isEqualTo(title1)
|
||||||
@@ -1811,15 +1820,17 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
fun bindRecommendation_noTitle_subtitleNotShown() {
|
fun bindRecommendation_noTitle_subtitleNotShown() {
|
||||||
player.attachRecommendation(recommendationViewHolder)
|
player.attachRecommendation(recommendationViewHolder)
|
||||||
|
|
||||||
val data = smartspaceData.copy(
|
val data =
|
||||||
recommendations = listOf(
|
smartspaceData.copy(
|
||||||
SmartspaceAction.Builder("id1", "")
|
recommendations =
|
||||||
.setSubtitle("fake subtitle")
|
listOf(
|
||||||
.setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
|
SmartspaceAction.Builder("id1", "")
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle("fake subtitle")
|
||||||
.build()
|
.setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
|
||||||
|
.setExtras(Bundle.EMPTY)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
player.bindRecommendation(data)
|
player.bindRecommendation(data)
|
||||||
|
|
||||||
assertThat(recSubtitle1.text).isEqualTo("")
|
assertThat(recSubtitle1.text).isEqualTo("")
|
||||||
@@ -1831,25 +1842,27 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
player.attachRecommendation(recommendationViewHolder)
|
player.attachRecommendation(recommendationViewHolder)
|
||||||
|
|
||||||
val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
|
val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
|
||||||
val data = smartspaceData.copy(
|
val data =
|
||||||
recommendations = listOf(
|
smartspaceData.copy(
|
||||||
SmartspaceAction.Builder("id1", "")
|
recommendations =
|
||||||
.setSubtitle("fake subtitle")
|
listOf(
|
||||||
.setIcon(icon)
|
SmartspaceAction.Builder("id1", "")
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle("fake subtitle")
|
||||||
.build(),
|
.setIcon(icon)
|
||||||
SmartspaceAction.Builder("id2", "title2")
|
.setExtras(Bundle.EMPTY)
|
||||||
.setSubtitle("fake subtitle")
|
.build(),
|
||||||
.setIcon(icon)
|
SmartspaceAction.Builder("id2", "title2")
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle("fake subtitle")
|
||||||
.build(),
|
.setIcon(icon)
|
||||||
SmartspaceAction.Builder("id3", "")
|
.setExtras(Bundle.EMPTY)
|
||||||
.setSubtitle("fake subtitle")
|
.build(),
|
||||||
.setIcon(icon)
|
SmartspaceAction.Builder("id3", "")
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle("fake subtitle")
|
||||||
.build()
|
.setIcon(icon)
|
||||||
|
.setExtras(Bundle.EMPTY)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
player.bindRecommendation(data)
|
player.bindRecommendation(data)
|
||||||
|
|
||||||
assertThat(expandedSet.getVisibility(recTitle1.id)).isEqualTo(ConstraintSet.VISIBLE)
|
assertThat(expandedSet.getVisibility(recTitle1.id)).isEqualTo(ConstraintSet.VISIBLE)
|
||||||
@@ -1863,25 +1876,27 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
player.attachRecommendation(recommendationViewHolder)
|
player.attachRecommendation(recommendationViewHolder)
|
||||||
|
|
||||||
val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
|
val icon = Icon.createWithResource(context, R.drawable.ic_1x_mobiledata)
|
||||||
val data = smartspaceData.copy(
|
val data =
|
||||||
recommendations = listOf(
|
smartspaceData.copy(
|
||||||
SmartspaceAction.Builder("id1", "")
|
recommendations =
|
||||||
.setSubtitle("")
|
listOf(
|
||||||
.setIcon(icon)
|
SmartspaceAction.Builder("id1", "")
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle("")
|
||||||
.build(),
|
.setIcon(icon)
|
||||||
SmartspaceAction.Builder("id2", "title2")
|
.setExtras(Bundle.EMPTY)
|
||||||
.setSubtitle("")
|
.build(),
|
||||||
.setIcon(icon)
|
SmartspaceAction.Builder("id2", "title2")
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle("")
|
||||||
.build(),
|
.setIcon(icon)
|
||||||
SmartspaceAction.Builder("id3", "title3")
|
.setExtras(Bundle.EMPTY)
|
||||||
.setSubtitle("subtitle3")
|
.build(),
|
||||||
.setIcon(icon)
|
SmartspaceAction.Builder("id3", "title3")
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle("subtitle3")
|
||||||
.build()
|
.setIcon(icon)
|
||||||
|
.setExtras(Bundle.EMPTY)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
player.bindRecommendation(data)
|
player.bindRecommendation(data)
|
||||||
|
|
||||||
assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.VISIBLE)
|
assertThat(expandedSet.getVisibility(recSubtitle1.id)).isEqualTo(ConstraintSet.VISIBLE)
|
||||||
@@ -1893,25 +1908,27 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
fun bindRecommendation_noneHaveSubtitles_subtitleViewsGone() {
|
fun bindRecommendation_noneHaveSubtitles_subtitleViewsGone() {
|
||||||
useRealConstraintSets()
|
useRealConstraintSets()
|
||||||
player.attachRecommendation(recommendationViewHolder)
|
player.attachRecommendation(recommendationViewHolder)
|
||||||
val data = smartspaceData.copy(
|
val data =
|
||||||
recommendations = listOf(
|
smartspaceData.copy(
|
||||||
SmartspaceAction.Builder("id1", "title1")
|
recommendations =
|
||||||
.setSubtitle("")
|
listOf(
|
||||||
.setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
|
SmartspaceAction.Builder("id1", "title1")
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle("")
|
||||||
.build(),
|
.setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
|
||||||
SmartspaceAction.Builder("id2", "title2")
|
.setExtras(Bundle.EMPTY)
|
||||||
.setSubtitle("")
|
.build(),
|
||||||
.setIcon(Icon.createWithResource(context, R.drawable.ic_alarm))
|
SmartspaceAction.Builder("id2", "title2")
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle("")
|
||||||
.build(),
|
.setIcon(Icon.createWithResource(context, R.drawable.ic_alarm))
|
||||||
SmartspaceAction.Builder("id3", "title3")
|
.setExtras(Bundle.EMPTY)
|
||||||
.setSubtitle("")
|
.build(),
|
||||||
.setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata))
|
SmartspaceAction.Builder("id3", "title3")
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle("")
|
||||||
.build()
|
.setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata))
|
||||||
|
.setExtras(Bundle.EMPTY)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
player.bindRecommendation(data)
|
player.bindRecommendation(data)
|
||||||
|
|
||||||
@@ -1924,25 +1941,27 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
fun bindRecommendation_noneHaveTitles_titleAndSubtitleViewsGone() {
|
fun bindRecommendation_noneHaveTitles_titleAndSubtitleViewsGone() {
|
||||||
useRealConstraintSets()
|
useRealConstraintSets()
|
||||||
player.attachRecommendation(recommendationViewHolder)
|
player.attachRecommendation(recommendationViewHolder)
|
||||||
val data = smartspaceData.copy(
|
val data =
|
||||||
recommendations = listOf(
|
smartspaceData.copy(
|
||||||
SmartspaceAction.Builder("id1", "")
|
recommendations =
|
||||||
.setSubtitle("subtitle1")
|
listOf(
|
||||||
.setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
|
SmartspaceAction.Builder("id1", "")
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle("subtitle1")
|
||||||
.build(),
|
.setIcon(Icon.createWithResource(context, R.drawable.ic_1x_mobiledata))
|
||||||
SmartspaceAction.Builder("id2", "")
|
.setExtras(Bundle.EMPTY)
|
||||||
.setSubtitle("subtitle2")
|
.build(),
|
||||||
.setIcon(Icon.createWithResource(context, R.drawable.ic_alarm))
|
SmartspaceAction.Builder("id2", "")
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle("subtitle2")
|
||||||
.build(),
|
.setIcon(Icon.createWithResource(context, R.drawable.ic_alarm))
|
||||||
SmartspaceAction.Builder("id3", "")
|
.setExtras(Bundle.EMPTY)
|
||||||
.setSubtitle("subtitle3")
|
.build(),
|
||||||
.setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata))
|
SmartspaceAction.Builder("id3", "")
|
||||||
.setExtras(Bundle.EMPTY)
|
.setSubtitle("subtitle3")
|
||||||
.build()
|
.setIcon(Icon.createWithResource(context, R.drawable.ic_3g_mobiledata))
|
||||||
|
.setExtras(Bundle.EMPTY)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
player.bindRecommendation(data)
|
player.bindRecommendation(data)
|
||||||
|
|
||||||
@@ -1955,20 +1974,23 @@ public class MediaControlPanelTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener =
|
private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener =
|
||||||
withArgCaptor { verify(seekBarViewModel).setScrubbingChangeListener(capture()) }
|
withArgCaptor {
|
||||||
|
verify(seekBarViewModel).setScrubbingChangeListener(capture())
|
||||||
|
}
|
||||||
|
|
||||||
private fun getEnabledChangeListener(): SeekBarViewModel.EnabledChangeListener =
|
private fun getEnabledChangeListener(): SeekBarViewModel.EnabledChangeListener = withArgCaptor {
|
||||||
withArgCaptor { verify(seekBarViewModel).setEnabledChangeListener(capture()) }
|
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
|
* 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
|
* visibilities. If we use mocks for the ConstraintSets, then action visibility changes are just
|
||||||
* just thrown away instead of being saved for reference later. This method sets us up to use
|
* thrown away instead of being saved for reference later. This method sets us up to use
|
||||||
* ConstraintSets so that we do save visibility changes.
|
* 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() {
|
private fun useRealConstraintSets() {
|
||||||
expandedSet = ConstraintSet()
|
expandedSet = ConstraintSet()
|
||||||
|
|||||||
@@ -84,10 +84,8 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
|
|||||||
private lateinit var statusBarCallback: ArgumentCaptor<(StatusBarStateController.StateListener)>
|
private lateinit var statusBarCallback: ArgumentCaptor<(StatusBarStateController.StateListener)>
|
||||||
@Captor
|
@Captor
|
||||||
private lateinit var dreamOverlayCallback:
|
private lateinit var dreamOverlayCallback:
|
||||||
ArgumentCaptor<(DreamOverlayStateController.Callback)>
|
ArgumentCaptor<(DreamOverlayStateController.Callback)>
|
||||||
@JvmField
|
@JvmField @Rule val mockito = MockitoJUnit.rule()
|
||||||
@Rule
|
|
||||||
val mockito = MockitoJUnit.rule()
|
|
||||||
private lateinit var mediaHierarchyManager: MediaHierarchyManager
|
private lateinit var mediaHierarchyManager: MediaHierarchyManager
|
||||||
private lateinit var mediaFrame: ViewGroup
|
private lateinit var mediaFrame: ViewGroup
|
||||||
private val configurationController = FakeConfigurationController()
|
private val configurationController = FakeConfigurationController()
|
||||||
@@ -98,13 +96,15 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
context.getOrCreateTestableResources().addOverride(
|
context
|
||||||
R.bool.config_use_split_notification_shade, false)
|
.getOrCreateTestableResources()
|
||||||
|
.addOverride(R.bool.config_use_split_notification_shade, false)
|
||||||
mediaFrame = FrameLayout(context)
|
mediaFrame = FrameLayout(context)
|
||||||
testableLooper = TestableLooper.get(this)
|
testableLooper = TestableLooper.get(this)
|
||||||
fakeHandler = FakeHandler(testableLooper.looper)
|
fakeHandler = FakeHandler(testableLooper.looper)
|
||||||
whenever(mediaCarouselController.mediaFrame).thenReturn(mediaFrame)
|
whenever(mediaCarouselController.mediaFrame).thenReturn(mediaFrame)
|
||||||
mediaHierarchyManager = MediaHierarchyManager(
|
mediaHierarchyManager =
|
||||||
|
MediaHierarchyManager(
|
||||||
context,
|
context,
|
||||||
statusBarStateController,
|
statusBarStateController,
|
||||||
keyguardStateController,
|
keyguardStateController,
|
||||||
@@ -116,7 +116,8 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
|
|||||||
wakefulnessLifecycle,
|
wakefulnessLifecycle,
|
||||||
notifPanelEvents,
|
notifPanelEvents,
|
||||||
settings,
|
settings,
|
||||||
fakeHandler,)
|
fakeHandler,
|
||||||
|
)
|
||||||
verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
|
verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
|
||||||
verify(statusBarStateController).addCallback(statusBarCallback.capture())
|
verify(statusBarStateController).addCallback(statusBarCallback.capture())
|
||||||
verify(dreamOverlayStateController).addCallback(dreamOverlayCallback.capture())
|
verify(dreamOverlayStateController).addCallback(dreamOverlayCallback.capture())
|
||||||
@@ -125,7 +126,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
|
|||||||
setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS, QQS_TOP)
|
setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS, QQS_TOP)
|
||||||
whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
|
whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
|
||||||
whenever(mediaCarouselController.mediaCarouselScrollHandler)
|
whenever(mediaCarouselController.mediaCarouselScrollHandler)
|
||||||
.thenReturn(mediaCarouselScrollHandler)
|
.thenReturn(mediaCarouselScrollHandler)
|
||||||
val observer = wakefullnessObserver.value
|
val observer = wakefullnessObserver.value
|
||||||
assertNotNull("lifecycle observer wasn't registered", observer)
|
assertNotNull("lifecycle observer wasn't registered", observer)
|
||||||
observer.onFinishedWakingUp()
|
observer.onFinishedWakingUp()
|
||||||
@@ -151,30 +152,53 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
|
|||||||
fun testBlockedWhenScreenTurningOff() {
|
fun testBlockedWhenScreenTurningOff() {
|
||||||
// Let's set it onto QS:
|
// Let's set it onto QS:
|
||||||
mediaHierarchyManager.qsExpansion = 1.0f
|
mediaHierarchyManager.qsExpansion = 1.0f
|
||||||
verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
|
verify(mediaCarouselController)
|
||||||
any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
|
.onDesiredLocationChanged(
|
||||||
|
ArgumentMatchers.anyInt(),
|
||||||
|
any(MediaHostState::class.java),
|
||||||
|
anyBoolean(),
|
||||||
|
anyLong(),
|
||||||
|
anyLong()
|
||||||
|
)
|
||||||
val observer = wakefullnessObserver.value
|
val observer = wakefullnessObserver.value
|
||||||
assertNotNull("lifecycle observer wasn't registered", observer)
|
assertNotNull("lifecycle observer wasn't registered", observer)
|
||||||
observer.onStartedGoingToSleep()
|
observer.onStartedGoingToSleep()
|
||||||
clearInvocations(mediaCarouselController)
|
clearInvocations(mediaCarouselController)
|
||||||
mediaHierarchyManager.qsExpansion = 0.0f
|
mediaHierarchyManager.qsExpansion = 0.0f
|
||||||
verify(mediaCarouselController, times(0))
|
verify(mediaCarouselController, times(0))
|
||||||
.onDesiredLocationChanged(ArgumentMatchers.anyInt(),
|
.onDesiredLocationChanged(
|
||||||
any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
|
ArgumentMatchers.anyInt(),
|
||||||
|
any(MediaHostState::class.java),
|
||||||
|
anyBoolean(),
|
||||||
|
anyLong(),
|
||||||
|
anyLong()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testAllowedWhenNotTurningOff() {
|
fun testAllowedWhenNotTurningOff() {
|
||||||
// Let's set it onto QS:
|
// Let's set it onto QS:
|
||||||
mediaHierarchyManager.qsExpansion = 1.0f
|
mediaHierarchyManager.qsExpansion = 1.0f
|
||||||
verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
|
verify(mediaCarouselController)
|
||||||
any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
|
.onDesiredLocationChanged(
|
||||||
|
ArgumentMatchers.anyInt(),
|
||||||
|
any(MediaHostState::class.java),
|
||||||
|
anyBoolean(),
|
||||||
|
anyLong(),
|
||||||
|
anyLong()
|
||||||
|
)
|
||||||
val observer = wakefullnessObserver.value
|
val observer = wakefullnessObserver.value
|
||||||
assertNotNull("lifecycle observer wasn't registered", observer)
|
assertNotNull("lifecycle observer wasn't registered", observer)
|
||||||
clearInvocations(mediaCarouselController)
|
clearInvocations(mediaCarouselController)
|
||||||
mediaHierarchyManager.qsExpansion = 0.0f
|
mediaHierarchyManager.qsExpansion = 0.0f
|
||||||
verify(mediaCarouselController).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
|
verify(mediaCarouselController)
|
||||||
any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
|
.onDesiredLocationChanged(
|
||||||
|
ArgumentMatchers.anyInt(),
|
||||||
|
any(MediaHostState::class.java),
|
||||||
|
anyBoolean(),
|
||||||
|
anyLong(),
|
||||||
|
anyLong()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -183,22 +207,26 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
// Let's transition all the way to full shade
|
// Let's transition all the way to full shade
|
||||||
mediaHierarchyManager.setTransitionToFullShadeAmount(100000f)
|
mediaHierarchyManager.setTransitionToFullShadeAmount(100000f)
|
||||||
verify(mediaCarouselController).onDesiredLocationChanged(
|
verify(mediaCarouselController)
|
||||||
eq(MediaHierarchyManager.LOCATION_QQS),
|
.onDesiredLocationChanged(
|
||||||
any(MediaHostState::class.java),
|
eq(MediaHierarchyManager.LOCATION_QQS),
|
||||||
eq(false),
|
any(MediaHostState::class.java),
|
||||||
anyLong(),
|
eq(false),
|
||||||
anyLong())
|
anyLong(),
|
||||||
|
anyLong()
|
||||||
|
)
|
||||||
clearInvocations(mediaCarouselController)
|
clearInvocations(mediaCarouselController)
|
||||||
|
|
||||||
// Let's go back to the lock screen
|
// Let's go back to the lock screen
|
||||||
mediaHierarchyManager.setTransitionToFullShadeAmount(0.0f)
|
mediaHierarchyManager.setTransitionToFullShadeAmount(0.0f)
|
||||||
verify(mediaCarouselController).onDesiredLocationChanged(
|
verify(mediaCarouselController)
|
||||||
eq(MediaHierarchyManager.LOCATION_LOCKSCREEN),
|
.onDesiredLocationChanged(
|
||||||
any(MediaHostState::class.java),
|
eq(MediaHierarchyManager.LOCATION_LOCKSCREEN),
|
||||||
eq(false),
|
any(MediaHostState::class.java),
|
||||||
anyLong(),
|
eq(false),
|
||||||
anyLong())
|
anyLong(),
|
||||||
|
anyLong()
|
||||||
|
)
|
||||||
|
|
||||||
// Let's make sure alpha is set
|
// Let's make sure alpha is set
|
||||||
mediaHierarchyManager.setTransitionToFullShadeAmount(2.0f)
|
mediaHierarchyManager.setTransitionToFullShadeAmount(2.0f)
|
||||||
@@ -302,7 +330,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
val expectedTranslation = LOCKSCREEN_TOP - QS_TOP
|
val expectedTranslation = LOCKSCREEN_TOP - QS_TOP
|
||||||
assertThat(mediaHierarchyManager.getGuidedTransformationTranslationY())
|
assertThat(mediaHierarchyManager.getGuidedTransformationTranslationY())
|
||||||
.isEqualTo(expectedTranslation)
|
.isEqualTo(expectedTranslation)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -343,27 +371,31 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
|
|||||||
fun testDream() {
|
fun testDream() {
|
||||||
goToDream()
|
goToDream()
|
||||||
setMediaDreamComplicationEnabled(true)
|
setMediaDreamComplicationEnabled(true)
|
||||||
verify(mediaCarouselController).onDesiredLocationChanged(
|
verify(mediaCarouselController)
|
||||||
|
.onDesiredLocationChanged(
|
||||||
eq(MediaHierarchyManager.LOCATION_DREAM_OVERLAY),
|
eq(MediaHierarchyManager.LOCATION_DREAM_OVERLAY),
|
||||||
nullable(),
|
nullable(),
|
||||||
eq(false),
|
eq(false),
|
||||||
anyLong(),
|
anyLong(),
|
||||||
anyLong())
|
anyLong()
|
||||||
|
)
|
||||||
clearInvocations(mediaCarouselController)
|
clearInvocations(mediaCarouselController)
|
||||||
|
|
||||||
setMediaDreamComplicationEnabled(false)
|
setMediaDreamComplicationEnabled(false)
|
||||||
verify(mediaCarouselController).onDesiredLocationChanged(
|
verify(mediaCarouselController)
|
||||||
|
.onDesiredLocationChanged(
|
||||||
eq(MediaHierarchyManager.LOCATION_QQS),
|
eq(MediaHierarchyManager.LOCATION_QQS),
|
||||||
any(MediaHostState::class.java),
|
any(MediaHostState::class.java),
|
||||||
eq(false),
|
eq(false),
|
||||||
anyLong(),
|
anyLong(),
|
||||||
anyLong())
|
anyLong()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun enableSplitShade() {
|
private fun enableSplitShade() {
|
||||||
context.getOrCreateTestableResources().addOverride(
|
context
|
||||||
R.bool.config_use_split_notification_shade, true
|
.getOrCreateTestableResources()
|
||||||
)
|
.addOverride(R.bool.config_use_split_notification_shade, true)
|
||||||
configurationController.notifyConfigurationChanged()
|
configurationController.notifyConfigurationChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,13 +35,10 @@ import org.mockito.junit.MockitoJUnit
|
|||||||
@RunWith(AndroidTestingRunner::class)
|
@RunWith(AndroidTestingRunner::class)
|
||||||
public class MediaPlayerDataTest : SysuiTestCase() {
|
public class MediaPlayerDataTest : SysuiTestCase() {
|
||||||
|
|
||||||
@Mock
|
@Mock private lateinit var playerIsPlaying: MediaControlPanel
|
||||||
private lateinit var playerIsPlaying: MediaControlPanel
|
|
||||||
private var systemClock: FakeSystemClock = FakeSystemClock()
|
private var systemClock: FakeSystemClock = FakeSystemClock()
|
||||||
|
|
||||||
@JvmField
|
@JvmField @Rule val mockito = MockitoJUnit.rule()
|
||||||
@Rule
|
|
||||||
val mockito = MockitoJUnit.rule()
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val LOCAL = MediaData.PLAYBACK_LOCAL
|
val LOCAL = MediaData.PLAYBACK_LOCAL
|
||||||
@@ -63,10 +60,20 @@ public class MediaPlayerDataTest : SysuiTestCase() {
|
|||||||
val playerIsRemote = mock(MediaControlPanel::class.java)
|
val playerIsRemote = mock(MediaControlPanel::class.java)
|
||||||
val dataIsRemote = createMediaData("app2", PLAYING, REMOTE, !RESUMPTION)
|
val dataIsRemote = createMediaData("app2", PLAYING, REMOTE, !RESUMPTION)
|
||||||
|
|
||||||
MediaPlayerData.addMediaPlayer("2", dataIsRemote, playerIsRemote, systemClock,
|
MediaPlayerData.addMediaPlayer(
|
||||||
isSsReactivated = false)
|
"2",
|
||||||
MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying, systemClock,
|
dataIsRemote,
|
||||||
isSsReactivated = false)
|
playerIsRemote,
|
||||||
|
systemClock,
|
||||||
|
isSsReactivated = false
|
||||||
|
)
|
||||||
|
MediaPlayerData.addMediaPlayer(
|
||||||
|
"1",
|
||||||
|
dataIsPlaying,
|
||||||
|
playerIsPlaying,
|
||||||
|
systemClock,
|
||||||
|
isSsReactivated = false
|
||||||
|
)
|
||||||
|
|
||||||
val players = MediaPlayerData.players()
|
val players = MediaPlayerData.players()
|
||||||
assertThat(players).hasSize(2)
|
assertThat(players).hasSize(2)
|
||||||
@@ -81,22 +88,42 @@ public class MediaPlayerDataTest : SysuiTestCase() {
|
|||||||
val playerIsPlaying2 = mock(MediaControlPanel::class.java)
|
val playerIsPlaying2 = mock(MediaControlPanel::class.java)
|
||||||
var dataIsPlaying2 = createMediaData("app2", !PLAYING, LOCAL, !RESUMPTION)
|
var dataIsPlaying2 = createMediaData("app2", !PLAYING, LOCAL, !RESUMPTION)
|
||||||
|
|
||||||
MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1, systemClock,
|
MediaPlayerData.addMediaPlayer(
|
||||||
isSsReactivated = false)
|
"1",
|
||||||
|
dataIsPlaying1,
|
||||||
|
playerIsPlaying1,
|
||||||
|
systemClock,
|
||||||
|
isSsReactivated = false
|
||||||
|
)
|
||||||
systemClock.advanceTime(1)
|
systemClock.advanceTime(1)
|
||||||
MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2, systemClock,
|
MediaPlayerData.addMediaPlayer(
|
||||||
isSsReactivated = false)
|
"2",
|
||||||
|
dataIsPlaying2,
|
||||||
|
playerIsPlaying2,
|
||||||
|
systemClock,
|
||||||
|
isSsReactivated = false
|
||||||
|
)
|
||||||
systemClock.advanceTime(1)
|
systemClock.advanceTime(1)
|
||||||
|
|
||||||
dataIsPlaying1 = createMediaData("app1", !PLAYING, LOCAL, !RESUMPTION)
|
dataIsPlaying1 = createMediaData("app1", !PLAYING, LOCAL, !RESUMPTION)
|
||||||
dataIsPlaying2 = createMediaData("app2", PLAYING, LOCAL, !RESUMPTION)
|
dataIsPlaying2 = createMediaData("app2", PLAYING, LOCAL, !RESUMPTION)
|
||||||
|
|
||||||
MediaPlayerData.addMediaPlayer("1", dataIsPlaying1, playerIsPlaying1, systemClock,
|
MediaPlayerData.addMediaPlayer(
|
||||||
isSsReactivated = false)
|
"1",
|
||||||
|
dataIsPlaying1,
|
||||||
|
playerIsPlaying1,
|
||||||
|
systemClock,
|
||||||
|
isSsReactivated = false
|
||||||
|
)
|
||||||
systemClock.advanceTime(1)
|
systemClock.advanceTime(1)
|
||||||
|
|
||||||
MediaPlayerData.addMediaPlayer("2", dataIsPlaying2, playerIsPlaying2, systemClock,
|
MediaPlayerData.addMediaPlayer(
|
||||||
isSsReactivated = false)
|
"2",
|
||||||
|
dataIsPlaying2,
|
||||||
|
playerIsPlaying2,
|
||||||
|
systemClock,
|
||||||
|
isSsReactivated = false
|
||||||
|
)
|
||||||
systemClock.advanceTime(1)
|
systemClock.advanceTime(1)
|
||||||
|
|
||||||
val players = MediaPlayerData.players()
|
val players = MediaPlayerData.players()
|
||||||
@@ -124,26 +151,60 @@ public class MediaPlayerDataTest : SysuiTestCase() {
|
|||||||
val dataUndetermined = createMediaData("app6", UNDETERMINED, LOCAL, RESUMPTION)
|
val dataUndetermined = createMediaData("app6", UNDETERMINED, LOCAL, RESUMPTION)
|
||||||
|
|
||||||
MediaPlayerData.addMediaPlayer(
|
MediaPlayerData.addMediaPlayer(
|
||||||
"3", dataIsStoppedAndLocal, playerIsStoppedAndLocal, systemClock,
|
"3",
|
||||||
isSsReactivated = false)
|
dataIsStoppedAndLocal,
|
||||||
|
playerIsStoppedAndLocal,
|
||||||
|
systemClock,
|
||||||
|
isSsReactivated = false
|
||||||
|
)
|
||||||
MediaPlayerData.addMediaPlayer(
|
MediaPlayerData.addMediaPlayer(
|
||||||
"5", dataIsStoppedAndRemote, playerIsStoppedAndRemote, systemClock,
|
"5",
|
||||||
isSsReactivated = false)
|
dataIsStoppedAndRemote,
|
||||||
MediaPlayerData.addMediaPlayer("4", dataCanResume, playerCanResume, systemClock,
|
playerIsStoppedAndRemote,
|
||||||
isSsReactivated = false)
|
systemClock,
|
||||||
MediaPlayerData.addMediaPlayer("1", dataIsPlaying, playerIsPlaying, systemClock,
|
isSsReactivated = false
|
||||||
isSsReactivated = false)
|
)
|
||||||
MediaPlayerData.addMediaPlayer(
|
MediaPlayerData.addMediaPlayer(
|
||||||
"2", dataIsPlayingAndRemote, playerIsPlayingAndRemote, systemClock,
|
"4",
|
||||||
isSsReactivated = false)
|
dataCanResume,
|
||||||
MediaPlayerData.addMediaPlayer("6", dataUndetermined, playerUndetermined, systemClock,
|
playerCanResume,
|
||||||
isSsReactivated = false)
|
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()
|
val players = MediaPlayerData.players()
|
||||||
assertThat(players).hasSize(6)
|
assertThat(players).hasSize(6)
|
||||||
assertThat(players).containsExactly(playerIsPlaying, playerIsPlayingAndRemote,
|
assertThat(players)
|
||||||
playerIsStoppedAndRemote, playerIsStoppedAndLocal, playerUndetermined,
|
.containsExactly(
|
||||||
playerCanResume).inOrder()
|
playerIsPlaying,
|
||||||
|
playerIsPlayingAndRemote,
|
||||||
|
playerIsStoppedAndRemote,
|
||||||
|
playerIsStoppedAndLocal,
|
||||||
|
playerUndetermined,
|
||||||
|
playerCanResume
|
||||||
|
)
|
||||||
|
.inOrder()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -155,13 +216,23 @@ public class MediaPlayerDataTest : SysuiTestCase() {
|
|||||||
|
|
||||||
assertThat(MediaPlayerData.players()).hasSize(0)
|
assertThat(MediaPlayerData.players()).hasSize(0)
|
||||||
|
|
||||||
MediaPlayerData.addMediaPlayer(keyA, data, playerIsPlaying, systemClock,
|
MediaPlayerData.addMediaPlayer(
|
||||||
isSsReactivated = false)
|
keyA,
|
||||||
|
data,
|
||||||
|
playerIsPlaying,
|
||||||
|
systemClock,
|
||||||
|
isSsReactivated = false
|
||||||
|
)
|
||||||
systemClock.advanceTime(1)
|
systemClock.advanceTime(1)
|
||||||
|
|
||||||
assertThat(MediaPlayerData.players()).hasSize(1)
|
assertThat(MediaPlayerData.players()).hasSize(1)
|
||||||
MediaPlayerData.addMediaPlayer(keyB, data, playerIsPlaying, systemClock,
|
MediaPlayerData.addMediaPlayer(
|
||||||
isSsReactivated = false)
|
keyB,
|
||||||
|
data,
|
||||||
|
playerIsPlaying,
|
||||||
|
systemClock,
|
||||||
|
isSsReactivated = false
|
||||||
|
)
|
||||||
systemClock.advanceTime(1)
|
systemClock.advanceTime(1)
|
||||||
|
|
||||||
assertThat(MediaPlayerData.players()).hasSize(2)
|
assertThat(MediaPlayerData.players()).hasSize(2)
|
||||||
@@ -179,12 +250,13 @@ public class MediaPlayerDataTest : SysuiTestCase() {
|
|||||||
isPlaying: Boolean?,
|
isPlaying: Boolean?,
|
||||||
location: Int,
|
location: Int,
|
||||||
resumption: Boolean
|
resumption: Boolean
|
||||||
) = MediaTestUtils.emptyMediaData.copy(
|
) =
|
||||||
app = app,
|
MediaTestUtils.emptyMediaData.copy(
|
||||||
packageName = "package: $app",
|
app = app,
|
||||||
playbackLocation = location,
|
packageName = "package: $app",
|
||||||
resumption = resumption,
|
playbackLocation = location,
|
||||||
notificationKey = "key: $app",
|
resumption = resumption,
|
||||||
isPlaying = isPlaying
|
notificationKey = "key: $app",
|
||||||
)
|
isPlaying = isPlaying
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package com.android.systemui.media.controls.ui
|
package com.android.systemui.media.controls.ui
|
||||||
|
|
||||||
import org.mockito.Mockito.`when` as whenever
|
|
||||||
import android.animation.Animator
|
import android.animation.Animator
|
||||||
import android.test.suitebuilder.annotation.SmallTest
|
import android.test.suitebuilder.annotation.SmallTest
|
||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
@@ -29,10 +28,11 @@ import org.junit.Rule
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito.verify
|
|
||||||
import org.mockito.Mockito.times
|
|
||||||
import org.mockito.Mockito.mock
|
import org.mockito.Mockito.mock
|
||||||
import org.mockito.Mockito.never
|
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
|
import org.mockito.junit.MockitoJUnit
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@@ -55,8 +55,7 @@ class MetadataAnimationHandlerTest : SysuiTestCase() {
|
|||||||
handler = MetadataAnimationHandler(exitAnimator, enterAnimator)
|
handler = MetadataAnimationHandler(exitAnimator, enterAnimator)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After fun tearDown() {}
|
||||||
fun tearDown() {}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun firstBind_startsAnimationSet() {
|
fun firstBind_startsAnimationSet() {
|
||||||
|
|||||||
@@ -50,12 +50,9 @@ class SquigglyProgressTest : SysuiTestCase() {
|
|||||||
private val tint = Color.GREEN
|
private val tint = Color.GREEN
|
||||||
|
|
||||||
lateinit var squigglyProgress: SquigglyProgress
|
lateinit var squigglyProgress: SquigglyProgress
|
||||||
@Mock
|
@Mock lateinit var canvas: Canvas
|
||||||
lateinit var canvas: Canvas
|
@Captor lateinit var paintCaptor: ArgumentCaptor<Paint>
|
||||||
@Captor
|
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
|
||||||
lateinit var paintCaptor: ArgumentCaptor<Paint>
|
|
||||||
@JvmField @Rule
|
|
||||||
val mockitoRule = MockitoJUnit.rule()
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
@@ -126,7 +123,6 @@ class SquigglyProgressTest : SysuiTestCase() {
|
|||||||
val (wavePaint, linePaint) = paintCaptor.getAllValues()
|
val (wavePaint, linePaint) = paintCaptor.getAllValues()
|
||||||
|
|
||||||
assertThat(wavePaint.color).isEqualTo(tint)
|
assertThat(wavePaint.color).isEqualTo(tint)
|
||||||
assertThat(linePaint.color).isEqualTo(
|
assertThat(linePaint.color).isEqualTo(ColorUtils.setAlphaComponent(tint, DISABLED_ALPHA))
|
||||||
ColorUtils.setAlphaComponent(tint, DISABLED_ALPHA))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user