OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.chrome.browser.compositor; |
| 6 |
| 7 import android.content.Context; |
| 8 import android.graphics.Canvas; |
| 9 import android.graphics.Paint; |
| 10 import android.graphics.Rect; |
| 11 import android.graphics.RectF; |
| 12 import android.os.Bundle; |
| 13 import android.os.Handler; |
| 14 import android.support.v4.view.ViewCompat; |
| 15 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; |
| 16 import android.support.v4.widget.ExploreByTouchHelper; |
| 17 import android.util.AttributeSet; |
| 18 import android.util.Pair; |
| 19 import android.view.MotionEvent; |
| 20 import android.view.SurfaceHolder; |
| 21 import android.view.SurfaceView; |
| 22 import android.view.View; |
| 23 import android.view.ViewGroup; |
| 24 import android.view.accessibility.AccessibilityEvent; |
| 25 import android.view.inputmethod.InputMethodManager; |
| 26 import android.widget.FrameLayout; |
| 27 |
| 28 import com.google.android.apps.chrome.R; |
| 29 |
| 30 import org.chromium.base.SysUtils; |
| 31 import org.chromium.base.TraceEvent; |
| 32 import org.chromium.base.annotations.SuppressFBWarnings; |
| 33 import org.chromium.chrome.browser.EmptyTabObserver; |
| 34 import org.chromium.chrome.browser.Tab; |
| 35 import org.chromium.chrome.browser.TabObserver; |
| 36 import org.chromium.chrome.browser.compositor.Invalidator.Client; |
| 37 import org.chromium.chrome.browser.compositor.layouts.LayoutManager; |
| 38 import org.chromium.chrome.browser.compositor.layouts.LayoutManagerHost; |
| 39 import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost; |
| 40 import org.chromium.chrome.browser.compositor.layouts.components.VirtualView; |
| 41 import org.chromium.chrome.browser.compositor.layouts.content.ContentOffsetProvi
der; |
| 42 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager; |
| 43 import org.chromium.chrome.browser.contextualsearch.ContextualSearchManagementDe
legate; |
| 44 import org.chromium.chrome.browser.device.DeviceClassManager; |
| 45 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager; |
| 46 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager.Fullscreen
Listener; |
| 47 import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver; |
| 48 import org.chromium.chrome.browser.tabmodel.TabCreatorManager; |
| 49 import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
| 50 import org.chromium.chrome.browser.widget.ControlContainer; |
| 51 import org.chromium.content.browser.ContentReadbackHandler; |
| 52 import org.chromium.content.browser.ContentViewCore; |
| 53 import org.chromium.content.browser.SPenSupport; |
| 54 import org.chromium.ui.UiUtils; |
| 55 import org.chromium.ui.base.DeviceFormFactor; |
| 56 import org.chromium.ui.base.WindowAndroid; |
| 57 import org.chromium.ui.resources.ResourceManager; |
| 58 import org.chromium.ui.resources.dynamics.DynamicResourceLoader; |
| 59 import org.chromium.ui.resources.dynamics.ViewResourceAdapter; |
| 60 |
| 61 import java.util.ArrayList; |
| 62 import java.util.List; |
| 63 |
| 64 /** |
| 65 * This class holds a {@link CompositorView}. This level of indirection is neede
d to benefit from |
| 66 * the {@link android.view.ViewGroup#onInterceptTouchEvent(android.view.MotionEv
ent)} capability on |
| 67 * available on {@link android.view.ViewGroup}s. |
| 68 * This class also holds the {@link LayoutManager} responsible to describe the i
tems to be |
| 69 * drawn by the UI compositor on the native side. |
| 70 */ |
| 71 public class CompositorViewHolder extends FrameLayout |
| 72 implements LayoutManagerHost, LayoutRenderHost, Invalidator.Host, Fullsc
reenListener { |
| 73 private static List<View> sCachedViewList = new ArrayList<View>(); |
| 74 private static List<ContentViewCore> sCachedCVCList = new ArrayList<ContentV
iewCore>(); |
| 75 |
| 76 private boolean mIsKeyboardShowing = false; |
| 77 |
| 78 private final Invalidator mInvalidator = new Invalidator(); |
| 79 private LayoutManager mLayoutManager; |
| 80 private LayerTitleCache mLayerTitleCache; |
| 81 private CompositorView mCompositorView; |
| 82 |
| 83 private boolean mContentOverlayVisiblity = true; |
| 84 |
| 85 private int mPendingSwapBuffersCount; |
| 86 |
| 87 private final ArrayList<Invalidator.Client> mPendingInvalidations = |
| 88 new ArrayList<Invalidator.Client>(); |
| 89 private boolean mSkipInvalidation = false; |
| 90 |
| 91 private boolean mSkipNextToolbarTextureUpdate = false; |
| 92 |
| 93 /** |
| 94 * A task to be performed after a resize event. |
| 95 */ |
| 96 private Runnable mPostHideKeyboardTask; |
| 97 |
| 98 private TabModelSelector mTabModelSelector; |
| 99 private ChromeFullscreenManager mFullscreenManager; |
| 100 private View mAccessibilityView; |
| 101 private CompositorAccessibilityProvider mNodeProvider; |
| 102 private boolean mFullscreenTouchEvent = false; |
| 103 private float mLastContentOffset = 0; |
| 104 private float mLastVisibleContentOffset = 0; |
| 105 |
| 106 /** The toolbar control container. **/ |
| 107 private ControlContainer mControlContainer; |
| 108 |
| 109 /** The currently visible Tab. */ |
| 110 private Tab mTabVisible; |
| 111 |
| 112 /** The currently attached View. */ |
| 113 private View mView; |
| 114 |
| 115 private TabObserver mTabObserver; |
| 116 private boolean mEnableCompositorTabStrip; |
| 117 |
| 118 // Cache objects that should not be created frequently. |
| 119 private final Rect mCacheViewport = new Rect(); |
| 120 private final Rect mCacheVisibleViewport = new Rect(); |
| 121 |
| 122 // If we've drawn at least one frame. |
| 123 private boolean mHasDrawnOnce = false; |
| 124 |
| 125 /** |
| 126 * This view is created on demand to display debugging information. |
| 127 */ |
| 128 private static class DebugOverlay extends View { |
| 129 private final List<Pair<Rect, Integer>> mRectangles = new ArrayList<Pair
<Rect, Integer>>(); |
| 130 private final Paint mPaint = new Paint(); |
| 131 private boolean mFirstPush = true; |
| 132 |
| 133 /** |
| 134 * @param context The current Android's context. |
| 135 */ |
| 136 public DebugOverlay(Context context) { |
| 137 super(context); |
| 138 } |
| 139 |
| 140 /** |
| 141 * Pushes a rectangle to be drawn on the screen on top of everything. |
| 142 * |
| 143 * @param rect The rectangle to be drawn on screen |
| 144 * @param color The color of the rectangle |
| 145 */ |
| 146 public void pushRect(Rect rect, int color) { |
| 147 if (mFirstPush) { |
| 148 mRectangles.clear(); |
| 149 mFirstPush = false; |
| 150 } |
| 151 mRectangles.add(new Pair<Rect, Integer>(rect, color)); |
| 152 invalidate(); |
| 153 } |
| 154 |
| 155 @SuppressFBWarnings("NP_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD") |
| 156 @Override |
| 157 protected void onDraw(Canvas canvas) { |
| 158 for (int i = 0; i < mRectangles.size(); i++) { |
| 159 mPaint.setColor(mRectangles.get(i).second); |
| 160 canvas.drawRect(mRectangles.get(i).first, mPaint); |
| 161 } |
| 162 mFirstPush = true; |
| 163 } |
| 164 } |
| 165 |
| 166 private DebugOverlay mDebugOverlay; |
| 167 |
| 168 private View mUrlBar; |
| 169 |
| 170 /** |
| 171 * Creates a {@link CompositorView}. |
| 172 * @param c The Context to create this {@link CompositorView} in. |
| 173 */ |
| 174 public CompositorViewHolder(Context c) { |
| 175 super(c); |
| 176 |
| 177 internalInit(); |
| 178 } |
| 179 |
| 180 /** |
| 181 * Creates a {@link CompositorView}. |
| 182 * @param c The Context to create this {@link CompositorView} in. |
| 183 * @param attrs The AttributeSet used to create this {@link CompositorView}. |
| 184 */ |
| 185 public CompositorViewHolder(Context c, AttributeSet attrs) { |
| 186 super(c, attrs); |
| 187 |
| 188 internalInit(); |
| 189 } |
| 190 |
| 191 private void internalInit() { |
| 192 mTabObserver = new EmptyTabObserver() { |
| 193 @Override |
| 194 public void onContentChanged(Tab tab) { |
| 195 CompositorViewHolder.this.onContentChanged(); |
| 196 } |
| 197 |
| 198 @Override |
| 199 public void onOverlayContentViewCoreAdded(Tab tab, ContentViewCore c
ontent) { |
| 200 initializeContentViewCore(content); |
| 201 setSizeOfUnattachedView(content.getContainerView()); |
| 202 } |
| 203 }; |
| 204 |
| 205 mEnableCompositorTabStrip = DeviceFormFactor.isTablet(getContext()); |
| 206 |
| 207 addOnLayoutChangeListener(new OnLayoutChangeListener() { |
| 208 @Override |
| 209 public void onLayoutChange(View v, int left, int top, int right, int
bottom, |
| 210 int oldLeft, int oldTop, int oldRight, int oldBottom) { |
| 211 propagateViewportToLayouts(right - left, bottom - top); |
| 212 |
| 213 // If there's an event that needs to occur after the keyboard is
hidden, post |
| 214 // it as a delayed event. Otherwise this happens in the midst o
f the |
| 215 // ContentView's relayout, which causes the ContentView to relay
out on top of the |
| 216 // stack view. The 30ms is arbitrary, hoping to let the view ge
t one repaint |
| 217 // in so the full page is shown. |
| 218 if (mPostHideKeyboardTask != null) { |
| 219 new Handler().postDelayed(mPostHideKeyboardTask, 30); |
| 220 mPostHideKeyboardTask = null; |
| 221 } |
| 222 } |
| 223 }); |
| 224 |
| 225 mCompositorView = new CompositorView(getContext(), this); |
| 226 addView(mCompositorView, |
| 227 new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutPa
rams.WRAP_CONTENT)); |
| 228 } |
| 229 |
| 230 /** |
| 231 * @param layoutManager The {@link LayoutManager} instance that will be driv
ing what |
| 232 * shows in this {@link CompositorViewHolder}. |
| 233 */ |
| 234 public void setLayoutManager(LayoutManager layoutManager) { |
| 235 mLayoutManager = layoutManager; |
| 236 propagateViewportToLayouts(getWidth(), getHeight()); |
| 237 } |
| 238 |
| 239 /** |
| 240 * @param view The root view of the hierarchy. |
| 241 */ |
| 242 public void setRootView(View view) { |
| 243 mCompositorView.setRootView(view); |
| 244 } |
| 245 |
| 246 /** |
| 247 * @param controlContainer The ControlContainer. |
| 248 */ |
| 249 public void setControlContainer(ControlContainer controlContainer) { |
| 250 DynamicResourceLoader loader = mCompositorView.getResourceManager() != n
ull |
| 251 ? mCompositorView.getResourceManager().getDynamicResourceLoader(
) |
| 252 : null; |
| 253 if (loader != null && mControlContainer != null) { |
| 254 loader.unregisterResource(R.id.control_container); |
| 255 loader.unregisterResource(R.id.progress); |
| 256 } |
| 257 mControlContainer = controlContainer; |
| 258 if (loader != null && mControlContainer != null) { |
| 259 loader.registerResource( |
| 260 R.id.control_container, mControlContainer.getToolbarResource
Adapter()); |
| 261 |
| 262 ViewResourceAdapter progressAdapter = mControlContainer.getProgressR
esourceAdapter(); |
| 263 if (progressAdapter != null) { |
| 264 loader.registerResource(R.id.progress, progressAdapter); |
| 265 } |
| 266 } |
| 267 } |
| 268 |
| 269 /** |
| 270 * @return The CompositorView. |
| 271 */ |
| 272 public SurfaceHolder.Callback2 getSurfaceHolderCallback2() { |
| 273 return mCompositorView; |
| 274 } |
| 275 |
| 276 /** |
| 277 * Reset command line flags. This gets called after the native library finis
hes |
| 278 * loading. |
| 279 */ |
| 280 public void resetFlags() { |
| 281 mCompositorView.resetFlags(); |
| 282 } |
| 283 |
| 284 /** |
| 285 * Should be called for cleanup when the CompositorView instance is no longe
r used. |
| 286 */ |
| 287 public void shutDown() { |
| 288 setTab(null); |
| 289 if (mLayerTitleCache != null) mLayerTitleCache.shutDown(); |
| 290 mCompositorView.shutDown(); |
| 291 } |
| 292 |
| 293 /** |
| 294 * This is called when the native library are ready. |
| 295 */ |
| 296 public void onNativeLibraryReady( |
| 297 WindowAndroid windowAndroid, TabContentManager tabContentManager) { |
| 298 assert mLayerTitleCache == null : "Should be called once"; |
| 299 |
| 300 if (DeviceClassManager.enableLayerDecorationCache()) { |
| 301 mLayerTitleCache = new LayerTitleCache(getContext()); |
| 302 } |
| 303 |
| 304 mCompositorView.initNativeCompositor( |
| 305 SysUtils.isLowEndDevice(), windowAndroid, mLayerTitleCache, tabC
ontentManager); |
| 306 |
| 307 if (mLayerTitleCache != null) { |
| 308 mLayerTitleCache.setResourceManager(getResourceManager()); |
| 309 } |
| 310 |
| 311 if (mControlContainer != null) { |
| 312 mCompositorView.getResourceManager().getDynamicResourceLoader().regi
sterResource( |
| 313 R.id.control_container, mControlContainer.getToolbarResource
Adapter()); |
| 314 |
| 315 ViewResourceAdapter progressAdapter = mControlContainer.getProgressR
esourceAdapter(); |
| 316 if (progressAdapter != null) { |
| 317 mCompositorView.getResourceManager().getDynamicResourceLoader().
registerResource( |
| 318 R.id.progress, progressAdapter); |
| 319 } |
| 320 } |
| 321 } |
| 322 |
| 323 @Override |
| 324 public ResourceManager getResourceManager() { |
| 325 return mCompositorView.getResourceManager(); |
| 326 } |
| 327 |
| 328 public ContentOffsetProvider getContentOffsetProvider() { |
| 329 return mCompositorView; |
| 330 } |
| 331 |
| 332 /** |
| 333 * @return The content readback handler. |
| 334 */ |
| 335 public ContentReadbackHandler getContentReadbackHandler() { |
| 336 if (mCompositorView == null) return null; |
| 337 return mCompositorView.getContentReadbackHandler(); |
| 338 } |
| 339 |
| 340 /** |
| 341 * @return The {@link DynamicResourceLoader} for registering resources. |
| 342 */ |
| 343 public DynamicResourceLoader getDynamicResourceLoader() { |
| 344 return mCompositorView.getResourceManager().getDynamicResourceLoader(); |
| 345 } |
| 346 |
| 347 /** |
| 348 * @return The {@link Invalidator} instance that is driven by this {@link Co
mpositorViewHolder}. |
| 349 */ |
| 350 public Invalidator getInvalidator() { |
| 351 return mInvalidator; |
| 352 } |
| 353 |
| 354 @Override |
| 355 public boolean onInterceptTouchEvent(MotionEvent e) { |
| 356 super.onInterceptTouchEvent(e); |
| 357 |
| 358 if (mLayoutManager == null) return false; |
| 359 |
| 360 mFullscreenTouchEvent = false; |
| 361 if (mFullscreenManager != null && mFullscreenManager.onInterceptMotionEv
ent(e) |
| 362 && !mEnableCompositorTabStrip) { |
| 363 // Don't eat the event if the new tab strip is enabled. |
| 364 mFullscreenTouchEvent = true; |
| 365 return true; |
| 366 } |
| 367 |
| 368 setContentViewMotionEventOffsets(e, false); |
| 369 return mLayoutManager.onInterceptTouchEvent(e, mIsKeyboardShowing); |
| 370 } |
| 371 |
| 372 @Override |
| 373 public boolean onTouchEvent(MotionEvent e) { |
| 374 if (mFullscreenManager != null) mFullscreenManager.onMotionEvent(e); |
| 375 if (mFullscreenTouchEvent) return true; |
| 376 boolean consumed = mLayoutManager != null && mLayoutManager.onTouchEvent
(e); |
| 377 setContentViewMotionEventOffsets(e, true); |
| 378 return consumed; |
| 379 } |
| 380 |
| 381 @Override |
| 382 public boolean onInterceptHoverEvent(MotionEvent e) { |
| 383 setContentViewMotionEventOffsets(e, true); |
| 384 return super.onInterceptHoverEvent(e); |
| 385 } |
| 386 |
| 387 @Override |
| 388 public boolean dispatchHoverEvent(MotionEvent e) { |
| 389 if (mNodeProvider != null) { |
| 390 if (mNodeProvider.dispatchHoverEvent(e)) { |
| 391 return true; |
| 392 } |
| 393 } |
| 394 return super.dispatchHoverEvent(e); |
| 395 } |
| 396 |
| 397 /** |
| 398 * @return The {@link LayoutManager} associated with this view. |
| 399 */ |
| 400 public LayoutManager getLayoutManager() { |
| 401 return mLayoutManager; |
| 402 } |
| 403 |
| 404 /** |
| 405 * @return The SurfaceView used by the Compositor. |
| 406 */ |
| 407 public SurfaceView getSurfaceView() { |
| 408 return mCompositorView; |
| 409 } |
| 410 |
| 411 @Override |
| 412 protected void onSizeChanged(int w, int h, int oldw, int oldh) { |
| 413 super.onSizeChanged(w, h, oldw, oldh); |
| 414 if (mLayoutManager == null) return; |
| 415 |
| 416 sCachedViewList.clear(); |
| 417 mLayoutManager.getActiveLayout().getAllViews(sCachedViewList); |
| 418 |
| 419 boolean resized = false; |
| 420 for (int i = 0; i < sCachedViewList.size(); i++) { |
| 421 resized |= setSizeOfUnattachedView(sCachedViewList.get(i)); |
| 422 } |
| 423 sCachedViewList.clear(); |
| 424 |
| 425 if (resized) requestRender(); |
| 426 } |
| 427 |
| 428 @Override |
| 429 public void onPhysicalBackingSizeChanged(int width, int height) { |
| 430 if (mLayoutManager == null) return; |
| 431 |
| 432 sCachedCVCList.clear(); |
| 433 mLayoutManager.getActiveLayout().getAllContentViewCores(sCachedCVCList); |
| 434 |
| 435 for (int i = 0; i < sCachedCVCList.size(); i++) { |
| 436 sCachedCVCList.get(i).onPhysicalBackingSizeChanged(width, height); |
| 437 } |
| 438 sCachedCVCList.clear(); |
| 439 } |
| 440 |
| 441 @Override |
| 442 public void onOverdrawBottomHeightChanged(int overdrawHeight) { |
| 443 if (mLayoutManager == null) return; |
| 444 |
| 445 sCachedCVCList.clear(); |
| 446 mLayoutManager.getActiveLayout().getAllContentViewCores(sCachedCVCList); |
| 447 |
| 448 for (int i = 0; i < sCachedCVCList.size(); i++) { |
| 449 sCachedCVCList.get(i).onOverdrawBottomHeightChanged(overdrawHeight); |
| 450 } |
| 451 sCachedCVCList.clear(); |
| 452 |
| 453 mSkipNextToolbarTextureUpdate = true; |
| 454 requestRender(); |
| 455 } |
| 456 |
| 457 @Override |
| 458 public int getCurrentOverdrawBottomHeight() { |
| 459 if (mTabVisible != null) { |
| 460 float overdrawBottomHeight = mTabVisible.getFullscreenOverdrawBottom
HeightPix(); |
| 461 if (!Float.isNaN(overdrawBottomHeight)) { |
| 462 return (int) overdrawBottomHeight; |
| 463 } |
| 464 } |
| 465 return mCompositorView.getOverdrawBottomHeight(); |
| 466 } |
| 467 |
| 468 /** |
| 469 * Called whenever the host activity is started. |
| 470 */ |
| 471 public void onStart() { |
| 472 if (mFullscreenManager != null) { |
| 473 mLastContentOffset = mFullscreenManager.getContentOffset(); |
| 474 mLastVisibleContentOffset = mFullscreenManager.getVisibleContentOffs
et(); |
| 475 mFullscreenManager.addListener(this); |
| 476 } |
| 477 requestRender(); |
| 478 } |
| 479 |
| 480 /** |
| 481 * Called whenever the host activity is stopped. |
| 482 */ |
| 483 public void onStop() { |
| 484 if (mFullscreenManager != null) mFullscreenManager.removeListener(this); |
| 485 } |
| 486 |
| 487 @Override |
| 488 public void onContentOffsetChanged(float offset) { |
| 489 mLastContentOffset = offset; |
| 490 propagateViewportToLayouts(getWidth(), getHeight()); |
| 491 } |
| 492 |
| 493 @Override |
| 494 public void onVisibleContentOffsetChanged(float offset) { |
| 495 mLastVisibleContentOffset = offset; |
| 496 propagateViewportToLayouts(getWidth(), getHeight()); |
| 497 requestRender(); |
| 498 } |
| 499 |
| 500 @Override |
| 501 public void onToggleOverlayVideoMode(boolean enabled) { |
| 502 if (mCompositorView != null) { |
| 503 mCompositorView.setOverlayVideoMode(enabled); |
| 504 } |
| 505 } |
| 506 |
| 507 private void setContentViewMotionEventOffsets(MotionEvent e, boolean canClea
r) { |
| 508 // TODO(dtrainor): Factor this out to LayoutDriver. |
| 509 if (e == null || mTabVisible == null) return; |
| 510 |
| 511 ContentViewCore contentViewCore = mTabVisible.getContentViewCore(); |
| 512 if (contentViewCore == null) return; |
| 513 |
| 514 int actionMasked = e.getActionMasked(); |
| 515 |
| 516 if (SPenSupport.isSPenSupported(getContext())) { |
| 517 actionMasked = SPenSupport.convertSPenEventAction(actionMasked); |
| 518 } |
| 519 |
| 520 if (actionMasked == MotionEvent.ACTION_DOWN |
| 521 || actionMasked == MotionEvent.ACTION_HOVER_ENTER) { |
| 522 if (mLayoutManager != null) mLayoutManager.getViewportPixel(mCacheVi
ewport); |
| 523 contentViewCore.setCurrentMotionEventOffsets(-mCacheViewport.left, -
mCacheViewport.top); |
| 524 } else if (canClear && (actionMasked == MotionEvent.ACTION_UP |
| 525 || actionMasked == MotionEvent.ACTION_CAN
CEL |
| 526 || actionMasked == MotionEvent.ACTION_HOV
ER_EXIT)) { |
| 527 contentViewCore.setCurrentMotionEventOffsets(0.f, 0.f); |
| 528 } |
| 529 } |
| 530 |
| 531 private void propagateViewportToLayouts(int contentWidth, int contentHeight)
{ |
| 532 int heightMinusTopControls = contentHeight - getTopControlsHeightPixels(
); |
| 533 mCacheViewport.set(0, (int) mLastContentOffset, contentWidth, contentHei
ght); |
| 534 mCacheVisibleViewport.set(0, (int) mLastVisibleContentOffset, contentWid
th, contentHeight); |
| 535 // TODO(changwan): check if this can be merged with setContentMotionEven
tOffsets. |
| 536 if (mTabVisible != null && mTabVisible.getContentViewCore() != null) { |
| 537 mTabVisible.getContentViewCore().setSmartClipOffsets( |
| 538 -mCacheViewport.left, -mCacheViewport.top); |
| 539 } |
| 540 if (mLayoutManager != null) { |
| 541 mLayoutManager.pushNewViewport( |
| 542 mCacheViewport, mCacheVisibleViewport, heightMinusTopControl
s); |
| 543 } |
| 544 } |
| 545 |
| 546 /** |
| 547 * To be called once a frame before commit. |
| 548 */ |
| 549 @Override |
| 550 public void onCompositorLayout() { |
| 551 TraceEvent.begin("CompositorViewHolder:layout"); |
| 552 if (mLayoutManager != null) { |
| 553 mLayoutManager.onUpdate(); |
| 554 mCompositorView.finalizeLayers(mLayoutManager, mSkipNextToolbarTextu
reUpdate); |
| 555 // TODO(changwan): Check if this hack can be removed. |
| 556 // This is a hack to draw one more frame if the screen just rotated
for Nexus 10 + L. |
| 557 // See http://crbug/440469 for more. |
| 558 if (mSkipNextToolbarTextureUpdate) { |
| 559 requestRender(); |
| 560 } |
| 561 } |
| 562 |
| 563 TraceEvent.end("CompositorViewHolder:layout"); |
| 564 mSkipNextToolbarTextureUpdate = false; |
| 565 } |
| 566 |
| 567 @Override |
| 568 public void requestRender() { |
| 569 mCompositorView.requestRender(); |
| 570 } |
| 571 |
| 572 @Override |
| 573 public void onSurfaceCreated() { |
| 574 mPendingSwapBuffersCount = 0; |
| 575 flushInvalidation(); |
| 576 } |
| 577 |
| 578 @Override |
| 579 public void onSwapBuffersCompleted(int pendingSwapBuffersCount) { |
| 580 TraceEvent.instant("onSwapBuffersCompleted"); |
| 581 |
| 582 // Wait until the second frame to turn off the placeholder background on |
| 583 // tablets so the tab strip has time to start drawing. |
| 584 final ViewGroup controlContainer = (ViewGroup) mControlContainer; |
| 585 if (controlContainer != null && controlContainer.getBackground() != null
&& mHasDrawnOnce) { |
| 586 post(new Runnable() { |
| 587 @Override |
| 588 public void run() { |
| 589 controlContainer.setBackgroundResource(0); |
| 590 } |
| 591 }); |
| 592 } |
| 593 |
| 594 mHasDrawnOnce = true; |
| 595 |
| 596 mPendingSwapBuffersCount = pendingSwapBuffersCount; |
| 597 |
| 598 if (!mSkipInvalidation || pendingSwapBuffersCount == 0) flushInvalidatio
n(); |
| 599 mSkipInvalidation = !mSkipInvalidation; |
| 600 } |
| 601 |
| 602 @Override |
| 603 public void setContentOverlayVisibility(boolean show) { |
| 604 if (show != mContentOverlayVisiblity) { |
| 605 mContentOverlayVisiblity = show; |
| 606 updateContentOverlayVisibility(mContentOverlayVisiblity); |
| 607 } |
| 608 } |
| 609 |
| 610 @Override |
| 611 public LayoutRenderHost getLayoutRenderHost() { |
| 612 return this; |
| 613 } |
| 614 |
| 615 @Override |
| 616 public int getLayoutTabsDrawnCount() { |
| 617 return mCompositorView.getLastLayerCount(); |
| 618 } |
| 619 |
| 620 @Override |
| 621 public void pushDebugRect(Rect rect, int color) { |
| 622 if (mDebugOverlay == null) { |
| 623 mDebugOverlay = new DebugOverlay(getContext()); |
| 624 addView(mDebugOverlay); |
| 625 } |
| 626 mDebugOverlay.pushRect(rect, color); |
| 627 } |
| 628 |
| 629 @Override |
| 630 public void loadPersitentTextureDataIfNeeded() {} |
| 631 |
| 632 @Override |
| 633 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| 634 super.onMeasure(widthMeasureSpec, heightMeasureSpec); |
| 635 mIsKeyboardShowing = UiUtils.isKeyboardShowing(getContext(), this); |
| 636 } |
| 637 |
| 638 @Override |
| 639 protected void onLayout(boolean changed, int l, int t, int r, int b) { |
| 640 if (changed) { |
| 641 propagateViewportToLayouts(r - l, b - t); |
| 642 } |
| 643 super.onLayout(changed, l, t, r, b); |
| 644 |
| 645 invalidateAccessibilityProvider(); |
| 646 } |
| 647 |
| 648 @Override |
| 649 public void clearChildFocus(View child) { |
| 650 // Override this method so that the ViewRoot doesn't go looking for a ne
w |
| 651 // view to take focus. It will find the URL Bar, focus it, then refocus
this |
| 652 // later, causing a keyboard flicker. |
| 653 } |
| 654 |
| 655 @Override |
| 656 public ChromeFullscreenManager getFullscreenManager() { |
| 657 return mFullscreenManager; |
| 658 } |
| 659 |
| 660 /** |
| 661 * Sets a fullscreen handler. |
| 662 * @param fullscreen A fullscreen handler. |
| 663 */ |
| 664 public void setFullscreenHandler(ChromeFullscreenManager fullscreen) { |
| 665 mFullscreenManager = fullscreen; |
| 666 if (mFullscreenManager != null) { |
| 667 mLastContentOffset = mFullscreenManager.getContentOffset(); |
| 668 mLastVisibleContentOffset = mFullscreenManager.getVisibleContentOffs
et(); |
| 669 mFullscreenManager.addListener(this); |
| 670 } |
| 671 propagateViewportToLayouts(getWidth(), getHeight()); |
| 672 } |
| 673 |
| 674 /** |
| 675 * Note that the returned rect is reused for other calls. |
| 676 */ |
| 677 @Override |
| 678 public Rect getVisibleViewport(Rect rect) { |
| 679 if (rect == null) rect = new Rect(); |
| 680 rect.set(0, (int) mLastVisibleContentOffset, getWidth(), getHeight()); |
| 681 return rect; |
| 682 } |
| 683 |
| 684 @Override |
| 685 public boolean areTopControlsPermanentlyHidden() { |
| 686 return mFullscreenManager != null && mFullscreenManager.areTopControlsPe
rmanentlyHidden(); |
| 687 } |
| 688 |
| 689 @Override |
| 690 public int getTopControlsHeightPixels() { |
| 691 return mFullscreenManager != null ? mFullscreenManager.getTopControlsHei
ght() : 0; |
| 692 } |
| 693 |
| 694 /** |
| 695 * Sets the URL bar. This is needed so that the ContentViewHolder can find o
ut |
| 696 * whether it can claim focus. |
| 697 */ |
| 698 public void setUrlBar(View urlBar) { |
| 699 mUrlBar = urlBar; |
| 700 } |
| 701 |
| 702 @Override |
| 703 protected void onAttachedToWindow() { |
| 704 mInvalidator.set(this); |
| 705 super.onAttachedToWindow(); |
| 706 } |
| 707 |
| 708 @Override |
| 709 protected void onDetachedFromWindow() { |
| 710 if (mLayoutManager != null) mLayoutManager.destroy(); |
| 711 flushInvalidation(); |
| 712 mInvalidator.set(null); |
| 713 super.onDetachedFromWindow(); |
| 714 |
| 715 // Removes the accessibility node provider from this view. |
| 716 if (mNodeProvider != null) { |
| 717 mAccessibilityView.setAccessibilityDelegate(null); |
| 718 mNodeProvider = null; |
| 719 removeView(mAccessibilityView); |
| 720 mAccessibilityView = null; |
| 721 } |
| 722 } |
| 723 |
| 724 /** |
| 725 * @return True if the currently active content view is shown in the normal
interactive mode. |
| 726 */ |
| 727 public boolean isTabInteractive() { |
| 728 return mLayoutManager != null && mLayoutManager.getActiveLayout() != nul
l |
| 729 && mLayoutManager.getActiveLayout().isTabInteractive() && mConte
ntOverlayVisiblity |
| 730 && mView != null; |
| 731 } |
| 732 |
| 733 /** |
| 734 * Hides the the keyboard if it was opened for the ContentView. |
| 735 * @param postHideTask A task to run after the keyboard is done hiding and t
he view's |
| 736 * layout has been updated. If the keyboard was not shown, the task
will run |
| 737 * immediately. |
| 738 */ |
| 739 public void hideKeyboard(Runnable postHideTask) { |
| 740 // When this is called we actually want to hide the keyboard whatever ow
ns it. |
| 741 // This includes hiding the keyboard, and dropping focus from the URL ba
r. |
| 742 // See http://crbug/236424 |
| 743 // TODO(aberent) Find a better place to put this, possibly as part of a
wider |
| 744 // redesign of focus control. |
| 745 if (mUrlBar != null) mUrlBar.clearFocus(); |
| 746 boolean wasVisible = false; |
| 747 if (hasFocus()) { |
| 748 wasVisible = UiUtils.hideKeyboard(this); |
| 749 } |
| 750 if (wasVisible) { |
| 751 mPostHideKeyboardTask = postHideTask; |
| 752 } else { |
| 753 postHideTask.run(); |
| 754 } |
| 755 } |
| 756 |
| 757 /** |
| 758 * Sets the appropriate objects this class should represent. |
| 759 * @param tabModelSelector The {@link TabModelSelector} this View sho
uld hold and |
| 760 * represent. |
| 761 * @param tabCreatorManager The {@link TabCreatorManager} for this vie
w. |
| 762 * @param tabContentManager The {@link TabContentManager} for the tabs
. |
| 763 * @param androidContentContainer The {@link ViewGroup} the {@link LayoutMan
ager} should bind |
| 764 * Android content to. |
| 765 * @param contextualSearchManager A {@link ContextualSearchManagementDelegat
e} instance. |
| 766 */ |
| 767 public void onFinishNativeInitialization(TabModelSelector tabModelSelector, |
| 768 TabCreatorManager tabCreatorManager, TabContentManager tabContentMan
ager, |
| 769 ViewGroup androidContentContainer, |
| 770 ContextualSearchManagementDelegate contextualSearchManager) { |
| 771 assert mLayoutManager != null; |
| 772 mLayoutManager.init(tabModelSelector, tabCreatorManager, tabContentManag
er, |
| 773 androidContentContainer, contextualSearchManager, |
| 774 mCompositorView.getResourceManager().getDynamicResourceLoader())
; |
| 775 mTabModelSelector = tabModelSelector; |
| 776 tabModelSelector.addObserver(new EmptyTabModelSelectorObserver() { |
| 777 @Override |
| 778 public void onChange() { |
| 779 onContentChanged(); |
| 780 } |
| 781 |
| 782 @Override |
| 783 public void onNewTabCreated(Tab tab) { |
| 784 initializeTab(tab); |
| 785 } |
| 786 }); |
| 787 |
| 788 onContentChanged(); |
| 789 } |
| 790 |
| 791 private void updateContentOverlayVisibility(boolean show) { |
| 792 if (mView == null) return; |
| 793 |
| 794 sCachedCVCList.clear(); |
| 795 if (mLayoutManager != null) { |
| 796 mLayoutManager.getActiveLayout().getAllContentViewCores(sCachedCVCLi
st); |
| 797 } |
| 798 if (show) { |
| 799 if (mView.getParent() != this) { |
| 800 // Make sure the view isn't a child of something else before we
attempt to add it. |
| 801 if (mView.getParent() != null && mView.getParent() instanceof Vi
ewGroup) { |
| 802 ((ViewGroup) mView.getParent()).removeView(mView); |
| 803 } |
| 804 |
| 805 for (int i = 0; i < sCachedCVCList.size(); i++) { |
| 806 ContentViewCore content = sCachedCVCList.get(i); |
| 807 assert content.isAlive(); |
| 808 content.getContainerView().setVisibility(View.VISIBLE); |
| 809 if (mFullscreenManager != null) { |
| 810 mFullscreenManager.updateContentViewViewportSize(content
); |
| 811 } |
| 812 } |
| 813 |
| 814 FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutPa
rams( |
| 815 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); |
| 816 addView(mView, layoutParams); |
| 817 setFocusable(false); |
| 818 setFocusableInTouchMode(false); |
| 819 |
| 820 // Claim focus for the new view unless the user is currently usi
ng the URL bar. |
| 821 if (mUrlBar == null || !mUrlBar.hasFocus()) mView.requestFocus()
; |
| 822 } |
| 823 } else { |
| 824 if (mView.getParent() == this) { |
| 825 setFocusable(true); |
| 826 setFocusableInTouchMode(true); |
| 827 |
| 828 for (int i = 0; i < sCachedCVCList.size(); i++) { |
| 829 ContentViewCore content = sCachedCVCList.get(i); |
| 830 if (content.isAlive()) content.getContainerView().setVisibil
ity(View.INVISIBLE); |
| 831 } |
| 832 |
| 833 if (hasFocus()) { |
| 834 InputMethodManager manager = (InputMethodManager) getContext
().getSystemService( |
| 835 Context.INPUT_METHOD_SERVICE); |
| 836 if (manager.isActive(this)) { |
| 837 manager.hideSoftInputFromWindow(getWindowToken(), 0, nul
l); |
| 838 } |
| 839 } |
| 840 removeView(mView); |
| 841 } |
| 842 } |
| 843 sCachedCVCList.clear(); |
| 844 } |
| 845 |
| 846 @Override |
| 847 public void onContentChanged() { |
| 848 if (mTabModelSelector == null) { |
| 849 // Not yet initialized, onContentChanged() will eventually get calle
d by |
| 850 // setTabModelSelector. |
| 851 return; |
| 852 } |
| 853 Tab tab = mTabModelSelector.getCurrentTab(); |
| 854 setTab(tab); |
| 855 } |
| 856 |
| 857 @Override |
| 858 public void onContentViewCoreAdded(ContentViewCore content) { |
| 859 // TODO(dtrainor): Look into rolling this into onContentChanged(). |
| 860 initializeContentViewCore(content); |
| 861 setSizeOfUnattachedView(content.getContainerView()); |
| 862 } |
| 863 |
| 864 private void setTab(Tab tab) { |
| 865 if (tab != null && tab.isFrozen()) tab.unfreezeContents(); |
| 866 |
| 867 View newView = tab != null ? tab.getView() : null; |
| 868 if (mView == newView) return; |
| 869 |
| 870 // TODO(dtrainor): Look into changing this only if the views differ, but
still parse the |
| 871 // ContentViewCore list even if they're the same. |
| 872 updateContentOverlayVisibility(false); |
| 873 |
| 874 if (mTabVisible != tab) { |
| 875 if (mTabVisible != null) mTabVisible.removeObserver(mTabObserver); |
| 876 if (tab != null) tab.addObserver(mTabObserver); |
| 877 } |
| 878 |
| 879 mTabVisible = tab; |
| 880 mView = newView; |
| 881 |
| 882 updateContentOverlayVisibility(mContentOverlayVisiblity); |
| 883 |
| 884 if (mTabVisible != null) initializeTab(mTabVisible); |
| 885 } |
| 886 |
| 887 /** |
| 888 * Sets the correct size for all {@link View}s on {@code tab} and sets the c
orrect rendering |
| 889 * parameters on all {@link ContentViewCore}s on {@code tab}. |
| 890 * @param tab The {@link Tab} to initialize. |
| 891 */ |
| 892 private void initializeTab(Tab tab) { |
| 893 sCachedCVCList.clear(); |
| 894 if (mLayoutManager != null) { |
| 895 mLayoutManager.getActiveLayout().getAllContentViewCores(sCachedCVCLi
st); |
| 896 } |
| 897 |
| 898 for (int i = 0; i < sCachedCVCList.size(); i++) { |
| 899 initializeContentViewCore(sCachedCVCList.get(i)); |
| 900 } |
| 901 sCachedCVCList.clear(); |
| 902 |
| 903 sCachedViewList.clear(); |
| 904 tab.getAllViews(sCachedViewList); |
| 905 |
| 906 for (int i = 0; i < sCachedViewList.size(); i++) { |
| 907 View view = sCachedViewList.get(i); |
| 908 // Calling View#measure() and View#layout() on a View before adding
it to the view |
| 909 // hierarchy seems to cause issues with compound drawables on some v
ersions of Android. |
| 910 // We don't need to proactively size the NTP as we don't need the An
droid view to render |
| 911 // if it's not actually attached to the view hierarchy (http://crbug
.com/462114). |
| 912 if (view == tab.getView() && tab.isNativePage()) continue; |
| 913 setSizeOfUnattachedView(view); |
| 914 } |
| 915 sCachedViewList.clear(); |
| 916 } |
| 917 |
| 918 /** |
| 919 * Initializes the rendering surface parameters of {@code contentViewCore}.
Note that this does |
| 920 * not size the actual {@link ContentViewCore}. |
| 921 * @param contentViewCore The {@link ContentViewCore} to initialize. |
| 922 */ |
| 923 private void initializeContentViewCore(ContentViewCore contentViewCore) { |
| 924 contentViewCore.setCurrentMotionEventOffsets(0.f, 0.f); |
| 925 contentViewCore.setTopControlsHeight( |
| 926 getTopControlsHeightPixels(), contentViewCore.doTopControlsShrin
kBlinkSize()); |
| 927 contentViewCore.onPhysicalBackingSizeChanged( |
| 928 mCompositorView.getWidth(), mCompositorView.getHeight()); |
| 929 contentViewCore.onOverdrawBottomHeightChanged(mCompositorView.getOverdra
wBottomHeight()); |
| 930 } |
| 931 |
| 932 /** |
| 933 * Resize {@code view} to match the size of this {@link FrameLayout}. This
will only happen if |
| 934 * {@code view} is not {@code null} and if {@link View#getWindowToken()} ret
urns {@code null} |
| 935 * (the {@link View} is not part of the view hierarchy). |
| 936 * @param view The {@link View} to resize. |
| 937 * @return Whether or not {@code view} was resized. |
| 938 */ |
| 939 private boolean setSizeOfUnattachedView(View view) { |
| 940 // Need to call layout() for the following View if it is not attached to
the view hierarchy. |
| 941 // Calling onSizeChanged() is dangerous because if the View has a differ
ent size than the |
| 942 // ContentViewCore it might think a future size update is a NOOP and not
call |
| 943 // onSizeChanged() on the ContentViewCore. |
| 944 if (view == null || view.getWindowToken() != null) return false; |
| 945 int width = getWidth(); |
| 946 int height = getHeight(); |
| 947 view.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), |
| 948 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); |
| 949 view.layout(0, 0, width, height); |
| 950 return true; |
| 951 } |
| 952 |
| 953 @Override |
| 954 public TitleCache getTitleCache() { |
| 955 return mLayerTitleCache; |
| 956 } |
| 957 |
| 958 @Override |
| 959 public void deferInvalidate(Client client) { |
| 960 if (mPendingSwapBuffersCount <= 0) { |
| 961 client.doInvalidate(); |
| 962 } else if (!mPendingInvalidations.contains(client)) { |
| 963 mPendingInvalidations.add(client); |
| 964 } |
| 965 } |
| 966 |
| 967 private void flushInvalidation() { |
| 968 if (mPendingInvalidations.isEmpty()) return; |
| 969 TraceEvent.instant("CompositorViewHolder.flushInvalidation"); |
| 970 for (int i = 0; i < mPendingInvalidations.size(); i++) { |
| 971 mPendingInvalidations.get(i).doInvalidate(); |
| 972 } |
| 973 mPendingInvalidations.clear(); |
| 974 } |
| 975 |
| 976 @Override |
| 977 public void invalidateAccessibilityProvider() { |
| 978 if (mNodeProvider != null) { |
| 979 mNodeProvider.invalidateRoot(); |
| 980 } |
| 981 } |
| 982 |
| 983 /** |
| 984 * Called when the accessibility enabled state changes. |
| 985 * @param enabled Whether accessibility is enabled. |
| 986 */ |
| 987 public void onAccessibilityStatusChanged(boolean enabled) { |
| 988 // Instantiate and install the accessibility node provider on this view
if necessary. |
| 989 // This overrides any hover event listeners or accessibility delegates |
| 990 // that may have been added elsewhere. |
| 991 if (enabled && (mNodeProvider == null)) { |
| 992 mAccessibilityView = new View(getContext()); |
| 993 addView(mAccessibilityView); |
| 994 mNodeProvider = new CompositorAccessibilityProvider(mAccessibilityVi
ew); |
| 995 ViewCompat.setAccessibilityDelegate(mAccessibilityView, mNodeProvide
r); |
| 996 } |
| 997 } |
| 998 |
| 999 /** |
| 1000 * Class used to provide a virtual view hierarchy to the Accessibility |
| 1001 * framework for this view and its contained items. |
| 1002 * <p> |
| 1003 * <strong>NOTE:</strong> This class is fully backwards compatible for |
| 1004 * compilation, but will only provide touch exploration on devices running |
| 1005 * Ice Cream Sandwich and above. |
| 1006 * </p> |
| 1007 */ |
| 1008 private class CompositorAccessibilityProvider extends ExploreByTouchHelper { |
| 1009 private final float mDpToPx; |
| 1010 List<VirtualView> mVirtualViews = new ArrayList<VirtualView>(); |
| 1011 private final Rect mPlaceHolderRect = new Rect(0, 0, 1, 1); |
| 1012 private static final String PLACE_HOLDER_STRING = ""; |
| 1013 private final RectF mTouchTarget = new RectF(); |
| 1014 private final Rect mPixelRect = new Rect(); |
| 1015 |
| 1016 public CompositorAccessibilityProvider(View forView) { |
| 1017 super(forView); |
| 1018 mDpToPx = getContext().getResources().getDisplayMetrics().density; |
| 1019 } |
| 1020 |
| 1021 @Override |
| 1022 protected int getVirtualViewAt(float x, float y) { |
| 1023 if (mVirtualViews == null) return INVALID_ID; |
| 1024 for (int i = 0; i < mVirtualViews.size(); i++) { |
| 1025 if (mVirtualViews.get(i).checkClicked(x / mDpToPx, y / mDpToPx))
{ |
| 1026 return i; |
| 1027 } |
| 1028 } |
| 1029 return INVALID_ID; |
| 1030 } |
| 1031 |
| 1032 @Override |
| 1033 protected void getVisibleVirtualViews(List<Integer> virtualViewIds) { |
| 1034 if (mLayoutManager == null) return; |
| 1035 mVirtualViews.clear(); |
| 1036 mLayoutManager.getVirtualViews(mVirtualViews); |
| 1037 for (int i = 0; i < mVirtualViews.size(); i++) { |
| 1038 virtualViewIds.add(i); |
| 1039 } |
| 1040 } |
| 1041 |
| 1042 @Override |
| 1043 protected boolean onPerformActionForVirtualView( |
| 1044 int virtualViewId, int action, Bundle arguments) { |
| 1045 switch (action) { |
| 1046 case AccessibilityNodeInfoCompat.ACTION_CLICK: |
| 1047 return true; |
| 1048 } |
| 1049 |
| 1050 return false; |
| 1051 } |
| 1052 |
| 1053 @Override |
| 1054 protected void onPopulateEventForVirtualView(int virtualViewId, Accessib
ilityEvent event) { |
| 1055 if (mVirtualViews == null || mVirtualViews.size() <= virtualViewId)
{ |
| 1056 // TODO(clholgat): Remove this work around when the Android bug
is fixed. |
| 1057 // crbug.com/420177 |
| 1058 event.setContentDescription(PLACE_HOLDER_STRING); |
| 1059 return; |
| 1060 } |
| 1061 VirtualView view = mVirtualViews.get(virtualViewId); |
| 1062 |
| 1063 event.setContentDescription(view.getAccessibilityDescription()); |
| 1064 event.setClassName(CompositorViewHolder.class.getName()); |
| 1065 } |
| 1066 |
| 1067 @Override |
| 1068 protected void onPopulateNodeForVirtualView( |
| 1069 int virtualViewId, AccessibilityNodeInfoCompat node) { |
| 1070 if (mVirtualViews == null || mVirtualViews.size() <= virtualViewId)
{ |
| 1071 // TODO(clholgat): Remove this work around when the Android bug
is fixed. |
| 1072 // crbug.com/420177 |
| 1073 node.setBoundsInParent(mPlaceHolderRect); |
| 1074 node.setContentDescription(PLACE_HOLDER_STRING); |
| 1075 return; |
| 1076 } |
| 1077 VirtualView view = mVirtualViews.get(virtualViewId); |
| 1078 view.getTouchTarget(mTouchTarget); |
| 1079 |
| 1080 node.setBoundsInParent(rectToPx(mTouchTarget)); |
| 1081 node.setContentDescription(view.getAccessibilityDescription()); |
| 1082 node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK); |
| 1083 node.addAction(AccessibilityNodeInfoCompat.ACTION_FOCUS); |
| 1084 node.addAction(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK); |
| 1085 } |
| 1086 |
| 1087 private Rect rectToPx(RectF rect) { |
| 1088 rect.roundOut(mPixelRect); |
| 1089 mPixelRect.left = (int) (mPixelRect.left * mDpToPx); |
| 1090 mPixelRect.top = (int) (mPixelRect.top * mDpToPx); |
| 1091 mPixelRect.right = (int) (mPixelRect.right * mDpToPx); |
| 1092 mPixelRect.bottom = (int) (mPixelRect.bottom * mDpToPx); |
| 1093 |
| 1094 // Don't let any zero sized rects through, they'll cause parent |
| 1095 // size errors in L. |
| 1096 if (mPixelRect.width() == 0) { |
| 1097 mPixelRect.right = mPixelRect.left + 1; |
| 1098 } |
| 1099 if (mPixelRect.height() == 0) { |
| 1100 mPixelRect.bottom = mPixelRect.top + 1; |
| 1101 } |
| 1102 return mPixelRect; |
| 1103 } |
| 1104 } |
| 1105 } |
OLD | NEW |