// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser.ntp.cards; import android.graphics.Rect; import android.support.annotation.Nullable; import android.support.v4.view.ViewCompat; import android.view.View; import android.view.ViewParent; import android.view.ViewTreeObserver; /** * A class that helps with tracking impressions. */ public class ImpressionTracker implements ViewTreeObserver.OnPreDrawListener, View.OnAttachStateChangeListener { /** * The Listener will be called back on each impression. Whenever at least 1/3 of the view's * height is visible, that counts as an impression. Note that this will get called often while * the view is visible; it's the implementer's responsibility to count only one impression or * reset the {@link ImpressionTracker}. * * @see ImpressionTracker#reset(View) * @see ImpressionTracker#wasTriggered() */ public interface Listener { void onImpression(); } /** * Currently tracked View. Can be {@code null} if the tracker was cleared. * @see #reset(View) */ @Nullable private View mView; private final Listener mListener; private boolean mTriggered; /** * Creates an {@link ImpressionTracker}. {@code view} can be {@code null} if the tracked should * not be registered on any View at construction time. */ public ImpressionTracker(@Nullable View view, Listener listener) { mListener = listener; reset(view); } /** * Changes the view the tracker should observe. * @param view The new View to observe. Set to {@code null} to completely stop observing. */ public void reset(@Nullable View view) { // Unregister the listeners for the current view. if (mView != null) { mView.removeOnAttachStateChangeListener(this); if (ViewCompat.isAttachedToWindow(mView)) { mView.getViewTreeObserver().removeOnPreDrawListener(this); } } // Register the listeners for the new view. mView = view; if (mView != null) { // Listen to onPreDraw only if view is potentially visible (attached to the window). mView.addOnAttachStateChangeListener(this); if (ViewCompat.isAttachedToWindow(mView)) { mView.getViewTreeObserver().addOnPreDrawListener(this); } } } /** @return whether this observer called {@link Listener#onImpression()} at least once. */ public boolean wasTriggered() { return mTriggered; } @Override public void onViewAttachedToWindow(View v) { mView.getViewTreeObserver().addOnPreDrawListener(this); } @Override public void onViewDetachedFromWindow(View v) { mView.getViewTreeObserver().removeOnPreDrawListener(this); } @Override public boolean onPreDraw() { ViewParent parent = mView.getParent(); if (parent != null) { Rect rect = new Rect(0, 0, mView.getWidth(), mView.getHeight()); parent.getChildVisibleRect(mView, rect, null); // Track impression if at least one third of the view is visible. if (rect.height() >= mView.getHeight() / 3) { mTriggered = true; mListener.onImpression(); } } // Proceed with the current drawing pass. return true; } }