NekoX/TMessagesProj/src/main/java/org/telegram/ui/Components/PipVideoOverlay.java

1146 lines
48 KiB
Java

package org.telegram.ui.Components;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.os.Build;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.WindowManager;
import android.webkit.WebView;
import android.widget.FrameLayout;
import android.widget.ImageView;
import androidx.core.math.MathUtils;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.FloatPropertyCompat;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import com.google.android.exoplayer2.C;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.R;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.LaunchActivity;
import org.telegram.ui.PhotoViewer;
import java.util.ArrayList;
import java.util.List;
public class PipVideoOverlay {
public final static boolean IS_TRANSITION_ANIMATION_SUPPORTED = true;
public final static float ROUNDED_CORNERS_DP = 10;
private final static float SIDE_PADDING_DP = 16;
private final static FloatPropertyCompat<PipVideoOverlay> PIP_X_PROPERTY = new SimpleFloatPropertyCompat<>("pipX", obj -> obj.pipX, (obj, value) -> {
obj.windowLayoutParams.x = (int) (obj.pipX = value);
try {
obj.windowManager.updateViewLayout(obj.contentView, obj.windowLayoutParams);
} catch (IllegalArgumentException e) {
obj.pipXSpring.cancel();
}
}), PIP_Y_PROPERTY = new SimpleFloatPropertyCompat<>("pipY", obj -> obj.pipY, (obj, value) -> {
obj.windowLayoutParams.y = (int) (obj.pipY = value);
try {
obj.windowManager.updateViewLayout(obj.contentView, obj.windowLayoutParams);
} catch (IllegalArgumentException e) {
obj.pipYSpring.cancel();
}
});
@SuppressLint("StaticFieldLeak")
private static PipVideoOverlay instance = new PipVideoOverlay();
private float minScaleFactor = 0.75f, maxScaleFactor = 1.4f;
private WindowManager windowManager;
private WindowManager.LayoutParams windowLayoutParams;
private ViewGroup contentView;
private FrameLayout contentFrameLayout;
private View innerView;
private FrameLayout controlsView;
private ScaleGestureDetector scaleGestureDetector;
private GestureDetectorFixDoubleTap gestureDetector;
private boolean isScrolling;
private boolean isScrollDisallowed;
private View consumingChild;
private boolean isShowingControls;
private ValueAnimator controlsAnimator;
private PipConfig pipConfig;
private int pipWidth, pipHeight;
private float scaleFactor = 1f;
private float pipX, pipY;
private SpringAnimation pipXSpring, pipYSpring;
private Float aspectRatio;
private boolean isVisible;
private VideoForwardDrawable videoForwardDrawable = new VideoForwardDrawable(false);
private int mVideoWidth, mVideoHeight;
private EmbedBottomSheet parentSheet;
private PhotoViewer photoViewer;
private ImageView playPauseButton;
private boolean isVideoCompleted;
private float videoProgress, bufferProgress;
private VideoProgressView videoProgressView;
private boolean isDismissing;
private boolean onSideToDismiss;
private Runnable progressRunnable = () -> {
if (photoViewer == null) {
return;
}
VideoPlayer videoPlayer = photoViewer.getVideoPlayer();
if (videoPlayer == null) {
return;
}
videoProgress = videoPlayer.getCurrentPosition() / (float) videoPlayer.getDuration();
if (photoViewer == null) {
bufferProgress = videoPlayer.getBufferedPosition() / (float) videoPlayer.getDuration();
}
videoProgressView.invalidate();
AndroidUtilities.runOnUIThread(this.progressRunnable, 500);
};
private boolean canLongClick;
private float[] longClickStartPoint = new float[2];
private Runnable longClickCallback = this::onLongClick;
private boolean postedDismissControls;
private Runnable dismissControlsCallback = () -> {
if (photoViewer != null && photoViewer.getVideoPlayerRewinder().rewindCount > 0) {
AndroidUtilities.runOnUIThread(this.dismissControlsCallback, 1500);
return;
}
toggleControls(isShowingControls = false);
postedDismissControls = false;
};
public static void onRewindCanceled() {
instance.onRewindCanceledInternal();
}
private void onRewindCanceledInternal() {
videoForwardDrawable.setShowing(false);
}
public static void onUpdateRewindProgressUi(long timeDiff, float progress, boolean rewindByBackSeek) {
instance.onUpdateRewindProgressUiInternal(timeDiff, progress, rewindByBackSeek);
}
private void onUpdateRewindProgressUiInternal(long timeDiff, float progress, boolean rewindByBackSeek) {
videoForwardDrawable.setTime(0);
if (rewindByBackSeek) {
videoProgress = progress;
if (videoProgressView != null) {
videoProgressView.invalidate();
}
if (controlsView != null) {
controlsView.invalidate();
}
}
}
public static void onRewindStart(boolean rewindForward) {
instance.onRewindStartInternal(rewindForward);
}
private void onRewindStartInternal(boolean rewindForward) {
videoForwardDrawable.setOneShootAnimation(false);
videoForwardDrawable.setLeftSide(!rewindForward);
videoForwardDrawable.setShowing(true);
if (videoProgressView != null) {
videoProgressView.invalidate();
}
if (controlsView != null) {
controlsView.invalidate();
}
}
protected void onLongClick() {
if (photoViewer == null || photoViewer.getVideoPlayer() == null || isDismissing || isVideoCompleted || isScrolling || scaleGestureDetector.isInProgress() || !canLongClick) {
return;
}
VideoPlayer videoPlayer = photoViewer.getVideoPlayer();
boolean forward = longClickStartPoint[0] >= getSuggestedWidth() * scaleFactor * 0.5f;
long current = videoPlayer.getCurrentPosition();
long total = videoPlayer.getDuration();
if (current == C.TIME_UNSET || total < 15 * 1000) {
return;
}
photoViewer.getVideoPlayerRewinder().startRewind(videoPlayer, forward, photoViewer.getCurrentVideoSpeed());
if (!isShowingControls) {
toggleControls(isShowingControls = true);
if (!postedDismissControls) {
AndroidUtilities.runOnUIThread(dismissControlsCallback, 1500);
postedDismissControls = true;
}
}
}
private PipConfig getPipConfig() {
if (pipConfig == null) {
pipConfig = new PipConfig(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y);
}
return pipConfig;
}
public static boolean isVisible() {
return instance.isVisible;
}
private int getSuggestedWidth() {
return getSuggestedWidth(getRatio());
}
private static int getSuggestedWidth(float ratio) {
if (ratio >= 1) {
return (int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.35f);
}
return (int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.6f);
}
private int getSuggestedHeight() {
return getSuggestedHeight(getRatio());
}
private static int getSuggestedHeight(float ratio) {
return (int) (getSuggestedWidth(ratio) * ratio);
}
private float getRatio() {
if (aspectRatio == null) {
aspectRatio = mVideoHeight / (float) mVideoWidth;
maxScaleFactor = (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(SIDE_PADDING_DP * 2)) / (float) getSuggestedWidth();
videoForwardDrawable.setPlayScaleFactor(aspectRatio < 1 ? 0.6f : 0.45f);
}
return aspectRatio;
}
private void toggleControls(boolean show) {
controlsAnimator = ValueAnimator.ofFloat(show ? 0 : 1, show ? 1 : 0f).setDuration(200);
controlsAnimator.setInterpolator(CubicBezierInterpolator.DEFAULT);
controlsAnimator.addUpdateListener(animation -> {
float value = (float) animation.getAnimatedValue();
controlsView.setAlpha(value);
});
controlsAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
controlsAnimator = null;
}
});
controlsAnimator.start();
}
public static void dimissAndDestroy() {
if (instance.parentSheet != null) {
instance.parentSheet.destroy();
} else if (instance.photoViewer != null) {
instance.photoViewer.destroyPhotoViewer();
}
dismiss();
}
public static void dismiss() {
dismiss(false);
}
public static void dismiss(boolean animate) {
instance.dismissInternal(animate);
}
private void dismissInternal(boolean animate) {
if (isDismissing) {
return;
}
isDismissing = true;
if (controlsAnimator != null) {
controlsAnimator.cancel();
}
if (postedDismissControls) {
AndroidUtilities.cancelRunOnUIThread(dismissControlsCallback);
postedDismissControls = false;
}
if (pipXSpring != null) {
pipXSpring.cancel();
pipYSpring.cancel();
}
// Animate is a flag for PhotoViewer transition, not ours
if (animate) {
AndroidUtilities.runOnUIThread(this::onDismissedInternal, 100);
} else {
AnimatorSet set = new AnimatorSet();
set.setDuration(250);
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
set.playTogether(
ObjectAnimator.ofFloat(contentView, View.ALPHA, 0f),
ObjectAnimator.ofFloat(contentView, View.SCALE_X, 0.1f),
ObjectAnimator.ofFloat(contentView, View.SCALE_Y, 0.1f)
);
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
onDismissedInternal();
}
});
set.start();
}
}
private void onDismissedInternal() {
try {
if (controlsView.getParent() != null) {
windowManager.removeViewImmediate(contentView);
}
} catch (IllegalArgumentException ignored) {}
videoProgressView = null;
innerView = null;
photoViewer = null;
parentSheet = null;
consumingChild = null;
isScrolling = false;
isVisible = false;
isDismissing = false;
canLongClick = false;
cancelRewind();
AndroidUtilities.cancelRunOnUIThread(longClickCallback);
}
private void cancelRewind() {
if (photoViewer == null) {
return;
}
if (photoViewer.getVideoPlayerRewinder().rewindCount > 0) {
photoViewer.getVideoPlayerRewinder().cancelRewind();
}
}
public static void updatePlayButton() {
instance.updatePlayButtonInternal();
}
private void updatePlayButtonInternal() {
if (photoViewer == null) {
return;
}
VideoPlayer videoPlayer = photoViewer.getVideoPlayer();
if (videoPlayer == null || playPauseButton == null) {
return;
}
AndroidUtilities.cancelRunOnUIThread(progressRunnable);
if (!videoPlayer.isPlaying()) {
if (isVideoCompleted) {
playPauseButton.setImageResource(R.drawable.pip_replay_large);
} else {
playPauseButton.setImageResource(R.drawable.pip_play_large);
}
} else {
playPauseButton.setImageResource(R.drawable.pip_pause_large);
AndroidUtilities.runOnUIThread(progressRunnable, 500);
}
}
public static void onVideoCompleted() {
instance.onVideoCompletedInternal();
}
private void onVideoCompletedInternal() {
if (!isVisible || videoProgressView == null) {
return;
}
isVideoCompleted = true;
videoProgress = 0f;
bufferProgress = 0f;
if (videoProgressView != null) {
videoProgressView.invalidate();
}
updatePlayButtonInternal();
AndroidUtilities.cancelRunOnUIThread(progressRunnable);
if (!isShowingControls) {
toggleControls(true);
AndroidUtilities.cancelRunOnUIThread(dismissControlsCallback);
}
}
public static void setBufferedProgress(float progress) {
instance.bufferProgress = progress;
if (instance.videoProgressView != null) {
instance.videoProgressView.invalidate();
}
}
public static void setParentSheet(EmbedBottomSheet parentSheet) {
instance.parentSheet = parentSheet;
}
public static void setPhotoViewer(PhotoViewer photoViewer) {
instance.photoViewer = photoViewer;
instance.updatePlayButtonInternal();
}
public static Rect getPipRect(boolean inAnimation, float aspectRatio) {
Rect rect = new Rect();
float ratio = 1f / aspectRatio;
if (!instance.isVisible || inAnimation) {
float savedPipX = instance.getPipConfig().getPipX(), savedPipY = instance.getPipConfig().getPipY();
float scaleFactor = instance.getPipConfig().getScaleFactor();
rect.width = getSuggestedWidth(ratio) * scaleFactor;
rect.height = getSuggestedHeight(ratio) * scaleFactor;
if (savedPipX != -1) {
rect.x = savedPipX + rect.width / 2f >= AndroidUtilities.displaySize.x / 2f ? AndroidUtilities.displaySize.x - rect.width - AndroidUtilities.dp(SIDE_PADDING_DP) : AndroidUtilities.dp(SIDE_PADDING_DP);
} else {
rect.x = AndroidUtilities.displaySize.x - rect.width - AndroidUtilities.dp(SIDE_PADDING_DP);
}
if (savedPipY != -1) {
rect.y = MathUtils.clamp(savedPipY, AndroidUtilities.dp(SIDE_PADDING_DP), AndroidUtilities.displaySize.y - AndroidUtilities.dp(SIDE_PADDING_DP) - rect.height) + AndroidUtilities.statusBarHeight;
} else {
rect.y = AndroidUtilities.dp(SIDE_PADDING_DP) + AndroidUtilities.statusBarHeight;
}
return rect;
}
rect.x = instance.pipX;
rect.y = instance.pipY + AndroidUtilities.statusBarHeight;
rect.width = instance.pipWidth;
rect.height = instance.pipHeight;
return rect;
}
public static boolean show(boolean inAppOnly, Activity activity, View pipContentView, int videoWidth, int videoHeight) {
return show(inAppOnly, activity, pipContentView, videoWidth, videoHeight, false);
}
public static boolean show(boolean inAppOnly, Activity activity, View pipContentView, int videoWidth, int videoHeight, boolean animate) {
return instance.showInternal(inAppOnly, activity, pipContentView, videoWidth, videoHeight, animate);
}
private boolean showInternal(boolean inAppOnly, Activity activity, View pipContentView, int videoWidth, int videoHeight, boolean animate) {
if (isVisible) {
return false;
}
isVisible = true;
mVideoWidth = videoWidth;
mVideoHeight = videoHeight;
aspectRatio = null;
float savedPipX = getPipConfig().getPipX(), savedPipY = getPipConfig().getPipY();
scaleFactor = getPipConfig().getScaleFactor();
pipWidth = (int) (getSuggestedWidth() * scaleFactor);
pipHeight = (int) (getSuggestedHeight() * scaleFactor);
isShowingControls = false;
float stiffness = 650f;
pipXSpring = new SpringAnimation(this, PIP_X_PROPERTY)
.setSpring(new SpringForce()
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
.setStiffness(stiffness))
.addEndListener((animation, canceled, value, velocity) -> getPipConfig().setPipX(value));
pipYSpring = new SpringAnimation(this, PIP_Y_PROPERTY)
.setSpring(new SpringForce()
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
.setStiffness(stiffness))
.addEndListener((animation, canceled, value, velocity) -> getPipConfig().setPipY(value));
Context context = ApplicationLoader.applicationContext;
int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
scaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.OnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
scaleFactor = MathUtils.clamp(scaleFactor * detector.getScaleFactor(), minScaleFactor, maxScaleFactor);
pipWidth = (int) (getSuggestedWidth() * scaleFactor);
pipHeight = (int) (getSuggestedHeight() * scaleFactor);
AndroidUtilities.runOnUIThread(()->{
contentView.invalidate();
contentFrameLayout.requestLayout();
});
float finalX = detector.getFocusX() >= AndroidUtilities.displaySize.x / 2f ? AndroidUtilities.displaySize.x - pipWidth - AndroidUtilities.dp(SIDE_PADDING_DP) : AndroidUtilities.dp(SIDE_PADDING_DP);
if (!pipXSpring.isRunning()) {
pipXSpring.setStartValue(pipX)
.getSpring()
.setFinalPosition(finalX);
} else {
pipXSpring.getSpring().setFinalPosition(finalX);
}
pipXSpring.start();
float finalY = MathUtils.clamp(detector.getFocusY() - pipHeight / 2f, AndroidUtilities.dp(SIDE_PADDING_DP), AndroidUtilities.displaySize.y - pipHeight - AndroidUtilities.dp(SIDE_PADDING_DP));
if (!pipYSpring.isRunning()) {
pipYSpring.setStartValue(pipY)
.getSpring()
.setFinalPosition(finalY);
} else {
pipYSpring.getSpring().setFinalPosition(finalY);
}
pipYSpring.start();
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
if (isScrolling) {
isScrolling = false;
canLongClick = false;
cancelRewind();
AndroidUtilities.cancelRunOnUIThread(longClickCallback);
}
isScrollDisallowed = true;
windowLayoutParams.width = (int) (getSuggestedWidth() * maxScaleFactor);
windowLayoutParams.height = (int) (getSuggestedHeight() * maxScaleFactor);
windowManager.updateViewLayout(contentView, windowLayoutParams);
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
if (pipXSpring.isRunning() || pipYSpring.isRunning()) {
List<SpringAnimation> springs = new ArrayList<>();
DynamicAnimation.OnAnimationEndListener endListener = new DynamicAnimation.OnAnimationEndListener() {
@Override
public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, float velocity) {
animation.removeEndListener(this);
springs.add((SpringAnimation) animation);
if (springs.size() == 2) {
updateLayout();
}
}
};
if (!pipXSpring.isRunning()) {
springs.add(pipXSpring);
} else {
pipXSpring.addEndListener(endListener);
}
if (!pipYSpring.isRunning()) {
springs.add(pipYSpring);
} else {
pipYSpring.addEndListener(endListener);
}
return;
}
updateLayout();
}
private void updateLayout() {
pipWidth = windowLayoutParams.width = (int) (getSuggestedWidth() * scaleFactor);
pipHeight = windowLayoutParams.height = (int) (getSuggestedHeight() * scaleFactor);
try {
windowManager.updateViewLayout(contentView, windowLayoutParams);
} catch (IllegalArgumentException ignored) {}
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
scaleGestureDetector.setQuickScaleEnabled(false);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
scaleGestureDetector.setStylusScaleEnabled(false);
}
gestureDetector = new GestureDetectorFixDoubleTap(context, new GestureDetectorFixDoubleTap.OnGestureListener() {
private float startPipX, startPipY;
@Override
public boolean onDown(MotionEvent e) {
if (isShowingControls) {
for (int i = 1; i < contentFrameLayout.getChildCount(); i++) {
View child = contentFrameLayout.getChildAt(i);
boolean consumed = child.dispatchTouchEvent(e);
if (consumed) {
consumingChild = child;
return true;
}
}
}
startPipX = pipX;
startPipY = pipY;
return true;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (controlsAnimator != null) {
return true;
}
if (postedDismissControls) {
AndroidUtilities.cancelRunOnUIThread(dismissControlsCallback);
postedDismissControls = false;
}
isShowingControls = !isShowingControls;
toggleControls(isShowingControls);
if (isShowingControls && !postedDismissControls) {
AndroidUtilities.runOnUIThread(dismissControlsCallback, 2500);
postedDismissControls = true;
}
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
if (photoViewer == null || photoViewer.getVideoPlayer() == null || isDismissing || isVideoCompleted || isScrolling || scaleGestureDetector.isInProgress() || !canLongClick) {
return false;
}
VideoPlayer videoPlayer = photoViewer.getVideoPlayer();
boolean forward = e.getX() >= getSuggestedWidth() * scaleFactor * 0.5f;
long current = videoPlayer.getCurrentPosition();
long total = videoPlayer.getDuration();
if (current == C.TIME_UNSET || total < 15 * 1000) {
return false;
}
long old = current;
if (forward) {
current += 10000;
} else {
current -= 10000;
}
if (old != current) {
boolean apply = true;
if (current > total) {
current = total;
} else if (current < 0) {
if (current < -9000) {
apply = false;
}
current = 0;
}
if (apply) {
videoForwardDrawable.setOneShootAnimation(true);
videoForwardDrawable.setLeftSide(!forward);
videoForwardDrawable.addTime(10000);
videoPlayer.seekTo(current);
onUpdateRewindProgressUiInternal(forward ? 10000 : -10000, current / (float) total, true);
if (!isShowingControls) {
toggleControls(isShowingControls = true);
if (!postedDismissControls) {
postedDismissControls = true;
AndroidUtilities.runOnUIThread(dismissControlsCallback, 2500);
}
}
}
return true;
}
return false;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
if (!hasDoubleTap()) {
return onSingleTapConfirmed(e);
}
return super.onSingleTapUp(e);
}
@Override
public boolean hasDoubleTap() {
if (photoViewer == null || photoViewer.getVideoPlayer() == null || isDismissing || isVideoCompleted || isScrolling || scaleGestureDetector.isInProgress() || !canLongClick) {
return false;
}
VideoPlayer videoPlayer = photoViewer.getVideoPlayer();
long current = videoPlayer.getCurrentPosition();
long total = videoPlayer.getDuration();
return current != C.TIME_UNSET && total >= 15 * 1000;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (isScrolling && !isScrollDisallowed) {
pipXSpring.setStartVelocity(velocityX)
.setStartValue(pipX)
.getSpring()
.setFinalPosition(pipX + pipWidth / 2f + velocityX / 7f >= AndroidUtilities.displaySize.x / 2f ? AndroidUtilities.displaySize.x - pipWidth - AndroidUtilities.dp(SIDE_PADDING_DP) : AndroidUtilities.dp(SIDE_PADDING_DP));
pipXSpring.start();
pipYSpring.setStartVelocity(velocityX)
.setStartValue(pipY)
.getSpring()
.setFinalPosition(MathUtils.clamp(pipY + velocityY / 10f, AndroidUtilities.dp(SIDE_PADDING_DP), AndroidUtilities.displaySize.y - pipHeight - AndroidUtilities.dp(SIDE_PADDING_DP)));
pipYSpring.start();
return true;
}
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (!isScrolling && controlsAnimator == null && !isScrollDisallowed) {
if (Math.abs(distanceX) >= touchSlop || Math.abs(distanceY) >= touchSlop) {
isScrolling = true;
pipXSpring.cancel();
pipYSpring.cancel();
canLongClick = false;
cancelRewind();
AndroidUtilities.cancelRunOnUIThread(longClickCallback);
}
}
if (isScrolling) {
float wasPipX = pipX;
float newPipX = startPipX + e2.getRawX() - e1.getRawX();
pipY = startPipY + e2.getRawY() - e1.getRawY();
if (newPipX <= -pipWidth * 0.25f || newPipX >= AndroidUtilities.displaySize.x - pipWidth * 0.75f) {
if (!onSideToDismiss) {
pipXSpring.setStartValue(wasPipX)
.getSpring()
.setFinalPosition(newPipX + pipWidth / 2f >= AndroidUtilities.displaySize.x / 2f ? AndroidUtilities.displaySize.x - AndroidUtilities.dp(SIDE_PADDING_DP) : AndroidUtilities.dp(SIDE_PADDING_DP) - pipWidth);
pipXSpring.start();
}
onSideToDismiss = true;
} else if (onSideToDismiss) {
if (onSideToDismiss) {
pipXSpring.addEndListener((animation, canceled, value, velocity) -> {
if (!canceled) {
pipXSpring.getSpring().setFinalPosition(newPipX + pipWidth / 2f >= AndroidUtilities.displaySize.x / 2f ? AndroidUtilities.displaySize.x - pipWidth - AndroidUtilities.dp(SIDE_PADDING_DP) : AndroidUtilities.dp(SIDE_PADDING_DP));
}
});
pipXSpring.setStartValue(wasPipX)
.getSpring()
.setFinalPosition(newPipX);
pipXSpring.start();
}
onSideToDismiss = false;
} else {
if (pipXSpring.isRunning()) {
pipXSpring.getSpring().setFinalPosition(newPipX);
} else {
windowLayoutParams.x = (int) (pipX = newPipX);
getPipConfig().setPipX(newPipX);
}
windowLayoutParams.y = (int) pipY;
getPipConfig().setPipY(pipY);
windowManager.updateViewLayout(contentView, windowLayoutParams);
}
}
return true;
}
});
contentFrameLayout = new FrameLayout(context) {
private Path path = new Path();
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) {
if (ev.getPointerCount() == 1) {
canLongClick = true;
longClickStartPoint = new float[]{ev.getX(), ev.getY()};
AndroidUtilities.runOnUIThread(longClickCallback, 500);
} else {
canLongClick = false;
cancelRewind();
AndroidUtilities.cancelRunOnUIThread(longClickCallback);
}
}
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_POINTER_UP) {
canLongClick = false;
cancelRewind();
AndroidUtilities.cancelRunOnUIThread(longClickCallback);
}
if (consumingChild != null) {
MotionEvent newEvent = MotionEvent.obtain(ev);
newEvent.offsetLocation(consumingChild.getX(), consumingChild.getY());
boolean consumed = consumingChild.dispatchTouchEvent(ev);
newEvent.recycle();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_POINTER_UP) {
consumingChild = null;
}
if (consumed) {
return true;
}
}
MotionEvent temp = MotionEvent.obtain(ev);
temp.offsetLocation(ev.getRawX() - ev.getX(), ev.getRawY() - ev.getY());
boolean scaleDetector = scaleGestureDetector.onTouchEvent(temp);
temp.recycle();
boolean detector = !scaleGestureDetector.isInProgress() && gestureDetector.onTouchEvent(ev);
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_POINTER_UP) {
isScrolling = false;
isScrollDisallowed = false;
if (onSideToDismiss) {
onSideToDismiss = false;
dimissAndDestroy();
} else {
if (!pipXSpring.isRunning()) {
pipXSpring.setStartValue(pipX)
.getSpring()
.setFinalPosition(pipX + pipWidth / 2f >= AndroidUtilities.displaySize.x / 2f ? AndroidUtilities.displaySize.x - pipWidth - AndroidUtilities.dp(SIDE_PADDING_DP) : AndroidUtilities.dp(SIDE_PADDING_DP));
pipXSpring.start();
}
if (!pipYSpring.isRunning()) {
pipYSpring.setStartValue(pipY)
.getSpring()
.setFinalPosition(MathUtils.clamp(pipY, AndroidUtilities.dp(SIDE_PADDING_DP), AndroidUtilities.displaySize.y - pipHeight - AndroidUtilities.dp(SIDE_PADDING_DP)));
pipYSpring.start();
}
}
}
return scaleDetector || detector;
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
AndroidUtilities.checkDisplaySize(getContext(), newConfig);
pipConfig = null;
if (pipWidth != getSuggestedWidth() * scaleFactor || pipHeight != getSuggestedHeight() * scaleFactor) {
windowLayoutParams.width = pipWidth = (int) (getSuggestedWidth() * scaleFactor);
windowLayoutParams.height = pipHeight = (int) (getSuggestedHeight() * scaleFactor);
windowManager.updateViewLayout(contentView, windowLayoutParams);
pipXSpring.setStartValue(pipX)
.getSpring()
.setFinalPosition(pipX + (getSuggestedWidth() * scaleFactor) / 2f >= AndroidUtilities.displaySize.x / 2f ? AndroidUtilities.displaySize.x - (getSuggestedWidth() * scaleFactor) - AndroidUtilities.dp(SIDE_PADDING_DP) : AndroidUtilities.dp(SIDE_PADDING_DP));
pipXSpring.start();
pipYSpring.setStartValue(pipY)
.getSpring()
.setFinalPosition(MathUtils.clamp(pipY, AndroidUtilities.dp(SIDE_PADDING_DP), AndroidUtilities.displaySize.y - (getSuggestedHeight() * scaleFactor) - AndroidUtilities.dp(SIDE_PADDING_DP)));
pipYSpring.start();
}
}
@Override
public void draw(Canvas canvas) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
super.draw(canvas);
} else {
canvas.save();
canvas.clipPath(path);
super.draw(canvas);
canvas.restore();
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
path.rewind();
AndroidUtilities.rectTmp.set(0, 0, w, h);
path.addRoundRect(AndroidUtilities.rectTmp, AndroidUtilities.dp(ROUNDED_CORNERS_DP), AndroidUtilities.dp(ROUNDED_CORNERS_DP), Path.Direction.CW);
}
};
contentView = new ViewGroup(context) {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
contentFrameLayout.layout(0, 0, pipWidth, pipHeight);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
contentFrameLayout.measure(MeasureSpec.makeMeasureSpec(pipWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(pipHeight, MeasureSpec.EXACTLY));
}
@Override
public void draw(Canvas canvas) {
canvas.save();
canvas.scale(pipWidth / (float)contentFrameLayout.getWidth(), pipHeight / (float)contentFrameLayout.getHeight());
super.draw(canvas);
canvas.restore();
}
};
contentView.addView(contentFrameLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
contentFrameLayout.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight(), AndroidUtilities.dp(ROUNDED_CORNERS_DP));
}
});
contentFrameLayout.setClipToOutline(true);
}
contentFrameLayout.setBackgroundColor(Theme.getColor(Theme.key_voipgroup_actionBar));
innerView = pipContentView;
if (innerView.getParent() != null) {
((ViewGroup)innerView.getParent()).removeView(innerView);
}
contentFrameLayout.addView(innerView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
videoForwardDrawable.setDelegate(new VideoForwardDrawable.VideoForwardDrawableDelegate() {
@Override
public void onAnimationEnd() {}
@Override
public void invalidate() {
controlsView.invalidate();
}
});
controlsView = new FrameLayout(context) {
@Override
protected void onDraw(Canvas canvas) {
if (videoForwardDrawable.isAnimating()) {
videoForwardDrawable.setBounds(getLeft(), getTop(), getRight(), getBottom());
videoForwardDrawable.draw(canvas);
}
}
};
controlsView.setWillNotDraw(false);
controlsView.setAlpha(0f);
View scrim = new View(context);
scrim.setBackgroundColor(0x4C000000);
controlsView.addView(scrim, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
int padding = AndroidUtilities.dp(8);
int margin = 4;
int buttonSize = 38;
ImageView closeButton = new ImageView(context);
closeButton.setImageResource(R.drawable.pip_video_close);
closeButton.setColorFilter(Theme.getColor(Theme.key_voipgroup_actionBarItems), PorterDuff.Mode.MULTIPLY);
closeButton.setBackground(Theme.createSelectorDrawable(Theme.getColor(Theme.key_listSelector)));
closeButton.setPadding(padding, padding, padding, padding);
closeButton.setOnClickListener(v -> dimissAndDestroy());
controlsView.addView(closeButton, LayoutHelper.createFrame(buttonSize, buttonSize, Gravity.RIGHT, 0, margin, margin, 0));
ImageView expandButton = new ImageView(context);
expandButton.setImageResource(R.drawable.pip_video_expand);
expandButton.setColorFilter(Theme.getColor(Theme.key_voipgroup_actionBarItems), PorterDuff.Mode.MULTIPLY);
expandButton.setBackground(Theme.createSelectorDrawable(Theme.getColor(Theme.key_listSelector)));
expandButton.setPadding(padding, padding, padding, padding);
expandButton.setOnClickListener(v -> {
boolean isResumedByActivityManager = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ActivityManager activityManager = (ActivityManager) v.getContext().getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> appProcessInfos = activityManager.getRunningAppProcesses();
if (!appProcessInfos.isEmpty()) {
isResumedByActivityManager = appProcessInfos.get(0).importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
}
}
if (!inAppOnly && (!isResumedByActivityManager || !LaunchActivity.isResumed)) {
LaunchActivity.onResumeStaticCallback = v::callOnClick;
Context ctx = ApplicationLoader.applicationContext;
Intent intent = new Intent(ctx, LaunchActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ctx.startActivity(intent);
} else {
if (parentSheet != null) {
parentSheet.exitFromPip();
} else if (photoViewer != null) {
photoViewer.exitFromPip();
}
}
});
controlsView.addView(expandButton, LayoutHelper.createFrame(buttonSize, buttonSize, Gravity.RIGHT, 0, margin, buttonSize + margin + 6, 0));
playPauseButton = new ImageView(context);
playPauseButton.setColorFilter(Theme.getColor(Theme.key_voipgroup_actionBarItems), PorterDuff.Mode.MULTIPLY);
playPauseButton.setBackground(Theme.createSelectorDrawable(Theme.getColor(Theme.key_listSelector)));
playPauseButton.setOnClickListener(v -> {
if (photoViewer == null) {
return;
}
VideoPlayer videoPlayer = photoViewer.getVideoPlayer();
if (videoPlayer == null) {
return;
}
if (videoPlayer.isPlaying()) {
videoPlayer.pause();
} else {
videoPlayer.play();
}
updatePlayButton();
});
playPauseButton.setVisibility(innerView instanceof WebView ? View.GONE : View.VISIBLE);
controlsView.addView(playPauseButton, LayoutHelper.createFrame(buttonSize, buttonSize, Gravity.CENTER));
videoProgressView = new VideoProgressView(context);
controlsView.addView(videoProgressView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
contentFrameLayout.addView(controlsView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
windowManager = (WindowManager) (inAppOnly ? activity : ApplicationLoader.applicationContext).getSystemService(Context.WINDOW_SERVICE);
windowLayoutParams = createWindowLayoutParams(inAppOnly);
windowLayoutParams.width = pipWidth;
windowLayoutParams.height = pipHeight;
if (savedPipX != -1) {
windowLayoutParams.x = (int) (pipX = savedPipX + pipWidth / 2f >= AndroidUtilities.displaySize.x / 2f ? AndroidUtilities.displaySize.x - pipWidth - AndroidUtilities.dp(SIDE_PADDING_DP) : AndroidUtilities.dp(SIDE_PADDING_DP));
} else {
windowLayoutParams.x = (int) (pipX = AndroidUtilities.displaySize.x - pipWidth - AndroidUtilities.dp(SIDE_PADDING_DP));
}
if (savedPipY != -1) {
windowLayoutParams.y = (int) (pipY = MathUtils.clamp(savedPipY, AndroidUtilities.dp(SIDE_PADDING_DP), AndroidUtilities.displaySize.y - AndroidUtilities.dp(SIDE_PADDING_DP) - pipHeight));
} else {
windowLayoutParams.y = (int) (pipY = AndroidUtilities.dp(SIDE_PADDING_DP));
}
windowLayoutParams.dimAmount = 0f;
windowLayoutParams.flags = FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
// Animate is a flag for PhotoViewer transition, not ours
if (animate) {
windowManager.addView(contentView, windowLayoutParams);
} else {
contentView.setAlpha(0f);
contentView.setScaleX(0.1f);
contentView.setScaleY(0.1f);
windowManager.addView(contentView, windowLayoutParams);
AnimatorSet set = new AnimatorSet();
set.setDuration(250);
set.setInterpolator(CubicBezierInterpolator.DEFAULT);
set.playTogether(
ObjectAnimator.ofFloat(contentView, View.ALPHA, 1f),
ObjectAnimator.ofFloat(contentView, View.SCALE_X, 1f),
ObjectAnimator.ofFloat(contentView, View.SCALE_Y, 1f)
);
set.start();
}
return true;
}
@SuppressLint("WrongConstant")
private WindowManager.LayoutParams createWindowLayoutParams(boolean inAppOnly) {
WindowManager.LayoutParams windowLayoutParams = new WindowManager.LayoutParams();
windowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
windowLayoutParams.format = PixelFormat.TRANSLUCENT;
if (!inAppOnly && AndroidUtilities.checkInlinePermissions(ApplicationLoader.applicationContext)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
windowLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
windowLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
} else {
windowLayoutParams.type = WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
}
windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
return windowLayoutParams;
}
private final class VideoProgressView extends View {
private Paint progressPaint = new Paint(), bufferPaint = new Paint();
public VideoProgressView(Context context) {
super(context);
progressPaint.setColor(Color.WHITE);
progressPaint.setStyle(Paint.Style.STROKE);
progressPaint.setStrokeCap(Paint.Cap.ROUND);
progressPaint.setStrokeWidth(AndroidUtilities.dp(2));
bufferPaint.setColor(progressPaint.getColor());
bufferPaint.setAlpha((int) (progressPaint.getAlpha() * 0.3f));
bufferPaint.setStyle(Paint.Style.STROKE);
bufferPaint.setStrokeCap(Paint.Cap.ROUND);
bufferPaint.setStrokeWidth(AndroidUtilities.dp(2));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int progressSidePadding = AndroidUtilities.dp(10);
int progressLeft = progressSidePadding;
int progressRight = progressLeft + (int) ((width - progressLeft - progressSidePadding) * videoProgress);
float y = getHeight() - AndroidUtilities.dp(8);
if (bufferProgress != 0) {
canvas.drawLine(progressLeft, y, progressLeft + (width - progressLeft - progressSidePadding) * bufferProgress, y, bufferPaint);
}
canvas.drawLine(progressLeft, y, progressRight, y, progressPaint);
}
}
private final static class PipConfig {
private SharedPreferences mPrefs;
private PipConfig(int width, int height) {
mPrefs = ApplicationLoader.applicationContext.getSharedPreferences("pip_layout_" + width + "_" + height, Context.MODE_PRIVATE);
}
private void setPipX(float x) {
mPrefs.edit().putFloat("x", x).apply();
}
private void setPipY(float y) {
mPrefs.edit().putFloat("y", y).apply();
}
private void setScaleFactor(float scaleFactor) {
mPrefs.edit().putFloat("scale_factor", scaleFactor).apply();
}
private float getScaleFactor() {
return mPrefs.getFloat("scale_factor", 1f);
}
private float getPipX() {
return mPrefs.getFloat("x", -1);
}
private float getPipY() {
return mPrefs.getFloat("y", -1);
}
}
}