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