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