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