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