1 /* 2 * Copyright (C) 2021 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.apis.app; 18 19 import android.app.Activity; 20 import android.app.PictureInPictureParams; 21 import android.content.res.Configuration; 22 import android.graphics.Rect; 23 import android.net.Uri; 24 import android.os.Bundle; 25 import android.util.Rational; 26 import android.view.Gravity; 27 import android.view.View; 28 import android.widget.Button; 29 import android.widget.FrameLayout; 30 import android.widget.VideoView; 31 32 import com.example.android.apis.R; 33 34 public class PictureInPictureSourceRectHint extends Activity { 35 36 private FrameLayout mFrameLayout; 37 private VideoView mVideoView; 38 private Button mEntryButton; 39 private Button mExitButton; 40 41 private int mPositionOnEntry; 42 private int mPositionOnExit; 43 44 @Override onCreate(Bundle savedInstanceState)45 protected void onCreate(Bundle savedInstanceState) { 46 super.onCreate(savedInstanceState); 47 getActionBar().hide(); 48 setContentView(R.layout.picture_in_picture_source_rect_hint); 49 50 mFrameLayout = findViewById(R.id.frame_layout); 51 mVideoView = findViewById(R.id.video_view); 52 mEntryButton = findViewById(R.id.entry_source_btn); 53 mExitButton = findViewById(R.id.exit_source_btn); 54 55 initPlayer(Uri.parse("android.resource://" + getPackageName() + 56 "/" + R.raw.videoviewdemo)); 57 58 59 setEntryState(Gravity.TOP); 60 setExitState(Gravity.TOP); 61 62 mEntryButton.setOnClickListener(v -> { 63 // Toggle the position and update the views. 64 setEntryState(mPositionOnEntry == Gravity.TOP ? Gravity.BOTTOM : Gravity.TOP); 65 }); 66 mExitButton.setOnClickListener(v -> { 67 // Toggle the position and update the views. 68 setExitState(mPositionOnExit == Gravity.TOP ? Gravity.BOTTOM : Gravity.TOP); 69 }); 70 71 mVideoView.addOnLayoutChangeListener( 72 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { 73 if (left == oldLeft && right == oldRight && top == oldTop 74 && bottom == oldBottom) return; 75 76 setPictureInPictureParams(new PictureInPictureParams.Builder() 77 .setSourceRectHint(getVideoRectHint()) 78 .build()); 79 }); 80 } 81 82 @Override onDestroy()83 protected void onDestroy() { 84 super.onDestroy(); 85 mVideoView.stopPlayback(); 86 } 87 88 @Override onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig)89 public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, 90 Configuration newConfig) { 91 mEntryButton.setVisibility(isInPictureInPictureMode ? View.GONE : View.VISIBLE); 92 mExitButton.setVisibility(isInPictureInPictureMode ? View.GONE : View.VISIBLE); 93 94 final FrameLayout.LayoutParams params; 95 if (isInPictureInPictureMode) { 96 // In PIP mode the video should take up the full width and height. 97 params = new FrameLayout.LayoutParams( 98 FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); 99 } else { 100 // Exiting PIP, return the video to its original size and place it to the preferred 101 // gravity selected before entering PIP mode. 102 params = new FrameLayout.LayoutParams( 103 FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT); 104 params.gravity = mPositionOnExit; 105 // The "exit" gravity becomes the current "entry" gravity, update it and its views. 106 setEntryState(mPositionOnExit); 107 } 108 mVideoView.setLayoutParams(params); 109 } 110 111 @Override onPictureInPictureRequested()112 public boolean onPictureInPictureRequested() { 113 final Rect hint = getVideoRectHint(); 114 enterPictureInPictureMode(new PictureInPictureParams.Builder() 115 .setSourceRectHint(hint) 116 .setAspectRatio(new Rational(hint.width(), hint.height())) 117 .build()); 118 return true; 119 } 120 initPlayer(Uri uri)121 private void initPlayer(Uri uri) { 122 mVideoView.setVideoURI(uri); 123 mVideoView.requestFocus(); 124 mVideoView.setOnPreparedListener(mp -> { 125 mp.setLooping(true); 126 mVideoView.start(); 127 }); 128 } 129 getVideoRectHint()130 private Rect getVideoRectHint() { 131 final Rect hint = new Rect(); 132 mVideoView.getGlobalVisibleRect(hint); 133 return hint; 134 } 135 setEntryState(int gravity)136 private void setEntryState(int gravity) { 137 mPositionOnEntry = gravity; 138 mEntryButton.setText(getString( 139 R.string.activity_picture_in_picture_source_rect_hint_current_position, 140 getGravityName(mPositionOnEntry))); 141 final FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mVideoView.getLayoutParams(); 142 p.gravity = mPositionOnEntry; 143 mVideoView.setLayoutParams(p); 144 } 145 setExitState(int gravity)146 private void setExitState(int gravity) { 147 mPositionOnExit = gravity; 148 mExitButton.setText(getString( 149 R.string.activity_picture_in_picture_source_rect_hint_position_on_exit, 150 getGravityName(mPositionOnExit))); 151 } 152 getGravityName(int gravity)153 private String getGravityName(int gravity) { 154 return gravity == Gravity.TOP ? "TOP" : "BOTTOM"; 155 } 156 } 157