PipHintTracker.kt
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.activity
import android.app.Activity
import android.app.PictureInPictureParams
import android.graphics.Rect
import android.os.Build
import android.view.View
import android.view.ViewTreeObserver
import androidx.annotation.RequiresApi
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
/**
* Sets the [View] that will be used as reference to set the
* [PictureInPictureParams.Builder.setSourceRectHint].
*
* Anytime the view position changes, [Activity.setPictureInPictureParams] will be called with
* the updated view's position in window coordinates as the
* [PictureInPictureParams.Builder.setSourceRectHint].
*
* @param view the view to use as the reference for the source rect hint
*/
@ExperimentalCoroutinesApi
@RequiresApi(Build.VERSION_CODES.O)
public suspend fun Activity.trackPipAnimationHintView(view: View) {
// Returns a rect of the window coordinates of a view.
fun View.positionInWindow(): Rect {
val position = Rect()
getGlobalVisibleRect(position)
return position
}
// Create a cold flow that will emit the most updated position of the view in the form of a
// rect as long as the view is attached to the window.
@Suppress("DEPRECATION")
val flow = callbackFlow<Rect> {
// Emit a new hint rect any time the view moves.
val layoutChangeListener = View.OnLayoutChangeListener { v, l, t, r, b, oldLeft, oldTop,
oldRight, oldBottom ->
if (l != oldLeft || r != oldRight || t != oldTop || b != oldBottom) {
trySend(v.positionInWindow())
}
}
val scrollChangeListener = ViewTreeObserver.OnScrollChangedListener {
trySend(view.positionInWindow())
}
// When the view is attached, emit the current position and start listening for layout
// changes to track movement.
val attachStateChangeListener = object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
trySend(view.positionInWindow())
view.viewTreeObserver.addOnScrollChangedListener(scrollChangeListener)
view.addOnLayoutChangeListener(layoutChangeListener)
}
override fun onViewDetachedFromWindow(v: View) {
v.viewTreeObserver.removeOnScrollChangedListener(scrollChangeListener)
v.removeOnLayoutChangeListener(layoutChangeListener)
}
}
// Check if the view is already attached to the window, if it is then emit the current
// position and start listening for layout changes to track movement.
if (Api19Impl.isAttachedToWindow(view)) {
trySend(view.positionInWindow())
view.viewTreeObserver.addOnScrollChangedListener(scrollChangeListener)
view.addOnLayoutChangeListener(layoutChangeListener)
}
view.addOnAttachStateChangeListener(attachStateChangeListener)
awaitClose {
view.viewTreeObserver.removeOnScrollChangedListener(scrollChangeListener)
view.removeOnLayoutChangeListener(layoutChangeListener)
view.removeOnAttachStateChangeListener(attachStateChangeListener)
}
}
flow.collect { hint ->
Api26Impl.setPipParamsSourceRectHint(this, hint)
}
}
@RequiresApi(Build.VERSION_CODES.KITKAT)
internal object Api19Impl {
fun isAttachedToWindow(view: View): Boolean = view.isAttachedToWindow
}
@RequiresApi(Build.VERSION_CODES.O)
internal object Api26Impl {
fun setPipParamsSourceRectHint(activity: Activity, hint: Rect) {
activity.setPictureInPictureParams(
PictureInPictureParams.Builder()
.setSourceRectHint(hint)
.build()
)
}
}