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.wm.app;
18 
19 import static android.server.wm.app.Components.PipActivity.ACTION_CHANGE_ASPECT_RATIO;
20 import static android.server.wm.app.Components.PipActivity.ACTION_ENTER_PIP;
21 import static android.server.wm.app.Components.PipActivity.ACTION_ENTER_PIP_AND_WAIT_FOR_UI_STATE;
22 import static android.server.wm.app.Components.PipActivity.ACTION_EXPAND_PIP;
23 import static android.server.wm.app.Components.PipActivity.ACTION_FINISH;
24 import static android.server.wm.app.Components.PipActivity.ACTION_LAUNCH_TRANSLUCENT_ACTIVITY;
25 import static android.server.wm.app.Components.PipActivity.ACTION_MOVE_TO_BACK;
26 import static android.server.wm.app.Components.PipActivity.ACTION_ON_PIP_REQUESTED;
27 import static android.server.wm.app.Components.PipActivity.ACTION_SET_ON_PAUSE_REMOTE_CALLBACK;
28 import static android.server.wm.app.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION;
29 import static android.server.wm.app.Components.PipActivity.ACTION_UPDATE_PIP_STATE;
30 import static android.server.wm.app.Components.PipActivity.EXTRA_ALLOW_AUTO_PIP;
31 import static android.server.wm.app.Components.PipActivity.EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP;
32 import static android.server.wm.app.Components.PipActivity.EXTRA_CLOSE_ACTION;
33 import static android.server.wm.app.Components.PipActivity.EXTRA_DISMISS_KEYGUARD;
34 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
35 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR;
36 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR;
37 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_BACK_PRESSED;
38 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_PAUSE;
39 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_PIP_REQUESTED;
40 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_USER_LEAVE_HINT;
41 import static android.server.wm.app.Components.PipActivity.EXTRA_EXPANDED_PIP_ASPECT_RATIO_DENOMINATOR;
42 import static android.server.wm.app.Components.PipActivity.EXTRA_EXPANDED_PIP_ASPECT_RATIO_NUMERATOR;
43 import static android.server.wm.app.Components.PipActivity.EXTRA_FINISH_SELF_ON_RESUME;
44 import static android.server.wm.app.Components.PipActivity.EXTRA_IS_SEAMLESS_RESIZE_ENABLED;
45 import static android.server.wm.app.Components.PipActivity.EXTRA_NUMBER_OF_CUSTOM_ACTIONS;
46 import static android.server.wm.app.Components.PipActivity.EXTRA_ON_PAUSE_DELAY;
47 import static android.server.wm.app.Components.PipActivity.EXTRA_PIP_ON_PAUSE_CALLBACK;
48 import static android.server.wm.app.Components.PipActivity.EXTRA_PIP_ORIENTATION;
49 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_DENOMINATOR;
50 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_NUMERATOR;
51 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR;
52 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR;
53 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_PIP_CALLBACK;
54 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_PIP_STASHED;
55 import static android.server.wm.app.Components.PipActivity.EXTRA_SHOW_OVER_KEYGUARD;
56 import static android.server.wm.app.Components.PipActivity.EXTRA_START_ACTIVITY;
57 import static android.server.wm.app.Components.PipActivity.EXTRA_SUBTITLE;
58 import static android.server.wm.app.Components.PipActivity.EXTRA_TAP_TO_FINISH;
59 import static android.server.wm.app.Components.PipActivity.EXTRA_TITLE;
60 import static android.server.wm.app.Components.PipActivity.IS_IN_PIP_MODE_RESULT;
61 import static android.server.wm.app.Components.PipActivity.UI_STATE_ENTERING_PIP_RESULT;
62 import static android.server.wm.app.Components.PipActivity.UI_STATE_STASHED_RESULT;
63 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
64 
65 import android.app.Activity;
66 import android.app.PendingIntent;
67 import android.app.PictureInPictureParams;
68 import android.app.PictureInPictureUiState;
69 import android.app.RemoteAction;
70 import android.content.BroadcastReceiver;
71 import android.content.ComponentName;
72 import android.content.Context;
73 import android.content.Intent;
74 import android.content.IntentFilter;
75 import android.content.res.Configuration;
76 import android.graphics.drawable.Icon;
77 import android.os.Bundle;
78 import android.os.Handler;
79 import android.os.RemoteCallback;
80 import android.os.SystemClock;
81 import android.server.wm.CommandSession;
82 import android.util.Log;
83 import android.util.Rational;
84 
85 import androidx.annotation.Nullable;
86 
87 import java.util.ArrayList;
88 import java.util.List;
89 
90 public class PipActivity extends AbstractLifecycleLogActivity {
91 
92     private boolean mEnteredPictureInPicture;
93     private boolean mEnterPipOnBackPressed;
94     private RemoteCallback mCb;
95     private RemoteCallback mOnPauseCallback;
96 
97     private Handler mHandler = new Handler();
98     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
99         @Override
100         public void onReceive(Context context, Intent intent) {
101             if (intent != null) {
102                 switch (intent.getAction()) {
103                     case ACTION_ENTER_PIP:
104                         enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
105                         if (intent.getExtras() != null) {
106                             mCb = (RemoteCallback) intent.getExtras().get(EXTRA_SET_PIP_CALLBACK);
107                             if (mCb != null) {
108                                 mCb.sendResult(new Bundle());
109                             }
110                         }
111                         break;
112                     case ACTION_ENTER_PIP_AND_WAIT_FOR_UI_STATE:
113                         // mCb will be callback-ed in onPictureInPictureUiStateChanged.
114                         mCb = (RemoteCallback) intent.getExtras().get(EXTRA_SET_PIP_CALLBACK);
115                         enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
116                         break;
117                     case ACTION_MOVE_TO_BACK:
118                         moveTaskToBack(false /* nonRoot */);
119                         break;
120                     case ACTION_UPDATE_PIP_STATE:
121                         mCb = (RemoteCallback) intent.getExtras().get(EXTRA_SET_PIP_CALLBACK);
122                         boolean stashed = intent.getBooleanExtra(EXTRA_SET_PIP_STASHED, false);
123                         onPictureInPictureUiStateChanged(new PictureInPictureUiState(stashed));
124                         break;
125                     case ACTION_EXPAND_PIP:
126                         // Trigger the activity to expand
127                         Intent startIntent = new Intent(PipActivity.this, PipActivity.class);
128                         startIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
129                         startActivity(startIntent);
130 
131                         if (intent.hasExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR)
132                                 && intent.hasExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR)) {
133                             // Ugly, but required to wait for the startActivity to actually start
134                             // the activity...
135                             mHandler.postDelayed(() -> {
136                                 final PictureInPictureParams.Builder builder =
137                                         new PictureInPictureParams.Builder();
138                                 builder.setAspectRatio(getAspectRatio(intent,
139                                         EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR,
140                                         EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR));
141                                 setPictureInPictureParams(builder.build());
142                             }, 100);
143                         }
144                         break;
145                     case ACTION_SET_REQUESTED_ORIENTATION:
146                         setRequestedOrientation(Integer.parseInt(intent.getStringExtra(
147                                 EXTRA_PIP_ORIENTATION)));
148                         break;
149                     case ACTION_FINISH:
150                         finish();
151                         break;
152                     case ACTION_ON_PIP_REQUESTED:
153                         onPictureInPictureRequested();
154                         break;
155                     case ACTION_CHANGE_ASPECT_RATIO:
156                         setPictureInPictureParams(new PictureInPictureParams.Builder()
157                                 .setAspectRatio(getAspectRatio(intent,
158                                         EXTRA_SET_ASPECT_RATIO_NUMERATOR,
159                                         EXTRA_SET_ASPECT_RATIO_DENOMINATOR))
160                                 .build());
161                         break;
162                     case ACTION_LAUNCH_TRANSLUCENT_ACTIVITY:
163                         startActivity(new Intent(PipActivity.this, TranslucentTestActivity.class));
164                         break;
165                     case ACTION_SET_ON_PAUSE_REMOTE_CALLBACK:
166                         mOnPauseCallback = intent.getParcelableExtra(
167                                 EXTRA_PIP_ON_PAUSE_CALLBACK, RemoteCallback.class);
168                         // Signals the caller that we have received the mOnPauseCallback
169                         final RemoteCallback setCallback = intent.getParcelableExtra(
170                                 EXTRA_SET_PIP_CALLBACK, RemoteCallback.class);
171                         setCallback.sendResult(Bundle.EMPTY);
172                         break;
173                 }
174             }
175         }
176     };
177 
178     @Override
onCreate(Bundle savedInstanceState)179     protected void onCreate(Bundle savedInstanceState) {
180         super.onCreate(savedInstanceState);
181 
182         // Set the fixed orientation if requested
183         if (getIntent().hasExtra(EXTRA_PIP_ORIENTATION)) {
184             final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_PIP_ORIENTATION));
185             setRequestedOrientation(ori);
186         }
187 
188         // Set the window flag to show over the keyguard
189         setShowWhenLocked(parseBooleanExtra(EXTRA_SHOW_OVER_KEYGUARD));
190 
191         // Set the window flag to dismiss the keyguard
192         if (parseBooleanExtra(EXTRA_DISMISS_KEYGUARD)) {
193             getWindow().addFlags(FLAG_DISMISS_KEYGUARD);
194         }
195 
196         boolean enteringPip = false;
197         // Enter picture in picture with the given aspect ratio if provided
198         if (parseBooleanExtra(EXTRA_ENTER_PIP)) {
199             if (getIntent().hasExtra(EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR)
200                     && getIntent().hasExtra(EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR)) {
201                 try {
202                     final PictureInPictureParams.Builder builder =
203                             new PictureInPictureParams.Builder();
204                     builder.setAspectRatio(getAspectRatio(getIntent(),
205                             EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR,
206                             EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR));
207                     if (shouldAddExpandedPipAspectRatios()) {
208                         builder.setExpandedAspectRatio(getAspectRatio(getIntent(),
209                                 EXTRA_EXPANDED_PIP_ASPECT_RATIO_NUMERATOR,
210                                 EXTRA_EXPANDED_PIP_ASPECT_RATIO_DENOMINATOR));
211                     }
212                     enteringPip = enterPictureInPictureMode(builder.build());
213                 } catch (Exception e) {
214                     // This call can fail intentionally if the aspect ratio is too extreme
215                 }
216             } else {
217                 enteringPip = enterPictureInPictureMode(
218                         new PictureInPictureParams.Builder().build());
219             }
220         }
221 
222         // We need to wait for either enterPictureInPicture() or requestAutoEnterPictureInPicture()
223         // to be called before setting the aspect ratio
224         if (getIntent().hasExtra(EXTRA_SET_ASPECT_RATIO_NUMERATOR)
225                 && getIntent().hasExtra(EXTRA_SET_ASPECT_RATIO_DENOMINATOR)) {
226             final PictureInPictureParams.Builder builder =
227                     new PictureInPictureParams.Builder();
228             builder.setAspectRatio(getAspectRatio(getIntent(),
229                     EXTRA_SET_ASPECT_RATIO_NUMERATOR, EXTRA_SET_ASPECT_RATIO_DENOMINATOR));
230             try {
231                 setPictureInPictureParams(builder.build());
232             } catch (Exception e) {
233                 // This call can fail intentionally if the aspect ratio is too extreme
234             }
235         }
236 
237         final PictureInPictureParams.Builder sharedBuilder = new PictureInPictureParams.Builder();
238         boolean sharedBuilderChanged = false;
239 
240         if (parseBooleanExtra(EXTRA_ALLOW_AUTO_PIP)) {
241             sharedBuilder.setAutoEnterEnabled(true);
242             sharedBuilderChanged = true;
243         }
244 
245         if (getIntent().hasExtra(EXTRA_IS_SEAMLESS_RESIZE_ENABLED)) {
246             sharedBuilder.setSeamlessResizeEnabled(
247                     getIntent().getBooleanExtra(EXTRA_IS_SEAMLESS_RESIZE_ENABLED, true));
248             sharedBuilderChanged = true;
249         }
250 
251         if (getIntent().hasExtra(EXTRA_TITLE)) {
252             sharedBuilder.setTitle(getIntent().getStringExtra(EXTRA_TITLE));
253             sharedBuilderChanged = true;
254         }
255 
256         if (getIntent().hasExtra(EXTRA_SUBTITLE)) {
257             sharedBuilder.setSubtitle(getIntent().getStringExtra(EXTRA_SUBTITLE));
258             sharedBuilderChanged = true;
259         }
260 
261         if (getIntent().hasExtra(EXTRA_CLOSE_ACTION)) {
262             if (getIntent().getBooleanExtra(EXTRA_CLOSE_ACTION, false)) {
263                 sharedBuilder.setCloseAction(createRemoteAction(0));
264             } else {
265                 sharedBuilder.setCloseAction(null);
266             }
267             sharedBuilderChanged = true;
268         }
269 
270         // Enable tap to finish if necessary
271         if (parseBooleanExtra(EXTRA_TAP_TO_FINISH)) {
272             setContentView(R.layout.tap_to_finish_pip_layout);
273             findViewById(R.id.content).setOnClickListener(v -> {
274                 finish();
275             });
276         }
277 
278         // Launch a new activity if requested
279         String launchActivityComponent = getIntent().getStringExtra(EXTRA_START_ACTIVITY);
280         if (launchActivityComponent != null) {
281             Intent launchIntent = new Intent();
282             launchIntent.setComponent(ComponentName.unflattenFromString(launchActivityComponent));
283             startActivity(launchIntent);
284         }
285 
286         // Set custom actions if requested
287         if (getIntent().hasExtra(EXTRA_NUMBER_OF_CUSTOM_ACTIONS)) {
288             final int numberOfCustomActions = Integer.valueOf(
289                     getIntent().getStringExtra(EXTRA_NUMBER_OF_CUSTOM_ACTIONS));
290             final List<RemoteAction> actions = new ArrayList<>(numberOfCustomActions);
291             for (int i = 0; i< numberOfCustomActions; i++) {
292                 actions.add(createRemoteAction(i));
293             }
294             sharedBuilder.setActions(actions);
295             sharedBuilderChanged = true;
296         }
297 
298         if (sharedBuilderChanged) {
299             setPictureInPictureParams(sharedBuilder.build());
300         }
301 
302         // Register the broadcast receiver
303         IntentFilter filter = new IntentFilter();
304         filter.addAction(ACTION_ENTER_PIP);
305         filter.addAction(ACTION_MOVE_TO_BACK);
306         filter.addAction(ACTION_EXPAND_PIP);
307         filter.addAction(ACTION_UPDATE_PIP_STATE);
308         filter.addAction(ACTION_ENTER_PIP_AND_WAIT_FOR_UI_STATE);
309         filter.addAction(ACTION_SET_REQUESTED_ORIENTATION);
310         filter.addAction(ACTION_FINISH);
311         filter.addAction(ACTION_ON_PIP_REQUESTED);
312         filter.addAction(ACTION_CHANGE_ASPECT_RATIO);
313         filter.addAction(ACTION_LAUNCH_TRANSLUCENT_ACTIVITY);
314         filter.addAction(ACTION_SET_ON_PAUSE_REMOTE_CALLBACK);
315         registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED);
316 
317         // Don't dump configuration when entering PIP to avoid the verifier getting the intermediate
318         // state. In this case it is expected that the verifier will check the changed configuration
319         // after onConfigurationChanged.
320         if (!enteringPip) {
321             // Dump applied display metrics.
322             dumpConfiguration(getResources().getConfiguration());
323             dumpConfigInfo();
324         }
325 
326         mEnterPipOnBackPressed = parseBooleanExtra(EXTRA_ENTER_PIP_ON_BACK_PRESSED);
327     }
328 
329     @Override
onResume()330     protected void onResume() {
331         super.onResume();
332 
333         // Finish self if requested
334         if (parseBooleanExtra(EXTRA_FINISH_SELF_ON_RESUME)) {
335             finish();
336         }
337     }
338 
339     @Override
onPause()340     protected void onPause() {
341         super.onPause();
342 
343         // Pause if requested
344         if (getIntent().hasExtra(EXTRA_ON_PAUSE_DELAY)) {
345             SystemClock.sleep(Long.valueOf(getIntent().getStringExtra(EXTRA_ON_PAUSE_DELAY)));
346         }
347 
348         // Enter PIP on move to background
349         if (parseBooleanExtra(EXTRA_ENTER_PIP_ON_PAUSE)) {
350             enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
351         }
352 
353         if (mOnPauseCallback != null) {
354             Bundle res = new Bundle(1);
355             res.putBoolean(IS_IN_PIP_MODE_RESULT, isInPictureInPictureMode());
356             mOnPauseCallback.sendResult(res);
357         }
358     }
359 
360     @Override
onStop()361     protected void onStop() {
362         super.onStop();
363 
364         if (parseBooleanExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP) && !mEnteredPictureInPicture) {
365             Log.w(getTag(), "Unexpected onStop() called before entering picture-in-picture");
366             finish();
367         }
368     }
369 
370     @Override
onDestroy()371     protected void onDestroy() {
372         super.onDestroy();
373 
374         unregisterReceiver(mReceiver);
375     }
376 
377     @Override
onUserLeaveHint()378     protected void onUserLeaveHint() {
379         super.onUserLeaveHint();
380         if (parseBooleanExtra(EXTRA_ENTER_PIP_ON_USER_LEAVE_HINT)) {
381             enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
382         }
383     }
384 
385     @Override
onPictureInPictureRequested()386     public boolean onPictureInPictureRequested() {
387         onCallback(CommandSession.ActivityCallback.ON_PICTURE_IN_PICTURE_REQUESTED);
388         if (parseBooleanExtra(EXTRA_ENTER_PIP_ON_PIP_REQUESTED)) {
389             enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
390             return true;
391         }
392         return super.onPictureInPictureRequested();
393     }
394 
395     @Override
onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig)396     public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode,
397             Configuration newConfig) {
398         super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
399 
400         // Fail early if the activity state does not match the dispatched state
401         if (isInPictureInPictureMode() != isInPictureInPictureMode) {
402             Log.w(getTag(), "Received onPictureInPictureModeChanged mode="
403                     + isInPictureInPictureMode + " activityState=" + isInPictureInPictureMode());
404             finish();
405         }
406 
407         // Mark that we've entered picture-in-picture so that we can stop checking for
408         // EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP
409         if (isInPictureInPictureMode) {
410             mEnteredPictureInPicture = true;
411         }
412     }
413 
414     @Override
onPictureInPictureUiStateChanged(PictureInPictureUiState pipState)415     public void onPictureInPictureUiStateChanged(PictureInPictureUiState pipState) {
416         Bundle res = new Bundle();
417         res.putBoolean(UI_STATE_STASHED_RESULT, pipState.isStashed());
418         res.putBoolean(UI_STATE_ENTERING_PIP_RESULT, pipState.isTransitioningToPip());
419         if (mCb != null) {
420             mCb.sendResult(res);
421         }
422     }
423 
424     @Override
onConfigurationChanged(Configuration newConfig)425     public void onConfigurationChanged(Configuration newConfig) {
426         super.onConfigurationChanged(newConfig);
427         dumpConfiguration(newConfig);
428         dumpConfigInfo();
429     }
430 
431     @Override
onBackPressed()432     public void onBackPressed() {
433         if (mEnterPipOnBackPressed) {
434             enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
435         } else {
436             super.onBackPressed();
437         }
438     }
439 
440     /**
441      * Launches a new instance of the PipActivity in the same task that will automatically enter
442      * PiP.
443      */
launchEnterPipActivity(Activity caller, @Nullable Bundle overrides)444     static void launchEnterPipActivity(Activity caller, @Nullable Bundle overrides) {
445         final Intent intent = new Intent(caller, PipActivity.class);
446         intent.putExtra(EXTRA_ENTER_PIP, "true");
447         intent.putExtra(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true");
448         if (overrides != null) {
449             intent.putExtras(overrides);
450         }
451         caller.startActivity(intent);
452     }
453 
parseBooleanExtra(String key)454     private boolean parseBooleanExtra(String key) {
455         return getIntent().hasExtra(key) && Boolean.parseBoolean(getIntent().getStringExtra(key));
456     }
457 
458     /**
459      * @return a {@link Rational} aspect ratio from the given intent and extras.
460      */
getAspectRatio(Intent intent, String extraNum, String extraDenom)461     private Rational getAspectRatio(Intent intent, String extraNum, String extraDenom) {
462         return new Rational(
463                 Integer.valueOf(intent.getStringExtra(extraNum)),
464                 Integer.valueOf(intent.getStringExtra(extraDenom)));
465     }
466 
467     /** @return {@link RemoteAction} instance titled after a given index */
createRemoteAction(int index)468     private RemoteAction createRemoteAction(int index) {
469         return new RemoteAction(Icon.createWithResource(this, R.drawable.red),
470                 "action " + index,
471                 "contentDescription " + index,
472                 PendingIntent.getBroadcast(this, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE));
473     }
474 
shouldAddExpandedPipAspectRatios()475     private boolean shouldAddExpandedPipAspectRatios() {
476         return getIntent().hasExtra(EXTRA_EXPANDED_PIP_ASPECT_RATIO_NUMERATOR)
477             && getIntent().hasExtra(EXTRA_EXPANDED_PIP_ASPECT_RATIO_DENOMINATOR);
478     }
479 }
480