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