1 /* 2 * Copyright (C) 2015 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.internal.policy; 18 19 import static android.app.WindowConfiguration.PINNED_WINDOWING_MODE_ELEVATION_IN_DIP; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 22 import static android.content.res.Configuration.ORIENTATION_PORTRAIT; 23 import static android.os.Build.VERSION_CODES.M; 24 import static android.os.Build.VERSION_CODES.N; 25 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; 26 import static android.view.InsetsState.ITYPE_STATUS_BAR; 27 import static android.view.View.MeasureSpec.AT_MOST; 28 import static android.view.View.MeasureSpec.EXACTLY; 29 import static android.view.View.MeasureSpec.getMode; 30 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 31 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 32 import static android.view.Window.DECOR_CAPTION_SHADE_DARK; 33 import static android.view.Window.DECOR_CAPTION_SHADE_LIGHT; 34 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; 35 import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; 36 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; 37 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; 38 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; 39 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; 40 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; 41 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; 42 import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION; 43 44 import static com.android.internal.policy.PhoneWindow.FEATURE_OPTIONS_PANEL; 45 46 import android.animation.Animator; 47 import android.animation.AnimatorListenerAdapter; 48 import android.animation.ObjectAnimator; 49 import android.annotation.Nullable; 50 import android.annotation.TestApi; 51 import android.app.WindowConfiguration; 52 import android.compat.annotation.UnsupportedAppUsage; 53 import android.content.Context; 54 import android.content.res.Configuration; 55 import android.content.res.Resources; 56 import android.graphics.Canvas; 57 import android.graphics.Color; 58 import android.graphics.Insets; 59 import android.graphics.LinearGradient; 60 import android.graphics.Outline; 61 import android.graphics.Paint; 62 import android.graphics.PixelFormat; 63 import android.graphics.RecordingCanvas; 64 import android.graphics.Rect; 65 import android.graphics.Region; 66 import android.graphics.Shader; 67 import android.graphics.drawable.ColorDrawable; 68 import android.graphics.drawable.Drawable; 69 import android.graphics.drawable.InsetDrawable; 70 import android.graphics.drawable.LayerDrawable; 71 import android.util.DisplayMetrics; 72 import android.util.Log; 73 import android.util.Pair; 74 import android.util.TypedValue; 75 import android.view.ActionMode; 76 import android.view.ContextThemeWrapper; 77 import android.view.Gravity; 78 import android.view.InputQueue; 79 import android.view.InsetsState; 80 import android.view.InsetsState.InternalInsetsType; 81 import android.view.KeyEvent; 82 import android.view.KeyboardShortcutGroup; 83 import android.view.LayoutInflater; 84 import android.view.Menu; 85 import android.view.MenuItem; 86 import android.view.MotionEvent; 87 import android.view.PendingInsetsController; 88 import android.view.ThreadedRenderer; 89 import android.view.View; 90 import android.view.ViewGroup; 91 import android.view.ViewOutlineProvider; 92 import android.view.ViewRootImpl; 93 import android.view.ViewStub; 94 import android.view.ViewTreeObserver; 95 import android.view.Window; 96 import android.view.WindowCallbacks; 97 import android.view.WindowInsets; 98 import android.view.WindowInsetsController; 99 import android.view.WindowManager; 100 import android.view.accessibility.AccessibilityEvent; 101 import android.view.accessibility.AccessibilityManager; 102 import android.view.accessibility.AccessibilityNodeInfo; 103 import android.view.animation.AnimationUtils; 104 import android.view.animation.Interpolator; 105 import android.widget.FrameLayout; 106 import android.widget.PopupWindow; 107 108 import com.android.internal.R; 109 import com.android.internal.policy.PhoneWindow.PanelFeatureState; 110 import com.android.internal.policy.PhoneWindow.PhoneWindowMenuCallback; 111 import com.android.internal.view.FloatingActionMode; 112 import com.android.internal.view.RootViewSurfaceTaker; 113 import com.android.internal.view.StandaloneActionMode; 114 import com.android.internal.view.menu.ContextMenuBuilder; 115 import com.android.internal.view.menu.MenuHelper; 116 import com.android.internal.widget.ActionBarContextView; 117 import com.android.internal.widget.BackgroundFallback; 118 import com.android.internal.widget.DecorCaptionView; 119 import com.android.internal.widget.FloatingToolbar; 120 121 import java.util.List; 122 123 /** @hide */ 124 public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks { 125 private static final String TAG = "DecorView"; 126 127 private static final boolean DEBUG_MEASURE = false; 128 129 private static final boolean SWEEP_OPEN_MENU = false; 130 131 // The height of a window which has focus in DIP. 132 public static final int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20; 133 // The height of a window which has not in DIP. 134 public static final int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5; 135 136 private static final int SCRIM_LIGHT = 0xe6ffffff; // 90% white 137 138 public static final ColorViewAttributes STATUS_BAR_COLOR_VIEW_ATTRIBUTES = 139 new ColorViewAttributes(SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS, 140 Gravity.TOP, Gravity.LEFT, Gravity.RIGHT, 141 Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME, 142 com.android.internal.R.id.statusBarBackground, 143 FLAG_FULLSCREEN, ITYPE_STATUS_BAR); 144 145 public static final ColorViewAttributes NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES = 146 new ColorViewAttributes( 147 SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION, 148 Gravity.BOTTOM, Gravity.RIGHT, Gravity.LEFT, 149 Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME, 150 com.android.internal.R.id.navigationBarBackground, 151 0 /* hideWindowFlag */, ITYPE_NAVIGATION_BAR); 152 153 // This is used to workaround an issue where the PiP shadow can be transparent if the window 154 // background is transparent 155 private static final ViewOutlineProvider PIP_OUTLINE_PROVIDER = new ViewOutlineProvider() { 156 @Override 157 public void getOutline(View view, Outline outline) { 158 outline.setRect(0, 0, view.getWidth(), view.getHeight()); 159 outline.setAlpha(1f); 160 } 161 }; 162 163 // Cludge to address b/22668382: Set the shadow size to the maximum so that the layer 164 // size calculation takes the shadow size into account. We set the elevation currently 165 // to max until the first layout command has been executed. 166 private boolean mAllowUpdateElevation = false; 167 168 private boolean mElevationAdjustedForStack = false; 169 170 // Keeps track of the picture-in-picture mode for the view shadow 171 private boolean mIsInPictureInPictureMode; 172 173 // Stores the previous outline provider prior to applying PIP_OUTLINE_PROVIDER 174 private ViewOutlineProvider mLastOutlineProvider; 175 176 int mDefaultOpacity = PixelFormat.OPAQUE; 177 178 /** The feature ID of the panel, or -1 if this is the application's DecorView */ 179 private final int mFeatureId; 180 181 private final Rect mDrawingBounds = new Rect(); 182 183 private final Rect mBackgroundPadding = new Rect(); 184 185 private final Rect mFramePadding = new Rect(); 186 187 private final Rect mFrameOffsets = new Rect(); 188 189 private boolean mHasCaption = false; 190 191 private boolean mChanging; 192 193 private Drawable mMenuBackground; 194 private boolean mWatchingForMenu; 195 private int mDownY; 196 197 ActionMode mPrimaryActionMode; 198 private ActionMode mFloatingActionMode; 199 private ActionBarContextView mPrimaryActionModeView; 200 private PopupWindow mPrimaryActionModePopup; 201 private Runnable mShowPrimaryActionModePopup; 202 private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener; 203 private View mFloatingActionModeOriginatingView; 204 private FloatingToolbar mFloatingToolbar; 205 private ObjectAnimator mFadeAnim; 206 207 // View added at runtime to draw under the status bar area 208 private View mStatusGuard; 209 210 private final ColorViewState mStatusColorViewState = 211 new ColorViewState(STATUS_BAR_COLOR_VIEW_ATTRIBUTES); 212 private final ColorViewState mNavigationColorViewState = 213 new ColorViewState(NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES); 214 215 private final Interpolator mShowInterpolator; 216 private final Interpolator mHideInterpolator; 217 private final int mBarEnterExitDuration; 218 final boolean mForceWindowDrawsBarBackgrounds; 219 private final int mSemiTransparentBarColor; 220 221 private final BackgroundFallback mBackgroundFallback = new BackgroundFallback(); 222 223 private int mLastTopInset = 0; 224 @UnsupportedAppUsage 225 private int mLastBottomInset = 0; 226 @UnsupportedAppUsage 227 private int mLastRightInset = 0; 228 @UnsupportedAppUsage 229 private int mLastLeftInset = 0; 230 private boolean mLastHasTopStableInset = false; 231 private boolean mLastHasBottomStableInset = false; 232 private boolean mLastHasRightStableInset = false; 233 private boolean mLastHasLeftStableInset = false; 234 private int mLastWindowFlags = 0; 235 private boolean mLastShouldAlwaysConsumeSystemBars = false; 236 237 private int mRootScrollY = 0; 238 239 @UnsupportedAppUsage 240 private PhoneWindow mWindow; 241 242 ViewGroup mContentRoot; 243 244 private Rect mTempRect; 245 246 // This is the caption view for the window, containing the caption and window control 247 // buttons. The visibility of this decor depends on the workspace and the window type. 248 // If the window type does not require such a view, this member might be null. 249 private DecorCaptionView mDecorCaptionView; 250 251 private boolean mWindowResizeCallbacksAdded = false; 252 private Drawable.Callback mLastBackgroundDrawableCb = null; 253 private BackdropFrameRenderer mBackdropFrameRenderer = null; 254 private Drawable mOriginalBackgroundDrawable; 255 private Drawable mLastOriginalBackgroundDrawable; 256 private Drawable mResizingBackgroundDrawable; 257 258 /** 259 * Temporary holder for a window background when it is set before {@link #mWindow} is 260 * initialized. It will be set as the actual background once {@link #setWindow(PhoneWindow)} is 261 * called. 262 */ 263 @Nullable 264 private Drawable mPendingWindowBackground; 265 private Drawable mCaptionBackgroundDrawable; 266 private Drawable mUserCaptionBackgroundDrawable; 267 268 private float mAvailableWidth; 269 270 String mLogTag = TAG; 271 private final Rect mFloatingInsets = new Rect(); 272 private boolean mApplyFloatingVerticalInsets = false; 273 private boolean mApplyFloatingHorizontalInsets = false; 274 275 private int mResizeMode = RESIZE_MODE_INVALID; 276 private final int mResizeShadowSize; 277 private final Paint mVerticalResizeShadowPaint = new Paint(); 278 private final Paint mHorizontalResizeShadowPaint = new Paint(); 279 private final Paint mLegacyNavigationBarBackgroundPaint = new Paint(); 280 private Insets mBackgroundInsets = Insets.NONE; 281 private Insets mLastBackgroundInsets = Insets.NONE; 282 private boolean mDrawLegacyNavigationBarBackground; 283 284 private PendingInsetsController mPendingInsetsController = new PendingInsetsController(); 285 DecorView(Context context, int featureId, PhoneWindow window, WindowManager.LayoutParams params)286 DecorView(Context context, int featureId, PhoneWindow window, 287 WindowManager.LayoutParams params) { 288 super(context); 289 mFeatureId = featureId; 290 291 mShowInterpolator = AnimationUtils.loadInterpolator(context, 292 android.R.interpolator.linear_out_slow_in); 293 mHideInterpolator = AnimationUtils.loadInterpolator(context, 294 android.R.interpolator.fast_out_linear_in); 295 296 mBarEnterExitDuration = context.getResources().getInteger( 297 R.integer.dock_enter_exit_duration); 298 mForceWindowDrawsBarBackgrounds = context.getResources().getBoolean( 299 R.bool.config_forceWindowDrawsStatusBarBackground) 300 && context.getApplicationInfo().targetSdkVersion >= N; 301 mSemiTransparentBarColor = context.getResources().getColor( 302 R.color.system_bar_background_semi_transparent, null /* theme */); 303 304 updateAvailableWidth(); 305 306 setWindow(window); 307 308 updateLogTag(params); 309 310 mResizeShadowSize = context.getResources().getDimensionPixelSize( 311 R.dimen.resize_shadow_size); 312 initResizingPaints(); 313 314 mLegacyNavigationBarBackgroundPaint.setColor(Color.BLACK); 315 } 316 setBackgroundFallback(@ullable Drawable fallbackDrawable)317 void setBackgroundFallback(@Nullable Drawable fallbackDrawable) { 318 mBackgroundFallback.setDrawable(fallbackDrawable); 319 setWillNotDraw(getBackground() == null && !mBackgroundFallback.hasFallback()); 320 } 321 322 @TestApi getBackgroundFallback()323 public @Nullable Drawable getBackgroundFallback() { 324 return mBackgroundFallback.getDrawable(); 325 } 326 getStatusBarBackgroundView()327 @Nullable View getStatusBarBackgroundView() { 328 return mStatusColorViewState.view; 329 } 330 getNavigationBarBackgroundView()331 @Nullable View getNavigationBarBackgroundView() { 332 return mNavigationColorViewState.view; 333 } 334 335 @Override gatherTransparentRegion(Region region)336 public boolean gatherTransparentRegion(Region region) { 337 boolean statusOpaque = gatherTransparentRegion(mStatusColorViewState, region); 338 boolean navOpaque = gatherTransparentRegion(mNavigationColorViewState, region); 339 boolean decorOpaque = super.gatherTransparentRegion(region); 340 341 // combine bools after computation, so each method above always executes 342 return statusOpaque || navOpaque || decorOpaque; 343 } 344 gatherTransparentRegion(ColorViewState colorViewState, Region region)345 boolean gatherTransparentRegion(ColorViewState colorViewState, Region region) { 346 if (colorViewState.view != null && colorViewState.visible && isResizing()) { 347 // If a visible ColorViewState is in a resizing host DecorView, forcibly register its 348 // opaque area, since it's drawn by a different root RenderNode. It would otherwise be 349 // rejected by ViewGroup#gatherTransparentRegion() for the view not being VISIBLE. 350 return colorViewState.view.gatherTransparentRegion(region); 351 } 352 return false; // no opaque area added 353 } 354 355 @Override onDraw(Canvas c)356 public void onDraw(Canvas c) { 357 super.onDraw(c); 358 359 mBackgroundFallback.draw(this, mContentRoot, c, mWindow.mContentParent, 360 mStatusColorViewState.view, mNavigationColorViewState.view); 361 } 362 363 @Override dispatchKeyEvent(KeyEvent event)364 public boolean dispatchKeyEvent(KeyEvent event) { 365 final int keyCode = event.getKeyCode(); 366 final int action = event.getAction(); 367 final boolean isDown = action == KeyEvent.ACTION_DOWN; 368 369 if (isDown && (event.getRepeatCount() == 0)) { 370 // First handle chording of panel key: if a panel key is held 371 // but not released, try to execute a shortcut in it. 372 if ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) { 373 boolean handled = dispatchKeyShortcutEvent(event); 374 if (handled) { 375 return true; 376 } 377 } 378 379 // If a panel is open, perform a shortcut on it without the 380 // chorded panel key 381 if ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) { 382 if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) { 383 return true; 384 } 385 } 386 } 387 388 if (!mWindow.isDestroyed()) { 389 final Window.Callback cb = mWindow.getCallback(); 390 final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event) 391 : super.dispatchKeyEvent(event); 392 if (handled) { 393 return true; 394 } 395 } 396 397 return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event) 398 : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event); 399 } 400 401 @Override 402 public boolean dispatchKeyShortcutEvent(KeyEvent ev) { 403 // If the panel is already prepared, then perform the shortcut using it. 404 boolean handled; 405 if (mWindow.mPreparedPanel != null) { 406 handled = mWindow.performPanelShortcut(mWindow.mPreparedPanel, ev.getKeyCode(), ev, 407 Menu.FLAG_PERFORM_NO_CLOSE); 408 if (handled) { 409 if (mWindow.mPreparedPanel != null) { 410 mWindow.mPreparedPanel.isHandled = true; 411 } 412 return true; 413 } 414 } 415 416 // Shortcut not handled by the panel. Dispatch to the view hierarchy. 417 final Window.Callback cb = mWindow.getCallback(); 418 handled = cb != null && !mWindow.isDestroyed() && mFeatureId < 0 419 ? cb.dispatchKeyShortcutEvent(ev) : super.dispatchKeyShortcutEvent(ev); 420 if (handled) { 421 return true; 422 } 423 424 // If the panel is not prepared, then we may be trying to handle a shortcut key 425 // combination such as Control+C. Temporarily prepare the panel then mark it 426 // unprepared again when finished to ensure that the panel will again be prepared 427 // the next time it is shown for real. 428 PhoneWindow.PanelFeatureState st = 429 mWindow.getPanelState(Window.FEATURE_OPTIONS_PANEL, false); 430 if (st != null && mWindow.mPreparedPanel == null) { 431 mWindow.preparePanel(st, ev); 432 handled = mWindow.performPanelShortcut(st, ev.getKeyCode(), ev, 433 Menu.FLAG_PERFORM_NO_CLOSE); 434 st.isPrepared = false; 435 if (handled) { 436 return true; 437 } 438 } 439 return false; 440 } 441 442 @Override 443 public boolean dispatchTouchEvent(MotionEvent ev) { 444 final Window.Callback cb = mWindow.getCallback(); 445 return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 446 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev); 447 } 448 449 @Override 450 public boolean dispatchTrackballEvent(MotionEvent ev) { 451 final Window.Callback cb = mWindow.getCallback(); 452 return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 453 ? cb.dispatchTrackballEvent(ev) : super.dispatchTrackballEvent(ev); 454 } 455 456 @Override 457 public boolean dispatchGenericMotionEvent(MotionEvent ev) { 458 final Window.Callback cb = mWindow.getCallback(); 459 return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 460 ? cb.dispatchGenericMotionEvent(ev) : super.dispatchGenericMotionEvent(ev); 461 } 462 463 public boolean superDispatchKeyEvent(KeyEvent event) { 464 // Give priority to closing action modes if applicable. 465 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { 466 final int action = event.getAction(); 467 // Back cancels action modes first. 468 if (mPrimaryActionMode != null) { 469 if (action == KeyEvent.ACTION_UP) { 470 mPrimaryActionMode.finish(); 471 } 472 return true; 473 } 474 } 475 476 if (super.dispatchKeyEvent(event)) { 477 return true; 478 } 479 480 return (getViewRootImpl() != null) && getViewRootImpl().dispatchUnhandledKeyEvent(event); 481 } 482 483 public boolean superDispatchKeyShortcutEvent(KeyEvent event) { 484 return super.dispatchKeyShortcutEvent(event); 485 } 486 487 public boolean superDispatchTouchEvent(MotionEvent event) { 488 return super.dispatchTouchEvent(event); 489 } 490 491 public boolean superDispatchTrackballEvent(MotionEvent event) { 492 return super.dispatchTrackballEvent(event); 493 } 494 495 public boolean superDispatchGenericMotionEvent(MotionEvent event) { 496 return super.dispatchGenericMotionEvent(event); 497 } 498 499 @Override 500 public boolean onTouchEvent(MotionEvent event) { 501 return onInterceptTouchEvent(event); 502 } 503 504 private boolean isOutOfInnerBounds(int x, int y) { 505 return x < 0 || y < 0 || x > getWidth() || y > getHeight(); 506 } 507 508 private boolean isOutOfBounds(int x, int y) { 509 return x < -5 || y < -5 || x > (getWidth() + 5) 510 || y > (getHeight() + 5); 511 } 512 513 @Override 514 public boolean onInterceptTouchEvent(MotionEvent event) { 515 int action = event.getAction(); 516 if (mHasCaption && isShowingCaption()) { 517 // Don't dispatch ACTION_DOWN to the captionr if the window is resizable and the event 518 // was (starting) outside the window. Window resizing events should be handled by 519 // WindowManager. 520 // TODO: Investigate how to handle the outside touch in window manager 521 // without generating these events. 522 // Currently we receive these because we need to enlarge the window's 523 // touch region so that the monitor channel receives the events 524 // in the outside touch area. 525 if (action == MotionEvent.ACTION_DOWN) { 526 final int x = (int) event.getX(); 527 final int y = (int) event.getY(); 528 if (isOutOfInnerBounds(x, y)) { 529 return true; 530 } 531 } 532 } 533 534 if (mFeatureId >= 0) { 535 if (action == MotionEvent.ACTION_DOWN) { 536 int x = (int)event.getX(); 537 int y = (int)event.getY(); 538 if (isOutOfBounds(x, y)) { 539 mWindow.closePanel(mFeatureId); 540 return true; 541 } 542 } 543 } 544 545 if (!SWEEP_OPEN_MENU) { 546 return false; 547 } 548 549 if (mFeatureId >= 0) { 550 if (action == MotionEvent.ACTION_DOWN) { 551 Log.i(mLogTag, "Watchiing!"); 552 mWatchingForMenu = true; 553 mDownY = (int) event.getY(); 554 return false; 555 } 556 557 if (!mWatchingForMenu) { 558 return false; 559 } 560 561 int y = (int)event.getY(); 562 if (action == MotionEvent.ACTION_MOVE) { 563 if (y > (mDownY+30)) { 564 Log.i(mLogTag, "Closing!"); 565 mWindow.closePanel(mFeatureId); 566 mWatchingForMenu = false; 567 return true; 568 } 569 } else if (action == MotionEvent.ACTION_UP) { 570 mWatchingForMenu = false; 571 } 572 573 return false; 574 } 575 576 //Log.i(mLogTag, "Intercept: action=" + action + " y=" + event.getY() 577 // + " (in " + getHeight() + ")"); 578 579 if (action == MotionEvent.ACTION_DOWN) { 580 int y = (int)event.getY(); 581 if (y >= (getHeight()-5) && !mWindow.hasChildren()) { 582 Log.i(mLogTag, "Watching!"); 583 mWatchingForMenu = true; 584 } 585 return false; 586 } 587 588 if (!mWatchingForMenu) { 589 return false; 590 } 591 592 int y = (int)event.getY(); 593 if (action == MotionEvent.ACTION_MOVE) { 594 if (y < (getHeight()-30)) { 595 Log.i(mLogTag, "Opening!"); 596 mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, new KeyEvent( 597 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU)); 598 mWatchingForMenu = false; 599 return true; 600 } 601 } else if (action == MotionEvent.ACTION_UP) { 602 mWatchingForMenu = false; 603 } 604 605 return false; 606 } 607 608 @Override 609 public void sendAccessibilityEvent(int eventType) { 610 if (!AccessibilityManager.getInstance(mContext).isEnabled()) { 611 return; 612 } 613 614 // if we are showing a feature that should be announced and one child 615 // make this child the event source since this is the feature itself 616 // otherwise the callback will take over and announce its client 617 if ((mFeatureId == Window.FEATURE_OPTIONS_PANEL || 618 mFeatureId == Window.FEATURE_CONTEXT_MENU || 619 mFeatureId == Window.FEATURE_PROGRESS || 620 mFeatureId == Window.FEATURE_INDETERMINATE_PROGRESS) 621 && getChildCount() == 1) { 622 getChildAt(0).sendAccessibilityEvent(eventType); 623 } else { 624 super.sendAccessibilityEvent(eventType); 625 } 626 } 627 628 @Override 629 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { 630 final Window.Callback cb = mWindow.getCallback(); 631 if (cb != null && !mWindow.isDestroyed()) { 632 if (cb.dispatchPopulateAccessibilityEvent(event)) { 633 return true; 634 } 635 } 636 return super.dispatchPopulateAccessibilityEventInternal(event); 637 } 638 639 @Override 640 protected boolean setFrame(int l, int t, int r, int b) { 641 boolean changed = super.setFrame(l, t, r, b); 642 if (changed) { 643 final Rect drawingBounds = mDrawingBounds; 644 getDrawingRect(drawingBounds); 645 646 Drawable fg = getForeground(); 647 if (fg != null) { 648 final Rect frameOffsets = mFrameOffsets; 649 drawingBounds.left += frameOffsets.left; 650 drawingBounds.top += frameOffsets.top; 651 drawingBounds.right -= frameOffsets.right; 652 drawingBounds.bottom -= frameOffsets.bottom; 653 fg.setBounds(drawingBounds); 654 final Rect framePadding = mFramePadding; 655 drawingBounds.left += framePadding.left - frameOffsets.left; 656 drawingBounds.top += framePadding.top - frameOffsets.top; 657 drawingBounds.right -= framePadding.right - frameOffsets.right; 658 drawingBounds.bottom -= framePadding.bottom - frameOffsets.bottom; 659 } 660 661 // Need to call super here as we pretend to be having the original background. 662 Drawable bg = super.getBackground(); 663 if (bg != null) { 664 bg.setBounds(drawingBounds); 665 } 666 667 if (SWEEP_OPEN_MENU) { 668 if (mMenuBackground == null && mFeatureId < 0 669 && mWindow.getAttributes().height 670 == WindowManager.LayoutParams.MATCH_PARENT) { 671 mMenuBackground = getContext().getDrawable( 672 R.drawable.menu_background); 673 } 674 if (mMenuBackground != null) { 675 mMenuBackground.setBounds(drawingBounds.left, 676 drawingBounds.bottom-6, drawingBounds.right, 677 drawingBounds.bottom+20); 678 } 679 } 680 } 681 return changed; 682 } 683 684 @Override 685 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 686 final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); 687 final boolean isPortrait = 688 getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT; 689 690 final int widthMode = getMode(widthMeasureSpec); 691 final int heightMode = getMode(heightMeasureSpec); 692 693 boolean fixedWidth = false; 694 mApplyFloatingHorizontalInsets = false; 695 if (widthMode == AT_MOST) { 696 final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor : mWindow.mFixedWidthMajor; 697 if (tvw != null && tvw.type != TypedValue.TYPE_NULL) { 698 final int w; 699 if (tvw.type == TypedValue.TYPE_DIMENSION) { 700 w = (int) tvw.getDimension(metrics); 701 } else if (tvw.type == TypedValue.TYPE_FRACTION) { 702 w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels); 703 } else { 704 w = 0; 705 } 706 if (DEBUG_MEASURE) Log.d(mLogTag, "Fixed width: " + w); 707 final int widthSize = MeasureSpec.getSize(widthMeasureSpec); 708 if (w > 0) { 709 widthMeasureSpec = MeasureSpec.makeMeasureSpec( 710 Math.min(w, widthSize), EXACTLY); 711 fixedWidth = true; 712 } else { 713 widthMeasureSpec = MeasureSpec.makeMeasureSpec( 714 widthSize - mFloatingInsets.left - mFloatingInsets.right, 715 AT_MOST); 716 mApplyFloatingHorizontalInsets = true; 717 } 718 } 719 } 720 721 mApplyFloatingVerticalInsets = false; 722 if (heightMode == AT_MOST) { 723 final TypedValue tvh = isPortrait ? mWindow.mFixedHeightMajor 724 : mWindow.mFixedHeightMinor; 725 if (tvh != null && tvh.type != TypedValue.TYPE_NULL) { 726 final int h; 727 if (tvh.type == TypedValue.TYPE_DIMENSION) { 728 h = (int) tvh.getDimension(metrics); 729 } else if (tvh.type == TypedValue.TYPE_FRACTION) { 730 h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels); 731 } else { 732 h = 0; 733 } 734 if (DEBUG_MEASURE) Log.d(mLogTag, "Fixed height: " + h); 735 final int heightSize = MeasureSpec.getSize(heightMeasureSpec); 736 if (h > 0) { 737 heightMeasureSpec = MeasureSpec.makeMeasureSpec( 738 Math.min(h, heightSize), EXACTLY); 739 } else if ((mWindow.getAttributes().flags & FLAG_LAYOUT_IN_SCREEN) == 0) { 740 heightMeasureSpec = MeasureSpec.makeMeasureSpec( 741 heightSize - mFloatingInsets.top - mFloatingInsets.bottom, AT_MOST); 742 mApplyFloatingVerticalInsets = true; 743 } 744 } 745 } 746 747 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 748 749 int width = getMeasuredWidth(); 750 boolean measure = false; 751 752 widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY); 753 754 if (!fixedWidth && widthMode == AT_MOST) { 755 final TypedValue tv = isPortrait ? mWindow.mMinWidthMinor : mWindow.mMinWidthMajor; 756 if (tv.type != TypedValue.TYPE_NULL) { 757 final int min; 758 if (tv.type == TypedValue.TYPE_DIMENSION) { 759 min = (int)tv.getDimension(metrics); 760 } else if (tv.type == TypedValue.TYPE_FRACTION) { 761 min = (int)tv.getFraction(mAvailableWidth, mAvailableWidth); 762 } else { 763 min = 0; 764 } 765 if (DEBUG_MEASURE) Log.d(mLogTag, "Adjust for min width: " + min + ", value::" 766 + tv.coerceToString() + ", mAvailableWidth=" + mAvailableWidth); 767 768 if (width < min) { 769 widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY); 770 measure = true; 771 } 772 } 773 } 774 775 // TODO: Support height? 776 777 if (measure) { 778 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 779 } 780 } 781 782 @Override 783 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 784 super.onLayout(changed, left, top, right, bottom); 785 if (mApplyFloatingVerticalInsets) { 786 offsetTopAndBottom(mFloatingInsets.top); 787 } 788 if (mApplyFloatingHorizontalInsets) { 789 offsetLeftAndRight(mFloatingInsets.left); 790 } 791 792 // If the application changed its SystemUI metrics, we might also have to adapt 793 // our shadow elevation. 794 updateElevation(); 795 mAllowUpdateElevation = true; 796 797 if (changed 798 && (mResizeMode == RESIZE_MODE_DOCKED_DIVIDER 799 || mDrawLegacyNavigationBarBackground)) { 800 getViewRootImpl().requestInvalidateRootRenderNode(); 801 } 802 } 803 804 @Override 805 public void draw(Canvas canvas) { 806 super.draw(canvas); 807 808 if (mMenuBackground != null) { 809 mMenuBackground.draw(canvas); 810 } 811 } 812 813 @Override 814 public boolean showContextMenuForChild(View originalView) { 815 return showContextMenuForChildInternal(originalView, Float.NaN, Float.NaN); 816 } 817 818 @Override 819 public boolean showContextMenuForChild(View originalView, float x, float y) { 820 return showContextMenuForChildInternal(originalView, x, y); 821 } 822 823 private boolean showContextMenuForChildInternal(View originalView, 824 float x, float y) { 825 // Only allow one context menu at a time. 826 if (mWindow.mContextMenuHelper != null) { 827 mWindow.mContextMenuHelper.dismiss(); 828 mWindow.mContextMenuHelper = null; 829 } 830 831 // Reuse the context menu builder. 832 final PhoneWindowMenuCallback callback = mWindow.mContextMenuCallback; 833 if (mWindow.mContextMenu == null) { 834 mWindow.mContextMenu = new ContextMenuBuilder(getContext()); 835 mWindow.mContextMenu.setCallback(callback); 836 } else { 837 mWindow.mContextMenu.clearAll(); 838 } 839 840 final MenuHelper helper; 841 final boolean isPopup = !Float.isNaN(x) && !Float.isNaN(y); 842 if (isPopup) { 843 helper = mWindow.mContextMenu.showPopup(getContext(), originalView, x, y); 844 } else { 845 helper = mWindow.mContextMenu.showDialog(originalView, originalView.getWindowToken()); 846 } 847 848 if (helper != null) { 849 // If it's a dialog, the callback needs to handle showing 850 // sub-menus. Either way, the callback is required for propagating 851 // selection to Context.onContextMenuItemSelected(). 852 callback.setShowDialogForSubmenu(!isPopup); 853 helper.setPresenterCallback(callback); 854 } 855 856 mWindow.mContextMenuHelper = helper; 857 return helper != null; 858 } 859 860 @Override 861 public ActionMode startActionModeForChild(View originalView, 862 ActionMode.Callback callback) { 863 return startActionModeForChild(originalView, callback, ActionMode.TYPE_PRIMARY); 864 } 865 866 @Override 867 public ActionMode startActionModeForChild( 868 View child, ActionMode.Callback callback, int type) { 869 return startActionMode(child, callback, type); 870 } 871 872 @Override 873 public ActionMode startActionMode(ActionMode.Callback callback) { 874 return startActionMode(callback, ActionMode.TYPE_PRIMARY); 875 } 876 877 @Override 878 public ActionMode startActionMode(ActionMode.Callback callback, int type) { 879 return startActionMode(this, callback, type); 880 } 881 882 private ActionMode startActionMode( 883 View originatingView, ActionMode.Callback callback, int type) { 884 ActionMode.Callback2 wrappedCallback = new ActionModeCallback2Wrapper(callback); 885 ActionMode mode = null; 886 if (mWindow.getCallback() != null && !mWindow.isDestroyed()) { 887 try { 888 mode = mWindow.getCallback().onWindowStartingActionMode(wrappedCallback, type); 889 } catch (AbstractMethodError ame) { 890 // Older apps might not implement the typed version of this method. 891 if (type == ActionMode.TYPE_PRIMARY) { 892 try { 893 mode = mWindow.getCallback().onWindowStartingActionMode( 894 wrappedCallback); 895 } catch (AbstractMethodError ame2) { 896 // Older apps might not implement this callback method at all. 897 } 898 } 899 } 900 } 901 if (mode != null) { 902 if (mode.getType() == ActionMode.TYPE_PRIMARY) { 903 cleanupPrimaryActionMode(); 904 mPrimaryActionMode = mode; 905 } else if (mode.getType() == ActionMode.TYPE_FLOATING) { 906 if (mFloatingActionMode != null) { 907 mFloatingActionMode.finish(); 908 } 909 mFloatingActionMode = mode; 910 } 911 } else { 912 mode = createActionMode(type, wrappedCallback, originatingView); 913 if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) { 914 setHandledActionMode(mode); 915 } else { 916 mode = null; 917 } 918 } 919 if (mode != null && mWindow.getCallback() != null && !mWindow.isDestroyed()) { 920 try { 921 mWindow.getCallback().onActionModeStarted(mode); 922 } catch (AbstractMethodError ame) { 923 // Older apps might not implement this callback method. 924 } 925 } 926 return mode; 927 } 928 929 private void cleanupPrimaryActionMode() { 930 if (mPrimaryActionMode != null) { 931 mPrimaryActionMode.finish(); 932 mPrimaryActionMode = null; 933 } 934 if (mPrimaryActionModeView != null) { 935 mPrimaryActionModeView.killMode(); 936 } 937 } 938 939 private void cleanupFloatingActionModeViews() { 940 if (mFloatingToolbar != null) { 941 mFloatingToolbar.dismiss(); 942 mFloatingToolbar = null; 943 } 944 if (mFloatingActionModeOriginatingView != null) { 945 if (mFloatingToolbarPreDrawListener != null) { 946 mFloatingActionModeOriginatingView.getViewTreeObserver() 947 .removeOnPreDrawListener(mFloatingToolbarPreDrawListener); 948 mFloatingToolbarPreDrawListener = null; 949 } 950 mFloatingActionModeOriginatingView = null; 951 } 952 } 953 954 void startChanging() { 955 mChanging = true; 956 } 957 958 void finishChanging() { 959 mChanging = false; 960 drawableChanged(); 961 } 962 963 public void setWindowBackground(Drawable drawable) { 964 if (mWindow == null) { 965 mPendingWindowBackground = drawable; 966 return; 967 } 968 if (mOriginalBackgroundDrawable != drawable) { 969 mOriginalBackgroundDrawable = drawable; 970 updateBackgroundDrawable(); 971 if (drawable != null) { 972 mResizingBackgroundDrawable = enforceNonTranslucentBackground(drawable, 973 mWindow.isTranslucent() || mWindow.isShowingWallpaper()); 974 } else { 975 mResizingBackgroundDrawable = getResizingBackgroundDrawable( 976 mWindow.mBackgroundDrawable, mWindow.mBackgroundFallbackDrawable, 977 mWindow.isTranslucent() || mWindow.isShowingWallpaper()); 978 } 979 if (mResizingBackgroundDrawable != null) { 980 mResizingBackgroundDrawable.getPadding(mBackgroundPadding); 981 } else { 982 mBackgroundPadding.setEmpty(); 983 } 984 if (!View.sBrokenWindowBackground) { 985 drawableChanged(); 986 } 987 } 988 } 989 990 @Override 991 public void setBackgroundDrawable(Drawable background) { 992 setWindowBackground(background); 993 } 994 995 public void setWindowFrame(Drawable drawable) { 996 if (getForeground() != drawable) { 997 setForeground(drawable); 998 if (drawable != null) { 999 drawable.getPadding(mFramePadding); 1000 } else { 1001 mFramePadding.setEmpty(); 1002 } 1003 drawableChanged(); 1004 } 1005 } 1006 1007 @Override 1008 public void onWindowSystemUiVisibilityChanged(int visible) { 1009 updateColorViews(null /* insets */, true /* animate */); 1010 updateDecorCaptionStatus(getResources().getConfiguration()); 1011 1012 if (mStatusGuard != null && mStatusGuard.getVisibility() == VISIBLE) { 1013 updateStatusGuardColor(); 1014 } 1015 } 1016 1017 @Override 1018 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 1019 final WindowManager.LayoutParams attrs = mWindow.getAttributes(); 1020 mFloatingInsets.setEmpty(); 1021 if ((attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0) { 1022 // For dialog windows we want to make sure they don't go over the status bar or nav bar. 1023 // We consume the system insets and we will reuse them later during the measure phase. 1024 // We allow the app to ignore this and handle insets itself by using 1025 // FLAG_LAYOUT_IN_SCREEN. 1026 if (attrs.height == WindowManager.LayoutParams.WRAP_CONTENT) { 1027 mFloatingInsets.top = insets.getSystemWindowInsetTop(); 1028 mFloatingInsets.bottom = insets.getSystemWindowInsetBottom(); 1029 insets = insets.inset(0, insets.getSystemWindowInsetTop(), 1030 0, insets.getSystemWindowInsetBottom()); 1031 } 1032 if (mWindow.getAttributes().width == WindowManager.LayoutParams.WRAP_CONTENT) { 1033 mFloatingInsets.left = insets.getSystemWindowInsetTop(); 1034 mFloatingInsets.right = insets.getSystemWindowInsetBottom(); 1035 insets = insets.inset(insets.getSystemWindowInsetLeft(), 0, 1036 insets.getSystemWindowInsetRight(), 0); 1037 } 1038 } 1039 mFrameOffsets.set(insets.getSystemWindowInsetsAsRect()); 1040 insets = updateColorViews(insets, true /* animate */); 1041 insets = updateStatusGuard(insets); 1042 if (getForeground() != null) { 1043 drawableChanged(); 1044 } 1045 return insets; 1046 } 1047 1048 @Override 1049 public boolean isTransitionGroup() { 1050 return false; 1051 } 1052 1053 public static boolean isNavBarToRightEdge(int bottomInset, int rightInset) { 1054 return bottomInset == 0 && rightInset > 0; 1055 } 1056 1057 public static boolean isNavBarToLeftEdge(int bottomInset, int leftInset) { 1058 return bottomInset == 0 && leftInset > 0; 1059 } 1060 getNavBarSize(int bottomInset, int rightInset, int leftInset)1061 public static int getNavBarSize(int bottomInset, int rightInset, int leftInset) { 1062 return isNavBarToRightEdge(bottomInset, rightInset) ? rightInset 1063 : isNavBarToLeftEdge(bottomInset, leftInset) ? leftInset : bottomInset; 1064 } 1065 getNavigationBarRect(int canvasWidth, int canvasHeight, Rect systemBarInsets, Rect outRect, float scale)1066 public static void getNavigationBarRect(int canvasWidth, int canvasHeight, Rect systemBarInsets, 1067 Rect outRect, float scale) { 1068 final int bottomInset = (int) (systemBarInsets.bottom * scale); 1069 final int leftInset = (int) (systemBarInsets.left * scale); 1070 final int rightInset = (int) (systemBarInsets.right * scale); 1071 final int size = getNavBarSize(bottomInset, rightInset, leftInset); 1072 if (isNavBarToRightEdge(bottomInset, rightInset)) { 1073 outRect.set(canvasWidth - size, 0, canvasWidth, canvasHeight); 1074 } else if (isNavBarToLeftEdge(bottomInset, leftInset)) { 1075 outRect.set(0, 0, size, canvasHeight); 1076 } else { 1077 outRect.set(0, canvasHeight - size, canvasWidth, canvasHeight); 1078 } 1079 } 1080 updateColorViews(WindowInsets insets, boolean animate)1081 WindowInsets updateColorViews(WindowInsets insets, boolean animate) { 1082 WindowManager.LayoutParams attrs = mWindow.getAttributes(); 1083 int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility(); 1084 1085 final WindowInsetsController controller = getWindowInsetsController(); 1086 1087 // IME is an exceptional floating window that requires color view. 1088 final boolean isImeWindow = 1089 mWindow.getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD; 1090 if (!mWindow.mIsFloating || isImeWindow) { 1091 boolean disallowAnimate = !isLaidOut(); 1092 disallowAnimate |= ((mLastWindowFlags ^ attrs.flags) 1093 & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0; 1094 mLastWindowFlags = attrs.flags; 1095 1096 if (insets != null) { 1097 final Insets stableBarInsets = insets.getInsetsIgnoringVisibility( 1098 WindowInsets.Type.systemBars()); 1099 final Insets systemInsets = Insets.min( 1100 insets.getInsets(WindowInsets.Type.systemBars() 1101 | WindowInsets.Type.displayCutout()), stableBarInsets); 1102 mLastTopInset = systemInsets.top; 1103 mLastBottomInset = systemInsets.bottom; 1104 mLastRightInset = systemInsets.right; 1105 mLastLeftInset = systemInsets.left; 1106 1107 // Don't animate if the presence of stable insets has changed, because that 1108 // indicates that the window was either just added and received them for the 1109 // first time, or the window size or position has changed. 1110 boolean hasTopStableInset = stableBarInsets.top != 0; 1111 disallowAnimate |= (hasTopStableInset != mLastHasTopStableInset); 1112 mLastHasTopStableInset = hasTopStableInset; 1113 1114 boolean hasBottomStableInset = stableBarInsets.bottom != 0; 1115 disallowAnimate |= (hasBottomStableInset != mLastHasBottomStableInset); 1116 mLastHasBottomStableInset = hasBottomStableInset; 1117 1118 boolean hasRightStableInset = stableBarInsets.right != 0; 1119 disallowAnimate |= (hasRightStableInset != mLastHasRightStableInset); 1120 mLastHasRightStableInset = hasRightStableInset; 1121 1122 boolean hasLeftStableInset = stableBarInsets.left != 0; 1123 disallowAnimate |= (hasLeftStableInset != mLastHasLeftStableInset); 1124 mLastHasLeftStableInset = hasLeftStableInset; 1125 1126 mLastShouldAlwaysConsumeSystemBars = insets.shouldAlwaysConsumeSystemBars(); 1127 } 1128 1129 boolean navBarToRightEdge = isNavBarToRightEdge(mLastBottomInset, mLastRightInset); 1130 boolean navBarToLeftEdge = isNavBarToLeftEdge(mLastBottomInset, mLastLeftInset); 1131 int navBarSize = getNavBarSize(mLastBottomInset, mLastRightInset, mLastLeftInset); 1132 updateColorViewInt(mNavigationColorViewState, sysUiVisibility, 1133 calculateNavigationBarColor(), mWindow.mNavigationBarDividerColor, navBarSize, 1134 navBarToRightEdge || navBarToLeftEdge, navBarToLeftEdge, 1135 0 /* sideInset */, animate && !disallowAnimate, 1136 mForceWindowDrawsBarBackgrounds, controller); 1137 boolean oldDrawLegacy = mDrawLegacyNavigationBarBackground; 1138 mDrawLegacyNavigationBarBackground = mNavigationColorViewState.visible 1139 && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0; 1140 if (oldDrawLegacy != mDrawLegacyNavigationBarBackground) { 1141 ViewRootImpl vri = getViewRootImpl(); 1142 if (vri != null) { 1143 vri.requestInvalidateRootRenderNode(); 1144 } 1145 } 1146 1147 boolean statusBarNeedsRightInset = navBarToRightEdge 1148 && mNavigationColorViewState.present; 1149 boolean statusBarNeedsLeftInset = navBarToLeftEdge 1150 && mNavigationColorViewState.present; 1151 int statusBarSideInset = statusBarNeedsRightInset ? mLastRightInset 1152 : statusBarNeedsLeftInset ? mLastLeftInset : 0; 1153 updateColorViewInt(mStatusColorViewState, sysUiVisibility, 1154 calculateStatusBarColor(), 0, mLastTopInset, 1155 false /* matchVertical */, statusBarNeedsLeftInset, statusBarSideInset, 1156 animate && !disallowAnimate, 1157 mForceWindowDrawsBarBackgrounds, controller); 1158 1159 if (mHasCaption) { 1160 final int captionColor = calculateStatusBarColor(); 1161 mDecorCaptionView.getCaption().setBackgroundColor(captionColor); 1162 updateDecorCaptionShade(); 1163 } 1164 } 1165 1166 // When we expand the window with FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS or 1167 // mForceWindowDrawsBarBackgrounds, we still need to ensure that the rest of the view 1168 // hierarchy doesn't notice it, unless they've explicitly asked for it. 1169 // 1170 // Note: We don't need to check for IN_SCREEN or INSET_DECOR because unlike the status bar, 1171 // these flags wouldn't make the window draw behind the navigation bar, unless 1172 // LAYOUT_HIDE_NAVIGATION was set. 1173 // 1174 // Note: Once the app uses the R+ Window.setDecorFitsSystemWindows(false) API we no longer 1175 // consume insets because they might no longer set SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION. 1176 boolean hideNavigation = (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0 1177 || !(controller == null || controller.isRequestedVisible(ITYPE_NAVIGATION_BAR)); 1178 boolean decorFitsSystemWindows = mWindow.mDecorFitsSystemWindows; 1179 boolean forceConsumingNavBar = (mForceWindowDrawsBarBackgrounds 1180 && (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0 1181 && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0 1182 && decorFitsSystemWindows 1183 && !hideNavigation) 1184 || (mLastShouldAlwaysConsumeSystemBars && hideNavigation); 1185 1186 boolean consumingNavBar = 1187 ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 1188 && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0 1189 && decorFitsSystemWindows 1190 && !hideNavigation) 1191 || forceConsumingNavBar; 1192 1193 // If we didn't request fullscreen layout, but we still got it because of the 1194 // mForceWindowDrawsBarBackgrounds flag, also consume top inset. 1195 // If we should always consume system bars, only consume that if the app wanted to go to 1196 // fullscreen, as othrewise we can expect the app to handle it. 1197 boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0 1198 || (attrs.flags & FLAG_FULLSCREEN) != 0 1199 || !(controller == null || controller.isRequestedVisible(ITYPE_STATUS_BAR)); 1200 boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0 1201 && decorFitsSystemWindows 1202 && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0 1203 && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0 1204 && mForceWindowDrawsBarBackgrounds 1205 && mLastTopInset != 0 1206 || (mLastShouldAlwaysConsumeSystemBars && fullscreen); 1207 1208 int consumedTop = consumingStatusBar ? mLastTopInset : 0; 1209 int consumedRight = consumingNavBar ? mLastRightInset : 0; 1210 int consumedBottom = consumingNavBar ? mLastBottomInset : 0; 1211 int consumedLeft = consumingNavBar ? mLastLeftInset : 0; 1212 1213 if (mContentRoot != null 1214 && mContentRoot.getLayoutParams() instanceof MarginLayoutParams) { 1215 MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams(); 1216 if (lp.topMargin != consumedTop || lp.rightMargin != consumedRight 1217 || lp.bottomMargin != consumedBottom || lp.leftMargin != consumedLeft) { 1218 lp.topMargin = consumedTop; 1219 lp.rightMargin = consumedRight; 1220 lp.bottomMargin = consumedBottom; 1221 lp.leftMargin = consumedLeft; 1222 mContentRoot.setLayoutParams(lp); 1223 1224 if (insets == null) { 1225 // The insets have changed, but we're not currently in the process 1226 // of dispatching them. 1227 requestApplyInsets(); 1228 } 1229 } 1230 if (insets != null) { 1231 insets = insets.inset(consumedLeft, consumedTop, consumedRight, consumedBottom); 1232 } 1233 } 1234 1235 if (forceConsumingNavBar) { 1236 mBackgroundInsets = Insets.of(mLastLeftInset, 0, mLastRightInset, mLastBottomInset); 1237 } else { 1238 mBackgroundInsets = Insets.NONE; 1239 } 1240 updateBackgroundDrawable(); 1241 1242 return insets; 1243 } 1244 1245 /** 1246 * Updates the background drawable, applying padding to it in case we {@link #mBackgroundInsets} 1247 * are set. 1248 */ updateBackgroundDrawable()1249 private void updateBackgroundDrawable() { 1250 // Background insets can be null if super constructor calls setBackgroundDrawable. 1251 if (mBackgroundInsets == null) { 1252 mBackgroundInsets = Insets.NONE; 1253 } 1254 if (mBackgroundInsets.equals(mLastBackgroundInsets) 1255 && mLastOriginalBackgroundDrawable == mOriginalBackgroundDrawable) { 1256 return; 1257 } 1258 if (mOriginalBackgroundDrawable == null || mBackgroundInsets.equals(Insets.NONE)) { 1259 1260 // Call super since we are intercepting setBackground on this class. 1261 super.setBackgroundDrawable(mOriginalBackgroundDrawable); 1262 } else { 1263 1264 // Call super since we are intercepting setBackground on this class. 1265 super.setBackgroundDrawable(new InsetDrawable(mOriginalBackgroundDrawable, 1266 mBackgroundInsets.left, mBackgroundInsets.top, 1267 mBackgroundInsets.right, mBackgroundInsets.bottom) { 1268 1269 /** 1270 * Return inner padding so we don't apply the padding again in 1271 * {@link DecorView#drawableChanged()} 1272 */ 1273 @Override 1274 public boolean getPadding(Rect padding) { 1275 return getDrawable().getPadding(padding); 1276 } 1277 }); 1278 } 1279 mLastBackgroundInsets = mBackgroundInsets; 1280 mLastOriginalBackgroundDrawable = mOriginalBackgroundDrawable; 1281 } 1282 1283 @Override getBackground()1284 public Drawable getBackground() { 1285 return mOriginalBackgroundDrawable; 1286 } 1287 calculateStatusBarColor()1288 private int calculateStatusBarColor() { 1289 return calculateBarColor(mWindow.getAttributes().flags, FLAG_TRANSLUCENT_STATUS, 1290 mSemiTransparentBarColor, mWindow.mStatusBarColor, 1291 getWindowSystemUiVisibility(), SYSTEM_UI_FLAG_LIGHT_STATUS_BAR, 1292 mWindow.mEnsureStatusBarContrastWhenTransparent); 1293 } 1294 calculateNavigationBarColor()1295 private int calculateNavigationBarColor() { 1296 return calculateBarColor(mWindow.getAttributes().flags, FLAG_TRANSLUCENT_NAVIGATION, 1297 mSemiTransparentBarColor, mWindow.mNavigationBarColor, 1298 getWindowSystemUiVisibility(), SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, 1299 mWindow.mEnsureNavigationBarContrastWhenTransparent 1300 && getContext().getResources().getBoolean(R.bool.config_navBarNeedsScrim)); 1301 } 1302 calculateBarColor(int flags, int translucentFlag, int semiTransparentBarColor, int barColor, int sysuiVis, int lightSysuiFlag, boolean scrimTransparent)1303 public static int calculateBarColor(int flags, int translucentFlag, int semiTransparentBarColor, 1304 int barColor, int sysuiVis, int lightSysuiFlag, boolean scrimTransparent) { 1305 if ((flags & translucentFlag) != 0) { 1306 return semiTransparentBarColor; 1307 } else if ((flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) { 1308 return Color.BLACK; 1309 } else if (scrimTransparent && Color.alpha(barColor) == 0) { 1310 boolean light = (sysuiVis & lightSysuiFlag) != 0; 1311 return light ? SCRIM_LIGHT : semiTransparentBarColor; 1312 } else { 1313 return barColor; 1314 } 1315 } 1316 getCurrentColor(ColorViewState state)1317 private int getCurrentColor(ColorViewState state) { 1318 if (state.visible) { 1319 return state.color; 1320 } else { 1321 return 0; 1322 } 1323 } 1324 1325 /** 1326 * Update a color view 1327 * 1328 * @param state the color view to update. 1329 * @param sysUiVis the current systemUiVisibility to apply. 1330 * @param color the current color to apply. 1331 * @param dividerColor the current divider color to apply. 1332 * @param size the current size in the non-parent-matching dimension. 1333 * @param verticalBar if true the view is attached to a vertical edge, otherwise to a 1334 * horizontal edge, 1335 * @param sideMargin sideMargin for the color view. 1336 * @param animate if true, the change will be animated. 1337 */ updateColorViewInt(final ColorViewState state, int sysUiVis, int color, int dividerColor, int size, boolean verticalBar, boolean seascape, int sideMargin, boolean animate, boolean force, WindowInsetsController controller)1338 private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color, 1339 int dividerColor, int size, boolean verticalBar, boolean seascape, int sideMargin, 1340 boolean animate, boolean force, WindowInsetsController controller) { 1341 state.present = ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL 1342 ? state.attributes.isPresent(sysUiVis, mWindow.getAttributes().flags, force) 1343 : state.attributes.isPresent( 1344 controller.isRequestedVisible(state.attributes.insetsType), 1345 mWindow.getAttributes().flags, force); 1346 boolean show = state.attributes.isVisible(state.present, color, 1347 mWindow.getAttributes().flags, force); 1348 boolean showView = show && !isResizing() && !mHasCaption && size > 0; 1349 1350 boolean visibilityChanged = false; 1351 View view = state.view; 1352 1353 int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size; 1354 int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT; 1355 int resolvedGravity = verticalBar 1356 ? (seascape ? state.attributes.seascapeGravity : state.attributes.horizontalGravity) 1357 : state.attributes.verticalGravity; 1358 1359 if (view == null) { 1360 if (showView) { 1361 state.view = view = new View(mContext); 1362 setColor(view, color, dividerColor, verticalBar, seascape); 1363 view.setTransitionName(state.attributes.transitionName); 1364 view.setId(state.attributes.id); 1365 visibilityChanged = true; 1366 view.setVisibility(INVISIBLE); 1367 state.targetVisibility = VISIBLE; 1368 1369 LayoutParams lp = new LayoutParams(resolvedWidth, resolvedHeight, 1370 resolvedGravity); 1371 if (seascape) { 1372 lp.leftMargin = sideMargin; 1373 } else { 1374 lp.rightMargin = sideMargin; 1375 } 1376 addView(view, lp); 1377 updateColorViewTranslations(); 1378 } 1379 } else { 1380 int vis = showView ? VISIBLE : INVISIBLE; 1381 visibilityChanged = state.targetVisibility != vis; 1382 state.targetVisibility = vis; 1383 LayoutParams lp = (LayoutParams) view.getLayoutParams(); 1384 int rightMargin = seascape ? 0 : sideMargin; 1385 int leftMargin = seascape ? sideMargin : 0; 1386 if (lp.height != resolvedHeight || lp.width != resolvedWidth 1387 || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin 1388 || lp.leftMargin != leftMargin) { 1389 lp.height = resolvedHeight; 1390 lp.width = resolvedWidth; 1391 lp.gravity = resolvedGravity; 1392 lp.rightMargin = rightMargin; 1393 lp.leftMargin = leftMargin; 1394 view.setLayoutParams(lp); 1395 } 1396 if (showView) { 1397 setColor(view, color, dividerColor, verticalBar, seascape); 1398 } 1399 } 1400 if (visibilityChanged) { 1401 view.animate().cancel(); 1402 if (animate && !isResizing()) { 1403 if (showView) { 1404 if (view.getVisibility() != VISIBLE) { 1405 view.setVisibility(VISIBLE); 1406 view.setAlpha(0.0f); 1407 } 1408 view.animate().alpha(1.0f).setInterpolator(mShowInterpolator). 1409 setDuration(mBarEnterExitDuration); 1410 } else { 1411 view.animate().alpha(0.0f).setInterpolator(mHideInterpolator) 1412 .setDuration(mBarEnterExitDuration) 1413 .withEndAction(new Runnable() { 1414 @Override 1415 public void run() { 1416 state.view.setAlpha(1.0f); 1417 state.view.setVisibility(INVISIBLE); 1418 } 1419 }); 1420 } 1421 } else { 1422 view.setAlpha(1.0f); 1423 view.setVisibility(showView ? VISIBLE : INVISIBLE); 1424 } 1425 } 1426 state.visible = show; 1427 state.color = color; 1428 } 1429 setColor(View v, int color, int dividerColor, boolean verticalBar, boolean seascape)1430 private static void setColor(View v, int color, int dividerColor, boolean verticalBar, 1431 boolean seascape) { 1432 if (dividerColor != 0) { 1433 final Pair<Boolean, Boolean> dir = (Pair<Boolean, Boolean>) v.getTag(); 1434 if (dir == null || dir.first != verticalBar || dir.second != seascape) { 1435 final int size = Math.round( 1436 TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, 1437 v.getContext().getResources().getDisplayMetrics())); 1438 // Use an inset to make the divider line on the side that faces the app. 1439 final InsetDrawable d = new InsetDrawable(new ColorDrawable(color), 1440 verticalBar && !seascape ? size : 0, 1441 !verticalBar ? size : 0, 1442 verticalBar && seascape ? size : 0, 0); 1443 v.setBackground(new LayerDrawable(new Drawable[] { 1444 new ColorDrawable(dividerColor), d })); 1445 v.setTag(new Pair<>(verticalBar, seascape)); 1446 } else { 1447 final LayerDrawable d = (LayerDrawable) v.getBackground(); 1448 final InsetDrawable inset = ((InsetDrawable) d.getDrawable(1)); 1449 ((ColorDrawable) inset.getDrawable()).setColor(color); 1450 ((ColorDrawable) d.getDrawable(0)).setColor(dividerColor); 1451 } 1452 } else { 1453 v.setTag(null); 1454 v.setBackgroundColor(color); 1455 } 1456 } 1457 updateColorViewTranslations()1458 private void updateColorViewTranslations() { 1459 // Put the color views back in place when they get moved off the screen 1460 // due to the the ViewRootImpl panning. 1461 int rootScrollY = mRootScrollY; 1462 if (mStatusColorViewState.view != null) { 1463 mStatusColorViewState.view.setTranslationY(rootScrollY > 0 ? rootScrollY : 0); 1464 } 1465 if (mNavigationColorViewState.view != null) { 1466 mNavigationColorViewState.view.setTranslationY(rootScrollY < 0 ? rootScrollY : 0); 1467 } 1468 } 1469 1470 private WindowInsets updateStatusGuard(WindowInsets insets) { 1471 boolean showStatusGuard = false; 1472 // Show the status guard when the non-overlay contextual action bar is showing 1473 if (mPrimaryActionModeView != null) { 1474 if (mPrimaryActionModeView.getLayoutParams() instanceof MarginLayoutParams) { 1475 // Insets are magic! 1476 final MarginLayoutParams mlp = (MarginLayoutParams) 1477 mPrimaryActionModeView.getLayoutParams(); 1478 boolean mlpChanged = false; 1479 if (mPrimaryActionModeView.isShown()) { 1480 if (mTempRect == null) { 1481 mTempRect = new Rect(); 1482 } 1483 final Rect rect = mTempRect; 1484 1485 // Apply the insets that have not been applied by the contentParent yet. 1486 WindowInsets innerInsets = 1487 mWindow.mContentParent.computeSystemWindowInsets(insets, rect); 1488 int newTopMargin = innerInsets.getSystemWindowInsetTop(); 1489 int newLeftMargin = innerInsets.getSystemWindowInsetLeft(); 1490 int newRightMargin = innerInsets.getSystemWindowInsetRight(); 1491 1492 // Must use root window insets for the guard, because the color views consume 1493 // the navigation bar inset if the window does not request LAYOUT_HIDE_NAV - but 1494 // the status guard is attached at the root. 1495 WindowInsets rootInsets = getRootWindowInsets(); 1496 int newGuardLeftMargin = rootInsets.getSystemWindowInsetLeft(); 1497 int newGuardRightMargin = rootInsets.getSystemWindowInsetRight(); 1498 1499 if (mlp.topMargin != newTopMargin || mlp.leftMargin != newLeftMargin 1500 || mlp.rightMargin != newRightMargin) { 1501 mlpChanged = true; 1502 mlp.topMargin = newTopMargin; 1503 mlp.leftMargin = newLeftMargin; 1504 mlp.rightMargin = newRightMargin; 1505 } 1506 1507 if (newTopMargin > 0 && mStatusGuard == null) { 1508 mStatusGuard = new View(mContext); 1509 mStatusGuard.setVisibility(GONE); 1510 final LayoutParams lp = new LayoutParams(MATCH_PARENT, 1511 mlp.topMargin, Gravity.LEFT | Gravity.TOP); 1512 lp.leftMargin = newGuardLeftMargin; 1513 lp.rightMargin = newGuardRightMargin; 1514 addView(mStatusGuard, indexOfChild(mStatusColorViewState.view), lp); 1515 } else if (mStatusGuard != null) { 1516 final LayoutParams lp = (LayoutParams) 1517 mStatusGuard.getLayoutParams(); 1518 if (lp.height != mlp.topMargin || lp.leftMargin != newGuardLeftMargin 1519 || lp.rightMargin != newGuardRightMargin) { 1520 lp.height = mlp.topMargin; 1521 lp.leftMargin = newGuardLeftMargin; 1522 lp.rightMargin = newGuardRightMargin; 1523 mStatusGuard.setLayoutParams(lp); 1524 } 1525 } 1526 1527 // The action mode's theme may differ from the app, so 1528 // always show the status guard above it if we have one. 1529 showStatusGuard = mStatusGuard != null; 1530 1531 if (showStatusGuard && mStatusGuard.getVisibility() != VISIBLE) { 1532 // If it wasn't previously shown, the color may be stale 1533 updateStatusGuardColor(); 1534 } 1535 1536 // We only need to consume the insets if the action 1537 // mode is overlaid on the app content (e.g. it's 1538 // sitting in a FrameLayout, see 1539 // screen_simple_overlay_action_mode.xml). 1540 final boolean nonOverlay = (mWindow.getLocalFeaturesPrivate() 1541 & (1 << Window.FEATURE_ACTION_MODE_OVERLAY)) == 0; 1542 if (nonOverlay && showStatusGuard) { 1543 insets = insets.inset(0, insets.getSystemWindowInsetTop(), 0, 0); 1544 } 1545 } else { 1546 // reset top margin 1547 if (mlp.topMargin != 0 || mlp.leftMargin != 0 || mlp.rightMargin != 0) { 1548 mlpChanged = true; 1549 mlp.topMargin = 0; 1550 } 1551 } 1552 if (mlpChanged) { 1553 mPrimaryActionModeView.setLayoutParams(mlp); 1554 } 1555 } 1556 } 1557 if (mStatusGuard != null) { 1558 mStatusGuard.setVisibility(showStatusGuard ? VISIBLE : GONE); 1559 } 1560 return insets; 1561 } 1562 updateStatusGuardColor()1563 private void updateStatusGuardColor() { 1564 boolean lightStatusBar = 1565 (getWindowSystemUiVisibility() & SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0; 1566 mStatusGuard.setBackgroundColor(lightStatusBar 1567 ? mContext.getColor(R.color.decor_view_status_guard_light) 1568 : mContext.getColor(R.color.decor_view_status_guard)); 1569 } 1570 1571 /** 1572 * Overrides the view outline when the activity enters picture-in-picture to ensure that it has 1573 * an opaque shadow even if the window background is completely transparent. This only applies 1574 * to activities that are currently the task root. 1575 */ updatePictureInPictureOutlineProvider(boolean isInPictureInPictureMode)1576 public void updatePictureInPictureOutlineProvider(boolean isInPictureInPictureMode) { 1577 if (mIsInPictureInPictureMode == isInPictureInPictureMode) { 1578 return; 1579 } 1580 1581 if (isInPictureInPictureMode) { 1582 final Window.WindowControllerCallback callback = 1583 mWindow.getWindowControllerCallback(); 1584 if (callback != null && callback.isTaskRoot()) { 1585 // Call super implementation directly as we don't want to save the PIP outline 1586 // provider to be restored 1587 super.setOutlineProvider(PIP_OUTLINE_PROVIDER); 1588 } 1589 } else { 1590 // Restore the previous outline provider 1591 if (getOutlineProvider() != mLastOutlineProvider) { 1592 setOutlineProvider(mLastOutlineProvider); 1593 } 1594 } 1595 mIsInPictureInPictureMode = isInPictureInPictureMode; 1596 } 1597 1598 @Override setOutlineProvider(ViewOutlineProvider provider)1599 public void setOutlineProvider(ViewOutlineProvider provider) { 1600 super.setOutlineProvider(provider); 1601 1602 // Save the outline provider set to ensure that we can restore when the activity leaves PiP 1603 mLastOutlineProvider = provider; 1604 } 1605 drawableChanged()1606 private void drawableChanged() { 1607 if (mChanging) { 1608 return; 1609 } 1610 1611 // Fields can be null if super constructor calls setBackgroundDrawable. 1612 Rect framePadding = mFramePadding != null ? mFramePadding : new Rect(); 1613 Rect backgroundPadding = mBackgroundPadding != null ? mBackgroundPadding : new Rect(); 1614 1615 setPadding(framePadding.left + backgroundPadding.left, 1616 framePadding.top + backgroundPadding.top, 1617 framePadding.right + backgroundPadding.right, 1618 framePadding.bottom + backgroundPadding.bottom); 1619 requestLayout(); 1620 invalidate(); 1621 1622 int opacity = PixelFormat.OPAQUE; 1623 final WindowConfiguration winConfig = getResources().getConfiguration().windowConfiguration; 1624 final boolean renderShadowsInCompositor = mWindow.mRenderShadowsInCompositor; 1625 // If we draw shadows in the compositor we don't need to force the surface to be 1626 // translucent. 1627 if (winConfig.hasWindowShadow() && !renderShadowsInCompositor) { 1628 // If the window has a shadow, it must be translucent. 1629 opacity = PixelFormat.TRANSLUCENT; 1630 } else{ 1631 // Note: If there is no background, we will assume opaque. The 1632 // common case seems to be that an application sets there to be 1633 // no background so it can draw everything itself. For that, 1634 // we would like to assume OPAQUE and let the app force it to 1635 // the slower TRANSLUCENT mode if that is really what it wants. 1636 Drawable bg = getBackground(); 1637 Drawable fg = getForeground(); 1638 if (bg != null) { 1639 if (fg == null) { 1640 opacity = bg.getOpacity(); 1641 } else if (framePadding.left <= 0 && framePadding.top <= 0 1642 && framePadding.right <= 0 && framePadding.bottom <= 0) { 1643 // If the frame padding is zero, then we can be opaque 1644 // if either the frame -or- the background is opaque. 1645 int fop = fg.getOpacity(); 1646 int bop = bg.getOpacity(); 1647 if (false) 1648 Log.v(mLogTag, "Background opacity: " + bop + ", Frame opacity: " + fop); 1649 if (fop == PixelFormat.OPAQUE || bop == PixelFormat.OPAQUE) { 1650 opacity = PixelFormat.OPAQUE; 1651 } else if (fop == PixelFormat.UNKNOWN) { 1652 opacity = bop; 1653 } else if (bop == PixelFormat.UNKNOWN) { 1654 opacity = fop; 1655 } else { 1656 opacity = Drawable.resolveOpacity(fop, bop); 1657 } 1658 } else { 1659 // For now we have to assume translucent if there is a 1660 // frame with padding... there is no way to tell if the 1661 // frame and background together will draw all pixels. 1662 if (false) 1663 Log.v(mLogTag, "Padding: " + mFramePadding); 1664 opacity = PixelFormat.TRANSLUCENT; 1665 } 1666 } 1667 if (false) 1668 Log.v(mLogTag, "Background: " + bg + ", Frame: " + fg); 1669 } 1670 1671 if (false) 1672 Log.v(mLogTag, "Selected default opacity: " + opacity); 1673 1674 mDefaultOpacity = opacity; 1675 if (mFeatureId < 0) { 1676 mWindow.setDefaultWindowFormat(opacity); 1677 } 1678 } 1679 1680 @Override onWindowFocusChanged(boolean hasWindowFocus)1681 public void onWindowFocusChanged(boolean hasWindowFocus) { 1682 super.onWindowFocusChanged(hasWindowFocus); 1683 1684 // If the user is chording a menu shortcut, release the chord since 1685 // this window lost focus 1686 if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL) && !hasWindowFocus 1687 && mWindow.mPanelChordingKey != 0) { 1688 mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL); 1689 } 1690 1691 final Window.Callback cb = mWindow.getCallback(); 1692 if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) { 1693 cb.onWindowFocusChanged(hasWindowFocus); 1694 } 1695 1696 if (mPrimaryActionMode != null) { 1697 mPrimaryActionMode.onWindowFocusChanged(hasWindowFocus); 1698 } 1699 if (mFloatingActionMode != null) { 1700 mFloatingActionMode.onWindowFocusChanged(hasWindowFocus); 1701 } 1702 1703 updateElevation(); 1704 } 1705 1706 @Override onAttachedToWindow()1707 protected void onAttachedToWindow() { 1708 super.onAttachedToWindow(); 1709 1710 final Window.Callback cb = mWindow.getCallback(); 1711 if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) { 1712 cb.onAttachedToWindow(); 1713 } 1714 1715 if (mFeatureId == -1) { 1716 /* 1717 * The main window has been attached, try to restore any panels 1718 * that may have been open before. This is called in cases where 1719 * an activity is being killed for configuration change and the 1720 * menu was open. When the activity is recreated, the menu 1721 * should be shown again. 1722 */ 1723 mWindow.openPanelsAfterRestore(); 1724 } 1725 1726 if (!mWindowResizeCallbacksAdded) { 1727 // If there is no window callback installed there was no window set before. Set it now. 1728 // Note that our ViewRootImpl object will not change. 1729 getViewRootImpl().addWindowCallbacks(this); 1730 mWindowResizeCallbacksAdded = true; 1731 } else if (mBackdropFrameRenderer != null) { 1732 // We are resizing and this call happened due to a configuration change. Tell the 1733 // renderer about it. 1734 mBackdropFrameRenderer.onConfigurationChange(); 1735 } 1736 mWindow.onViewRootImplSet(getViewRootImpl()); 1737 } 1738 1739 @Override onDetachedFromWindow()1740 protected void onDetachedFromWindow() { 1741 super.onDetachedFromWindow(); 1742 1743 final Window.Callback cb = mWindow.getCallback(); 1744 if (cb != null && mFeatureId < 0) { 1745 cb.onDetachedFromWindow(); 1746 } 1747 1748 if (mWindow.mDecorContentParent != null) { 1749 mWindow.mDecorContentParent.dismissPopups(); 1750 } 1751 1752 if (mPrimaryActionModePopup != null) { 1753 removeCallbacks(mShowPrimaryActionModePopup); 1754 if (mPrimaryActionModePopup.isShowing()) { 1755 mPrimaryActionModePopup.dismiss(); 1756 } 1757 mPrimaryActionModePopup = null; 1758 } 1759 if (mFloatingToolbar != null) { 1760 mFloatingToolbar.dismiss(); 1761 mFloatingToolbar = null; 1762 } 1763 1764 PhoneWindow.PanelFeatureState st = mWindow.getPanelState(Window.FEATURE_OPTIONS_PANEL, false); 1765 if (st != null && st.menu != null && mFeatureId < 0) { 1766 st.menu.close(); 1767 } 1768 1769 releaseThreadedRenderer(); 1770 1771 if (mWindowResizeCallbacksAdded) { 1772 getViewRootImpl().removeWindowCallbacks(this); 1773 mWindowResizeCallbacksAdded = false; 1774 } 1775 1776 mPendingInsetsController.detach(); 1777 } 1778 1779 @Override onCloseSystemDialogs(String reason)1780 public void onCloseSystemDialogs(String reason) { 1781 if (mFeatureId >= 0) { 1782 mWindow.closeAllPanels(); 1783 } 1784 } 1785 willYouTakeTheSurface()1786 public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() { 1787 return mFeatureId < 0 ? mWindow.mTakeSurfaceCallback : null; 1788 } 1789 willYouTakeTheInputQueue()1790 public InputQueue.Callback willYouTakeTheInputQueue() { 1791 return mFeatureId < 0 ? mWindow.mTakeInputQueueCallback : null; 1792 } 1793 setSurfaceType(int type)1794 public void setSurfaceType(int type) { 1795 mWindow.setType(type); 1796 } 1797 setSurfaceFormat(int format)1798 public void setSurfaceFormat(int format) { 1799 mWindow.setFormat(format); 1800 } 1801 setSurfaceKeepScreenOn(boolean keepOn)1802 public void setSurfaceKeepScreenOn(boolean keepOn) { 1803 if (keepOn) mWindow.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1804 else mWindow.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 1805 } 1806 1807 @Override onRootViewScrollYChanged(int rootScrollY)1808 public void onRootViewScrollYChanged(int rootScrollY) { 1809 mRootScrollY = rootScrollY; 1810 if (mDecorCaptionView != null) { 1811 mDecorCaptionView.onRootViewScrollYChanged(rootScrollY); 1812 } 1813 updateColorViewTranslations(); 1814 } 1815 1816 @Override providePendingInsetsController()1817 public PendingInsetsController providePendingInsetsController() { 1818 return mPendingInsetsController; 1819 } 1820 createActionMode( int type, ActionMode.Callback2 callback, View originatingView)1821 private ActionMode createActionMode( 1822 int type, ActionMode.Callback2 callback, View originatingView) { 1823 switch (type) { 1824 case ActionMode.TYPE_PRIMARY: 1825 default: 1826 return createStandaloneActionMode(callback); 1827 case ActionMode.TYPE_FLOATING: 1828 return createFloatingActionMode(originatingView, callback); 1829 } 1830 } 1831 setHandledActionMode(ActionMode mode)1832 private void setHandledActionMode(ActionMode mode) { 1833 if (mode.getType() == ActionMode.TYPE_PRIMARY) { 1834 setHandledPrimaryActionMode(mode); 1835 } else if (mode.getType() == ActionMode.TYPE_FLOATING) { 1836 setHandledFloatingActionMode(mode); 1837 } 1838 } 1839 createStandaloneActionMode(ActionMode.Callback callback)1840 private ActionMode createStandaloneActionMode(ActionMode.Callback callback) { 1841 endOnGoingFadeAnimation(); 1842 cleanupPrimaryActionMode(); 1843 // We want to create new mPrimaryActionModeView in two cases: if there is no existing 1844 // instance at all, or if there is one, but it is detached from window. The latter case 1845 // might happen when app is resized in multi-window mode and decor view is preserved 1846 // along with the main app window. Keeping mPrimaryActionModeView reference doesn't cause 1847 // app memory leaks because killMode() is called when the dismiss animation ends and from 1848 // cleanupPrimaryActionMode() invocation above. 1849 if (mPrimaryActionModeView == null || !mPrimaryActionModeView.isAttachedToWindow()) { 1850 if (mWindow.isFloating()) { 1851 // Use the action bar theme. 1852 final TypedValue outValue = new TypedValue(); 1853 final Resources.Theme baseTheme = mContext.getTheme(); 1854 baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true); 1855 1856 final Context actionBarContext; 1857 if (outValue.resourceId != 0) { 1858 final Resources.Theme actionBarTheme = mContext.getResources().newTheme(); 1859 actionBarTheme.setTo(baseTheme); 1860 actionBarTheme.applyStyle(outValue.resourceId, true); 1861 1862 actionBarContext = new ContextThemeWrapper(mContext, 0); 1863 actionBarContext.getTheme().setTo(actionBarTheme); 1864 } else { 1865 actionBarContext = mContext; 1866 } 1867 1868 mPrimaryActionModeView = new ActionBarContextView(actionBarContext); 1869 mPrimaryActionModePopup = new PopupWindow(actionBarContext, null, 1870 R.attr.actionModePopupWindowStyle); 1871 mPrimaryActionModePopup.setWindowLayoutType( 1872 WindowManager.LayoutParams.TYPE_APPLICATION); 1873 mPrimaryActionModePopup.setContentView(mPrimaryActionModeView); 1874 mPrimaryActionModePopup.setWidth(MATCH_PARENT); 1875 1876 actionBarContext.getTheme().resolveAttribute( 1877 R.attr.actionBarSize, outValue, true); 1878 final int height = TypedValue.complexToDimensionPixelSize(outValue.data, 1879 actionBarContext.getResources().getDisplayMetrics()); 1880 mPrimaryActionModeView.setContentHeight(height); 1881 mPrimaryActionModePopup.setHeight(WRAP_CONTENT); 1882 mShowPrimaryActionModePopup = new Runnable() { 1883 public void run() { 1884 mPrimaryActionModePopup.showAtLocation( 1885 mPrimaryActionModeView.getApplicationWindowToken(), 1886 Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0); 1887 endOnGoingFadeAnimation(); 1888 1889 if (shouldAnimatePrimaryActionModeView()) { 1890 mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA, 1891 0f, 1f); 1892 mFadeAnim.addListener(new AnimatorListenerAdapter() { 1893 @Override 1894 public void onAnimationStart(Animator animation) { 1895 mPrimaryActionModeView.setVisibility(VISIBLE); 1896 } 1897 1898 @Override 1899 public void onAnimationEnd(Animator animation) { 1900 mPrimaryActionModeView.setAlpha(1f); 1901 mFadeAnim = null; 1902 } 1903 }); 1904 mFadeAnim.start(); 1905 } else { 1906 mPrimaryActionModeView.setAlpha(1f); 1907 mPrimaryActionModeView.setVisibility(VISIBLE); 1908 } 1909 } 1910 }; 1911 } else { 1912 ViewStub stub = findViewById(R.id.action_mode_bar_stub); 1913 if (stub != null) { 1914 mPrimaryActionModeView = (ActionBarContextView) stub.inflate(); 1915 mPrimaryActionModePopup = null; 1916 } 1917 } 1918 } 1919 if (mPrimaryActionModeView != null) { 1920 mPrimaryActionModeView.killMode(); 1921 ActionMode mode = new StandaloneActionMode( 1922 mPrimaryActionModeView.getContext(), mPrimaryActionModeView, 1923 callback, mPrimaryActionModePopup == null); 1924 return mode; 1925 } 1926 return null; 1927 } 1928 endOnGoingFadeAnimation()1929 private void endOnGoingFadeAnimation() { 1930 if (mFadeAnim != null) { 1931 mFadeAnim.end(); 1932 } 1933 } 1934 setHandledPrimaryActionMode(ActionMode mode)1935 private void setHandledPrimaryActionMode(ActionMode mode) { 1936 endOnGoingFadeAnimation(); 1937 mPrimaryActionMode = mode; 1938 mPrimaryActionMode.invalidate(); 1939 mPrimaryActionModeView.initForMode(mPrimaryActionMode); 1940 if (mPrimaryActionModePopup != null) { 1941 post(mShowPrimaryActionModePopup); 1942 } else { 1943 if (shouldAnimatePrimaryActionModeView()) { 1944 mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA, 0f, 1f); 1945 mFadeAnim.addListener(new AnimatorListenerAdapter() { 1946 @Override 1947 public void onAnimationStart(Animator animation) { 1948 mPrimaryActionModeView.setVisibility(View.VISIBLE); 1949 } 1950 1951 @Override 1952 public void onAnimationEnd(Animator animation) { 1953 mPrimaryActionModeView.setAlpha(1f); 1954 mFadeAnim = null; 1955 } 1956 }); 1957 mFadeAnim.start(); 1958 } else { 1959 mPrimaryActionModeView.setAlpha(1f); 1960 mPrimaryActionModeView.setVisibility(View.VISIBLE); 1961 } 1962 } 1963 mPrimaryActionModeView.sendAccessibilityEvent( 1964 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 1965 } 1966 shouldAnimatePrimaryActionModeView()1967 boolean shouldAnimatePrimaryActionModeView() { 1968 // We only to animate the action mode in if the decor has already been laid out. 1969 // If it hasn't been laid out, it hasn't been drawn to screen yet. 1970 return isLaidOut(); 1971 } 1972 createFloatingActionMode( View originatingView, ActionMode.Callback2 callback)1973 private ActionMode createFloatingActionMode( 1974 View originatingView, ActionMode.Callback2 callback) { 1975 if (mFloatingActionMode != null) { 1976 mFloatingActionMode.finish(); 1977 } 1978 cleanupFloatingActionModeViews(); 1979 mFloatingToolbar = new FloatingToolbar(mWindow); 1980 final FloatingActionMode mode = 1981 new FloatingActionMode(mContext, callback, originatingView, mFloatingToolbar); 1982 mFloatingActionModeOriginatingView = originatingView; 1983 mFloatingToolbarPreDrawListener = 1984 new ViewTreeObserver.OnPreDrawListener() { 1985 @Override 1986 public boolean onPreDraw() { 1987 mode.updateViewLocationInWindow(); 1988 return true; 1989 } 1990 }; 1991 return mode; 1992 } 1993 setHandledFloatingActionMode(ActionMode mode)1994 private void setHandledFloatingActionMode(ActionMode mode) { 1995 mFloatingActionMode = mode; 1996 mFloatingActionMode.invalidate(); // Will show the floating toolbar if necessary. 1997 mFloatingActionModeOriginatingView.getViewTreeObserver() 1998 .addOnPreDrawListener(mFloatingToolbarPreDrawListener); 1999 } 2000 2001 /** 2002 * Informs the decor if the caption is attached and visible. 2003 * @param attachedAndVisible true when the decor is visible. 2004 * Note that this will even be called if there is no caption. 2005 **/ enableCaption(boolean attachedAndVisible)2006 void enableCaption(boolean attachedAndVisible) { 2007 if (mHasCaption != attachedAndVisible) { 2008 mHasCaption = attachedAndVisible; 2009 if (getForeground() != null) { 2010 drawableChanged(); 2011 } 2012 } 2013 } 2014 2015 /** 2016 * An interface to be called when the caption visibility or height changed, to report the 2017 * corresponding insets change to the InsetsController. 2018 */ notifyCaptionHeightChanged()2019 public void notifyCaptionHeightChanged() { 2020 getWindowInsetsController().setCaptionInsetsHeight(getCaptionInsetsHeight()); 2021 } 2022 setWindow(PhoneWindow phoneWindow)2023 void setWindow(PhoneWindow phoneWindow) { 2024 mWindow = phoneWindow; 2025 Context context = getContext(); 2026 if (context instanceof DecorContext) { 2027 DecorContext decorContext = (DecorContext) context; 2028 decorContext.setPhoneWindow(mWindow); 2029 } 2030 if (mPendingWindowBackground != null) { 2031 Drawable background = mPendingWindowBackground; 2032 mPendingWindowBackground = null; 2033 setWindowBackground(background); 2034 } 2035 } 2036 2037 @Override getResources()2038 public Resources getResources() { 2039 // Make sure the Resources object is propogated from the Context since it can be updated in 2040 // the Context object. 2041 return getContext().getResources(); 2042 } 2043 2044 @Override onConfigurationChanged(Configuration newConfig)2045 protected void onConfigurationChanged(Configuration newConfig) { 2046 super.onConfigurationChanged(newConfig); 2047 2048 updateDecorCaptionStatus(newConfig); 2049 2050 updateAvailableWidth(); 2051 initializeElevation(); 2052 } 2053 2054 @Override onMovedToDisplay(int displayId, Configuration config)2055 public void onMovedToDisplay(int displayId, Configuration config) { 2056 super.onMovedToDisplay(displayId, config); 2057 // Have to explicitly update displayId because it may use DecorContext 2058 getContext().updateDisplay(displayId); 2059 } 2060 2061 /** 2062 * Determines if the workspace is entirely covered by the window. 2063 * @return {@code true} when the window is filling the entire screen/workspace. 2064 **/ isFillingScreen(Configuration config)2065 private boolean isFillingScreen(Configuration config) { 2066 final boolean isFullscreen = config.windowConfiguration.getWindowingMode() 2067 == WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 2068 return isFullscreen && (0 != ((getWindowSystemUiVisibility() | getSystemUiVisibility()) 2069 & View.SYSTEM_UI_FLAG_FULLSCREEN)); 2070 } 2071 updateDecorCaptionStatus(Configuration config)2072 private void updateDecorCaptionStatus(Configuration config) { 2073 final boolean displayWindowDecor = config.windowConfiguration.hasWindowDecorCaption() 2074 && !isFillingScreen(config); 2075 if (mDecorCaptionView == null && displayWindowDecor) { 2076 // Configuration now requires a caption. 2077 final LayoutInflater inflater = mWindow.getLayoutInflater(); 2078 mDecorCaptionView = createDecorCaptionView(inflater); 2079 if (mDecorCaptionView != null) { 2080 if (mDecorCaptionView.getParent() == null) { 2081 addView(mDecorCaptionView, 0, 2082 new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); 2083 } 2084 removeView(mContentRoot); 2085 mDecorCaptionView.addView(mContentRoot, 2086 new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT)); 2087 } 2088 } else if (mDecorCaptionView != null) { 2089 // We might have to change the kind of surface before we do anything else. 2090 mDecorCaptionView.onConfigurationChanged(displayWindowDecor); 2091 enableCaption(displayWindowDecor); 2092 } 2093 } 2094 onResourcesLoaded(LayoutInflater inflater, int layoutResource)2095 void onResourcesLoaded(LayoutInflater inflater, int layoutResource) { 2096 if (mBackdropFrameRenderer != null) { 2097 loadBackgroundDrawablesIfNeeded(); 2098 mBackdropFrameRenderer.onResourcesLoaded( 2099 this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable, 2100 mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState), 2101 getCurrentColor(mNavigationColorViewState)); 2102 } 2103 2104 mDecorCaptionView = createDecorCaptionView(inflater); 2105 final View root = inflater.inflate(layoutResource, null); 2106 if (mDecorCaptionView != null) { 2107 if (mDecorCaptionView.getParent() == null) { 2108 addView(mDecorCaptionView, 2109 new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); 2110 } 2111 mDecorCaptionView.addView(root, 2112 new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT)); 2113 } else { 2114 2115 // Put it below the color views. 2116 addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); 2117 } 2118 mContentRoot = (ViewGroup) root; 2119 initializeElevation(); 2120 } 2121 loadBackgroundDrawablesIfNeeded()2122 private void loadBackgroundDrawablesIfNeeded() { 2123 if (mResizingBackgroundDrawable == null) { 2124 mResizingBackgroundDrawable = getResizingBackgroundDrawable(mWindow.mBackgroundDrawable, 2125 mWindow.mBackgroundFallbackDrawable, mWindow.isTranslucent() 2126 || mWindow.isShowingWallpaper()); 2127 if (mResizingBackgroundDrawable == null) { 2128 // We shouldn't really get here as the background fallback should be always 2129 // available since it is defaulted by the system. 2130 Log.w(mLogTag, "Failed to find background drawable for PhoneWindow=" + mWindow); 2131 } 2132 } 2133 if (mCaptionBackgroundDrawable == null) { 2134 mCaptionBackgroundDrawable = getContext().getDrawable( 2135 R.drawable.decor_caption_title_focused); 2136 } 2137 if (mResizingBackgroundDrawable != null) { 2138 mLastBackgroundDrawableCb = mResizingBackgroundDrawable.getCallback(); 2139 mResizingBackgroundDrawable.setCallback(null); 2140 } 2141 } 2142 2143 // Free floating overlapping windows require a caption. createDecorCaptionView(LayoutInflater inflater)2144 private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) { 2145 DecorCaptionView decorCaptionView = null; 2146 for (int i = getChildCount() - 1; i >= 0 && decorCaptionView == null; i--) { 2147 View view = getChildAt(i); 2148 if (view instanceof DecorCaptionView) { 2149 // The decor was most likely saved from a relaunch - so reuse it. 2150 decorCaptionView = (DecorCaptionView) view; 2151 removeViewAt(i); 2152 } 2153 } 2154 final WindowManager.LayoutParams attrs = mWindow.getAttributes(); 2155 final boolean isApplication = attrs.type == TYPE_BASE_APPLICATION || 2156 attrs.type == TYPE_APPLICATION || attrs.type == TYPE_DRAWN_APPLICATION; 2157 final WindowConfiguration winConfig = getResources().getConfiguration().windowConfiguration; 2158 // Only a non floating application window on one of the allowed workspaces can get a caption 2159 if (!mWindow.isFloating() && isApplication && winConfig.hasWindowDecorCaption()) { 2160 // Dependent on the brightness of the used title we either use the 2161 // dark or the light button frame. 2162 if (decorCaptionView == null) { 2163 decorCaptionView = inflateDecorCaptionView(inflater); 2164 } 2165 decorCaptionView.setPhoneWindow(mWindow, true /*showDecor*/); 2166 } else { 2167 decorCaptionView = null; 2168 } 2169 2170 // Tell the decor if it has a visible caption. 2171 enableCaption(decorCaptionView != null); 2172 return decorCaptionView; 2173 } 2174 inflateDecorCaptionView(LayoutInflater inflater)2175 private DecorCaptionView inflateDecorCaptionView(LayoutInflater inflater) { 2176 final Context context = getContext(); 2177 // We make a copy of the inflater, so it has the right context associated with it. 2178 inflater = inflater.from(context); 2179 final DecorCaptionView view = (DecorCaptionView) inflater.inflate(R.layout.decor_caption, 2180 null); 2181 setDecorCaptionShade(view); 2182 return view; 2183 } 2184 setDecorCaptionShade(DecorCaptionView view)2185 private void setDecorCaptionShade(DecorCaptionView view) { 2186 final int shade = mWindow.getDecorCaptionShade(); 2187 switch (shade) { 2188 case DECOR_CAPTION_SHADE_LIGHT: 2189 setLightDecorCaptionShade(view); 2190 break; 2191 case DECOR_CAPTION_SHADE_DARK: 2192 setDarkDecorCaptionShade(view); 2193 break; 2194 default: { 2195 if ((getWindowSystemUiVisibility() & SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0) { 2196 setDarkDecorCaptionShade(view); 2197 } else { 2198 setLightDecorCaptionShade(view); 2199 } 2200 break; 2201 } 2202 } 2203 } 2204 updateDecorCaptionShade()2205 void updateDecorCaptionShade() { 2206 if (mDecorCaptionView != null) { 2207 setDecorCaptionShade(mDecorCaptionView); 2208 } 2209 } 2210 setLightDecorCaptionShade(DecorCaptionView view)2211 private void setLightDecorCaptionShade(DecorCaptionView view) { 2212 view.findViewById(R.id.maximize_window).setBackgroundResource( 2213 R.drawable.decor_maximize_button_light); 2214 view.findViewById(R.id.close_window).setBackgroundResource( 2215 R.drawable.decor_close_button_light); 2216 } 2217 setDarkDecorCaptionShade(DecorCaptionView view)2218 private void setDarkDecorCaptionShade(DecorCaptionView view) { 2219 view.findViewById(R.id.maximize_window).setBackgroundResource( 2220 R.drawable.decor_maximize_button_dark); 2221 view.findViewById(R.id.close_window).setBackgroundResource( 2222 R.drawable.decor_close_button_dark); 2223 } 2224 2225 /** 2226 * Returns the color used to fill areas the app has not rendered content to yet when the 2227 * user is resizing the window of an activity in multi-window mode. 2228 */ getResizingBackgroundDrawable(@ullable Drawable backgroundDrawable, @Nullable Drawable fallbackDrawable, boolean windowTranslucent)2229 public static Drawable getResizingBackgroundDrawable(@Nullable Drawable backgroundDrawable, 2230 @Nullable Drawable fallbackDrawable, boolean windowTranslucent) { 2231 if (backgroundDrawable != null) { 2232 return enforceNonTranslucentBackground(backgroundDrawable, windowTranslucent); 2233 } 2234 2235 if (fallbackDrawable != null) { 2236 return enforceNonTranslucentBackground(fallbackDrawable, windowTranslucent); 2237 } 2238 return new ColorDrawable(Color.BLACK); 2239 } 2240 2241 /** 2242 * Enforces a drawable to be non-translucent to act as a background if needed, i.e. if the 2243 * window is not translucent. 2244 */ enforceNonTranslucentBackground(Drawable drawable, boolean windowTranslucent)2245 private static Drawable enforceNonTranslucentBackground(Drawable drawable, 2246 boolean windowTranslucent) { 2247 if (!windowTranslucent && drawable instanceof ColorDrawable) { 2248 ColorDrawable colorDrawable = (ColorDrawable) drawable; 2249 int color = colorDrawable.getColor(); 2250 if (Color.alpha(color) != 255) { 2251 ColorDrawable copy = (ColorDrawable) colorDrawable.getConstantState().newDrawable() 2252 .mutate(); 2253 copy.setColor( 2254 Color.argb(255, Color.red(color), Color.green(color), Color.blue(color))); 2255 return copy; 2256 } 2257 } 2258 return drawable; 2259 } 2260 clearContentView()2261 void clearContentView() { 2262 if (mDecorCaptionView != null) { 2263 mDecorCaptionView.removeContentView(); 2264 } else { 2265 // This window doesn't have caption, so we need to remove everything except our views 2266 // we might have added. 2267 for (int i = getChildCount() - 1; i >= 0; i--) { 2268 View v = getChildAt(i); 2269 if (v != mStatusColorViewState.view && v != mNavigationColorViewState.view 2270 && v != mStatusGuard) { 2271 removeViewAt(i); 2272 } 2273 } 2274 } 2275 } 2276 2277 @Override onWindowSizeIsChanging(Rect newBounds, boolean fullscreen, Rect systemInsets, Rect stableInsets)2278 public void onWindowSizeIsChanging(Rect newBounds, boolean fullscreen, Rect systemInsets, 2279 Rect stableInsets) { 2280 if (mBackdropFrameRenderer != null) { 2281 mBackdropFrameRenderer.setTargetRect(newBounds, fullscreen, systemInsets); 2282 } 2283 } 2284 2285 @Override onWindowDragResizeStart(Rect initialBounds, boolean fullscreen, Rect systemInsets, Rect stableInsets, int resizeMode)2286 public void onWindowDragResizeStart(Rect initialBounds, boolean fullscreen, Rect systemInsets, 2287 Rect stableInsets, int resizeMode) { 2288 if (mWindow.isDestroyed()) { 2289 // If the owner's window is gone, we should not be able to come here anymore. 2290 releaseThreadedRenderer(); 2291 return; 2292 } 2293 if (mBackdropFrameRenderer != null) { 2294 return; 2295 } 2296 final ThreadedRenderer renderer = getThreadedRenderer(); 2297 if (renderer != null) { 2298 loadBackgroundDrawablesIfNeeded(); 2299 WindowInsets rootInsets = getRootWindowInsets(); 2300 mBackdropFrameRenderer = new BackdropFrameRenderer(this, renderer, 2301 initialBounds, mResizingBackgroundDrawable, mCaptionBackgroundDrawable, 2302 mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState), 2303 getCurrentColor(mNavigationColorViewState), fullscreen, 2304 rootInsets.getInsets(WindowInsets.Type.systemBars())); 2305 2306 // Get rid of the shadow while we are resizing. Shadow drawing takes considerable time. 2307 // If we want to get the shadow shown while resizing, we would need to elevate a new 2308 // element which owns the caption and has the elevation. 2309 updateElevation(); 2310 2311 updateColorViews(null /* insets */, false); 2312 } 2313 mResizeMode = resizeMode; 2314 getViewRootImpl().requestInvalidateRootRenderNode(); 2315 } 2316 2317 @Override onWindowDragResizeEnd()2318 public void onWindowDragResizeEnd() { 2319 releaseThreadedRenderer(); 2320 updateColorViews(null /* insets */, false); 2321 mResizeMode = RESIZE_MODE_INVALID; 2322 getViewRootImpl().requestInvalidateRootRenderNode(); 2323 } 2324 2325 @Override onContentDrawn(int offsetX, int offsetY, int sizeX, int sizeY)2326 public boolean onContentDrawn(int offsetX, int offsetY, int sizeX, int sizeY) { 2327 if (mBackdropFrameRenderer == null) { 2328 return false; 2329 } 2330 return mBackdropFrameRenderer.onContentDrawn(offsetX, offsetY, sizeX, sizeY); 2331 } 2332 2333 @Override onRequestDraw(boolean reportNextDraw)2334 public void onRequestDraw(boolean reportNextDraw) { 2335 if (mBackdropFrameRenderer != null) { 2336 mBackdropFrameRenderer.onRequestDraw(reportNextDraw); 2337 } else if (reportNextDraw) { 2338 // If render thread is gone, just report immediately. 2339 if (isAttachedToWindow()) { 2340 getViewRootImpl().reportDrawFinish(); 2341 } 2342 } 2343 } 2344 2345 @Override onPostDraw(RecordingCanvas canvas)2346 public void onPostDraw(RecordingCanvas canvas) { 2347 drawResizingShadowIfNeeded(canvas); 2348 drawLegacyNavigationBarBackground(canvas); 2349 } 2350 initResizingPaints()2351 private void initResizingPaints() { 2352 final int startColor = mContext.getResources().getColor( 2353 R.color.resize_shadow_start_color, null); 2354 final int endColor = mContext.getResources().getColor( 2355 R.color.resize_shadow_end_color, null); 2356 final int middleColor = (startColor + endColor) / 2; 2357 mHorizontalResizeShadowPaint.setShader(new LinearGradient( 2358 0, 0, 0, mResizeShadowSize, new int[] { startColor, middleColor, endColor }, 2359 new float[] { 0f, 0.3f, 1f }, Shader.TileMode.CLAMP)); 2360 mVerticalResizeShadowPaint.setShader(new LinearGradient( 2361 0, 0, mResizeShadowSize, 0, new int[] { startColor, middleColor, endColor }, 2362 new float[] { 0f, 0.3f, 1f }, Shader.TileMode.CLAMP)); 2363 } 2364 drawResizingShadowIfNeeded(RecordingCanvas canvas)2365 private void drawResizingShadowIfNeeded(RecordingCanvas canvas) { 2366 if (mResizeMode != RESIZE_MODE_DOCKED_DIVIDER || mWindow.mIsFloating 2367 || mWindow.isTranslucent() 2368 || mWindow.isShowingWallpaper()) { 2369 return; 2370 } 2371 canvas.save(); 2372 canvas.translate(0, getHeight() - mFrameOffsets.bottom); 2373 canvas.drawRect(0, 0, getWidth(), mResizeShadowSize, mHorizontalResizeShadowPaint); 2374 canvas.restore(); 2375 canvas.save(); 2376 canvas.translate(getWidth() - mFrameOffsets.right, 0); 2377 canvas.drawRect(0, 0, mResizeShadowSize, getHeight(), mVerticalResizeShadowPaint); 2378 canvas.restore(); 2379 } 2380 drawLegacyNavigationBarBackground(RecordingCanvas canvas)2381 private void drawLegacyNavigationBarBackground(RecordingCanvas canvas) { 2382 if (!mDrawLegacyNavigationBarBackground) { 2383 return; 2384 } 2385 View v = mNavigationColorViewState.view; 2386 if (v == null) { 2387 return; 2388 } 2389 canvas.drawRect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom(), 2390 mLegacyNavigationBarBackgroundPaint); 2391 } 2392 2393 /** Release the renderer thread which is usually done when the user stops resizing. */ releaseThreadedRenderer()2394 private void releaseThreadedRenderer() { 2395 if (mResizingBackgroundDrawable != null && mLastBackgroundDrawableCb != null) { 2396 mResizingBackgroundDrawable.setCallback(mLastBackgroundDrawableCb); 2397 mLastBackgroundDrawableCb = null; 2398 } 2399 2400 if (mBackdropFrameRenderer != null) { 2401 mBackdropFrameRenderer.releaseRenderer(); 2402 mBackdropFrameRenderer = null; 2403 // Bring the shadow back. 2404 updateElevation(); 2405 } 2406 } 2407 isResizing()2408 private boolean isResizing() { 2409 return mBackdropFrameRenderer != null; 2410 } 2411 2412 /** 2413 * The elevation gets set for the first time and the framework needs to be informed that 2414 * the surface layer gets created with the shadow size in mind. 2415 */ initializeElevation()2416 private void initializeElevation() { 2417 // TODO(skuhne): Call setMaxElevation here accordingly after b/22668382 got fixed. 2418 mAllowUpdateElevation = false; 2419 updateElevation(); 2420 } 2421 updateElevation()2422 private void updateElevation() { 2423 final int windowingMode = 2424 getResources().getConfiguration().windowConfiguration.getWindowingMode(); 2425 final boolean renderShadowsInCompositor = mWindow.mRenderShadowsInCompositor; 2426 // If rendering shadows in the compositor, don't set an elevation on the view 2427 if (renderShadowsInCompositor) { 2428 return; 2429 } 2430 float elevation = 0; 2431 final boolean wasAdjustedForStack = mElevationAdjustedForStack; 2432 // Do not use a shadow when we are in resizing mode (mBackdropFrameRenderer not null) 2433 // since the shadow is bound to the content size and not the target size. 2434 if ((windowingMode == WINDOWING_MODE_FREEFORM) && !isResizing()) { 2435 elevation = hasWindowFocus() ? 2436 DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP : DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP; 2437 // Add a maximum shadow height value to the top level view. 2438 // Note that pinned stack doesn't have focus 2439 // so maximum shadow height adjustment isn't needed. 2440 // TODO(skuhne): Remove this if clause once b/22668382 got fixed. 2441 if (!mAllowUpdateElevation) { 2442 elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP; 2443 } 2444 // Convert the DP elevation into physical pixels. 2445 elevation = dipToPx(elevation); 2446 mElevationAdjustedForStack = true; 2447 } else if (windowingMode == WINDOWING_MODE_PINNED) { 2448 elevation = dipToPx(PINNED_WINDOWING_MODE_ELEVATION_IN_DIP); 2449 mElevationAdjustedForStack = true; 2450 } else { 2451 mElevationAdjustedForStack = false; 2452 } 2453 2454 // Don't change the elevation if we didn't previously adjust it for the stack it was in 2455 // or it didn't change. 2456 if ((wasAdjustedForStack || mElevationAdjustedForStack) 2457 && getElevation() != elevation) { 2458 if (!isResizing()) { 2459 mWindow.setElevation(elevation); 2460 } else { 2461 // Just suppress the shadow when resizing, don't adjust surface insets because it'll 2462 // cause a flicker when drag resize for freeform window starts. #onContentDrawn() 2463 // will compensate the offset when passing to BackdropFrameRenderer. 2464 setElevation(elevation); 2465 } 2466 } 2467 } 2468 isShowingCaption()2469 boolean isShowingCaption() { 2470 return mDecorCaptionView != null && mDecorCaptionView.isCaptionShowing(); 2471 } 2472 getCaptionHeight()2473 int getCaptionHeight() { 2474 return isShowingCaption() ? mDecorCaptionView.getCaptionHeight() : 0; 2475 } 2476 2477 /** 2478 * @hide 2479 * @return the height of insets covering the top of window content area. 2480 */ getCaptionInsetsHeight()2481 public int getCaptionInsetsHeight() { 2482 if (!mWindow.isOverlayWithDecorCaptionEnabled()) return 0; 2483 return getCaptionHeight(); 2484 } 2485 2486 /** 2487 * Converts a DIP measure into physical pixels. 2488 * @param dip The dip value. 2489 * @return Returns the number of pixels. 2490 */ dipToPx(float dip)2491 private float dipToPx(float dip) { 2492 return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, 2493 getResources().getDisplayMetrics()); 2494 } 2495 2496 /** 2497 * Provide an override of the caption background drawable. 2498 */ setUserCaptionBackgroundDrawable(Drawable drawable)2499 void setUserCaptionBackgroundDrawable(Drawable drawable) { 2500 mUserCaptionBackgroundDrawable = drawable; 2501 if (mBackdropFrameRenderer != null) { 2502 mBackdropFrameRenderer.setUserCaptionBackgroundDrawable(drawable); 2503 } 2504 } 2505 getTitleSuffix(WindowManager.LayoutParams params)2506 private static String getTitleSuffix(WindowManager.LayoutParams params) { 2507 if (params == null) { 2508 return ""; 2509 } 2510 final String[] split = params.getTitle().toString().split("\\."); 2511 if (split.length > 0) { 2512 return split[split.length - 1]; 2513 } else { 2514 return ""; 2515 } 2516 } 2517 updateLogTag(WindowManager.LayoutParams params)2518 void updateLogTag(WindowManager.LayoutParams params) { 2519 mLogTag = TAG + "[" + getTitleSuffix(params) + "]"; 2520 } 2521 updateAvailableWidth()2522 private void updateAvailableWidth() { 2523 Resources res = getResources(); 2524 mAvailableWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2525 res.getConfiguration().screenWidthDp, res.getDisplayMetrics()); 2526 } 2527 2528 /** 2529 * @hide 2530 */ 2531 @Override requestKeyboardShortcuts(List<KeyboardShortcutGroup> list, int deviceId)2532 public void requestKeyboardShortcuts(List<KeyboardShortcutGroup> list, int deviceId) { 2533 final PanelFeatureState st = mWindow.getPanelState(FEATURE_OPTIONS_PANEL, false); 2534 final Menu menu = st != null ? st.menu : null; 2535 if (!mWindow.isDestroyed() && mWindow.getCallback() != null) { 2536 mWindow.getCallback().onProvideKeyboardShortcuts(list, menu, deviceId); 2537 } 2538 } 2539 2540 @Override dispatchPointerCaptureChanged(boolean hasCapture)2541 public void dispatchPointerCaptureChanged(boolean hasCapture) { 2542 super.dispatchPointerCaptureChanged(hasCapture); 2543 if (!mWindow.isDestroyed() && mWindow.getCallback() != null) { 2544 mWindow.getCallback().onPointerCaptureChanged(hasCapture); 2545 } 2546 } 2547 2548 @Override getAccessibilityViewId()2549 public int getAccessibilityViewId() { 2550 return AccessibilityNodeInfo.ROOT_ITEM_ID; 2551 } 2552 2553 @Override getWindowInsetsController()2554 public WindowInsetsController getWindowInsetsController() { 2555 if (isAttachedToWindow()) { 2556 return super.getWindowInsetsController(); 2557 } else { 2558 return mPendingInsetsController; 2559 } 2560 } 2561 2562 @Override toString()2563 public String toString() { 2564 return "DecorView@" + Integer.toHexString(this.hashCode()) + "[" 2565 + getTitleSuffix(mWindow.getAttributes()) + "]"; 2566 } 2567 2568 private static class ColorViewState { 2569 View view = null; 2570 int targetVisibility = View.INVISIBLE; 2571 boolean present = false; 2572 boolean visible; 2573 int color; 2574 2575 final ColorViewAttributes attributes; 2576 ColorViewState(ColorViewAttributes attributes)2577 ColorViewState(ColorViewAttributes attributes) { 2578 this.attributes = attributes; 2579 } 2580 } 2581 2582 public static class ColorViewAttributes { 2583 2584 final int id; 2585 final int systemUiHideFlag; 2586 final int translucentFlag; 2587 final int verticalGravity; 2588 final int horizontalGravity; 2589 final int seascapeGravity; 2590 final String transitionName; 2591 final int hideWindowFlag; 2592 final @InternalInsetsType int insetsType; 2593 ColorViewAttributes(int systemUiHideFlag, int translucentFlag, int verticalGravity, int horizontalGravity, int seascapeGravity, String transitionName, int id, int hideWindowFlag, @InternalInsetsType int insetsType)2594 private ColorViewAttributes(int systemUiHideFlag, int translucentFlag, int verticalGravity, 2595 int horizontalGravity, int seascapeGravity, String transitionName, int id, 2596 int hideWindowFlag, @InternalInsetsType int insetsType) { 2597 this.id = id; 2598 this.systemUiHideFlag = systemUiHideFlag; 2599 this.translucentFlag = translucentFlag; 2600 this.verticalGravity = verticalGravity; 2601 this.horizontalGravity = horizontalGravity; 2602 this.seascapeGravity = seascapeGravity; 2603 this.transitionName = transitionName; 2604 this.hideWindowFlag = hideWindowFlag; 2605 this.insetsType = insetsType; 2606 } 2607 2608 // TODO(b/118118435): remove after migration isPresent(int sysUiVis, int windowFlags, boolean force)2609 public boolean isPresent(int sysUiVis, int windowFlags, boolean force) { 2610 return (sysUiVis & systemUiHideFlag) == 0 2611 && (windowFlags & hideWindowFlag) == 0 2612 && ((windowFlags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 2613 || force); 2614 } 2615 isPresent(boolean requestedVisible, int windowFlags, boolean force)2616 public boolean isPresent(boolean requestedVisible, int windowFlags, boolean force) { 2617 return requestedVisible 2618 && ((windowFlags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 || force); 2619 } 2620 isVisible(boolean present, int color, int windowFlags, boolean force)2621 public boolean isVisible(boolean present, int color, int windowFlags, boolean force) { 2622 return present 2623 && (color & Color.BLACK) != 0 2624 && ((windowFlags & translucentFlag) == 0 || force); 2625 } 2626 2627 // TODO(b/118118435): remove after migration isVisible(int sysUiVis, int color, int windowFlags, boolean force)2628 public boolean isVisible(int sysUiVis, int color, int windowFlags, boolean force) { 2629 final boolean present = isPresent(sysUiVis, windowFlags, force); 2630 return isVisible(present, color, windowFlags, force); 2631 } 2632 isVisible(InsetsState state, int color, int windowFlags, boolean force)2633 public boolean isVisible(InsetsState state, int color, int windowFlags, boolean force) { 2634 final boolean present = isPresent(state.getSource(insetsType).isVisible(), windowFlags, 2635 force); 2636 return isVisible(present, color, windowFlags, force); 2637 } 2638 } 2639 2640 /** 2641 * Clears out internal references when the action mode is destroyed. 2642 */ 2643 private class ActionModeCallback2Wrapper extends ActionMode.Callback2 { 2644 private final ActionMode.Callback mWrapped; 2645 ActionModeCallback2Wrapper(ActionMode.Callback wrapped)2646 public ActionModeCallback2Wrapper(ActionMode.Callback wrapped) { 2647 mWrapped = wrapped; 2648 } 2649 onCreateActionMode(ActionMode mode, Menu menu)2650 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 2651 return mWrapped.onCreateActionMode(mode, menu); 2652 } 2653 onPrepareActionMode(ActionMode mode, Menu menu)2654 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 2655 requestFitSystemWindows(); 2656 return mWrapped.onPrepareActionMode(mode, menu); 2657 } 2658 onActionItemClicked(ActionMode mode, MenuItem item)2659 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 2660 return mWrapped.onActionItemClicked(mode, item); 2661 } 2662 onDestroyActionMode(ActionMode mode)2663 public void onDestroyActionMode(ActionMode mode) { 2664 mWrapped.onDestroyActionMode(mode); 2665 final boolean isMncApp = mContext.getApplicationInfo().targetSdkVersion 2666 >= M; 2667 final boolean isPrimary; 2668 final boolean isFloating; 2669 if (isMncApp) { 2670 isPrimary = mode == mPrimaryActionMode; 2671 isFloating = mode == mFloatingActionMode; 2672 if (!isPrimary && mode.getType() == ActionMode.TYPE_PRIMARY) { 2673 Log.e(mLogTag, "Destroying unexpected ActionMode instance of TYPE_PRIMARY; " 2674 + mode + " was not the current primary action mode! Expected " 2675 + mPrimaryActionMode); 2676 } 2677 if (!isFloating && mode.getType() == ActionMode.TYPE_FLOATING) { 2678 Log.e(mLogTag, "Destroying unexpected ActionMode instance of TYPE_FLOATING; " 2679 + mode + " was not the current floating action mode! Expected " 2680 + mFloatingActionMode); 2681 } 2682 } else { 2683 isPrimary = mode.getType() == ActionMode.TYPE_PRIMARY; 2684 isFloating = mode.getType() == ActionMode.TYPE_FLOATING; 2685 } 2686 if (isPrimary) { 2687 if (mPrimaryActionModePopup != null) { 2688 removeCallbacks(mShowPrimaryActionModePopup); 2689 } 2690 if (mPrimaryActionModeView != null) { 2691 endOnGoingFadeAnimation(); 2692 // Store action mode view reference, so we can access it safely when animation 2693 // ends. mPrimaryActionModePopup is set together with mPrimaryActionModeView, 2694 // so no need to store reference to it in separate variable. 2695 final ActionBarContextView lastActionModeView = mPrimaryActionModeView; 2696 mFadeAnim = ObjectAnimator.ofFloat(mPrimaryActionModeView, View.ALPHA, 2697 1f, 0f); 2698 mFadeAnim.addListener(new Animator.AnimatorListener() { 2699 2700 @Override 2701 public void onAnimationStart(Animator animation) { 2702 2703 } 2704 2705 @Override 2706 public void onAnimationEnd(Animator animation) { 2707 // If mPrimaryActionModeView has changed - it means that we've 2708 // cleared the content while preserving decor view. We don't 2709 // want to change the state of new instances accidentally here. 2710 if (lastActionModeView == mPrimaryActionModeView) { 2711 lastActionModeView.setVisibility(GONE); 2712 if (mPrimaryActionModePopup != null) { 2713 mPrimaryActionModePopup.dismiss(); 2714 } 2715 lastActionModeView.killMode(); 2716 mFadeAnim = null; 2717 requestApplyInsets(); 2718 } 2719 } 2720 2721 @Override 2722 public void onAnimationCancel(Animator animation) { 2723 2724 } 2725 2726 @Override 2727 public void onAnimationRepeat(Animator animation) { 2728 2729 } 2730 }); 2731 mFadeAnim.start(); 2732 } 2733 2734 mPrimaryActionMode = null; 2735 } else if (isFloating) { 2736 cleanupFloatingActionModeViews(); 2737 mFloatingActionMode = null; 2738 } 2739 if (mWindow.getCallback() != null && !mWindow.isDestroyed()) { 2740 try { 2741 mWindow.getCallback().onActionModeFinished(mode); 2742 } catch (AbstractMethodError ame) { 2743 // Older apps might not implement this callback method. 2744 } 2745 } 2746 requestFitSystemWindows(); 2747 } 2748 2749 @Override onGetContentRect(ActionMode mode, View view, Rect outRect)2750 public void onGetContentRect(ActionMode mode, View view, Rect outRect) { 2751 if (mWrapped instanceof ActionMode.Callback2) { 2752 ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect); 2753 } else { 2754 super.onGetContentRect(mode, view, outRect); 2755 } 2756 } 2757 } 2758 } 2759