/* * Copyright (c) 2014-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.stetho.inspector.elements.android; import android.annotation.TargetApi; import android.graphics.Rect; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.view.View; import com.facebook.stetho.common.LogUtil; import com.facebook.stetho.common.Util; import javax.annotation.Nullable; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; abstract class ViewHighlighter { public static ViewHighlighter newInstance() { // TODO: find ways to do highlighting on older versions too if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { return new OverlayHighlighter(); } else { LogUtil.w("Running on pre-JBMR2: View highlighting is not supported"); return new NoopHighlighter(); } } protected ViewHighlighter() { } public abstract void clearHighlight(); public abstract void setHighlightedView(View view, @Nullable Rect bounds, int color); private static final class NoopHighlighter extends ViewHighlighter { @Override public void clearHighlight() { } @Override public void setHighlightedView(View view, @Nullable Rect bounds, int color) { } } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) private static final class OverlayHighlighter extends ViewHighlighter { // TODO: use the top-level ViewGroupOverlay instead of ViewOverlay so that we don't end up // causing every single view to allocate a ViewOverlay private final Handler mHandler; private final ViewHighlightOverlays mHighlightOverlays = ViewHighlightOverlays.newInstance(); // Only assigned on the UI thread private View mHighlightedView; private final Rect mHighlightedBounds = new Rect(); private final Rect mEmptyRect = new Rect(); private AtomicReference<View> mViewToHighlight = new AtomicReference<View>(); private AtomicReference<Rect> mBoundsToHighlight = new AtomicReference<Rect>(); private AtomicInteger mContentColor = new AtomicInteger(); private final Runnable mHighlightViewOnUiThreadRunnable = new Runnable() { @Override public void run() { highlightViewOnUiThread(); } }; public OverlayHighlighter() { mHandler = new Handler(Looper.getMainLooper()); } @Override public void clearHighlight() { setHighlightedViewImpl(null, null, 0); } @Override public void setHighlightedView(View view, @Nullable Rect bounds, int color) { setHighlightedViewImpl(Util.throwIfNull(view), bounds, color); } private void setHighlightedViewImpl(@Nullable View view, @Nullable Rect bounds, int color) { mHandler.removeCallbacks(mHighlightViewOnUiThreadRunnable); mViewToHighlight.set(view); mBoundsToHighlight.set(bounds); mContentColor.set(color); mHandler.postDelayed(mHighlightViewOnUiThreadRunnable, 100); } private void highlightViewOnUiThread() { final View viewToHighlight = mViewToHighlight.getAndSet(null); Rect boundsToHighlight = mBoundsToHighlight.getAndSet(null); if (boundsToHighlight == null) { boundsToHighlight = mEmptyRect; } if (viewToHighlight == mHighlightedView && mHighlightedBounds.equals(boundsToHighlight)) { return; } if (mHighlightedView != null) { mHighlightOverlays.removeHighlight(mHighlightedView); } if (viewToHighlight != null) { mHighlightOverlays.highlightView( viewToHighlight, boundsToHighlight, mContentColor.get()); } mHighlightedView = viewToHighlight; if (boundsToHighlight == null) { mHighlightedBounds.setEmpty(); } else { mHighlightedBounds.set(boundsToHighlight); } } } }