Commit 2555b6a3 authored by Finnur Thorarinsson's avatar Finnur Thorarinsson Committed by Commit Bot

[Android]: Add a test for the PWA Install IPH.

This CL also introduces a wrapped tracker object
that can proxy calls from C++ over to Java, which
allows the test to cover the IPH functionality from
end to end (including showing the IPH and making
sure the events to suppress it finds its way to the
Java test).

Bug: 1135551
Change-Id: I9e6a76a5a116aed6a6ca2047f01d7e32ee01f291
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2468120
Commit-Queue: Finnur Thorarinsson <finnur@chromium.org>
Reviewed-by: default avatarTommy Nyquist <nyquist@chromium.org>
Cr-Commit-Position: refs/heads/master@{#818300}
parent 6eeddff1
...@@ -158,6 +158,11 @@ public class KeyboardAccessoryModernViewTest { ...@@ -158,6 +158,11 @@ public class KeyboardAccessoryModernViewTest {
public void addOnInitializedCallback(Callback<Boolean> callback) { public void addOnInitializedCallback(Callback<Boolean> callback) {
assert false : "Implement addOnInitializedCallback if you need it."; assert false : "Implement addOnInitializedCallback if you need it.";
} }
@Override
public void injectTracker(Tracker tracker) {
assert false : "This should only be called on a production tracker";
}
} }
@Before @Before
......
...@@ -4,6 +4,17 @@ ...@@ -4,6 +4,17 @@
package org.chromium.chrome.browser.banners; package org.chromium.chrome.browser.banners;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.chromium.chrome.test.util.ViewUtils.waitForView;
import android.app.Activity; import android.app.Activity;
import android.app.Instrumentation; import android.app.Instrumentation;
import android.app.Instrumentation.ActivityMonitor; import android.app.Instrumentation.ActivityMonitor;
...@@ -18,9 +29,12 @@ import android.support.test.uiautomator.UiObject; ...@@ -18,9 +29,12 @@ import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiSelector; import android.support.test.uiautomator.UiSelector;
import android.view.View; import android.view.View;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.matcher.RootMatchers;
import androidx.test.filters.MediumTest; import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
...@@ -36,6 +50,7 @@ import org.mockito.quality.Strictness; ...@@ -36,6 +50,7 @@ import org.mockito.quality.Strictness;
import org.chromium.base.ThreadUtils; import org.chromium.base.ThreadUtils;
import org.chromium.base.metrics.RecordHistogram; import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.task.PostTask; import org.chromium.base.task.PostTask;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags; import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.DisabledTest; import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.Feature; import org.chromium.base.test.util.Feature;
...@@ -46,6 +61,7 @@ import org.chromium.chrome.browser.app.ChromeActivity; ...@@ -46,6 +61,7 @@ import org.chromium.chrome.browser.app.ChromeActivity;
import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule; import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
import org.chromium.chrome.browser.customtabs.CustomTabsTestUtils; import org.chromium.chrome.browser.customtabs.CustomTabsTestUtils;
import org.chromium.chrome.browser.engagement.SiteEngagementService; import org.chromium.chrome.browser.engagement.SiteEngagementService;
import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.infobar.InfoBarContainer; import org.chromium.chrome.browser.infobar.InfoBarContainer;
...@@ -53,6 +69,8 @@ import org.chromium.chrome.browser.infobar.InstallableAmbientBadgeInfoBar; ...@@ -53,6 +69,8 @@ import org.chromium.chrome.browser.infobar.InstallableAmbientBadgeInfoBar;
import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabUtils; import org.chromium.chrome.browser.tab.TabUtils;
import org.chromium.chrome.browser.ui.appmenu.AppMenuCoordinator;
import org.chromium.chrome.browser.ui.appmenu.AppMenuTestSupport;
import org.chromium.chrome.browser.webapps.WebappDataStorage; import org.chromium.chrome.browser.webapps.WebappDataStorage;
import org.chromium.chrome.test.ChromeActivityTestRule; import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner; import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
...@@ -61,6 +79,9 @@ import org.chromium.chrome.test.util.InfoBarUtil; ...@@ -61,6 +79,9 @@ import org.chromium.chrome.test.util.InfoBarUtil;
import org.chromium.chrome.test.util.browser.TabLoadObserver; import org.chromium.chrome.test.util.browser.TabLoadObserver;
import org.chromium.chrome.test.util.browser.TabTitleObserver; import org.chromium.chrome.test.util.browser.TabTitleObserver;
import org.chromium.chrome.test.util.browser.webapps.WebappTestPage; import org.chromium.chrome.test.util.browser.webapps.WebappTestPage;
import org.chromium.components.feature_engagement.CppWrappedTestTracker;
import org.chromium.components.feature_engagement.EventConstants;
import org.chromium.components.feature_engagement.FeatureConstants;
import org.chromium.components.infobars.InfoBar; import org.chromium.components.infobars.InfoBar;
import org.chromium.components.infobars.InfoBarAnimationListener; import org.chromium.components.infobars.InfoBarAnimationListener;
import org.chromium.components.infobars.InfoBarUiItem; import org.chromium.components.infobars.InfoBarUiItem;
...@@ -82,7 +103,7 @@ import java.util.List; ...@@ -82,7 +103,7 @@ import java.util.List;
* Tests the app banners. * Tests the app banners.
*/ */
@RunWith(ChromeJUnit4ClassRunner.class) @RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, "use-java-proxy-tracker"})
public class AppBannerManagerTest { public class AppBannerManagerTest {
@Rule @Rule
public ChromeTabbedActivityTestRule mTabbedActivityTestRule = public ChromeTabbedActivityTestRule mTabbedActivityTestRule =
...@@ -94,6 +115,12 @@ public class AppBannerManagerTest { ...@@ -94,6 +115,12 @@ public class AppBannerManagerTest {
@Rule @Rule
public MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS); public MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
// A callback that fires when the IPH system sends an event.
private final CallbackHelper mOnEventCallback = new CallbackHelper();
// The ID of the last event received.
private String mLastNotifyEvent;
private static final String NATIVE_APP_MANIFEST_WITH_ID = private static final String NATIVE_APP_MANIFEST_WITH_ID =
"/chrome/test/data/banners/play_app_manifest.json"; "/chrome/test/data/banners/play_app_manifest.json";
...@@ -183,6 +210,7 @@ public class AppBannerManagerTest { ...@@ -183,6 +210,7 @@ public class AppBannerManagerTest {
private PackageManager mPackageManager; private PackageManager mPackageManager;
private EmbeddedTestServer mTestServer; private EmbeddedTestServer mTestServer;
private UiDevice mUiDevice; private UiDevice mUiDevice;
private CppWrappedTestTracker mTracker;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
...@@ -198,8 +226,20 @@ public class AppBannerManagerTest { ...@@ -198,8 +226,20 @@ public class AppBannerManagerTest {
mTabbedActivityTestRule.startMainActivityOnBlankPage(); mTabbedActivityTestRule.startMainActivityOnBlankPage();
// Must be set after native has loaded. // Must be set after native has loaded.
mDetailsDelegate = new MockAppDetailsDelegate(); mDetailsDelegate = new MockAppDetailsDelegate();
ThreadUtils.runOnUiThreadBlocking( mTracker = new CppWrappedTestTracker(FeatureConstants.PWA_INSTALL_AVAILABLE_FEATURE) {
() -> { AppBannerManager.setAppDetailsDelegate(mDetailsDelegate); }); @Override
public void notifyEvent(String event) {
super.notifyEvent(event);
mOnEventCallback.notifyCalled();
}
};
ThreadUtils.runOnUiThreadBlocking(() -> {
AppBannerManager.setAppDetailsDelegate(mDetailsDelegate);
Profile profile = Profile.getLastUsedRegularProfile();
TrackerFactory.getTrackerForProfile(profile).injectTracker(mTracker);
});
AppBannerManager.ignoreChromeChannelForTesting(); AppBannerManager.ignoreChromeChannelForTesting();
AppBannerManager.setTotalEngagementForTesting(10); AppBannerManager.setTotalEngagementForTesting(10);
...@@ -209,7 +249,9 @@ public class AppBannerManagerTest { ...@@ -209,7 +249,9 @@ public class AppBannerManagerTest {
@After @After
public void tearDown() { public void tearDown() {
mTestServer.stopAndDestroyServer(); if (mTestServer != null) {
mTestServer.stopAndDestroyServer();
}
} }
private void resetEngagementForUrl(final String url, final double engagement) { private void resetEngagementForUrl(final String url, final double engagement) {
...@@ -656,4 +698,43 @@ public class AppBannerManagerTest { ...@@ -656,4 +698,43 @@ public class AppBannerManagerTest {
WEB_APP_MANIFEST_WITH_UNSUPPORTED_PLATFORM, "call_stashed_prompt_on_click"), WEB_APP_MANIFEST_WITH_UNSUPPORTED_PLATFORM, "call_stashed_prompt_on_click"),
false); false);
} }
@Test
@MediumTest
@Feature({"AppBanners"})
@CommandLineFlags.Add("enable-features=" + ChromeFeatureList.INSTALLABLE_AMBIENT_BADGE_INFOBAR)
public void testInProductHelp() throws Exception {
// Visit a site that is a PWA. The ambient badge should show.
String webBannerUrl = WebappTestPage.getServiceWorkerUrl(mTestServer);
resetEngagementForUrl(webBannerUrl, 10);
InfoBarContainer container = mTabbedActivityTestRule.getInfoBarContainer();
final InfobarListener listener = new InfobarListener();
container.addAnimationListener(listener);
Tab tab = mTabbedActivityTestRule.getActivity().getActivityTab();
new TabLoadObserver(tab).fullyLoadUrl(webBannerUrl);
waitUntilAmbientBadgeInfoBarAppears(mTabbedActivityTestRule);
waitForHelpBubble(withText(R.string.iph_pwa_install_available_text)).perform(click());
assertThat(mTracker.wasDismissed(), is(true));
int callCount = mOnEventCallback.getCallCount();
TestThreadUtils.runOnUiThreadBlocking(() -> {
AppMenuCoordinator coordinator = mTabbedActivityTestRule.getAppMenuCoordinator();
AppMenuTestSupport.showAppMenu(coordinator, null, false);
AppMenuTestSupport.callOnItemClick(coordinator, R.id.add_to_homescreen_id);
});
mOnEventCallback.waitForCallback(callCount, 1);
assertThat(mTracker.getLastEvent(), is(EventConstants.PWA_INSTALL_MENU_SELECTED));
}
private ViewInteraction waitForHelpBubble(Matcher<View> matcher) {
View mainDecorView = mTabbedActivityTestRule.getActivity().getWindow().getDecorView();
return onView(isRoot())
.inRoot(RootMatchers.withDecorView(not(is(mainDecorView))))
.check(waitForView(matcher));
}
} }
...@@ -58,6 +58,8 @@ static_library("internal") { ...@@ -58,6 +58,8 @@ static_library("internal") {
"single_invalid_configuration.h", "single_invalid_configuration.h",
"stats.cc", "stats.cc",
"stats.h", "stats.h",
"switches.cc",
"switches.h",
"system_time_provider.cc", "system_time_provider.cc",
"system_time_provider.h", "system_time_provider.h",
"time_provider.h", "time_provider.h",
...@@ -78,9 +80,14 @@ static_library("internal") { ...@@ -78,9 +80,14 @@ static_library("internal") {
sources += [ sources += [
"android/tracker_impl_android.cc", "android/tracker_impl_android.cc",
"android/tracker_impl_android.h", "android/tracker_impl_android.h",
"android/wrapping_test_tracker.cc",
"android/wrapping_test_tracker.h",
] ]
deps += [ ":jni_headers" ] deps += [
":jni_headers",
"//components/feature_engagement/public:jni_headers",
]
} }
} }
......
...@@ -128,6 +128,12 @@ public class TrackerImpl implements Tracker { ...@@ -128,6 +128,12 @@ public class TrackerImpl implements Tracker {
TrackerImplJni.get().addOnInitializedCallback(mNativePtr, TrackerImpl.this, callback); TrackerImplJni.get().addOnInitializedCallback(mNativePtr, TrackerImpl.this, callback);
} }
@Override
public void injectTracker(Tracker tracker) {
assert mNativePtr != 0;
TrackerImplJni.get().injectTracker(mNativePtr, TrackerImpl.this, tracker);
}
@CalledByNative @CalledByNative
private void clearNativePtr() { private void clearNativePtr() {
mNativePtr = 0; mNativePtr = 0;
...@@ -155,6 +161,7 @@ public class TrackerImpl implements Tracker { ...@@ -155,6 +161,7 @@ public class TrackerImpl implements Tracker {
boolean isInitialized(long nativeTrackerImplAndroid, TrackerImpl caller); boolean isInitialized(long nativeTrackerImplAndroid, TrackerImpl caller);
void addOnInitializedCallback( void addOnInitializedCallback(
long nativeTrackerImplAndroid, TrackerImpl caller, Callback<Boolean> callback); long nativeTrackerImplAndroid, TrackerImpl caller, Callback<Boolean> callback);
void injectTracker(long nativeTrackerImplAndroid, TrackerImpl caller, Tracker tracker);
void release(long nativeDisplayLockHandleAndroid); void release(long nativeDisplayLockHandleAndroid);
} }
} }
...@@ -13,9 +13,12 @@ ...@@ -13,9 +13,12 @@
#include "base/android/jni_string.h" #include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h" #include "base/android/scoped_java_ref.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/command_line.h"
#include "base/feature_list.h" #include "base/feature_list.h"
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "components/feature_engagement/internal/android/wrapping_test_tracker.h"
#include "components/feature_engagement/internal/jni_headers/TrackerImpl_jni.h" #include "components/feature_engagement/internal/jni_headers/TrackerImpl_jni.h"
#include "components/feature_engagement/internal/switches.h"
#include "components/feature_engagement/public/feature_list.h" #include "components/feature_engagement/public/feature_list.h"
#include "components/feature_engagement/public/tracker.h" #include "components/feature_engagement/public/tracker.h"
...@@ -175,6 +178,20 @@ void TrackerImplAndroid::AddOnInitializedCallback( ...@@ -175,6 +178,20 @@ void TrackerImplAndroid::AddOnInitializedCallback(
base::android::ScopedJavaGlobalRef<jobject>(j_callback_obj))); base::android::ScopedJavaGlobalRef<jobject>(j_callback_obj)));
} }
void TrackerImplAndroid::InjectTracker(
JNIEnv* env,
const base::android::JavaRef<jobject>& jobj,
const base::android::JavaRef<jobject>& jtracker) {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (!command_line->HasSwitch(switches::kUseJavaProxyTracker)) {
NOTREACHED();
return;
}
WrappingTestTracker* test_tracker_ =
static_cast<WrappingTestTracker*>(tracker_impl_);
test_tracker_->InjectTracker(jtracker);
}
DisplayLockHandleAndroid::DisplayLockHandleAndroid( DisplayLockHandleAndroid::DisplayLockHandleAndroid(
std::unique_ptr<DisplayLockHandle> display_lock_handle) std::unique_ptr<DisplayLockHandle> display_lock_handle)
: display_lock_handle_(std::move(display_lock_handle)) { : display_lock_handle_(std::move(display_lock_handle)) {
......
...@@ -102,6 +102,14 @@ class TrackerImplAndroid : public base::SupportsUserData::Data { ...@@ -102,6 +102,14 @@ class TrackerImplAndroid : public base::SupportsUserData::Data {
const base::android::JavaRef<jobject>& jobj, const base::android::JavaRef<jobject>& jobj,
const base::android::JavaParamRef<jobject>& j_callback_obj); const base::android::JavaParamRef<jobject>& j_callback_obj);
/**
* Injects a Java tracker for encapsulation. For further details, see
* WrappingTestTracker::InjectTracker.
*/
void InjectTracker(JNIEnv* env,
const base::android::JavaRef<jobject>& jobj,
const base::android::JavaRef<jobject>& jtracker);
private: private:
// A map from the feature name to the base::Feature, to ensure that the Java // A map from the feature name to the base::Feature, to ensure that the Java
// version of the API can use the string name. If base::Feature becomes a Java // version of the API can use the string name. If base::Feature becomes a Java
......
// 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.
#include "components/feature_engagement/internal/android/wrapping_test_tracker.h"
#include <memory>
#include <utility>
#include "base/android/jni_string.h"
#include "components/feature_engagement/internal/availability_model_impl.h"
#include "components/feature_engagement/internal/display_lock_controller_impl.h"
#include "components/feature_engagement/internal/editable_configuration.h"
#include "components/feature_engagement/internal/event_model_impl.h"
#include "components/feature_engagement/internal/feature_config_condition_validator.h"
#include "components/feature_engagement/internal/system_time_provider.h"
#include "components/feature_engagement/public/jni_headers/CppWrappedTestTracker_jni.h"
namespace feature_engagement {
WrappingTestTracker::WrappingTestTracker(
std::unique_ptr<EventModel> event_model,
std::unique_ptr<AvailabilityModel> availability_model,
std::unique_ptr<Configuration> configuration,
std::unique_ptr<DisplayLockController> display_lock_controller,
std::unique_ptr<ConditionValidator> condition_validator,
std::unique_ptr<TimeProvider> time_provider)
: TrackerImpl(std::move(event_model),
std::move(availability_model),
std::move(configuration),
std::make_unique<DisplayLockControllerImpl>(),
std::move(condition_validator),
std::move(time_provider)) {}
WrappingTestTracker::~WrappingTestTracker() {}
void WrappingTestTracker::InjectTracker(
const base::android::JavaRef<jobject>& jtracker) {
java_tracker_ = jtracker;
}
void WrappingTestTracker::NotifyEvent(const std::string& event) {
if (!java_tracker_) {
TrackerImpl::NotifyEvent(event);
return;
}
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaLocalRef<jstring> jevent(
base::android::ConvertUTF8ToJavaString(env, event.c_str()));
Java_CppWrappedTestTracker_notifyEvent(base::android::AttachCurrentThread(),
java_tracker_, jevent);
}
bool WrappingTestTracker::ShouldTriggerHelpUI(const base::Feature& feature) {
if (!java_tracker_)
return TrackerImpl::ShouldTriggerHelpUI(feature);
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaLocalRef<jstring> jfeature(
base::android::ConvertUTF8ToJavaString(env, feature.name));
return Java_CppWrappedTestTracker_shouldTriggerHelpUI(
base::android::AttachCurrentThread(), java_tracker_, jfeature);
}
bool WrappingTestTracker::WouldTriggerHelpUI(
const base::Feature& feature) const {
if (!java_tracker_)
return TrackerImpl::WouldTriggerHelpUI(feature);
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaLocalRef<jstring> jfeature(
base::android::ConvertUTF8ToJavaString(env, feature.name));
return Java_CppWrappedTestTracker_wouldTriggerHelpUI(
base::android::AttachCurrentThread(), java_tracker_, jfeature);
}
bool WrappingTestTracker::HasEverTriggered(const base::Feature& feature,
bool from_window) const {
if (!java_tracker_)
return TrackerImpl::HasEverTriggered(feature, from_window);
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaLocalRef<jstring> jfeature(
base::android::ConvertUTF8ToJavaString(env, feature.name));
return Java_CppWrappedTestTracker_hasEverTriggered(
base::android::AttachCurrentThread(), java_tracker_, jfeature,
from_window);
}
Tracker::TriggerState WrappingTestTracker::GetTriggerState(
const base::Feature& feature) const {
if (!java_tracker_)
return TrackerImpl::GetTriggerState(feature);
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaLocalRef<jstring> jfeature(
base::android::ConvertUTF8ToJavaString(env, feature.name));
return static_cast<Tracker::TriggerState>(
Java_CppWrappedTestTracker_getTriggerState(
base::android::AttachCurrentThread(), java_tracker_, jfeature));
}
void WrappingTestTracker::Dismissed(const base::Feature& feature) {
if (!java_tracker_) {
TrackerImpl::Dismissed(feature);
return;
}
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaLocalRef<jstring> jfeature(
base::android::ConvertUTF8ToJavaString(env, feature.name));
Java_CppWrappedTestTracker_dismissed(base::android::AttachCurrentThread(),
java_tracker_, jfeature);
}
std::unique_ptr<DisplayLockHandle> WrappingTestTracker::AcquireDisplayLock() {
return TrackerImpl::AcquireDisplayLock();
}
bool WrappingTestTracker::IsInitialized() const {
if (!java_tracker_)
return TrackerImpl::IsInitialized();
return Java_CppWrappedTestTracker_isInitialized(
base::android::AttachCurrentThread(), java_tracker_);
}
void WrappingTestTracker::AddOnInitializedCallback(
OnInitializedCallback callback) {
TrackerImpl::AddOnInitializedCallback(std::move(callback));
}
} // namespace feature_engagement
// 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.
#ifndef COMPONENTS_FEATURE_ENGAGEMENT_INTERNAL_ANDROID_WRAPPING_TEST_TRACKER_H_
#define COMPONENTS_FEATURE_ENGAGEMENT_INTERNAL_ANDROID_WRAPPING_TEST_TRACKER_H_
#include <memory>
#include <string>
#include "base/callback.h"
#include "base/macros.h"
#include "build/build_config.h"
#include "components/feature_engagement/internal/tracker_impl.h"
#if defined(OS_ANDROID)
#include "base/android/jni_android.h"
#endif // defined(OS_ANDROID)
namespace feature_engagement {
class AvailabilityModel;
class Configuration;
class ConditionValidator;
class DisplayLockController;
class EventModel;
class TimeProvider;
// This class is a thin wrapper around TrackerImpl and has two modes of
// operating. To start with, it does nothing but forward all calls to its base
// class implementation. However, at some point a Java test can inject a
// tracker, at
// which point this class will start to forward the calls to Java. See
// InjectTracker for details.
class WrappingTestTracker : public TrackerImpl {
public:
WrappingTestTracker(
std::unique_ptr<EventModel> event_model,
std::unique_ptr<AvailabilityModel> availability_model,
std::unique_ptr<Configuration> configuration,
std::unique_ptr<DisplayLockController> display_lock_controller,
std::unique_ptr<ConditionValidator> condition_validator,
std::unique_ptr<TimeProvider> time_provider);
~WrappingTestTracker() override;
// Injects a tracker from Java to proxy calls to. By default, all functions
// will be forwarded to the base class for processing until this function is
// called, at which point the proxying calls to Java starts. Note that not all
// functions are proxied to Java. Some are still handled by the base class.
// See .cc for details.
void InjectTracker(const base::android::JavaRef<jobject>& jtracker);
// TrackerImpl:
void NotifyEvent(const std::string& event) override;
bool ShouldTriggerHelpUI(const base::Feature& feature) override;
bool WouldTriggerHelpUI(const base::Feature& feature) const override;
bool HasEverTriggered(const base::Feature& feature,
bool from_window) const override;
TriggerState GetTriggerState(const base::Feature& feature) const override;
void Dismissed(const base::Feature& feature) override;
std::unique_ptr<DisplayLockHandle> AcquireDisplayLock() override;
bool IsInitialized() const override;
void AddOnInitializedCallback(OnInitializedCallback callback) override;
private:
base::android::ScopedJavaGlobalRef<jobject> java_tracker_;
DISALLOW_COPY_AND_ASSIGN(WrappingTestTracker);
};
} // namespace feature_engagement
#endif // COMPONENTS_FEATURE_ENGAGEMENT_INTERNAL_ANDROID_WRAPPING_TEST_TRACKER_H_
// 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.
#include "components/feature_engagement/internal/switches.h"
namespace feature_engagement {
namespace switches {
// This switch causes TrackerImpl to be wrapped, so that functions can be
// proxied up into Java land, to facilitate end-to-end Java tests that exercise
// the feature engagement.
const char kUseJavaProxyTracker[] = "use-java-proxy-tracker";
} // namespace switches
} // namespace feature_engagement
\ No newline at end of file
// 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.
#ifndef COMPONENTS_FEATURE_ENGAGEMENT_INTERNAL_SWITCHES_H_
#define COMPONENTS_FEATURE_ENGAGEMENT_INTERNAL_SWITCHES_H_
namespace feature_engagement {
namespace switches {
extern const char kUseJavaProxyTracker[];
} // namespace switches
} // namespace feature_engagement
#endif // COMPONENTS_FEATURE_ENGAGEMENT_INTERNAL_SWITCHES_H_
...@@ -8,12 +8,14 @@ ...@@ -8,12 +8,14 @@
#include <utility> #include <utility>
#include "base/bind.h" #include "base/bind.h"
#include "base/command_line.h"
#include "base/feature_list.h" #include "base/feature_list.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/metrics/field_trial_params.h" #include "base/metrics/field_trial_params.h"
#include "base/metrics/user_metrics.h" #include "base/metrics/user_metrics.h"
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "components/feature_engagement/internal/availability_model_impl.h" #include "components/feature_engagement/internal/availability_model_impl.h"
#include "components/feature_engagement/internal/chrome_variations_configuration.h" #include "components/feature_engagement/internal/chrome_variations_configuration.h"
#include "components/feature_engagement/internal/display_lock_controller_impl.h" #include "components/feature_engagement/internal/display_lock_controller_impl.h"
...@@ -30,11 +32,16 @@ ...@@ -30,11 +32,16 @@
#include "components/feature_engagement/internal/persistent_event_store.h" #include "components/feature_engagement/internal/persistent_event_store.h"
#include "components/feature_engagement/internal/proto/availability.pb.h" #include "components/feature_engagement/internal/proto/availability.pb.h"
#include "components/feature_engagement/internal/stats.h" #include "components/feature_engagement/internal/stats.h"
#include "components/feature_engagement/internal/switches.h"
#include "components/feature_engagement/internal/system_time_provider.h" #include "components/feature_engagement/internal/system_time_provider.h"
#include "components/feature_engagement/public/feature_constants.h" #include "components/feature_engagement/public/feature_constants.h"
#include "components/feature_engagement/public/feature_list.h" #include "components/feature_engagement/public/feature_list.h"
#include "components/leveldb_proto/public/proto_database_provider.h" #include "components/leveldb_proto/public/proto_database_provider.h"
#if defined(OS_ANDROID)
#include "components/feature_engagement/internal/android/wrapping_test_tracker.h"
#endif
namespace feature_engagement { namespace feature_engagement {
namespace { namespace {
...@@ -133,6 +140,16 @@ Tracker* Tracker::Create( ...@@ -133,6 +140,16 @@ Tracker* Tracker::Create(
auto availability_model = std::make_unique<AvailabilityModelImpl>( auto availability_model = std::make_unique<AvailabilityModelImpl>(
std::move(availability_store_loader)); std::move(availability_store_loader));
#if defined(OS_ANDROID)
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kUseJavaProxyTracker)) {
return new WrappingTestTracker(
std::move(event_model), std::move(availability_model),
std::move(configuration), std::make_unique<DisplayLockControllerImpl>(),
std::move(condition_validator), std::move(time_provider));
}
#endif
return new TrackerImpl( return new TrackerImpl(
std::move(event_model), std::move(availability_model), std::move(event_model), std::move(availability_model),
std::move(configuration), std::make_unique<DisplayLockControllerImpl>(), std::move(configuration), std::make_unique<DisplayLockControllerImpl>(),
......
...@@ -48,15 +48,18 @@ source_set("unit_tests") { ...@@ -48,15 +48,18 @@ source_set("unit_tests") {
if (is_android) { if (is_android) {
android_library("public_java") { android_library("public_java") {
sources = [ sources = [
"android/java/src/org/chromium/components/feature_engagement/CppWrappedTestTracker.java",
"android/java/src/org/chromium/components/feature_engagement/EventConstants.java", "android/java/src/org/chromium/components/feature_engagement/EventConstants.java",
"android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java", "android/java/src/org/chromium/components/feature_engagement/FeatureConstants.java",
"android/java/src/org/chromium/components/feature_engagement/Tracker.java", "android/java/src/org/chromium/components/feature_engagement/Tracker.java",
] ]
deps = [ deps = [
":jni_headers",
"//base:base_java", "//base:base_java",
"//third_party/android_deps:androidx_annotation_annotation_java", "//third_party/android_deps:androidx_annotation_annotation_java",
] ]
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
srcjar_deps = [ ":public_java_enums_srcjar" ] srcjar_deps = [ ":public_java_enums_srcjar" ]
} }
...@@ -66,4 +69,8 @@ if (is_android) { ...@@ -66,4 +69,8 @@ if (is_android) {
sources = [ "tracker.h" ] sources = [ "tracker.h" ]
} }
generate_jni("jni_headers") {
sources = [ "android/java/src/org/chromium/components/feature_engagement/CppWrappedTestTracker.java" ]
}
} }
// 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.feature_engagement;
import android.text.TextUtils;
import androidx.annotation.CheckResult;
import androidx.annotation.Nullable;
import org.chromium.base.Callback;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
/**
* CppWrappedTestTracker is a Java implementation of a {@link Tracker} object that is
* encapsulated by a Tracker object in C++, which will proxy (most) calls over from C++ to
* Java.
*
* NOTE: For testing, most of the time this class is overkill and it suffices to create a test
* object that derives from Tracker and call TrackerFactory#setTrackerForTests on it. However, this
* will only replace the Tracker object on the Java side, and that object will never receive the
* notifyEvent calls that the actual tracker in C++ receives. So, if receiving events is important
* for your test, you may need {@link CppWrapperTestTracker}.
*
* Example usage in tests:
*
* mTracker = new CppWrappedTestTracker(FeatureConstants.YOU_FEATURE_HERE) {
* @Override
* public void notifyEvent(String event) {
* super.notifyEvent(event);
* // Validate that the right event was received.
* }
* };
* TrackerFactory.getTrackerForProfile(profile).injectTracker(mTracker);
*/
@JNINamespace("feature_engagement")
public class CppWrappedTestTracker implements Tracker {
private String mOurFeature;
private boolean mWasDismissed;
private String mLastEvent;
public CppWrappedTestTracker(String feature) {
mOurFeature = feature;
}
public boolean wasDismissed() {
return mWasDismissed;
}
public String getLastEvent() {
return mLastEvent;
}
@CalledByNative
@Override
public void notifyEvent(String event) {
mLastEvent = event;
}
@CheckResult
@CalledByNative
@Override
public boolean shouldTriggerHelpUI(String feature) {
return ourFeature(feature);
}
@CalledByNative
@Override
public boolean wouldTriggerHelpUI(String feature) {
return ourFeature(feature);
}
@CalledByNative
@Override
public boolean hasEverTriggered(String feature, boolean fromWindow) {
return true;
}
@TriggerState
@CalledByNative
@Override
public int getTriggerState(String feature) {
return ourFeature(feature) ? TriggerState.HAS_NOT_BEEN_DISPLAYED
: TriggerState.HAS_BEEN_DISPLAYED;
}
@CalledByNative
@Override
public void dismissed(String feature) {
if (ourFeature(feature)) {
mWasDismissed = true;
}
}
@CheckResult
@Nullable
@Override
public DisplayLockHandle acquireDisplayLock() {
assert false : "This should only be called on a production tracker";
return () -> {};
}
@CalledByNative
@Override
public boolean isInitialized() {
return true;
}
@Override
public void addOnInitializedCallback(Callback<Boolean> callback) {
assert false : "This should only be called on a production tracker";
callback.onResult(true);
}
@Override
public final void injectTracker(Tracker tracker) {
assert false : "This should only be called on a production tracker";
}
private boolean ourFeature(String feature) {
return TextUtils.equals(mOurFeature, feature);
}
}
...@@ -202,6 +202,9 @@ public final class EventConstants { ...@@ -202,6 +202,9 @@ public final class EventConstants {
/** Reengagement events. */ /** Reengagement events. */
public static final String STARTED_FROM_MAIN_INTENT = "started_from_main_intent"; public static final String STARTED_FROM_MAIN_INTENT = "started_from_main_intent";
/** PWA install events. */
public static final String PWA_INSTALL_MENU_SELECTED = "pwa_install_menu_clicked";
/** /**
* Do not instantiate. * Do not instantiate.
*/ */
......
...@@ -127,4 +127,11 @@ public interface Tracker { ...@@ -127,4 +127,11 @@ public interface Tracker {
* ready to receive calls. * ready to receive calls.
*/ */
void addOnInitializedCallback(Callback<Boolean> callback); void addOnInitializedCallback(Callback<Boolean> callback);
/**
* Instructs the tracker in C++ to start proxying calls to Java. See {@link
* CppWrappedTestTracker} for usage and details.
* @param tracker The tracker object that should be receiving the calls.
*/
void injectTracker(Tracker tracker);
} }
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