Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,13 @@
android:name=".PiPSampleActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:exported="true"
android:supportsPictureInPicture="true"
android:launchMode="singleTask"
android:theme="@style/PiPAppTheme"
tools:targetApi="26" />

<activity
android:name=".PiPMovieActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:supportsPictureInPicture="true"
android:launchMode="singleTask"
android:theme="@style/PiPAppTheme"
tools:targetApi="o" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@

package com.example.android.pip

import android.app.PictureInPictureParams
import android.app.PictureInPictureUiState
import android.content.res.Configuration
import android.graphics.Rect
import android.os.Build
import android.os.Bundle
import android.support.v4.media.MediaMetadataCompat
Expand All @@ -28,14 +25,12 @@ import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
import android.text.util.Linkify
import android.util.Log
import android.util.Rational
import android.view.View
import androidx.activity.ComponentActivity
import androidx.annotation.RequiresApi
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.doOnLayout
import com.example.android.pip.databinding.PipMovieActivityBinding
import com.example.android.pip.widget.MovieView

Expand Down Expand Up @@ -90,11 +85,6 @@ class PiPMovieActivity : ComponentActivity() {
binding.movie.getVideoResourceId(),
)
}

override fun onMovieMinimized() {
// The MovieView wants us to minimize it. We enter Picture-in-Picture mode now.
minimize()
}
}

override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -107,11 +97,6 @@ class PiPMovieActivity : ComponentActivity() {
} catch (e: Exception) {
Log.w("PiP", "Failed to add links", e)
}
binding.pip.setOnClickListener { minimize() }

// Configure parameters for the picture-in-picture mode. We do this at the first layout of
// the MovieView because we use its layout position and size.
binding.movie.doOnLayout { updatePictureInPictureParams() }

// Set up the video; it automatically starts.
binding.movie.setMovieListener(movieListener)
Expand Down Expand Up @@ -175,58 +160,6 @@ class PiPMovieActivity : ComponentActivity() {
}
}

override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean, newConfig: Configuration,
) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
if (isInPictureInPictureMode) {
// Hide the controls in picture-in-picture mode.
binding.movie.hideControls()
} else {
// Show the video controls if the video is not playing
if (!binding.movie.isPlaying) {
binding.movie.showControls()
}
}
}

@RequiresApi(35)
override fun onPictureInPictureUiStateChanged(pipState: PictureInPictureUiState) {
super.onPictureInPictureUiStateChanged(pipState)
if (pipState.isTransitioningToPip) {
binding.movie.hideControls()
}
}


private fun updatePictureInPictureParams(): PictureInPictureParams {
// Calculate the aspect ratio of the PiP screen.
val aspectRatio = Rational(binding.movie.width, binding.movie.height)
// The movie view turns into the picture-in-picture mode.
val visibleRect = Rect()
binding.movie.getGlobalVisibleRect(visibleRect)
val params = PictureInPictureParams.Builder()
.setAspectRatio(aspectRatio)
// Specify the portion of the screen that turns into the picture-in-picture mode.
// This makes the transition animation smoother.
.setSourceRectHint(visibleRect)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// The screen automatically turns into the picture-in-picture mode when it is hidden
// by the "Home" button.
params.setAutoEnterEnabled(true)
}
return params.build().also {
setPictureInPictureParams(it)
}
}

/**
* Enters Picture-in-Picture mode.
*/
private fun minimize() {
enterPictureInPictureMode(updatePictureInPictureParams())
}

/**
* Adjusts immersive full-screen flags depending on the screen orientation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,44 +16,11 @@

package com.example.android.pip

import android.app.PendingIntent
import android.app.PictureInPictureParams
import android.app.PictureInPictureUiState
import android.app.RemoteAction
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.res.Configuration
import android.graphics.drawable.Icon
import android.os.Build
import android.os.Bundle
import android.util.Rational
import android.view.View
import androidx.activity.ComponentActivity
import androidx.activity.trackPipAnimationHintView
import androidx.activity.viewModels
import androidx.annotation.DrawableRes
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.example.android.pip.databinding.PipActivityBinding
import kotlinx.coroutines.launch

/** Intent action for stopwatch controls from Picture-in-Picture mode. */
private const val ACTION_STOPWATCH_CONTROL = "stopwatch_control"

/** Intent extra for stopwatch controls from Picture-in-Picture mode. */
private const val EXTRA_CONTROL_TYPE = "control_type"
private const val CONTROL_TYPE_CLEAR = 1
private const val CONTROL_TYPE_START_OR_PAUSE = 2

private const val REQUEST_CLEAR = 3
private const val REQUEST_START_OR_PAUSE = 4

/**
* Demonstrates usage of Picture-in-Picture mode on phones and tablets.
Expand All @@ -64,23 +31,6 @@ class PiPSampleActivity : ComponentActivity() {
private val viewModel: PiPViewModel by viewModels()
private lateinit var binding: PipActivityBinding

/**
* A [BroadcastReceiver] for handling action items on the picture-in-picture mode.
*/
private val broadcastReceiver = object : BroadcastReceiver() {

// Called when an item is clicked.
override fun onReceive(context: Context?, intent: Intent?) {
if (intent == null || intent.action != ACTION_STOPWATCH_CONTROL) {
return
}
when (intent.getIntExtra(EXTRA_CONTROL_TYPE, 0)) {
CONTROL_TYPE_START_OR_PAUSE -> viewModel.startOrPause()
CONTROL_TYPE_CLEAR -> viewModel.clear()
}
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = PipActivityBinding.inflate(layoutInflater)
Expand All @@ -89,130 +39,13 @@ class PiPSampleActivity : ComponentActivity() {
binding.clear.setOnClickListener { viewModel.clear() }
binding.startOrPause.setOnClickListener { viewModel.startOrPause() }
binding.pip.setOnClickListener {
enterPictureInPictureMode(updatePictureInPictureParams(viewModel.started.value == true))
}
// Observe data from the viewModel.
viewModel.time.observe(this) { time -> binding.time.text = time }
viewModel.started.observe(this) { started ->
binding.startOrPause.setImageResource(
if (started) R.drawable.ic_pause_24dp else R.drawable.ic_play_arrow_24dp,
)
updatePictureInPictureParams(started)
}

// Use trackPipAnimationHint view to make a smooth enter/exit pip transition.
// See https://android.devsite.corp.google.com/develop/ui/views/picture-in-picture#smoother-transition
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
trackPipAnimationHintView(binding.stopwatchBackground)
}
}

// Handle events from the action icons on the picture-in-picture mode.
ActivityCompat.registerReceiver(
this,
broadcastReceiver,
IntentFilter(ACTION_STOPWATCH_CONTROL),
ContextCompat.RECEIVER_NOT_EXPORTED
)
}

// This is called when the activity gets into or out of the picture-in-picture mode.
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration,
) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
// Toggle visibility of in-app buttons. They cannot be interacted in the picture-in-picture
// mode, and their features are provided as the action icons.
toggleControls(if (isInPictureInPictureMode) View.GONE else View.VISIBLE)
}

private fun toggleControls(view: Int) {
binding.clear.visibility = view
binding.startOrPause.visibility = view
}

@RequiresApi(35)
override fun onPictureInPictureUiStateChanged(pipState: PictureInPictureUiState) {
super.onPictureInPictureUiStateChanged(pipState)
if (pipState.isTransitioningToPip) {
toggleControls(View.GONE)
}
}

/**
* Updates the parameters of the picture-in-picture mode for this activity based on the current
* [started] state of the stopwatch.
*/
private fun updatePictureInPictureParams(started: Boolean): PictureInPictureParams {
val params = PictureInPictureParams.Builder()
// Set action items for the picture-in-picture mode. These are the only custom controls
// available during the picture-in-picture mode.
.setActions(
listOf(
// "Clear" action.
createRemoteAction(
R.drawable.ic_refresh_24dp,
R.string.clear,
REQUEST_CLEAR,
CONTROL_TYPE_CLEAR,
),
if (started) {
// "Pause" action when the stopwatch is already started.
createRemoteAction(
R.drawable.ic_pause_24dp,
R.string.pause,
REQUEST_START_OR_PAUSE,
CONTROL_TYPE_START_OR_PAUSE,
)
} else {
// "Start" action when the stopwatch is not started.
createRemoteAction(
R.drawable.ic_play_arrow_24dp,
R.string.start,
REQUEST_START_OR_PAUSE,
CONTROL_TYPE_START_OR_PAUSE,
)
},
),
)
// Set the aspect ratio of the picture-in-picture mode.
.setAspectRatio(Rational(16, 9))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Turn the screen into the picture-in-picture mode if it's hidden by the "Home" button.
params.setAutoEnterEnabled(true)
// Disables the seamless resize. The seamless resize works great for videos where the
// content can be arbitrarily scaled, but you can disable this for non-video content so
// that the picture-in-picture mode is resized with a cross fade animation.
.setSeamlessResizeEnabled(false)
}
return params.build().also {
setPictureInPictureParams(it)
}
}

/**
* Creates a [RemoteAction]. It is used as an action icon on the overlay of the
* picture-in-picture mode.
*/
private fun createRemoteAction(
@DrawableRes iconResId: Int,
@StringRes titleResId: Int,
requestCode: Int,
controlType: Int,
): RemoteAction {
return RemoteAction(
Icon.createWithResource(this, iconResId),
getString(titleResId),
getString(titleResId),
PendingIntent.getBroadcast(
this,
requestCode,
Intent(ACTION_STOPWATCH_CONTROL)
.putExtra(EXTRA_CONTROL_TYPE, controlType),
PendingIntent.FLAG_IMMUTABLE,
),
)
}
}
Loading