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