1 /*
2  * Copyright (C) 2016 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.android.systemui.pip.phone;
18 
19 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
20 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
21 import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;
22 
23 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ACTIONS;
24 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ALLOW_TIMEOUT;
25 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER;
26 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_WILL_RESIZE_MENU;
27 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_DISMISS_FRACTION;
28 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_MOVEMENT_BOUNDS;
29 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_MENU_STATE;
30 import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_STACK_BOUNDS;
31 
32 import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
33 import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE;
34 import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
35 
36 import android.animation.Animator;
37 import android.animation.AnimatorListenerAdapter;
38 import android.animation.AnimatorSet;
39 import android.animation.ObjectAnimator;
40 import android.animation.ValueAnimator;
41 import android.annotation.Nullable;
42 import android.app.Activity;
43 import android.app.ActivityManager;
44 import android.app.PendingIntent.CanceledException;
45 import android.app.RemoteAction;
46 import android.content.ComponentName;
47 import android.content.Intent;
48 import android.content.pm.ParceledListSlice;
49 import android.graphics.Color;
50 import android.graphics.PointF;
51 import android.graphics.Rect;
52 import android.graphics.drawable.ColorDrawable;
53 import android.graphics.drawable.Drawable;
54 import android.net.Uri;
55 import android.os.Bundle;
56 import android.os.Handler;
57 import android.os.Message;
58 import android.os.Messenger;
59 import android.os.RemoteException;
60 import android.os.UserHandle;
61 import android.util.Log;
62 import android.util.Pair;
63 import android.view.LayoutInflater;
64 import android.view.MotionEvent;
65 import android.view.View;
66 import android.view.ViewConfiguration;
67 import android.view.ViewGroup;
68 import android.view.WindowManager.LayoutParams;
69 import android.widget.FrameLayout;
70 import android.widget.ImageView;
71 import android.widget.LinearLayout;
72 
73 import com.android.systemui.Interpolators;
74 import com.android.systemui.R;
75 import com.android.systemui.recents.events.EventBus;
76 import com.android.systemui.recents.events.component.HidePipMenuEvent;
77 
78 import java.util.ArrayList;
79 import java.util.Collections;
80 import java.util.List;
81 
82 /**
83  * Translucent activity that gets started on top of a task in PIP to allow the user to control it.
84  */
85 public class PipMenuActivity extends Activity {
86 
87     private static final String TAG = "PipMenuActivity";
88 
89     public static final int MESSAGE_SHOW_MENU = 1;
90     public static final int MESSAGE_POKE_MENU = 2;
91     public static final int MESSAGE_HIDE_MENU = 3;
92     public static final int MESSAGE_UPDATE_ACTIONS = 4;
93     public static final int MESSAGE_UPDATE_DISMISS_FRACTION = 5;
94     public static final int MESSAGE_ANIMATION_ENDED = 6;
95 
96     private static final long INITIAL_DISMISS_DELAY = 3500;
97     private static final long POST_INTERACTION_DISMISS_DELAY = 2000;
98     private static final long MENU_FADE_DURATION = 125;
99 
100     private static final float MENU_BACKGROUND_ALPHA = 0.3f;
101     private static final float DISMISS_BACKGROUND_ALPHA = 0.6f;
102 
103     private static final float DISABLED_ACTION_ALPHA = 0.54f;
104 
105     private int mMenuState;
106     private boolean mAllowMenuTimeout = true;
107     private boolean mAllowTouches = true;
108 
109     private final List<RemoteAction> mActions = new ArrayList<>();
110 
111     private View mViewRoot;
112     private Drawable mBackgroundDrawable;
113     private View mMenuContainer;
114     private LinearLayout mActionsGroup;
115     private View mSettingsButton;
116     private View mDismissButton;
117     private ImageView mExpandButton;
118     private int mBetweenActionPaddingLand;
119 
120     private AnimatorSet mMenuContainerAnimator;
121 
122     private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener =
123             new ValueAnimator.AnimatorUpdateListener() {
124                 @Override
125                 public void onAnimationUpdate(ValueAnimator animation) {
126                     final float alpha = (float) animation.getAnimatedValue();
127                     mBackgroundDrawable.setAlpha((int) (MENU_BACKGROUND_ALPHA*alpha*255));
128                 }
129             };
130 
131     private PipTouchState mTouchState;
132     private PointF mDownPosition = new PointF();
133     private PointF mDownDelta = new PointF();
134     private ViewConfiguration mViewConfig;
135     private Handler mHandler = new Handler();
136     private Messenger mToControllerMessenger;
137     private Messenger mMessenger = new Messenger(new Handler() {
138         @Override
139         public void handleMessage(Message msg) {
140             switch (msg.what) {
141                 case MESSAGE_SHOW_MENU: {
142                     final Bundle data = (Bundle) msg.obj;
143                     showMenu(data.getInt(EXTRA_MENU_STATE),
144                             data.getParcelable(EXTRA_STACK_BOUNDS),
145                             data.getParcelable(EXTRA_MOVEMENT_BOUNDS),
146                             data.getBoolean(EXTRA_ALLOW_TIMEOUT),
147                             data.getBoolean(EXTRA_WILL_RESIZE_MENU));
148                     break;
149                 }
150                 case MESSAGE_POKE_MENU:
151                     cancelDelayedFinish();
152                     break;
153                 case MESSAGE_HIDE_MENU:
154                     hideMenu();
155                     break;
156                 case MESSAGE_UPDATE_ACTIONS: {
157                     final Bundle data = (Bundle) msg.obj;
158                     final ParceledListSlice actions = data.getParcelable(EXTRA_ACTIONS);
159                     setActions(data.getParcelable(EXTRA_STACK_BOUNDS), actions != null
160                             ? actions.getList() : Collections.EMPTY_LIST);
161                     break;
162                 }
163                 case MESSAGE_UPDATE_DISMISS_FRACTION: {
164                     final Bundle data = (Bundle) msg.obj;
165                     updateDismissFraction(data.getFloat(EXTRA_DISMISS_FRACTION));
166                     break;
167                 }
168                 case MESSAGE_ANIMATION_ENDED: {
169                     mAllowTouches = true;
170                     break;
171                 }
172             }
173         }
174     });
175 
176     private final Runnable mFinishRunnable = new Runnable() {
177         @Override
178         public void run() {
179             hideMenu();
180         }
181     };
182 
183     @Override
onCreate(@ullable Bundle savedInstanceState)184     protected void onCreate(@Nullable Bundle savedInstanceState) {
185         // Set the flags to allow us to watch for outside touches and also hide the menu and start
186         // manipulating the PIP in the same touch gesture
187         mViewConfig = ViewConfiguration.get(this);
188         mTouchState = new PipTouchState(mViewConfig, mHandler, () -> {
189             if (mMenuState == MENU_STATE_CLOSE) {
190                 showPipMenu();
191             } else {
192                 expandPip();
193             }
194         });
195         getWindow().addFlags(LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | LayoutParams.FLAG_SLIPPERY);
196 
197         super.onCreate(savedInstanceState);
198         setContentView(R.layout.pip_menu_activity);
199 
200         mBackgroundDrawable = new ColorDrawable(Color.BLACK);
201         mBackgroundDrawable.setAlpha(0);
202         mViewRoot = findViewById(R.id.background);
203         mViewRoot.setBackground(mBackgroundDrawable);
204         mMenuContainer = findViewById(R.id.menu_container);
205         mMenuContainer.setAlpha(0);
206         mMenuContainer.setOnTouchListener((v, event) -> {
207             mTouchState.onTouchEvent(event);
208             switch (event.getAction()) {
209                 case MotionEvent.ACTION_UP:
210                     if (mTouchState.isDoubleTap() || mMenuState == MENU_STATE_FULL) {
211                         // Expand to fullscreen if this is a double tap or we are already expanded
212                         expandPip();
213                     } else if (!mTouchState.isWaitingForDoubleTap()) {
214                         // User has stalled long enough for this not to be a drag or a double tap,
215                         // just expand the menu if necessary
216                         if (mMenuState == MENU_STATE_CLOSE) {
217                             showPipMenu();
218                         }
219                     } else {
220                         // Next touch event _may_ be the second tap for the double-tap, schedule a
221                         // fallback runnable to trigger the menu if no touch event occurs before the
222                         // next tap
223                         mTouchState.scheduleDoubleTapTimeoutCallback();
224                     }
225                     break;
226             }
227             return true;
228         });
229         mSettingsButton = findViewById(R.id.settings);
230         mSettingsButton.setAlpha(0);
231         mSettingsButton.setOnClickListener((v) -> {
232             showSettings();
233         });
234         mDismissButton = findViewById(R.id.dismiss);
235         mDismissButton.setAlpha(0);
236         mDismissButton.setOnClickListener((v) -> {
237             dismissPip();
238         });
239         mActionsGroup = findViewById(R.id.actions_group);
240         mBetweenActionPaddingLand = getResources().getDimensionPixelSize(
241                 R.dimen.pip_between_action_padding_land);
242         mExpandButton = findViewById(R.id.expand_button);
243 
244         updateFromIntent(getIntent());
245         setTitle(R.string.pip_menu_title);
246         setDisablePreviewScreenshots(true);
247     }
248 
249     @Override
onNewIntent(Intent intent)250     protected void onNewIntent(Intent intent) {
251         super.onNewIntent(intent);
252         updateFromIntent(intent);
253     }
254 
255     @Override
onUserInteraction()256     public void onUserInteraction() {
257         if (mAllowMenuTimeout) {
258             repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY);
259         }
260     }
261 
262     @Override
onUserLeaveHint()263     protected void onUserLeaveHint() {
264         super.onUserLeaveHint();
265 
266         // If another task is starting on top of the menu, then hide and finish it so that it can be
267         // recreated on the top next time it starts
268         hideMenu();
269     }
270 
271     @Override
onStop()272     protected void onStop() {
273         super.onStop();
274 
275         cancelDelayedFinish();
276         EventBus.getDefault().unregister(this);
277     }
278 
279     @Override
onDestroy()280     protected void onDestroy() {
281         super.onDestroy();
282 
283         // Fallback, if we are destroyed for any other reason (like when the task is being reset),
284         // also reset the callback.
285         notifyActivityCallback(null);
286     }
287 
288     @Override
onPictureInPictureModeChanged(boolean isInPictureInPictureMode)289     public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
290         if (!isInPictureInPictureMode) {
291             finish();
292         }
293     }
294 
295     @Override
dispatchTouchEvent(MotionEvent ev)296     public boolean dispatchTouchEvent(MotionEvent ev) {
297         if (!mAllowTouches) {
298             return super.dispatchTouchEvent(ev);
299         }
300 
301         // On the first action outside the window, hide the menu
302         switch (ev.getAction()) {
303             case MotionEvent.ACTION_OUTSIDE:
304                 hideMenu();
305                 break;
306             case MotionEvent.ACTION_DOWN:
307                 mDownPosition.set(ev.getX(), ev.getY());
308                 mDownDelta.set(0f, 0f);
309                 break;
310             case MotionEvent.ACTION_MOVE:
311                 mDownDelta.set(ev.getX() - mDownPosition.x, ev.getY() - mDownPosition.y);
312                 if (mDownDelta.length() > mViewConfig.getScaledTouchSlop()
313                         && mMenuState != MENU_STATE_NONE) {
314                     // Restore the input consumer and let that drive the movement of this menu
315                     notifyRegisterInputConsumer();
316                     cancelDelayedFinish();
317                 }
318                 break;
319         }
320         return super.dispatchTouchEvent(ev);
321     }
322 
323     @Override
finish()324     public void finish() {
325         notifyActivityCallback(null);
326         super.finish();
327         // Hide without an animation (the menu should already be invisible at this point)
328         overridePendingTransition(0, 0);
329     }
330 
331     @Override
setTaskDescription(ActivityManager.TaskDescription taskDescription)332     public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
333         // Do nothing
334     }
335 
onBusEvent(HidePipMenuEvent event)336     public final void onBusEvent(HidePipMenuEvent event) {
337         if (mMenuState != MENU_STATE_NONE) {
338             // If the menu is visible in either the closed or full state, then hide the menu and
339             // trigger the animation trigger afterwards
340             event.getAnimationTrigger().increment();
341             hideMenu(() -> {
342                 mHandler.post(() -> {
343                     event.getAnimationTrigger().decrement();
344                 });
345             }, true /* notifyMenuVisibility */, false /* isDismissing */);
346         }
347     }
348 
showMenu(int menuState, Rect stackBounds, Rect movementBounds, boolean allowMenuTimeout, boolean resizeMenuOnShow)349     private void showMenu(int menuState, Rect stackBounds, Rect movementBounds,
350             boolean allowMenuTimeout, boolean resizeMenuOnShow) {
351         mAllowMenuTimeout = allowMenuTimeout;
352         if (mMenuState != menuState) {
353             // Disallow touches if the menu needs to resize while showing, and we are transitioning
354             // to/from a full menu state.
355             boolean disallowTouchesUntilAnimationEnd = resizeMenuOnShow &&
356                     (mMenuState == MENU_STATE_FULL || menuState == MENU_STATE_FULL);
357             mAllowTouches = !disallowTouchesUntilAnimationEnd;
358             cancelDelayedFinish();
359             updateActionViews(stackBounds);
360             if (mMenuContainerAnimator != null) {
361                 mMenuContainerAnimator.cancel();
362             }
363             notifyMenuStateChange(menuState);
364             mMenuContainerAnimator = new AnimatorSet();
365             ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
366                     mMenuContainer.getAlpha(), 1f);
367             menuAnim.addUpdateListener(mMenuBgUpdateListener);
368             ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
369                     mSettingsButton.getAlpha(), 1f);
370             ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
371                     mDismissButton.getAlpha(), 1f);
372             if (menuState == MENU_STATE_FULL) {
373                 mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
374             } else {
375                 mMenuContainerAnimator.playTogether(dismissAnim);
376             }
377             mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
378             mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
379             if (allowMenuTimeout) {
380                 mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
381                     @Override
382                     public void onAnimationEnd(Animator animation) {
383                         repostDelayedFinish(INITIAL_DISMISS_DELAY);
384                     }
385                 });
386             }
387             mMenuContainerAnimator.start();
388         } else {
389             // If we are already visible, then just start the delayed dismiss and unregister any
390             // existing input consumers from the previous drag
391             if (allowMenuTimeout) {
392                 repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY);
393             }
394             notifyUnregisterInputConsumer();
395         }
396     }
397 
hideMenu()398     private void hideMenu() {
399         hideMenu(null /* animationFinishedRunnable */, true /* notifyMenuVisibility */,
400                 false /* isDismissing */);
401     }
402 
hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility, boolean isDismissing)403     private void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility,
404             boolean isDismissing) {
405         if (mMenuState != MENU_STATE_NONE) {
406             cancelDelayedFinish();
407             if (notifyMenuVisibility) {
408                 notifyMenuStateChange(MENU_STATE_NONE);
409             }
410             mMenuContainerAnimator = new AnimatorSet();
411             ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
412                     mMenuContainer.getAlpha(), 0f);
413             menuAnim.addUpdateListener(mMenuBgUpdateListener);
414             ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
415                     mSettingsButton.getAlpha(), 0f);
416             ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
417                     mDismissButton.getAlpha(), 0f);
418             mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
419             mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
420             mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
421             mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
422                 @Override
423                 public void onAnimationEnd(Animator animation) {
424                     if (animationFinishedRunnable != null) {
425                         animationFinishedRunnable.run();
426                     }
427 
428                     if (!isDismissing) {
429                         // If we are dismissing the PiP, then don't try to pre-emptively finish the
430                         // menu activity
431                         finish();
432                     }
433                 }
434             });
435             mMenuContainerAnimator.start();
436         } else {
437             // If the menu is not visible, just finish now
438             finish();
439         }
440     }
441 
updateFromIntent(Intent intent)442     private void updateFromIntent(Intent intent) {
443         mToControllerMessenger = intent.getParcelableExtra(EXTRA_CONTROLLER_MESSENGER);
444         if (mToControllerMessenger == null) {
445             Log.w(TAG, "Controller messenger is null. Stopping.");
446             finish();
447             return;
448         }
449         notifyActivityCallback(mMessenger);
450 
451         // Register for HidePipMenuEvents once we notify the controller of this activity
452         EventBus.getDefault().register(this);
453 
454         ParceledListSlice actions = intent.getParcelableExtra(EXTRA_ACTIONS);
455         if (actions != null) {
456             mActions.clear();
457             mActions.addAll(actions.getList());
458         }
459 
460         final int menuState = intent.getIntExtra(EXTRA_MENU_STATE, MENU_STATE_NONE);
461         if (menuState != MENU_STATE_NONE) {
462             Rect stackBounds = intent.getParcelableExtra(EXTRA_STACK_BOUNDS);
463             Rect movementBounds = intent.getParcelableExtra(EXTRA_MOVEMENT_BOUNDS);
464             boolean allowMenuTimeout = intent.getBooleanExtra(EXTRA_ALLOW_TIMEOUT, true);
465             boolean willResizeMenu = intent.getBooleanExtra(EXTRA_WILL_RESIZE_MENU, false);
466             showMenu(menuState, stackBounds, movementBounds, allowMenuTimeout, willResizeMenu);
467         }
468     }
469 
setActions(Rect stackBounds, List<RemoteAction> actions)470     private void setActions(Rect stackBounds, List<RemoteAction> actions) {
471         mActions.clear();
472         mActions.addAll(actions);
473         updateActionViews(stackBounds);
474     }
475 
updateActionViews(Rect stackBounds)476     private void updateActionViews(Rect stackBounds) {
477         ViewGroup expandContainer = findViewById(R.id.expand_container);
478         ViewGroup actionsContainer = findViewById(R.id.actions_container);
479         actionsContainer.setOnTouchListener((v, ev) -> {
480             // Do nothing, prevent click through to parent
481             return true;
482         });
483 
484         if (mActions.isEmpty() || mMenuState == MENU_STATE_CLOSE) {
485             actionsContainer.setVisibility(View.INVISIBLE);
486         } else {
487             actionsContainer.setVisibility(View.VISIBLE);
488             if (mActionsGroup != null) {
489                 // Ensure we have as many buttons as actions
490                 final LayoutInflater inflater = LayoutInflater.from(this);
491                 while (mActionsGroup.getChildCount() < mActions.size()) {
492                     final ImageView actionView = (ImageView) inflater.inflate(
493                             R.layout.pip_menu_action, mActionsGroup, false);
494                     mActionsGroup.addView(actionView);
495                 }
496 
497                 // Update the visibility of all views
498                 for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
499                     mActionsGroup.getChildAt(i).setVisibility(i < mActions.size()
500                             ? View.VISIBLE
501                             : View.GONE);
502                 }
503 
504                 // Recreate the layout
505                 final boolean isLandscapePip = stackBounds != null &&
506                         (stackBounds.width() > stackBounds.height());
507                 for (int i = 0; i < mActions.size(); i++) {
508                     final RemoteAction action = mActions.get(i);
509                     final ImageView actionView = (ImageView) mActionsGroup.getChildAt(i);
510 
511                     // TODO: Check if the action drawable has changed before we reload it
512                     action.getIcon().loadDrawableAsync(this, d -> {
513                         d.setTint(Color.WHITE);
514                         actionView.setImageDrawable(d);
515                     }, mHandler);
516                     actionView.setContentDescription(action.getContentDescription());
517                     if (action.isEnabled()) {
518                         actionView.setOnClickListener(v -> {
519                             try {
520                                 action.getActionIntent().send();
521                             } catch (CanceledException e) {
522                                 Log.w(TAG, "Failed to send action", e);
523                             }
524                         });
525                     }
526                     actionView.setEnabled(action.isEnabled());
527                     actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
528 
529                     // Update the margin between actions
530                     LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
531                             actionView.getLayoutParams();
532                     lp.leftMargin = (isLandscapePip && i > 0) ? mBetweenActionPaddingLand : 0;
533                 }
534             }
535 
536             // Update the expand container margin to adjust the center of the expand button to
537             // account for the existence of the action container
538             FrameLayout.LayoutParams expandedLp =
539                     (FrameLayout.LayoutParams) expandContainer.getLayoutParams();
540             expandedLp.topMargin = getResources().getDimensionPixelSize(
541                     R.dimen.pip_action_padding);
542             expandedLp.bottomMargin = getResources().getDimensionPixelSize(
543                     R.dimen.pip_expand_container_edge_margin);
544             expandContainer.requestLayout();
545         }
546     }
547 
updateDismissFraction(float fraction)548     private void updateDismissFraction(float fraction) {
549         int alpha;
550         final float menuAlpha = 1 - fraction;
551         if (mMenuState == MENU_STATE_FULL) {
552             mMenuContainer.setAlpha(menuAlpha);
553             mSettingsButton.setAlpha(menuAlpha);
554             mDismissButton.setAlpha(menuAlpha);
555             final float interpolatedAlpha =
556                     MENU_BACKGROUND_ALPHA * menuAlpha + DISMISS_BACKGROUND_ALPHA * fraction;
557             alpha = (int) (interpolatedAlpha * 255);
558         } else {
559             if (mMenuState == MENU_STATE_CLOSE) {
560                 mDismissButton.setAlpha(menuAlpha);
561             }
562             alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255);
563         }
564         mBackgroundDrawable.setAlpha(alpha);
565     }
566 
notifyRegisterInputConsumer()567     private void notifyRegisterInputConsumer() {
568         Message m = Message.obtain();
569         m.what = PipMenuActivityController.MESSAGE_REGISTER_INPUT_CONSUMER;
570         sendMessage(m, "Could not notify controller to register input consumer");
571     }
572 
notifyUnregisterInputConsumer()573     private void notifyUnregisterInputConsumer() {
574         Message m = Message.obtain();
575         m.what = PipMenuActivityController.MESSAGE_UNREGISTER_INPUT_CONSUMER;
576         sendMessage(m, "Could not notify controller to unregister input consumer");
577     }
578 
notifyMenuStateChange(int menuState)579     private void notifyMenuStateChange(int menuState) {
580         mMenuState = menuState;
581         Message m = Message.obtain();
582         m.what = PipMenuActivityController.MESSAGE_MENU_STATE_CHANGED;
583         m.arg1 = menuState;
584         sendMessage(m, "Could not notify controller of PIP menu visibility");
585     }
586 
expandPip()587     private void expandPip() {
588         // Do not notify menu visibility when hiding the menu, the controller will do this when it
589         // handles the message
590         hideMenu(() -> {
591             sendEmptyMessage(PipMenuActivityController.MESSAGE_EXPAND_PIP,
592                     "Could not notify controller to expand PIP");
593         }, false /* notifyMenuVisibility */, false /* isDismissing */);
594     }
595 
minimizePip()596     private void minimizePip() {
597         sendEmptyMessage(PipMenuActivityController.MESSAGE_MINIMIZE_PIP,
598                 "Could not notify controller to minimize PIP");
599     }
600 
dismissPip()601     private void dismissPip() {
602         // Do not notify menu visibility when hiding the menu, the controller will do this when it
603         // handles the message
604         hideMenu(() -> {
605             sendEmptyMessage(PipMenuActivityController.MESSAGE_DISMISS_PIP,
606                     "Could not notify controller to dismiss PIP");
607         }, false /* notifyMenuVisibility */, true /* isDismissing */);
608     }
609 
showPipMenu()610     private void showPipMenu() {
611         Message m = Message.obtain();
612         m.what = PipMenuActivityController.MESSAGE_SHOW_MENU;
613         sendMessage(m, "Could not notify controller to show PIP menu");
614     }
615 
showSettings()616     private void showSettings() {
617         final Pair<ComponentName, Integer> topPipActivityInfo =
618                 PipUtils.getTopPinnedActivity(this, ActivityManager.getService());
619         if (topPipActivityInfo.first != null) {
620             final UserHandle user = UserHandle.of(topPipActivityInfo.second);
621             final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
622                     Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null));
623             settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
624             settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
625             startActivity(settingsIntent);
626         }
627     }
628 
notifyActivityCallback(Messenger callback)629     private void notifyActivityCallback(Messenger callback) {
630         Message m = Message.obtain();
631         m.what = PipMenuActivityController.MESSAGE_UPDATE_ACTIVITY_CALLBACK;
632         m.replyTo = callback;
633         sendMessage(m, "Could not notify controller of activity finished");
634     }
635 
sendEmptyMessage(int what, String errorMsg)636     private void sendEmptyMessage(int what, String errorMsg) {
637         Message m = Message.obtain();
638         m.what = what;
639         sendMessage(m, errorMsg);
640     }
641 
sendMessage(Message m, String errorMsg)642     private void sendMessage(Message m, String errorMsg) {
643         if (mToControllerMessenger == null) {
644             return;
645         }
646         try {
647             mToControllerMessenger.send(m);
648         } catch (RemoteException e) {
649             Log.e(TAG, errorMsg, e);
650         }
651     }
652 
cancelDelayedFinish()653     private void cancelDelayedFinish() {
654         mHandler.removeCallbacks(mFinishRunnable);
655     }
656 
repostDelayedFinish(long delay)657     private void repostDelayedFinish(long delay) {
658         mHandler.removeCallbacks(mFinishRunnable);
659         mHandler.postDelayed(mFinishRunnable, delay);
660     }
661 }
662