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