Home | History | Annotate | Download | only in phone
      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
    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
    250     protected void onNewIntent(Intent intent) {
    251         super.onNewIntent(intent);
    252         updateFromIntent(intent);
    253     }
    254 
    255     @Override
    256     public void onUserInteraction() {
    257         if (mAllowMenuTimeout) {
    258             repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY);
    259         }
    260     }
    261 
    262     @Override
    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
    272     protected void onStop() {
    273         super.onStop();
    274 
    275         cancelDelayedFinish();
    276         EventBus.getDefault().unregister(this);
    277     }
    278 
    279     @Override
    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
    289     public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
    290         if (!isInPictureInPictureMode) {
    291             finish();
    292         }
    293     }
    294 
    295     @Override
    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
    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
    332     public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
    333         // Do nothing
    334     }
    335 
    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 
    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 
    398     private void hideMenu() {
    399         hideMenu(null /* animationFinishedRunnable */, true /* notifyMenuVisibility */,
    400                 false /* isDismissing */);
    401     }
    402 
    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 
    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 
    470     private void setActions(Rect stackBounds, List<RemoteAction> actions) {
    471         mActions.clear();
    472         mActions.addAll(actions);
    473         updateActionViews(stackBounds);
    474     }
    475 
    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 
    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 
    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 
    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 
    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 
    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 
    596     private void minimizePip() {
    597         sendEmptyMessage(PipMenuActivityController.MESSAGE_MINIMIZE_PIP,
    598                 "Could not notify controller to minimize PIP");
    599     }
    600 
    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 
    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 
    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 
    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 
    636     private void sendEmptyMessage(int what, String errorMsg) {
    637         Message m = Message.obtain();
    638         m.what = what;
    639         sendMessage(m, errorMsg);
    640     }
    641 
    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 
    653     private void cancelDelayedFinish() {
    654         mHandler.removeCallbacks(mFinishRunnable);
    655     }
    656 
    657     private void repostDelayedFinish(long delay) {
    658         mHandler.removeCallbacks(mFinishRunnable);
    659         mHandler.postDelayed(mFinishRunnable, delay);
    660     }
    661 }
    662