blob: cf8a636a2b67eda3f7a4d680f4fadebb3ef8158c [file] [log] [blame]
Lucas Dupin6f0bd312020-05-28 18:19:29 -07001/*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.media
18
19import android.media.session.MediaController
20import android.media.session.PlaybackState
21import android.os.SystemProperties
22import android.util.Log
23import com.android.systemui.dagger.qualifiers.Main
24import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
25import com.android.systemui.util.concurrency.DelayableExecutor
26import java.util.concurrent.TimeUnit
27import javax.inject.Inject
28import javax.inject.Singleton
29
30private const val DEBUG = true
31private const val TAG = "MediaTimeout"
32private val PAUSED_MEDIA_TIMEOUT = SystemProperties
33 .getLong("debug.sysui.media_timeout", TimeUnit.MINUTES.toMillis(10))
34
35/**
36 * Controller responsible for keeping track of playback states and expiring inactive streams.
37 */
38@Singleton
39class MediaTimeoutListener @Inject constructor(
40 private val mediaControllerFactory: MediaControllerFactory,
41 @Main private val mainExecutor: DelayableExecutor
42) : MediaDataManager.Listener {
43
44 private val mediaListeners: MutableMap<String, PlaybackStateListener> = mutableMapOf()
45
Lucas Dupin84f5a0e2020-06-08 19:55:33 -070046 /**
47 * Callback representing that a media object is now expired:
48 * @param token Media session unique identifier
49 * @param pauseTimeuot True when expired for {@code PAUSED_MEDIA_TIMEOUT}
50 */
Lucas Dupin6f0bd312020-05-28 18:19:29 -070051 lateinit var timeoutCallback: (String, Boolean) -> Unit
52
Beth Thibodeauf55bc6a2020-05-20 02:01:31 -040053 override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
Lucas Dupin6f0bd312020-05-28 18:19:29 -070054 if (mediaListeners.containsKey(key)) {
55 return
56 }
57 mediaListeners[key] = PlaybackStateListener(key, data)
58 }
59
60 override fun onMediaDataRemoved(key: String) {
61 mediaListeners.remove(key)?.destroy()
62 }
63
64 fun isTimedOut(key: String): Boolean {
65 return mediaListeners[key]?.timedOut ?: false
66 }
67
68 private inner class PlaybackStateListener(
69 private val key: String,
70 data: MediaData
71 ) : MediaController.Callback() {
72
73 var timedOut = false
74
Beth Thibodeauf55bc6a2020-05-20 02:01:31 -040075 // Resume controls may have null token
76 private val mediaController = if (data.token != null) {
77 mediaControllerFactory.create(data.token)
78 } else {
79 null
80 }
Lucas Dupin6f0bd312020-05-28 18:19:29 -070081 private var cancellation: Runnable? = null
82
83 init {
Beth Thibodeauf55bc6a2020-05-20 02:01:31 -040084 mediaController?.registerCallback(this)
Lucas Dupin281d6462020-06-16 16:51:03 -070085 onPlaybackStateChanged(mediaController?.playbackState)
Lucas Dupin6f0bd312020-05-28 18:19:29 -070086 }
87
88 fun destroy() {
Beth Thibodeauf55bc6a2020-05-20 02:01:31 -040089 mediaController?.unregisterCallback(this)
Lucas Dupin6f0bd312020-05-28 18:19:29 -070090 }
91
92 override fun onPlaybackStateChanged(state: PlaybackState?) {
93 if (DEBUG) {
94 Log.v(TAG, "onPlaybackStateChanged: $state")
95 }
Lucas Dupin6f0bd312020-05-28 18:19:29 -070096
97 if (state == null || !isPlayingState(state.state)) {
98 if (DEBUG) {
99 Log.v(TAG, "schedule timeout for $key")
100 }
Lucas Dupin6c4a1d02020-06-04 18:22:36 -0700101 if (cancellation != null) {
102 if (DEBUG) Log.d(TAG, "cancellation already exists, continuing.")
103 return
104 }
Lucas Dupin6f0bd312020-05-28 18:19:29 -0700105 expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state")
106 cancellation = mainExecutor.executeDelayed({
107 cancellation = null
108 if (DEBUG) {
109 Log.v(TAG, "Execute timeout for $key")
110 }
111 timedOut = true
112 timeoutCallback(key, timedOut)
113 }, PAUSED_MEDIA_TIMEOUT)
114 } else {
Lucas Dupin6c4a1d02020-06-04 18:22:36 -0700115 expireMediaTimeout(key, "playback started - $state, $key")
Lucas Dupin6f0bd312020-05-28 18:19:29 -0700116 timedOut = false
117 timeoutCallback(key, timedOut)
118 }
119 }
120
Lucas Dupin84f5a0e2020-06-08 19:55:33 -0700121 private fun expireMediaTimeout(mediaKey: String, reason: String) {
Lucas Dupin6f0bd312020-05-28 18:19:29 -0700122 cancellation?.apply {
123 if (DEBUG) {
124 Log.v(TAG,
Lucas Dupin84f5a0e2020-06-08 19:55:33 -0700125 "media timeout cancelled for $mediaKey, reason: $reason")
Lucas Dupin6f0bd312020-05-28 18:19:29 -0700126 }
127 run()
128 }
129 cancellation = null
130 }
131 }
132}