[go: up one dir, main page]

blob: fff06922fb1dfea00b186d0ce766909073a9f556 [file] [log] [blame]
// Copyright 2018 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.feed;
import android.support.annotation.NonNull;
import com.google.android.libraries.feed.api.client.stream.Stream.ScrollListener;
import com.google.android.libraries.feed.api.host.logging.ActionType;
import com.google.android.libraries.feed.api.host.logging.BasicLoggingApi;
import com.google.android.libraries.feed.api.host.logging.ContentLoggingData;
import com.google.android.libraries.feed.api.host.logging.ElementLoggingData;
import com.google.android.libraries.feed.api.host.logging.InternalFeedError;
import com.google.android.libraries.feed.api.host.logging.RequestReason;
import com.google.android.libraries.feed.api.host.logging.ScrollType;
import com.google.android.libraries.feed.api.host.logging.SessionEvent;
import com.google.android.libraries.feed.api.host.logging.SpinnerType;
import com.google.android.libraries.feed.api.host.logging.Task;
import com.google.android.libraries.feed.api.host.logging.ZeroStateShowReason;
import com.google.search.now.ui.action.FeedActionProto;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.chrome.browser.ntp.NewTabPageUma;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.ui.mojom.WindowOpenDisposition;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Implementation of {@link BasicLoggingApi} that log actions performed on the Feed,
* and provides access to native implementation of feed logging.
*/
@JNINamespace("feed")
public class FeedLoggingBridge implements BasicLoggingApi {
private long mNativeFeedLoggingBridge;
/**
* Creates a {@link FeedLoggingBridge} for accessing native feed logging
* implementation for the current user, and initial native side bridge.
*
* @param profile {@link Profile} of the user we are rendering the Feed for.
*/
public FeedLoggingBridge(Profile profile) {
mNativeFeedLoggingBridge = nativeInit(profile);
}
/** Cleans up native half of this bridge. */
public void destroy() {
// Bridge could have been destroyed for policy when this is called.
// See https://crbug.com/901414.
if (mNativeFeedLoggingBridge == 0) return;
nativeDestroy(mNativeFeedLoggingBridge);
mNativeFeedLoggingBridge = 0;
}
@Override
public void onContentViewed(ContentLoggingData data) {
// Bridge could have been destroyed for policy when this is called.
// See https://crbug.com/901414.
if (mNativeFeedLoggingBridge == 0) return;
nativeOnContentViewed(mNativeFeedLoggingBridge, data.getPositionInStream(),
TimeUnit.SECONDS.toMillis(data.getPublishedTimeSeconds()),
TimeUnit.SECONDS.toMillis(data.getTimeContentBecameAvailable()), data.getScore(),
data.isAvailableOffline());
}
@Override
public void onContentDismissed(ContentLoggingData data, boolean wasCommitted) {
// Bridge could have been destroyed for policy when this is called.
// See https://crbug.com/901414.
if (mNativeFeedLoggingBridge == 0) return;
nativeOnContentDismissed(mNativeFeedLoggingBridge, data.getPositionInStream(),
data.getRepresentationUri(), wasCommitted);
}
@Override
public void onContentSwiped(ContentLoggingData data) {
// Bridge could have been destroyed for policy when this is called.
// See https://crbug.com/901414.
if (mNativeFeedLoggingBridge == 0) return;
nativeOnContentSwiped(mNativeFeedLoggingBridge);
}
@Override
public void onContentClicked(ContentLoggingData data) {
// Records content's clicks in onClientAction. When a user clicks on content, Feed libraries
// will call both onClientAction and onContentClicked, and onClientAction will receive
// ActionType.OPEN_URL in this case. so to avoid double counts, we records content's clicks
// in onClientAction.
}
@Override
public void onClientAction(ContentLoggingData data, @ActionType int actionType) {
// Bridge could have been destroyed for policy when this is called.
// See https://crbug.com/901414.
if (mNativeFeedLoggingBridge == 0) return;
recordUserAction(actionType);
nativeOnClientAction(mNativeFeedLoggingBridge,
feedActionToWindowOpenDisposition(actionType), data.getPositionInStream(),
TimeUnit.SECONDS.toMillis(data.getPublishedTimeSeconds()), data.getScore(),
data.isAvailableOffline());
}
@Override
public void onContentContextMenuOpened(ContentLoggingData data) {
// Bridge could have been destroyed for policy when this is called.
// See https://crbug.com/901414.
if (mNativeFeedLoggingBridge == 0) return;
nativeOnContentContextMenuOpened(mNativeFeedLoggingBridge, data.getPositionInStream(),
TimeUnit.SECONDS.toMillis(data.getPublishedTimeSeconds()), data.getScore());
}
@Override
public void onMoreButtonViewed(int position) {
// Bridge could have been destroyed for policy when this is called.
// See https://crbug.com/901414.
if (mNativeFeedLoggingBridge == 0) return;
nativeOnMoreButtonViewed(mNativeFeedLoggingBridge, position);
}
@Override
public void onMoreButtonClicked(int position) {
// Bridge could have been destroyed for policy when this is called.
// See https://crbug.com/901414.
if (mNativeFeedLoggingBridge == 0) return;
nativeOnMoreButtonClicked(mNativeFeedLoggingBridge, position);
}
@Override
public void onNotInterestedIn(int interestType, ContentLoggingData data, boolean wasCommitted) {
// Bridge could have been destroyed for policy when this is called.
// See https://crbug.com/901414.
if (mNativeFeedLoggingBridge == 0) return;
// TODO(crbug.com/935602): Fail to compile when new values are added to NotInterestedInData.
if (interestType == FeedActionProto.NotInterestedInData.RecordedInterestType.TOPIC_VALUE) {
nativeOnNotInterestedInTopic(
mNativeFeedLoggingBridge, data.getPositionInStream(), wasCommitted);
} else if (interestType
== FeedActionProto.NotInterestedInData.RecordedInterestType.SOURCE_VALUE) {
nativeOnNotInterestedInSource(
mNativeFeedLoggingBridge, data.getPositionInStream(), wasCommitted);
}
}
@Override
public void onOpenedWithContent(int timeToPopulateMs, int contentCount) {
// Bridge could have been destroyed for policy when this is called.
// See https://crbug.com/901414.
if (mNativeFeedLoggingBridge == 0) return;
nativeOnOpenedWithContent(mNativeFeedLoggingBridge, timeToPopulateMs, contentCount);
}
@Override
public void onOpenedWithNoImmediateContent() {
// Bridge could have been destroyed for policy when this is called.
// See https://crbug.com/901414.
if (mNativeFeedLoggingBridge == 0) return;
nativeOnOpenedWithNoImmediateContent(mNativeFeedLoggingBridge);
}
@Override
public void onOpenedWithNoContent() {
// Bridge could have been destroyed for policy when this is called.
// See https://crbug.com/901414.
if (mNativeFeedLoggingBridge == 0) return;
nativeOnOpenedWithNoContent(mNativeFeedLoggingBridge);
}
@Override
public void onSpinnerStarted(@SpinnerType int spinnerType) {
// Bridge could have been destroyed for policy when this is called.
// See https://crbug.com/901414.
if (mNativeFeedLoggingBridge == 0) return;
nativeOnSpinnerStarted(mNativeFeedLoggingBridge, spinnerType);
}
@Override
public void onSpinnerFinished(int timeShownMs, @SpinnerType int spinnerType) {
// Bridge could have been destroyed for policy when this is called.
// See https://crbug.com/901414.
if (mNativeFeedLoggingBridge == 0) return;
nativeOnSpinnerFinished(mNativeFeedLoggingBridge, timeShownMs, spinnerType);
}
@Override
public void onSpinnerDestroyedWithoutCompleting(int timeShownMs, @SpinnerType int spinnerType) {
// Bridge could have been destroyed for policy when this is called.
// See https://crbug.com/901414.
if (mNativeFeedLoggingBridge == 0) return;
nativeOnSpinnerDestroyedWithoutCompleting(
mNativeFeedLoggingBridge, timeShownMs, spinnerType);
}
@Override
public void onPietFrameRenderingEvent(List<Integer> pietErrorCodes) {
int[] pietErrorCodesArray = new int[pietErrorCodes.size()];
for (int i = 0; i < pietErrorCodes.size(); ++i) {
pietErrorCodesArray[i] = pietErrorCodes.get(i);
}
nativeOnPietFrameRenderingEvent(mNativeFeedLoggingBridge, pietErrorCodesArray);
}
@Override
public void onVisualElementClicked(ElementLoggingData data, int elementType) {
// TODO(https://crbug.com/924739): Implementation.
}
@Override
public void onVisualElementViewed(ElementLoggingData data, int elementType) {
// TODO(https://crbug.com/924739): Implementation.
}
@Override
public void onInternalError(@InternalFeedError int internalError) {
if (mNativeFeedLoggingBridge == 0) return;
nativeOnInternalError(mNativeFeedLoggingBridge, internalError);
}
@Override
public void onTokenCompleted(boolean wasSynthetic, int contentCount, int tokenCount) {
if (mNativeFeedLoggingBridge == 0) return;
nativeOnTokenCompleted(mNativeFeedLoggingBridge, wasSynthetic, contentCount, tokenCount);
}
@Override
public void onTokenFailedToComplete(boolean wasSynthetic, int failureCount) {
if (mNativeFeedLoggingBridge == 0) return;
nativeOnTokenFailedToComplete(mNativeFeedLoggingBridge, wasSynthetic, failureCount);
}
@Override
public void onServerRequest(@RequestReason int requestReason) {
if (mNativeFeedLoggingBridge == 0) return;
nativeOnServerRequest(mNativeFeedLoggingBridge, requestReason);
}
@Override
public void onZeroStateShown(@ZeroStateShowReason int zeroStateShowReason) {
if (mNativeFeedLoggingBridge == 0) return;
nativeOnZeroStateShown(mNativeFeedLoggingBridge, zeroStateShowReason);
}
@Override
public void onZeroStateRefreshCompleted(int newContentCount, int newTokenCount) {
if (mNativeFeedLoggingBridge == 0) return;
nativeOnZeroStateRefreshCompleted(mNativeFeedLoggingBridge, newContentCount, newTokenCount);
}
@Override
public void onInitialSessionEvent(
@SessionEvent int sessionEvent, int timeFromRegisteringMs, int sessionCount) {
// TODO(https://crbug.com/924739): Implementation.
}
@Override
public void onScroll(@ScrollType int scrollType, int distanceScrolled) {
// TODO(https://crbug.com/924739): Implementation.
}
@Override
public void onTaskFinished(@Task int task, int delayTime, int taskTime) {
if (mNativeFeedLoggingBridge == 0) return;
nativeOnTaskFinished(mNativeFeedLoggingBridge, task, delayTime, taskTime);
}
/**
* Reports how long a user spends on the page.
*
* @param visitTimeMs Time spent reading the page.
* @param isOffline If the page is viewed in offline mode or not.
* @param returnToNtp User backed to NTP after visit the page.
*/
public void onContentTargetVisited(long visitTimeMs, boolean isOffline, boolean returnToNtp) {
// We cannot assume that the|mNativeFeedLoggingBridge| is always available like other
// methods. This method is called by objects not controlled by Feed lifetimes, and destroy()
// may have already been called if Feed is disabled by policy.
if (mNativeFeedLoggingBridge != 0) {
nativeOnContentTargetVisited(
mNativeFeedLoggingBridge, visitTimeMs, isOffline, returnToNtp);
}
}
private int feedActionToWindowOpenDisposition(@ActionType int actionType) {
switch (actionType) {
case ActionType.OPEN_URL:
return WindowOpenDisposition.CURRENT_TAB;
case ActionType.OPEN_URL_INCOGNITO:
return WindowOpenDisposition.OFF_THE_RECORD;
case ActionType.OPEN_URL_NEW_TAB:
return WindowOpenDisposition.NEW_BACKGROUND_TAB;
case ActionType.OPEN_URL_NEW_WINDOW:
return WindowOpenDisposition.NEW_WINDOW;
case ActionType.DOWNLOAD:
return WindowOpenDisposition.SAVE_TO_DISK;
case ActionType.LEARN_MORE:
case ActionType.UNKNOWN:
default:
return WindowOpenDisposition.UNKNOWN;
}
}
private void recordUserAction(@ActionType int actionType) {
switch (actionType) {
case ActionType.OPEN_URL:
case ActionType.OPEN_URL_INCOGNITO:
case ActionType.OPEN_URL_NEW_TAB:
case ActionType.OPEN_URL_NEW_WINDOW:
NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_SNIPPET);
break;
case ActionType.LEARN_MORE:
NewTabPageUma.recordAction(NewTabPageUma.ACTION_CLICKED_LEARN_MORE);
break;
case ActionType.DOWNLOAD:
case ActionType.UNKNOWN:
default:
break;
}
}
private void reportScrolledAfterOpen() {
// Bridge could have been destroyed for policy when this is called.
// See https://crbug.com/901414.
if (mNativeFeedLoggingBridge == 0) return;
nativeReportScrolledAfterOpen(mNativeFeedLoggingBridge);
}
/**
* One-shot reporter that records the first time the user scrolls in the {@link Stream}.
*/
public static class ScrollEventReporter implements ScrollListener {
private final FeedLoggingBridge mLoggingBridge;
private boolean mFired;
public ScrollEventReporter(@NonNull FeedLoggingBridge loggingBridge) {
super();
mLoggingBridge = loggingBridge;
}
@Override
public void onScrollStateChanged(@ScrollState int state) {
if (mFired) return;
if (state != ScrollState.DRAGGING) return;
mLoggingBridge.reportScrolledAfterOpen();
mFired = true;
}
@Override
public void onScrolled(int dx, int dy) {}
}
private native long nativeInit(Profile profile);
private native void nativeDestroy(long nativeFeedLoggingBridge);
private native void nativeOnContentViewed(long nativeFeedLoggingBridge, int position,
long publishedTimeMs, long timeContentBecameAvailableMs, float score,
boolean isAvailableOffline);
private native void nativeOnContentDismissed(
long nativeFeedLoggingBridge, int position, String uri, boolean wasCommitted);
private native void nativeOnContentSwiped(long nativeFeedLoggingBridge);
private native void nativeOnClientAction(long nativeFeedLoggingBridge,
int windowOpenDisposition, int position, long publishedTimeMs, float score,
boolean isAvailableOffline);
private native void nativeOnContentContextMenuOpened(
long nativeFeedLoggingBridge, int position, long publishedTimeMs, float score);
private native void nativeOnMoreButtonViewed(long nativeFeedLoggingBridge, int position);
private native void nativeOnMoreButtonClicked(long nativeFeedLoggingBridge, int position);
private native void nativeOnNotInterestedInSource(
long nativeFeedLoggingBridge, int position, boolean wasCommitted);
private native void nativeOnNotInterestedInTopic(
long nativeFeedLoggingBridge, int position, boolean wasCommitted);
private native void nativeOnOpenedWithContent(
long nativeFeedLoggingBridge, int timeToPopulateMs, int contentCount);
private native void nativeOnOpenedWithNoImmediateContent(long nativeFeedLoggingBridge);
private native void nativeOnOpenedWithNoContent(long nativeFeedLoggingBridge);
private native void nativeOnSpinnerStarted(long nativeFeedLoggingBridge, int spinnerType);
private native void nativeOnSpinnerFinished(
long nativeFeedLoggingBridge, long spinnerShownTimeMs, int spinnerType);
private native void nativeOnSpinnerDestroyedWithoutCompleting(
long nativeFeedLoggingBridge, long spinnerShownTimeMs, int spinnerType);
private native void nativeOnPietFrameRenderingEvent(
long nativeFeedLoggingBridge, int[] pietErrorCodes);
private native void nativeOnInternalError(long nativeFeedLoggingBridge, int internalError);
private native void nativeOnTokenCompleted(
long nativeFeedLoggingBridge, boolean wasSynthetic, int contentCount, int tokenCount);
private native void nativeOnTokenFailedToComplete(
long nativeFeedLoggingBridge, boolean wasSynthetic, int failureCount);
private native void nativeOnServerRequest(long nativeFeedLoggingBridge, int requestReason);
private native void nativeOnZeroStateShown(
long nativeFeedLoggingBridge, int zeroStateShowReason);
private native void nativeOnZeroStateRefreshCompleted(
long nativeFeedLoggingBridge, int newContentCount, int newTokenCount);
private native void nativeOnTaskFinished(
long nativeFeedLoggingBridge, int task, int delayTimeMs, int taskTimeMs);
private native void nativeOnContentTargetVisited(
long nativeFeedLoggingBridge, long visitTimeMs, boolean isOffline, boolean returnToNtp);
private native void nativeReportScrolledAfterOpen(long nativeFeedLoggingBridge);
}