Lucas Dupin | 6f0bd31 | 2020-05-28 18:19:29 -0700 | [diff] [blame] | 1 | /* |
| 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 | |
| 17 | package com.android.systemui.media |
| 18 | |
| 19 | import android.media.session.MediaController |
| 20 | import android.media.session.PlaybackState |
| 21 | import android.os.SystemProperties |
| 22 | import android.util.Log |
| 23 | import com.android.systemui.dagger.qualifiers.Main |
| 24 | import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState |
| 25 | import com.android.systemui.util.concurrency.DelayableExecutor |
| 26 | import java.util.concurrent.TimeUnit |
| 27 | import javax.inject.Inject |
| 28 | import javax.inject.Singleton |
| 29 | |
| 30 | private const val DEBUG = true |
| 31 | private const val TAG = "MediaTimeout" |
| 32 | private 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 |
| 39 | class 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 Dupin | 84f5a0e | 2020-06-08 19:55:33 -0700 | [diff] [blame] | 46 | /** |
| 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 Dupin | 6f0bd31 | 2020-05-28 18:19:29 -0700 | [diff] [blame] | 51 | lateinit var timeoutCallback: (String, Boolean) -> Unit |
| 52 | |
Beth Thibodeau | f55bc6a | 2020-05-20 02:01:31 -0400 | [diff] [blame] | 53 | override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { |
Lucas Dupin | 6f0bd31 | 2020-05-28 18:19:29 -0700 | [diff] [blame] | 54 | 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 |
Lucas Dupin | e267f4d | 2020-06-22 22:55:23 -0700 | [diff] [blame] | 74 | private var playing: Boolean? = null |
Lucas Dupin | 6f0bd31 | 2020-05-28 18:19:29 -0700 | [diff] [blame] | 75 | |
Beth Thibodeau | f55bc6a | 2020-05-20 02:01:31 -0400 | [diff] [blame] | 76 | // Resume controls may have null token |
| 77 | private val mediaController = if (data.token != null) { |
| 78 | mediaControllerFactory.create(data.token) |
| 79 | } else { |
| 80 | null |
| 81 | } |
Lucas Dupin | 6f0bd31 | 2020-05-28 18:19:29 -0700 | [diff] [blame] | 82 | private var cancellation: Runnable? = null |
| 83 | |
| 84 | init { |
Beth Thibodeau | f55bc6a | 2020-05-20 02:01:31 -0400 | [diff] [blame] | 85 | mediaController?.registerCallback(this) |
Lucas Dupin | 281d646 | 2020-06-16 16:51:03 -0700 | [diff] [blame] | 86 | onPlaybackStateChanged(mediaController?.playbackState) |
Lucas Dupin | 6f0bd31 | 2020-05-28 18:19:29 -0700 | [diff] [blame] | 87 | } |
| 88 | |
| 89 | fun destroy() { |
Beth Thibodeau | f55bc6a | 2020-05-20 02:01:31 -0400 | [diff] [blame] | 90 | mediaController?.unregisterCallback(this) |
Lucas Dupin | 6f0bd31 | 2020-05-28 18:19:29 -0700 | [diff] [blame] | 91 | } |
| 92 | |
| 93 | override fun onPlaybackStateChanged(state: PlaybackState?) { |
| 94 | if (DEBUG) { |
| 95 | Log.v(TAG, "onPlaybackStateChanged: $state") |
| 96 | } |
Lucas Dupin | 6f0bd31 | 2020-05-28 18:19:29 -0700 | [diff] [blame] | 97 | |
Lucas Dupin | e267f4d | 2020-06-22 22:55:23 -0700 | [diff] [blame] | 98 | val isPlaying = state != null && isPlayingState(state.state) |
| 99 | if (playing == isPlaying && playing != null) { |
| 100 | return |
| 101 | } |
| 102 | playing = isPlaying |
| 103 | |
| 104 | if (!isPlaying) { |
Lucas Dupin | 6f0bd31 | 2020-05-28 18:19:29 -0700 | [diff] [blame] | 105 | if (DEBUG) { |
| 106 | Log.v(TAG, "schedule timeout for $key") |
| 107 | } |
Lucas Dupin | 6c4a1d0 | 2020-06-04 18:22:36 -0700 | [diff] [blame] | 108 | if (cancellation != null) { |
| 109 | if (DEBUG) Log.d(TAG, "cancellation already exists, continuing.") |
| 110 | return |
| 111 | } |
Lucas Dupin | 6f0bd31 | 2020-05-28 18:19:29 -0700 | [diff] [blame] | 112 | expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state") |
| 113 | cancellation = mainExecutor.executeDelayed({ |
| 114 | cancellation = null |
| 115 | if (DEBUG) { |
| 116 | Log.v(TAG, "Execute timeout for $key") |
| 117 | } |
| 118 | timedOut = true |
| 119 | timeoutCallback(key, timedOut) |
| 120 | }, PAUSED_MEDIA_TIMEOUT) |
| 121 | } else { |
Lucas Dupin | 6c4a1d0 | 2020-06-04 18:22:36 -0700 | [diff] [blame] | 122 | expireMediaTimeout(key, "playback started - $state, $key") |
Lucas Dupin | 6f0bd31 | 2020-05-28 18:19:29 -0700 | [diff] [blame] | 123 | timedOut = false |
| 124 | timeoutCallback(key, timedOut) |
| 125 | } |
| 126 | } |
| 127 | |
Lucas Dupin | 84f5a0e | 2020-06-08 19:55:33 -0700 | [diff] [blame] | 128 | private fun expireMediaTimeout(mediaKey: String, reason: String) { |
Lucas Dupin | 6f0bd31 | 2020-05-28 18:19:29 -0700 | [diff] [blame] | 129 | cancellation?.apply { |
| 130 | if (DEBUG) { |
| 131 | Log.v(TAG, |
Lucas Dupin | 84f5a0e | 2020-06-08 19:55:33 -0700 | [diff] [blame] | 132 | "media timeout cancelled for $mediaKey, reason: $reason") |
Lucas Dupin | 6f0bd31 | 2020-05-28 18:19:29 -0700 | [diff] [blame] | 133 | } |
| 134 | run() |
| 135 | } |
| 136 | cancellation = null |
| 137 | } |
| 138 | } |
| 139 | } |