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