1 /* <lambda>null2 * 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 android.systemui.cts.tv.pip 18 19 import android.app.Activity 20 import android.app.PictureInPictureParams 21 import android.app.RemoteAction 22 import android.content.BroadcastReceiver 23 import android.content.Context 24 import android.content.Intent 25 import android.content.IntentFilter 26 import android.graphics.Rect 27 import android.media.MediaMetadata 28 import android.media.session.MediaSession 29 import android.media.session.PlaybackState 30 import android.media.session.PlaybackState.ACTION_PAUSE 31 import android.media.session.PlaybackState.ACTION_PLAY 32 import android.media.session.PlaybackState.STATE_PAUSED 33 import android.media.session.PlaybackState.STATE_PLAYING 34 import android.media.session.PlaybackState.STATE_STOPPED 35 import android.os.Bundle 36 import android.systemui.tv.cts.PipActivity.ACTION_ENTER_PIP 37 import android.systemui.tv.cts.PipActivity.ACTION_MEDIA_PAUSE 38 import android.systemui.tv.cts.PipActivity.ACTION_MEDIA_PLAY 39 import android.systemui.tv.cts.PipActivity.ACTION_SET_MEDIA_TITLE 40 import android.systemui.tv.cts.PipActivity.ACTION_NO_OP 41 import android.systemui.tv.cts.PipActivity.EXTRA_ASPECT_RATIO_DENOMINATOR 42 import android.systemui.tv.cts.PipActivity.EXTRA_ASPECT_RATIO_NUMERATOR 43 import android.systemui.tv.cts.PipActivity.EXTRA_SET_CUSTOM_ACTIONS 44 import android.systemui.tv.cts.PipActivity.EXTRA_ENTER_PIP 45 import android.systemui.tv.cts.PipActivity.EXTRA_MEDIA_SESSION_ACTIONS 46 import android.systemui.tv.cts.PipActivity.EXTRA_MEDIA_SESSION_ACTIVE 47 import android.systemui.tv.cts.PipActivity.EXTRA_MEDIA_SESSION_TITLE 48 import android.systemui.tv.cts.PipActivity.EXTRA_SOURCE_RECT_HINT 49 import android.systemui.tv.cts.PipActivity.EXTRA_TURN_ON_SCREEN 50 import android.systemui.tv.cts.PipActivity.MEDIA_SESSION_TITLE 51 import android.util.Log 52 import android.util.Rational 53 import java.net.URLDecoder 54 55 /** A simple PiP test activity */ 56 class PipTestActivity : Activity() { 57 companion object { 58 private const val TAG = "PipTestActivity" 59 } 60 61 private lateinit var pipParams: PictureInPictureParams 62 private lateinit var mediaSession: MediaSession 63 private val playbackBuilder = PlaybackState.Builder() 64 .setActions(ACTION_PAUSE or ACTION_PLAY) 65 .setState(STATE_STOPPED) 66 67 private val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() { 68 override fun onReceive(context: Context?, intent: Intent?) = handle(intent) 69 } 70 71 private val intentFilter = IntentFilter().apply { 72 addAction(ACTION_SET_MEDIA_TITLE) 73 addAction(ACTION_MEDIA_PLAY) 74 addAction(ACTION_MEDIA_PAUSE) 75 addAction(ACTION_NO_OP) 76 } 77 78 private val mediaCallback = object : MediaSession.Callback() { 79 override fun onPlay() = 80 mediaSession.setPlaybackState(playbackBuilder.setState(STATE_PLAYING).build()) 81 82 override fun onPause() = 83 mediaSession.setPlaybackState(playbackBuilder.setState(STATE_PAUSED).build()) 84 } 85 86 override fun onCreate(savedInstanceState: Bundle?) { 87 super.onCreate(savedInstanceState) 88 89 mediaSession = MediaSession(this, MEDIA_SESSION_TITLE).apply { 90 setPlaybackState(playbackBuilder.build()) 91 setCallback(mediaCallback) 92 } 93 registerReceiver(broadcastReceiver, intentFilter) 94 handle(intent) 95 } 96 97 override fun onNewIntent(intent: Intent?) = handle(intent) 98 99 private fun handle(intent: Intent?) { 100 if (intent == null) { 101 return 102 } 103 104 handleScreenExtras(intent) 105 106 handleMediaExtras(intent) 107 108 handlePipExtras(intent) 109 110 when (intent.action) { 111 ACTION_NO_OP -> { 112 // explicitly do nothing 113 } 114 ACTION_MEDIA_PLAY -> { 115 Log.d(TAG, "Playing media") 116 mediaSession.controller.transportControls.play() 117 } 118 ACTION_MEDIA_PAUSE -> { 119 Log.d(TAG, "Pausing media") 120 mediaSession.controller.transportControls.pause() 121 } 122 } 123 124 if (intent.action == ACTION_ENTER_PIP || intent.getBooleanExtra(EXTRA_ENTER_PIP, false)) { 125 Log.d(TAG, "Entering PIP. Currently in PIP = $isInPictureInPictureMode") 126 val res = enterPictureInPictureMode(pipParams) 127 Log.d(TAG, "Entered PIP = $res. Currently in PIP = $isInPictureInPictureMode") 128 } 129 } 130 131 /** 132 * Applies the pip parameters from the intent to the current pip window if there is one, or 133 * sets them for when pip mode will be entered next. 134 * 135 * Also stores the new parameters in [pipParams]. 136 */ 137 private fun handlePipExtras(intent: Intent) { 138 pipParams = buildPipParams(intent.extras) 139 setPictureInPictureParams(pipParams) 140 } 141 142 /** Updates the state of the [mediaSession]. */ 143 private fun handleMediaExtras(intent: Intent) { 144 if (intent.hasExtra(EXTRA_MEDIA_SESSION_ACTIVE)) { 145 intent.extras?.getBoolean(EXTRA_MEDIA_SESSION_ACTIVE)?.let { 146 Log.d(TAG, "Setting media session active = $it") 147 mediaSession.isActive = it 148 } 149 } 150 151 intent.getStringExtra(EXTRA_MEDIA_SESSION_TITLE)?.let { 152 // We expect the media session title to be url encoded. 153 // This is needed to be able to set arbitrary titles over adb 154 val title: String = URLDecoder.decode(it, "UTF-8") 155 Log.d(TAG, "Setting media session title = $title") 156 mediaSession.setMetadata( 157 MediaMetadata.Builder() 158 .putText(MediaMetadata.METADATA_KEY_TITLE, title) 159 .build() 160 ) 161 } 162 163 if (intent.hasExtra(EXTRA_MEDIA_SESSION_ACTIONS)) { 164 val requestedActions = 165 intent.getLongExtra(EXTRA_MEDIA_SESSION_ACTIONS, ACTION_PAUSE or ACTION_PLAY) 166 mediaSession.setPlaybackState(playbackBuilder.setActions(requestedActions).build()) 167 } 168 } 169 170 /** Calls [android.app.Activity.setTurnScreenOn] if needed. */ 171 private fun handleScreenExtras(intent: Intent) { 172 if (intent.getBooleanExtra(EXTRA_TURN_ON_SCREEN, false)) { 173 Log.d(TAG, "Setting setTurnScreenOn") 174 setTurnScreenOn(true) 175 } 176 } 177 178 private fun buildPipParams(bundle: Bundle?): PictureInPictureParams { 179 val builder = PictureInPictureParams.Builder() 180 bundle?.run { 181 if (containsKey(EXTRA_ASPECT_RATIO_NUMERATOR) && 182 containsKey(EXTRA_ASPECT_RATIO_DENOMINATOR)) { 183 builder.setAspectRatio(Rational( 184 getInt(EXTRA_ASPECT_RATIO_NUMERATOR), 185 getInt(EXTRA_ASPECT_RATIO_DENOMINATOR))) 186 } 187 188 getString(EXTRA_SOURCE_RECT_HINT)?.let { 189 builder.setSourceRectHint(Rect.unflattenFromString(it)) 190 } 191 192 getParcelableArrayList<RemoteAction>(EXTRA_SET_CUSTOM_ACTIONS)?.let { actions -> 193 val names = actions.joinToString(", ") { it.title } 194 Log.d(TAG, "Setting custom pip actions: $names") 195 builder.setActions(actions) 196 } 197 } 198 return builder.build() 199 } 200 201 /** Just set the playback state without updating the position or playback speed. */ 202 private fun PlaybackState.Builder.setState(state: Int) = apply { 203 setState(state, 0, 0f) 204 } 205 206 override fun onDestroy() { 207 unregisterReceiver(broadcastReceiver) 208 super.onDestroy() 209 } 210 }