Home | History | Annotate | Download | only in pictureinpicture
      1 /*
      2  * Copyright (C) 2017 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.example.android.pictureinpicture
     18 
     19 import android.app.PendingIntent
     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.content.res.Configuration
     27 import android.graphics.drawable.Icon
     28 import android.net.Uri
     29 import android.os.Bundle
     30 import android.support.annotation.DrawableRes
     31 import android.support.v7.app.AppCompatActivity
     32 import android.util.Rational
     33 import android.view.View
     34 import android.widget.Button
     35 import android.widget.ScrollView
     36 import com.example.android.pictureinpicture.widget.MovieView
     37 import java.util.*
     38 
     39 
     40 /**
     41  * Demonstrates usage of Picture-in-Picture mode on phones and tablets.
     42  */
     43 class MainActivity : AppCompatActivity() {
     44 
     45     companion object {
     46 
     47         /** Intent action for media controls from Picture-in-Picture mode.  */
     48         private val ACTION_MEDIA_CONTROL = "media_control"
     49 
     50         /** Intent extra for media controls from Picture-in-Picture mode.  */
     51         private val EXTRA_CONTROL_TYPE = "control_type"
     52 
     53         /** The request code for play action PendingIntent.  */
     54         private val REQUEST_PLAY = 1
     55 
     56         /** The request code for pause action PendingIntent.  */
     57         private val REQUEST_PAUSE = 2
     58 
     59         /** The request code for info action PendingIntent.  */
     60         private val REQUEST_INFO = 3
     61 
     62         /** The intent extra value for play action.  */
     63         private val CONTROL_TYPE_PLAY = 1
     64 
     65         /** The intent extra value for pause action.  */
     66         private val CONTROL_TYPE_PAUSE = 2
     67 
     68     }
     69 
     70     /** The arguments to be used for Picture-in-Picture mode.  */
     71     private val mPictureInPictureParamsBuilder = PictureInPictureParams.Builder()
     72 
     73     /** This shows the video.  */
     74     private lateinit var mMovieView: MovieView
     75 
     76     /** The bottom half of the screen; hidden on landscape  */
     77     private lateinit var mScrollView: ScrollView
     78 
     79     /** A [BroadcastReceiver] to receive action item events from Picture-in-Picture mode.  */
     80     private val mReceiver = object : BroadcastReceiver() {
     81         override fun onReceive(context: Context, intent: Intent?) {
     82             intent?.let { intent ->
     83                 if (intent.action != ACTION_MEDIA_CONTROL) {
     84                     return
     85                 }
     86 
     87                 // This is where we are called back from Picture-in-Picture action items.
     88                 val controlType = intent.getIntExtra(EXTRA_CONTROL_TYPE, 0)
     89                 when (controlType) {
     90                     CONTROL_TYPE_PLAY -> mMovieView.play()
     91                     CONTROL_TYPE_PAUSE -> mMovieView.pause()
     92                 }
     93             }
     94         }
     95     }
     96 
     97     private val labelPlay: String by lazy { getString(R.string.play) }
     98     private val labelPause: String by lazy { getString(R.string.pause) }
     99 
    100     /**
    101      * Callbacks from the [MovieView] showing the video playback.
    102      */
    103     private val mMovieListener = object : MovieView.MovieListener() {
    104 
    105         override fun onMovieStarted() {
    106             // We are playing the video now. In PiP mode, we want to show an action item to pause
    107             // the video.
    108             updatePictureInPictureActions(R.drawable.ic_pause_24dp, labelPause,
    109                     CONTROL_TYPE_PAUSE, REQUEST_PAUSE)
    110         }
    111 
    112         override fun onMovieStopped() {
    113             // The video stopped or reached its end. In PiP mode, we want to show an action item
    114             // to play the video.
    115             updatePictureInPictureActions(R.drawable.ic_play_arrow_24dp, labelPlay,
    116                     CONTROL_TYPE_PLAY, REQUEST_PLAY)
    117         }
    118 
    119         override fun onMovieMinimized() {
    120             // The MovieView wants us to minimize it. We enter Picture-in-Picture mode now.
    121             minimize()
    122         }
    123 
    124     }
    125 
    126     /**
    127      * Update the state of pause/resume action item in Picture-in-Picture mode.
    128 
    129      * @param iconId      The icon to be used.
    130      * *
    131      * @param title       The title text.
    132      * *
    133      * @param controlType The type of the action. either [.CONTROL_TYPE_PLAY] or
    134      * *                    [.CONTROL_TYPE_PAUSE].
    135      * *
    136      * @param requestCode The request code for the [PendingIntent].
    137      */
    138     internal fun updatePictureInPictureActions(@DrawableRes iconId: Int, title: String,
    139                                                controlType: Int, requestCode: Int) {
    140         val actions = ArrayList<RemoteAction>()
    141 
    142         // This is the PendingIntent that is invoked when a user clicks on the action item.
    143         // You need to use distinct request codes for play and pause, or the PendingIntent won't
    144         // be properly updated.
    145         val intent = PendingIntent.getBroadcast(this@MainActivity,
    146                 requestCode, Intent(ACTION_MEDIA_CONTROL)
    147                 .putExtra(EXTRA_CONTROL_TYPE, controlType), 0)
    148         val icon = Icon.createWithResource(this@MainActivity, iconId)
    149         actions.add(RemoteAction(icon, title, title, intent))
    150 
    151         // Another action item. This is a fixed action.
    152         actions.add(RemoteAction(
    153                 Icon.createWithResource(this@MainActivity, R.drawable.ic_info_24dp),
    154                 getString(R.string.info), getString(R.string.info_description),
    155                 PendingIntent.getActivity(this@MainActivity, REQUEST_INFO,
    156                         Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.info_uri))),
    157                         0)))
    158 
    159         mPictureInPictureParamsBuilder.setActions(actions)
    160 
    161         // This is how you can update action items (or aspect ratio) for Picture-in-Picture mode.
    162         // Note this call can happen even when the app is not in PiP mode. In that case, the
    163         // arguments will be used for at the next call of #enterPictureInPictureMode.
    164         setPictureInPictureParams(mPictureInPictureParamsBuilder.build())
    165     }
    166 
    167     override fun onCreate(savedInstanceState: Bundle?) {
    168         super.onCreate(savedInstanceState)
    169         setContentView(R.layout.activity_main)
    170 
    171         // View references
    172         mMovieView = findViewById<MovieView>(R.id.movie)
    173         mScrollView = findViewById<ScrollView>(R.id.scroll)
    174 
    175         val switchExampleButton = findViewById<Button>(R.id.switch_example)
    176         switchExampleButton.text = getString(R.string.switch_media_session)
    177         switchExampleButton.setOnClickListener(SwitchActivityOnClick())
    178 
    179         // Set up the video; it automatically starts.
    180         mMovieView.setMovieListener(mMovieListener)
    181         findViewById<Button>(R.id.pip).setOnClickListener { minimize() }
    182     }
    183 
    184     override fun onStop() {
    185         // On entering Picture-in-Picture mode, onPause is called, but not onStop.
    186         // For this reason, this is the place where we should pause the video playback.
    187         mMovieView.pause()
    188         super.onStop()
    189     }
    190 
    191     override fun onRestart() {
    192         super.onRestart()
    193         // Show the video controls so the video can be easily resumed.
    194         if (!isInPictureInPictureMode) {
    195             mMovieView.showControls()
    196         }
    197     }
    198 
    199     override fun onConfigurationChanged(newConfig: Configuration) {
    200         super.onConfigurationChanged(newConfig)
    201         adjustFullScreen(newConfig)
    202     }
    203 
    204     override fun onWindowFocusChanged(hasFocus: Boolean) {
    205         super.onWindowFocusChanged(hasFocus)
    206         if (hasFocus) {
    207             adjustFullScreen(resources.configuration)
    208         }
    209     }
    210 
    211     override fun onPictureInPictureModeChanged(
    212             isInPictureInPictureMode: Boolean, newConfig: Configuration) {
    213         super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
    214         if (isInPictureInPictureMode) {
    215             // Starts receiving events from action items in PiP mode.
    216             registerReceiver(mReceiver, IntentFilter(ACTION_MEDIA_CONTROL))
    217         } else {
    218             // We are out of PiP mode. We can stop receiving events from it.
    219             unregisterReceiver(mReceiver)
    220             // Show the video controls if the video is not playing
    221             if (!mMovieView.isPlaying) {
    222                 mMovieView.showControls()
    223             }
    224         }
    225     }
    226 
    227     /**
    228      * Enters Picture-in-Picture mode.
    229      */
    230     internal fun minimize() {
    231         // Hide the controls in picture-in-picture mode.
    232         mMovieView.hideControls()
    233         // Calculate the aspect ratio of the PiP screen.
    234         mPictureInPictureParamsBuilder.setAspectRatio(Rational(mMovieView.width, mMovieView.height))
    235         enterPictureInPictureMode(mPictureInPictureParamsBuilder.build())
    236     }
    237 
    238     /**
    239      * Adjusts immersive full-screen flags depending on the screen orientation.
    240 
    241      * @param config The current [Configuration].
    242      */
    243     private fun adjustFullScreen(config: Configuration) {
    244         val decorView = window.decorView
    245         if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
    246             decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
    247                     View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
    248                     View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
    249                     View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
    250                     View.SYSTEM_UI_FLAG_FULLSCREEN or
    251                     View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
    252             mScrollView.visibility = View.GONE
    253             mMovieView.setAdjustViewBounds(false)
    254         } else {
    255             decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
    256             mScrollView.visibility = View.VISIBLE
    257             mMovieView.setAdjustViewBounds(true)
    258         }
    259     }
    260 
    261     /**
    262      * Launches [MediaSessionPlaybackActivity] and closes this activity.
    263      */
    264     private inner class SwitchActivityOnClick : View.OnClickListener {
    265         override fun onClick(view: View) {
    266             startActivity(Intent(view.context, MediaSessionPlaybackActivity::class.java))
    267             finish()
    268         }
    269     }
    270 }
    271