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