Home | History | Annotate | Download | only in am
      1 /*
      2  * Copyright (C) 2015 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 android.server.am;
     18 
     19 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
     20 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
     21 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
     22 import static android.server.am.Components.PipActivity.ACTION_ENTER_PIP;
     23 import static android.server.am.Components.PipActivity.ACTION_EXPAND_PIP;
     24 import static android.server.am.Components.PipActivity.ACTION_FINISH;
     25 import static android.server.am.Components.PipActivity.ACTION_MOVE_TO_BACK;
     26 import static android.server.am.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION;
     27 import static android.server.am.Components.PipActivity.EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP;
     28 import static android.server.am.Components.PipActivity.EXTRA_ENTER_PIP;
     29 import static android.server.am.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR;
     30 import static android.server.am.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR;
     31 import static android.server.am.Components.PipActivity.EXTRA_ENTER_PIP_ON_PAUSE;
     32 import static android.server.am.Components.PipActivity.EXTRA_FINISH_SELF_ON_RESUME;
     33 import static android.server.am.Components.PipActivity.EXTRA_ON_PAUSE_DELAY;
     34 import static android.server.am.Components.PipActivity.EXTRA_PIP_ORIENTATION;
     35 import static android.server.am.Components.PipActivity.EXTRA_REENTER_PIP_ON_EXIT;
     36 import static android.server.am.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_DENOMINATOR;
     37 import static android.server.am.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_NUMERATOR;
     38 import static android.server.am.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR;
     39 import static android.server.am.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR;
     40 import static android.server.am.Components.PipActivity.EXTRA_SHOW_OVER_KEYGUARD;
     41 import static android.server.am.Components.PipActivity.EXTRA_START_ACTIVITY;
     42 import static android.server.am.Components.PipActivity.EXTRA_TAP_TO_FINISH;
     43 
     44 import android.app.Activity;
     45 import android.app.ActivityOptions;
     46 import android.app.PictureInPictureParams;
     47 import android.content.BroadcastReceiver;
     48 import android.content.ComponentName;
     49 import android.content.Context;
     50 import android.content.Intent;
     51 import android.content.IntentFilter;
     52 import android.content.res.Configuration;
     53 import android.graphics.Rect;
     54 import android.os.Bundle;
     55 import android.os.Handler;
     56 import android.os.SystemClock;
     57 import android.util.Log;
     58 import android.util.Rational;
     59 import android.view.WindowManager;
     60 
     61 public class PipActivity extends AbstractLifecycleLogActivity {
     62 
     63     private boolean mEnteredPictureInPicture;
     64 
     65     private Handler mHandler = new Handler();
     66     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
     67         @Override
     68         public void onReceive(Context context, Intent intent) {
     69             if (intent != null) {
     70                 switch (intent.getAction()) {
     71                     case ACTION_ENTER_PIP:
     72                         enterPictureInPictureMode();
     73                         break;
     74                     case ACTION_MOVE_TO_BACK:
     75                         moveTaskToBack(false /* nonRoot */);
     76                         break;
     77                     case ACTION_EXPAND_PIP:
     78                         // Trigger the activity to expand
     79                         Intent startIntent = new Intent(PipActivity.this, PipActivity.class);
     80                         startIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
     81                         startActivity(startIntent);
     82 
     83                         if (intent.hasExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR)
     84                                 && intent.hasExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR)) {
     85                             // Ugly, but required to wait for the startActivity to actually start
     86                             // the activity...
     87                             mHandler.postDelayed(() -> {
     88                                 final PictureInPictureParams.Builder builder =
     89                                         new PictureInPictureParams.Builder();
     90                                 builder.setAspectRatio(getAspectRatio(intent,
     91                                         EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR,
     92                                         EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR));
     93                                 setPictureInPictureParams(builder.build());
     94                             }, 100);
     95                         }
     96                         break;
     97                     case ACTION_SET_REQUESTED_ORIENTATION:
     98                         setRequestedOrientation(Integer.parseInt(intent.getStringExtra(
     99                                 EXTRA_PIP_ORIENTATION)));
    100                         break;
    101                     case ACTION_FINISH:
    102                         finish();
    103                         break;
    104                 }
    105             }
    106         }
    107     };
    108 
    109     @Override
    110     protected void onCreate(Bundle savedInstanceState) {
    111         super.onCreate(savedInstanceState);
    112 
    113         // Set the fixed orientation if requested
    114         if (getIntent().hasExtra(EXTRA_PIP_ORIENTATION)) {
    115             final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_PIP_ORIENTATION));
    116             setRequestedOrientation(ori);
    117         }
    118 
    119         // Set the window flag to show over the keyguard
    120         if (getIntent().hasExtra(EXTRA_SHOW_OVER_KEYGUARD)) {
    121             setShowWhenLocked(true);
    122         }
    123 
    124         // Enter picture in picture with the given aspect ratio if provided
    125         if (getIntent().hasExtra(EXTRA_ENTER_PIP)) {
    126             if (getIntent().hasExtra(EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR)
    127                     && getIntent().hasExtra(EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR)) {
    128                 try {
    129                     final PictureInPictureParams.Builder builder =
    130                             new PictureInPictureParams.Builder();
    131                     builder.setAspectRatio(getAspectRatio(getIntent(),
    132                             EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR,
    133                             EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR));
    134                     enterPictureInPictureMode(builder.build());
    135                 } catch (Exception e) {
    136                     // This call can fail intentionally if the aspect ratio is too extreme
    137                 }
    138             } else {
    139                 enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
    140             }
    141         }
    142 
    143         // We need to wait for either enterPictureInPicture() or requestAutoEnterPictureInPicture()
    144         // to be called before setting the aspect ratio
    145         if (getIntent().hasExtra(EXTRA_SET_ASPECT_RATIO_NUMERATOR)
    146                 && getIntent().hasExtra(EXTRA_SET_ASPECT_RATIO_DENOMINATOR)) {
    147             final PictureInPictureParams.Builder builder =
    148                     new PictureInPictureParams.Builder();
    149             builder.setAspectRatio(getAspectRatio(getIntent(),
    150                     EXTRA_SET_ASPECT_RATIO_NUMERATOR, EXTRA_SET_ASPECT_RATIO_DENOMINATOR));
    151             try {
    152                 setPictureInPictureParams(builder.build());
    153             } catch (Exception e) {
    154                 // This call can fail intentionally if the aspect ratio is too extreme
    155             }
    156         }
    157 
    158         // Enable tap to finish if necessary
    159         if (getIntent().hasExtra(EXTRA_TAP_TO_FINISH)) {
    160             setContentView(R.layout.tap_to_finish_pip_layout);
    161             findViewById(R.id.content).setOnClickListener(v -> {
    162                 finish();
    163             });
    164         }
    165 
    166         // Launch a new activity if requested
    167         String launchActivityComponent = getIntent().getStringExtra(EXTRA_START_ACTIVITY);
    168         if (launchActivityComponent != null) {
    169             Intent launchIntent = new Intent();
    170             launchIntent.setComponent(ComponentName.unflattenFromString(launchActivityComponent));
    171             startActivity(launchIntent);
    172         }
    173 
    174         // Register the broadcast receiver
    175         IntentFilter filter = new IntentFilter();
    176         filter.addAction(ACTION_ENTER_PIP);
    177         filter.addAction(ACTION_MOVE_TO_BACK);
    178         filter.addAction(ACTION_EXPAND_PIP);
    179         filter.addAction(ACTION_SET_REQUESTED_ORIENTATION);
    180         filter.addAction(ACTION_FINISH);
    181         registerReceiver(mReceiver, filter);
    182 
    183         // Dump applied display metrics.
    184         Configuration config = getResources().getConfiguration();
    185         dumpDisplaySize(config);
    186         dumpConfiguration(config);
    187     }
    188 
    189     @Override
    190     protected void onResume() {
    191         super.onResume();
    192 
    193         // Finish self if requested
    194         if (getIntent().hasExtra(EXTRA_FINISH_SELF_ON_RESUME)) {
    195             finish();
    196         }
    197     }
    198 
    199     @Override
    200     protected void onPause() {
    201         super.onPause();
    202 
    203         // Pause if requested
    204         if (getIntent().hasExtra(EXTRA_ON_PAUSE_DELAY)) {
    205             SystemClock.sleep(Long.valueOf(getIntent().getStringExtra(EXTRA_ON_PAUSE_DELAY)));
    206         }
    207 
    208         // Enter PIP on move to background
    209         if (getIntent().hasExtra(EXTRA_ENTER_PIP_ON_PAUSE)) {
    210             enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
    211         }
    212     }
    213 
    214     @Override
    215     protected void onStop() {
    216         super.onStop();
    217 
    218         if (getIntent().hasExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP) && !mEnteredPictureInPicture) {
    219             Log.w(getTag(), "Unexpected onStop() called before entering picture-in-picture");
    220             finish();
    221         }
    222     }
    223 
    224     @Override
    225     protected void onDestroy() {
    226         super.onDestroy();
    227 
    228         unregisterReceiver(mReceiver);
    229     }
    230 
    231     @Override
    232     public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode,
    233             Configuration newConfig) {
    234         super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
    235 
    236         // Fail early if the activity state does not match the dispatched state
    237         if (isInPictureInPictureMode() != isInPictureInPictureMode) {
    238             Log.w(getTag(), "Received onPictureInPictureModeChanged mode="
    239                     + isInPictureInPictureMode + " activityState=" + isInPictureInPictureMode());
    240             finish();
    241         }
    242 
    243         // Mark that we've entered picture-in-picture so that we can stop checking for
    244         // EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP
    245         if (isInPictureInPictureMode) {
    246             mEnteredPictureInPicture = true;
    247         }
    248 
    249         if (!isInPictureInPictureMode && getIntent().hasExtra(EXTRA_REENTER_PIP_ON_EXIT)) {
    250             // This call to re-enter PIP can happen too quickly (host side tests can have difficulty
    251             // checking that the stacks ever changed). Therefor, we need to delay here slightly to
    252             // allow the tests to verify that the stacks have changed before re-entering.
    253             mHandler.postDelayed(() -> {
    254                 enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
    255             }, 1000);
    256         }
    257     }
    258 
    259     @Override
    260     public void onConfigurationChanged(Configuration newConfig) {
    261         super.onConfigurationChanged(newConfig);
    262         dumpDisplaySize(newConfig);
    263         dumpConfiguration(newConfig);
    264     }
    265 
    266     /**
    267      * Launches a new instance of the PipActivity directly into the pinned stack.
    268      */
    269     static void launchActivityIntoPinnedStack(Activity caller, Rect bounds) {
    270         final Intent intent = new Intent(caller, PipActivity.class);
    271         intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
    272         intent.putExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
    273 
    274         final ActivityOptions options = ActivityOptions.makeBasic();
    275         options.setLaunchBounds(bounds);
    276         options.setLaunchWindowingMode(WINDOWING_MODE_PINNED);
    277         caller.startActivity(intent, options.toBundle());
    278     }
    279 
    280     /**
    281      * Launches a new instance of the PipActivity in the same task that will automatically enter
    282      * PiP.
    283      */
    284     static void launchEnterPipActivity(Activity caller) {
    285         final Intent intent = new Intent(caller, PipActivity.class);
    286         intent.putExtra(EXTRA_ENTER_PIP, "true");
    287         intent.putExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
    288         caller.startActivity(intent);
    289     }
    290 
    291     /**
    292      * @return a {@link Rational} aspect ratio from the given intent and extras.
    293      */
    294     private Rational getAspectRatio(Intent intent, String extraNum, String extraDenom) {
    295         return new Rational(
    296                 Integer.valueOf(intent.getStringExtra(extraNum)),
    297                 Integer.valueOf(intent.getStringExtra(extraDenom)));
    298     }
    299 }
    300