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