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