Home | History | Annotate | Download | only in pictureinpicture
      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