Commit d38daecd authored by Wenyu Fu's avatar Wenyu Fu Committed by Commit Bot

[HomepagePromo] Integrate ImpressionTracker into PromoCard component

This work involves:
1. Moving ImpressionTracker from //chrome/metrics to
//component/browser_ui as it could be beneficial for widgets that wants
integrate tracking impression within.
2. Add another API for ImpressionTracker to adjust the impression
threshold with a ratio, rather than the default 2/3 if pixel level
threshold is not set;
3. Track impression for the primary button for HomepagePromo with ratio
as 75%.

Bug: 1068831
Change-Id: Ia7366cfa4b7d53dcea03d2b9143c59203f604302
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2199360
Commit-Queue: Wenyu Fu <wenyufu@chromium.org>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Cr-Commit-Position: refs/heads/master@{#768878}
parent 0d0d3a60
...@@ -903,13 +903,11 @@ chrome_java_sources = [ ...@@ -903,13 +903,11 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/media/ui/MediaSessionTabHelper.java", "java/src/org/chromium/chrome/browser/media/ui/MediaSessionTabHelper.java",
"java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java", "java/src/org/chromium/chrome/browser/metrics/ActivityTabStartupMetricsTracker.java",
"java/src/org/chromium/chrome/browser/metrics/BackgroundTaskMemoryMetricsEmitter.java", "java/src/org/chromium/chrome/browser/metrics/BackgroundTaskMemoryMetricsEmitter.java",
"java/src/org/chromium/chrome/browser/metrics/ImpressionTracker.java",
"java/src/org/chromium/chrome/browser/metrics/LaunchMetrics.java", "java/src/org/chromium/chrome/browser/metrics/LaunchMetrics.java",
"java/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetrics.java", "java/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetrics.java",
"java/src/org/chromium/chrome/browser/metrics/MediaNotificationUma.java", "java/src/org/chromium/chrome/browser/metrics/MediaNotificationUma.java",
"java/src/org/chromium/chrome/browser/metrics/MediaSessionUMA.java", "java/src/org/chromium/chrome/browser/metrics/MediaSessionUMA.java",
"java/src/org/chromium/chrome/browser/metrics/OmniboxStartupMetrics.java", "java/src/org/chromium/chrome/browser/metrics/OmniboxStartupMetrics.java",
"java/src/org/chromium/chrome/browser/metrics/OneShotImpressionListener.java",
"java/src/org/chromium/chrome/browser/metrics/PackageMetrics.java", "java/src/org/chromium/chrome/browser/metrics/PackageMetrics.java",
"java/src/org/chromium/chrome/browser/metrics/PageLoadMetrics.java", "java/src/org/chromium/chrome/browser/metrics/PageLoadMetrics.java",
"java/src/org/chromium/chrome/browser/metrics/UkmRecorder.java", "java/src/org/chromium/chrome/browser/metrics/UkmRecorder.java",
......
...@@ -16,8 +16,6 @@ import androidx.appcompat.content.res.AppCompatResources; ...@@ -16,8 +16,6 @@ import androidx.appcompat.content.res.AppCompatResources;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.homepage.HomepageManager; import org.chromium.chrome.browser.homepage.HomepageManager;
import org.chromium.chrome.browser.homepage.HomepageManager.HomepageStateListener; import org.chromium.chrome.browser.homepage.HomepageManager.HomepageStateListener;
import org.chromium.chrome.browser.metrics.ImpressionTracker;
import org.chromium.chrome.browser.metrics.OneShotImpressionListener;
import org.chromium.chrome.browser.ntp.cards.promo.HomepagePromoUtils.HomepagePromoAction; import org.chromium.chrome.browser.ntp.cards.promo.HomepagePromoUtils.HomepagePromoAction;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager; import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.components.browser_ui.widget.promo.PromoCardCoordinator; import org.chromium.components.browser_ui.widget.promo.PromoCardCoordinator;
...@@ -57,7 +55,6 @@ public class HomepagePromoController implements HomepageStateListener { ...@@ -57,7 +55,6 @@ public class HomepagePromoController implements HomepageStateListener {
private HomepagePromoStateListener mStateListener; private HomepagePromoStateListener mStateListener;
private PromoCardCoordinator mPromoCoordinator; private PromoCardCoordinator mPromoCoordinator;
private PropertyModel mModel; private PropertyModel mModel;
private @Nullable ImpressionTracker mImpressionTracker;
private boolean mIsPromoShowing; private boolean mIsPromoShowing;
...@@ -139,11 +136,6 @@ public class HomepagePromoController implements HomepageStateListener { ...@@ -139,11 +136,6 @@ public class HomepagePromoController implements HomepageStateListener {
HomepagePromoUtils.recordHomepagePromoEvent(HomepagePromoAction.CREATED); HomepagePromoUtils.recordHomepagePromoEvent(HomepagePromoAction.CREATED);
// Set impression for the promo.
mImpressionTracker =
new ImpressionTracker(mPromoCoordinator.getKeyViewForImpressionTracking());
mImpressionTracker.setListener(new OneShotImpressionListener(this::onPromoSeen));
return mPromoCoordinator; return mPromoCoordinator;
} }
...@@ -152,7 +144,9 @@ public class HomepagePromoController implements HomepageStateListener { ...@@ -152,7 +144,9 @@ public class HomepagePromoController implements HomepageStateListener {
PropertyModel.Builder builder = new PropertyModel.Builder(PromoCardProperties.ALL_KEYS); PropertyModel.Builder builder = new PropertyModel.Builder(PromoCardProperties.ALL_KEYS);
builder.with(PromoCardProperties.PRIMARY_BUTTON_CALLBACK, (v) -> onPrimaryButtonClicked()); builder.with(PromoCardProperties.PRIMARY_BUTTON_CALLBACK, (v) -> onPrimaryButtonClicked())
.with(PromoCardProperties.IMPRESSION_SEEN_CALLBACK, this::onPromoSeen)
.with(PromoCardProperties.IS_IMPRESSION_ON_PRIMARY_BUTTON, true);
if (layoutStyle == LayoutStyle.SLIM) { if (layoutStyle == LayoutStyle.SLIM) {
Drawable homeIcon = Drawable homeIcon =
...@@ -216,10 +210,6 @@ public class HomepagePromoController implements HomepageStateListener { ...@@ -216,10 +210,6 @@ public class HomepagePromoController implements HomepageStateListener {
private void dismissPromoInternal() { private void dismissPromoInternal() {
mIsPromoShowing = false; mIsPromoShowing = false;
mTracker.dismissed(FeatureConstants.HOMEPAGE_PROMO_CARD_FEATURE); mTracker.dismissed(FeatureConstants.HOMEPAGE_PROMO_CARD_FEATURE);
if (mImpressionTracker != null) {
mImpressionTracker.setListener(null);
mImpressionTracker = null;
}
if (mStateListener != null) mStateListener.onHomepagePromoStateChange(); if (mStateListener != null) mStateListener.onHomepagePromoStateChange();
} }
......
...@@ -17,11 +17,11 @@ import androidx.annotation.VisibleForTesting; ...@@ -17,11 +17,11 @@ import androidx.annotation.VisibleForTesting;
import org.chromium.base.metrics.RecordHistogram; import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction; import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.metrics.ImpressionTracker;
import org.chromium.chrome.browser.metrics.OneShotImpressionListener;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys; import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.SharedPreferencesManager; import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
import org.chromium.chrome.browser.signin.SigninActivity.AccessPoint; import org.chromium.chrome.browser.signin.SigninActivity.AccessPoint;
import org.chromium.components.browser_ui.widget.impression.ImpressionTracker;
import org.chromium.components.browser_ui.widget.impression.OneShotImpressionListener;
import org.chromium.components.signin.metrics.SigninAccessPoint; import org.chromium.components.signin.metrics.SigninAccessPoint;
/** /**
......
...@@ -61,6 +61,8 @@ android_library("java") { ...@@ -61,6 +61,8 @@ android_library("java") {
"java/src/org/chromium/components/browser_ui/widget/image_tiles/TileSizeSupplier.java", "java/src/org/chromium/components/browser_ui/widget/image_tiles/TileSizeSupplier.java",
"java/src/org/chromium/components/browser_ui/widget/image_tiles/TileViewHolder.java", "java/src/org/chromium/components/browser_ui/widget/image_tiles/TileViewHolder.java",
"java/src/org/chromium/components/browser_ui/widget/image_tiles/TileViewHolderFactory.java", "java/src/org/chromium/components/browser_ui/widget/image_tiles/TileViewHolderFactory.java",
"java/src/org/chromium/components/browser_ui/widget/impression/ImpressionTracker.java",
"java/src/org/chromium/components/browser_ui/widget/impression/OneShotImpressionListener.java",
"java/src/org/chromium/components/browser_ui/widget/listmenu/BasicListMenu.java", "java/src/org/chromium/components/browser_ui/widget/listmenu/BasicListMenu.java",
"java/src/org/chromium/components/browser_ui/widget/listmenu/ListMenu.java", "java/src/org/chromium/components/browser_ui/widget/listmenu/ListMenu.java",
"java/src/org/chromium/components/browser_ui/widget/listmenu/ListMenuButton.java", "java/src/org/chromium/components/browser_ui/widget/listmenu/ListMenuButton.java",
...@@ -220,6 +222,7 @@ android_library("javatests") { ...@@ -220,6 +222,7 @@ android_library("javatests") {
"java/src/org/chromium/components/browser_ui/widget/highlight/ViewHighlighterTest.java", "java/src/org/chromium/components/browser_ui/widget/highlight/ViewHighlighterTest.java",
"java/src/org/chromium/components/browser_ui/widget/listmenu/ListMenuRenderTest.java", "java/src/org/chromium/components/browser_ui/widget/listmenu/ListMenuRenderTest.java",
"java/src/org/chromium/components/browser_ui/widget/promo/PromoCardCoordinatorTest.java", "java/src/org/chromium/components/browser_ui/widget/promo/PromoCardCoordinatorTest.java",
"java/src/org/chromium/components/browser_ui/widget/promo/PromoCardImpressionTest.java",
"java/src/org/chromium/components/browser_ui/widget/promo/PromoCardViewRenderTest.java", "java/src/org/chromium/components/browser_ui/widget/promo/PromoCardViewRenderTest.java",
"java/src/org/chromium/components/browser_ui/widget/scrim/ScrimTest.java", "java/src/org/chromium/components/browser_ui/widget/scrim/ScrimTest.java",
] ]
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
package org.chromium.chrome.browser.metrics; package org.chromium.components.browser_ui.widget.impression;
import android.graphics.Rect; import android.graphics.Rect;
import android.view.View; import android.view.View;
...@@ -35,6 +35,7 @@ public class ImpressionTracker ...@@ -35,6 +35,7 @@ public class ImpressionTracker
private final View mView; private final View mView;
private @Nullable Listener mListener; private @Nullable Listener mListener;
private int mImpressionThresholdPx; private int mImpressionThresholdPx;
private double mImpressionThresholdRatio;
/** /**
* Creates a new instance tracking the given {@code view} as soon as and while a listener is * Creates a new instance tracking the given {@code view} as soon as and while a listener is
...@@ -68,6 +69,20 @@ public class ImpressionTracker ...@@ -68,6 +69,20 @@ public class ImpressionTracker
mImpressionThresholdPx = impressionThresholdPx; mImpressionThresholdPx = impressionThresholdPx;
} }
/**
* Sets a custom threshold ratio that defines "impression". If not set, the default ratio will
* be 2/3.
*
* If impression pixel are set to a non-zero value through {@link #setImpressionThreshold(int)},
* the px will precedence over this ratio.
*
* @param ratio The fraction of the view that needs to be visible.
*/
public void setImpressionThresholdRatio(double ratio) {
assert ratio > 0 && ratio <= 1;
mImpressionThresholdRatio = ratio;
}
/** /**
* Registers listeners for the current view. * Registers listeners for the current view.
*/ */
...@@ -108,7 +123,13 @@ public class ImpressionTracker ...@@ -108,7 +123,13 @@ public class ImpressionTracker
int impressionThresholdPx = mImpressionThresholdPx; int impressionThresholdPx = mImpressionThresholdPx;
// If no threshold is specified, track impression if at least 2/3 of the view is // If no threshold is specified, track impression if at least 2/3 of the view is
// visible. // visible.
if (impressionThresholdPx == 0) impressionThresholdPx = 2 * mView.getHeight() / 3; if (impressionThresholdPx == 0) {
if (mImpressionThresholdRatio != 0.0) {
impressionThresholdPx = (int) (mView.getHeight() * mImpressionThresholdRatio);
} else {
impressionThresholdPx = 2 * mView.getHeight() / 3;
}
}
// |getChildVisibleRect| returns false when the view is empty, which may happen when // |getChildVisibleRect| returns false when the view is empty, which may happen when
// dismissing or reassigning a View. In this case |rect| appears to be invalid. // dismissing or reassigning a View. In this case |rect| appears to be invalid.
......
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
package org.chromium.chrome.browser.metrics; package org.chromium.components.browser_ui.widget.impression;
import org.chromium.chrome.browser.metrics.ImpressionTracker.Listener; import org.chromium.components.browser_ui.widget.impression.ImpressionTracker.Listener;
/** /**
* Filters {@link ImpressionTracker} impressions to only forward the first impression. * Filters {@link ImpressionTracker} impressions to only forward the first impression.
......
...@@ -10,8 +10,11 @@ import android.view.View; ...@@ -10,8 +10,11 @@ import android.view.View;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.LayoutRes; import androidx.annotation.LayoutRes;
import androidx.annotation.Nullable;
import org.chromium.components.browser_ui.widget.R; import org.chromium.components.browser_ui.widget.R;
import org.chromium.components.browser_ui.widget.impression.ImpressionTracker;
import org.chromium.components.browser_ui.widget.impression.OneShotImpressionListener;
import org.chromium.ui.modelutil.PropertyModel; import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor; import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
...@@ -32,10 +35,14 @@ public class PromoCardCoordinator { ...@@ -32,10 +35,14 @@ public class PromoCardCoordinator {
int SLIM = 2; int SLIM = 2;
} }
private static final double IMPRESSION_THRESHOLD_RATIO = 0.75;
private PromoCardView mPromoCardView; private PromoCardView mPromoCardView;
private PropertyModelChangeProcessor mModelChangeProcessor; private PropertyModelChangeProcessor mModelChangeProcessor;
private String mFeatureName; private String mFeatureName;
private @Nullable ImpressionTracker mImpressionTracker;
/** /**
* Create the Coordinator of PromoCard that owns the view and the change process. Default to * Create the Coordinator of PromoCard that owns the view and the change process. Default to
* create the large variance. * create the large variance.
...@@ -63,13 +70,27 @@ public class PromoCardCoordinator { ...@@ -63,13 +70,27 @@ public class PromoCardCoordinator {
mModelChangeProcessor = PropertyModelChangeProcessor.create( mModelChangeProcessor = PropertyModelChangeProcessor.create(
model, mPromoCardView, new PromoCardViewBinder()); model, mPromoCardView, new PromoCardViewBinder());
mFeatureName = featureName; mFeatureName = featureName;
// Manage impression related properties.
Runnable impressionCallback = model.get(PromoCardProperties.IMPRESSION_SEEN_CALLBACK);
if (impressionCallback != null) {
boolean isImpressionOnPrimaryButton =
model.get(PromoCardProperties.IS_IMPRESSION_ON_PRIMARY_BUTTON);
mImpressionTracker = new ImpressionTracker(
isImpressionOnPrimaryButton ? mPromoCardView.mPrimaryButton : mPromoCardView);
// TODO(wenyufu): Maybe make the ratio configurable?
mImpressionTracker.setImpressionThresholdRatio(IMPRESSION_THRESHOLD_RATIO);
mImpressionTracker.setListener(new OneShotImpressionListener(impressionCallback::run));
}
} }
/** /**
* Destroy the PromoCard component and release the PropertyModelChangeProcessor. * Destroy the PromoCard component and release dependencies.
*/ */
public void destroy() { public void destroy() {
mModelChangeProcessor.destroy(); mModelChangeProcessor.destroy();
if (mImpressionTracker != null) mImpressionTracker.setListener(null);
mImpressionTracker = null;
} }
/** /**
...@@ -79,15 +100,6 @@ public class PromoCardCoordinator { ...@@ -79,15 +100,6 @@ public class PromoCardCoordinator {
return mPromoCardView; return mPromoCardView;
} }
/**
* @return The key view that will be tracking with impression tracker. Currently this returns
* the primary button in the promo view.
* TODO(wenyufu): Remove me when ImpressionTracker is moved to //component!
*/
public View getKeyViewForImpressionTracking() {
return mPromoCardView.mPrimaryButton;
}
/** /**
* @return Name of the feature this promo is representing. * @return Name of the feature this promo is representing.
*/ */
......
// Copyright 2020 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.components.browser_ui.widget.promo;
import android.app.Activity;
import android.support.test.filters.SmallTest;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.FrameLayout;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.test.util.DummyUiActivityTestCase;
import java.util.concurrent.TimeoutException;
/**
* Tests targeting functionality to track impression on PromoCard component.
* TODO(wenyufu): Add the test when the primary button is hidden initially.
*/
@RunWith(BaseJUnit4ClassRunner.class)
public class PromoCardImpressionTest extends DummyUiActivityTestCase {
private PropertyModel mModel;
private PromoCardCoordinator mCoordinator;
private FrameLayout mContent;
private final CallbackHelper mPromoSeenCallback = new CallbackHelper();
private void setUpPromoCard(boolean trackPrimary, boolean hidePromo) {
mModel = new PropertyModel.Builder(PromoCardProperties.ALL_KEYS)
.with(PromoCardProperties.IMPRESSION_SEEN_CALLBACK,
mPromoSeenCallback::notifyCalled)
.with(PromoCardProperties.IS_IMPRESSION_ON_PRIMARY_BUTTON, trackPrimary)
.with(PromoCardProperties.TITLE, "Title")
.with(PromoCardProperties.DESCRIPTION, "Description")
.with(PromoCardProperties.PRIMARY_BUTTON_TEXT, "Primary")
.with(PromoCardProperties.HAS_SECONDARY_BUTTON, false)
.build();
Activity activity = getActivity();
mCoordinator = new PromoCardCoordinator(activity, mModel, "impression-test");
View promoView = mCoordinator.getView();
TestThreadUtils.runOnUiThreadBlocking(() -> {
if (hidePromo) promoView.setVisibility(View.INVISIBLE);
// Set the content and add the promo card into the window
mContent = new FrameLayout(activity);
activity.setContentView(mContent);
mContent.addView(promoView,
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
});
}
@Test
@SmallTest
public void testImpression_Card_Seen() throws TimeoutException {
int initCount = mPromoSeenCallback.getCallCount();
setUpPromoCard(false, false);
mPromoSeenCallback.waitForCallback("PromoCard is never seen.", initCount);
}
@Test
@SmallTest
public void testImpression_PrimaryButton_Seen() throws TimeoutException {
int initCount = mPromoSeenCallback.getCallCount();
setUpPromoCard(true, false);
mPromoSeenCallback.waitForCallback("PromoCard's primary button is never seen.", initCount);
}
@Test
@SmallTest
public void testImpression_Card_Hide() throws TimeoutException {
int initCount = mPromoSeenCallback.getCallCount();
setUpPromoCard(false, true);
Assert.assertEquals(
"Promo should not be seen yet.", initCount, mPromoSeenCallback.getCallCount());
TestThreadUtils.runOnUiThreadBlocking(
() -> { mCoordinator.getView().setVisibility(View.VISIBLE); });
mPromoSeenCallback.waitForCallback("PromoCard is never seen.", initCount);
}
}
...@@ -10,6 +10,8 @@ import android.view.View; ...@@ -10,6 +10,8 @@ import android.view.View;
import org.chromium.base.Callback; import org.chromium.base.Callback;
import org.chromium.ui.modelutil.PropertyKey; import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel.ReadableBooleanPropertyKey;
import org.chromium.ui.modelutil.PropertyModel.ReadableObjectPropertyKey;
import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey; import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey;
import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey; import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
...@@ -46,10 +48,29 @@ public class PromoCardProperties { ...@@ -46,10 +48,29 @@ public class PromoCardProperties {
public static final WritableObjectPropertyKey<Callback<View>> SECONDARY_BUTTON_CALLBACK = public static final WritableObjectPropertyKey<Callback<View>> SECONDARY_BUTTON_CALLBACK =
new WritableObjectPropertyKey<>(); new WritableObjectPropertyKey<>();
// Impression related properties
/**
* If true, track the impression on the primary button. Otherwise, track impression on the
* entire promo card. Only take effect when {@link #IMPRESSION_SEEN_CALLBACK} is set.
*/
public static final ReadableBooleanPropertyKey IS_IMPRESSION_ON_PRIMARY_BUTTON =
new ReadableBooleanPropertyKey();
/**
* Assign the callback when 75% of the promo is seen on the screen. Set {@link
* #IS_IMPRESSION_ON_PRIMARY_BUTTON} to change the impression object from the entire promo card
* to the primary button.
*
* @see org.chromium.components.browser_ui.widget.impression.ImpressionTracker
*/
public static final ReadableObjectPropertyKey<Runnable> IMPRESSION_SEEN_CALLBACK =
new ReadableObjectPropertyKey<>();
/** /**
* All the property keys needed to create the model for {@link PromoCardView}. * All the property keys needed to create the model for {@link PromoCardView}.
*/ */
public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {HAS_SECONDARY_BUTTON, IMAGE, public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {HAS_SECONDARY_BUTTON, IMAGE,
ICON_TINT, TITLE, DESCRIPTION, PRIMARY_BUTTON_TEXT, SECONDARY_BUTTON_TEXT, ICON_TINT, TITLE, DESCRIPTION, PRIMARY_BUTTON_TEXT, SECONDARY_BUTTON_TEXT,
PRIMARY_BUTTON_CALLBACK, SECONDARY_BUTTON_CALLBACK}; PRIMARY_BUTTON_CALLBACK, SECONDARY_BUTTON_CALLBACK, IS_IMPRESSION_ON_PRIMARY_BUTTON,
IMPRESSION_SEEN_CALLBACK};
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment