Adds offline status to ContentLoggingData.

PiperOrigin-RevId: 250711487
Change-Id: I08046448df5b8d88cea139f2f6843c1917733619
diff --git a/src/main/java/com/google/android/libraries/feed/api/host/logging/ContentLoggingData.java b/src/main/java/com/google/android/libraries/feed/api/host/logging/ContentLoggingData.java
index e632553..a6e1c1d 100644
--- a/src/main/java/com/google/android/libraries/feed/api/host/logging/ContentLoggingData.java
+++ b/src/main/java/com/google/android/libraries/feed/api/host/logging/ContentLoggingData.java
@@ -23,4 +23,13 @@
   /** Gets the score which was given to content from NowStream. */
   float getScore();
 
+  /**
+   * Gets offline availability status.
+   *
+   * <p>Note: The offline availability status for one piece of content can change. When this is
+   * logged, it should be logged with the offline status as of logging time. This means that one
+   * piece of content can emit multiple {@link ContentLoggingData} instances which are not equal to
+   * each other.
+   */
+  boolean isAvailableOffline();
 }
diff --git a/src/main/java/com/google/android/libraries/feed/basicstream/internal/actions/BUILD b/src/main/java/com/google/android/libraries/feed/basicstream/internal/actions/BUILD
index bd37b15..55b43e7 100644
--- a/src/main/java/com/google/android/libraries/feed/basicstream/internal/actions/BUILD
+++ b/src/main/java/com/google/android/libraries/feed/basicstream/internal/actions/BUILD
@@ -13,6 +13,7 @@
         "//src/main/java/com/google/android/libraries/feed/api/internal/actionmanager",
         "//src/main/java/com/google/android/libraries/feed/api/internal/actionparser",
         "//src/main/java/com/google/android/libraries/feed/basicstream/internal/pendingdismiss",
+        "//src/main/java/com/google/android/libraries/feed/common/functional",
         "//src/main/java/com/google/android/libraries/feed/sharedstream/contextmenumanager",
         "//src/main/java/com/google/android/libraries/feed/sharedstream/pendingdismiss",
         "//src/main/proto/com/google/android/libraries/feed/api/internal/proto:client_feed_java_proto_lite",
diff --git a/src/main/java/com/google/android/libraries/feed/basicstream/internal/actions/StreamActionApiImpl.java b/src/main/java/com/google/android/libraries/feed/basicstream/internal/actions/StreamActionApiImpl.java
index a889c9d..d41e9a6 100644
--- a/src/main/java/com/google/android/libraries/feed/basicstream/internal/actions/StreamActionApiImpl.java
+++ b/src/main/java/com/google/android/libraries/feed/basicstream/internal/actions/StreamActionApiImpl.java
@@ -28,6 +28,7 @@
 import com.google.android.libraries.feed.api.internal.actionparser.ActionParser;
 import com.google.android.libraries.feed.api.internal.actionparser.ActionSource;
 import com.google.android.libraries.feed.basicstream.internal.pendingdismiss.ClusterPendingDismissHelper;
+import com.google.android.libraries.feed.common.functional.Supplier;
 import com.google.android.libraries.feed.sharedstream.contextmenumanager.ContextMenuManager;
 import com.google.android.libraries.feed.sharedstream.pendingdismiss.PendingDismissCallback;
 import com.google.search.now.feed.client.StreamDataProto.StreamDataOperation;
@@ -49,7 +50,7 @@
   private final ActionParser actionParser;
   private final ActionManager actionManager;
   private final BasicLoggingApi basicLoggingApi;
-  private final ContentLoggingData contentLoggingData;
+  private final Supplier<ContentLoggingData> contentLoggingData;
   private final ContextMenuManager contextMenuManager;
   private final ClusterPendingDismissHelper clusterPendingDismissHelper;
   private final ViewElementActionHandler viewElementActionHandler;
@@ -63,7 +64,7 @@
       ActionParser actionParser,
       ActionManager actionManager,
       BasicLoggingApi basicLoggingApi,
-      ContentLoggingData contentLoggingData,
+      Supplier<ContentLoggingData> contentLoggingData,
       ContextMenuManager contextMenuManager,
       /*@Nullable*/ String sessionId,
       ClusterPendingDismissHelper clusterPendingDismissHelper,
@@ -107,7 +108,7 @@
                     ActionSource.CONTEXT_MENU));
 
     if (menuOpened) {
-      basicLoggingApi.onContentContextMenuOpened(contentLoggingData);
+      basicLoggingApi.onContentContextMenuOpened(contentLoggingData.get());
     }
   }
 
@@ -135,7 +136,7 @@
     if (!undoAction.hasConfirmationLabel()) {
       dismiss(dataOperations);
       basicLoggingApi.onNotInterestedIn(
-          interestType, contentLoggingData, /* wasCommitted = */ true);
+          interestType, contentLoggingData.get(), /* wasCommitted = */ true);
     } else {
       dismissWithSnackbar(
           undoAction,
@@ -143,7 +144,7 @@
             @Override
             public void onDismissReverted() {
               basicLoggingApi.onNotInterestedIn(
-                  interestType, contentLoggingData, /* wasCommitted = */ false);
+                  interestType, contentLoggingData.get(), /* wasCommitted = */ false);
             }
 
             @Override
@@ -151,7 +152,7 @@
               dismiss(dataOperations);
               actionManager.createAndUploadAction(contentId, payload);
               basicLoggingApi.onNotInterestedIn(
-                  interestType, contentLoggingData, /* wasCommitted = */ true);
+                  interestType, contentLoggingData.get(), /* wasCommitted = */ true);
             }
           });
     }
@@ -165,14 +166,15 @@
       ActionPayload payload) {
     if (!undoAction.hasConfirmationLabel()) {
       dismissLocal(contentId, dataOperations);
-      basicLoggingApi.onContentDismissed(contentLoggingData, /* wasCommitted = */ true);
+      basicLoggingApi.onContentDismissed(contentLoggingData.get(), /* wasCommitted = */ true);
     } else {
       dismissWithSnackbar(
           undoAction,
           new PendingDismissCallback() {
             @Override
             public void onDismissReverted() {
-              basicLoggingApi.onContentDismissed(contentLoggingData, /* wasCommitted = */ false);
+              basicLoggingApi.onContentDismissed(
+                  contentLoggingData.get(), /* wasCommitted = */ false);
             }
 
             @Override
@@ -180,7 +182,8 @@
               dismissLocal(contentId, dataOperations);
               dismiss(dataOperations);
               actionManager.createAndUploadAction(contentId, payload);
-              basicLoggingApi.onContentDismissed(contentLoggingData, /* wasCommitted = */ true);
+              basicLoggingApi.onContentDismissed(
+                  contentLoggingData.get(), /* wasCommitted = */ true);
             }
           });
     }
@@ -201,7 +204,7 @@
 
   @Override
   public void onClientAction(@ActionType int actionType) {
-    basicLoggingApi.onClientAction(contentLoggingData, actionType);
+    basicLoggingApi.onClientAction(contentLoggingData.get(), actionType);
   }
 
   @Override
@@ -331,7 +334,7 @@
 
   @Override
   public void onElementClick(int elementType) {
-    basicLoggingApi.onVisualElementClicked(contentLoggingData, elementType);
+    basicLoggingApi.onVisualElementClicked(contentLoggingData.get(), elementType);
   }
 
   @Override
diff --git a/src/main/java/com/google/android/libraries/feed/basicstream/internal/drivers/ContentDriver.java b/src/main/java/com/google/android/libraries/feed/basicstream/internal/drivers/ContentDriver.java
index 726078b..386d82d 100644
--- a/src/main/java/com/google/android/libraries/feed/basicstream/internal/drivers/ContentDriver.java
+++ b/src/main/java/com/google/android/libraries/feed/basicstream/internal/drivers/ContentDriver.java
@@ -44,6 +44,7 @@
 import com.google.android.libraries.feed.common.concurrent.CancelableTask;
 import com.google.android.libraries.feed.common.concurrent.MainThreadRunner;
 import com.google.android.libraries.feed.common.functional.Consumer;
+import com.google.android.libraries.feed.common.functional.Supplier;
 import com.google.android.libraries.feed.common.logging.Logger;
 import com.google.android.libraries.feed.sharedstream.constants.Constants;
 import com.google.android.libraries.feed.sharedstream.contextmenumanager.ContextMenuManager;
@@ -55,7 +56,6 @@
 import com.google.search.now.ui.piet.PietProto.Frame;
 import com.google.search.now.ui.piet.PietProto.PietSharedState;
 import com.google.search.now.ui.stream.StreamStructureProto;
-import com.google.search.now.ui.stream.StreamStructureProto.BasicLoggingMetadata;
 import com.google.search.now.ui.stream.StreamStructureProto.Content;
 import com.google.search.now.ui.stream.StreamStructureProto.PietContent;
 import com.google.search.now.ui.stream.StreamStructureProto.RepresentationData;
@@ -71,7 +71,6 @@
   private static final String TAG = "ContentDriver";
 
   private final BasicLoggingApi basicLoggingApi;
-  private final ContentLoggingData contentLoggingData;
   private final List<PietSharedState> pietSharedStates;
   private final Frame frame;
   private final StreamActionApi streamActionApi;
@@ -88,6 +87,7 @@
   private final ViewLoggingUpdater viewLoggingUpdater;
   private final ResettableOneShotVisibilityLoggingListener loggingListener;
 
+  private StreamContentLoggingData contentLoggingData;
   private boolean availableOffline;
   /*@Nullable*/ private PietViewHolder viewHolder;
 
@@ -125,8 +125,13 @@
     pietSharedStates = getPietSharedStates(pietContent, modelProvider, basicLoggingApi);
     contentId = contentFeatureModel.getStreamFeature().getContentId();
     RepresentationData representationData = content.getRepresentationData();
+    contentUrl = representationData.getUri();
+    availableOffline = streamOfflineMonitor.isAvailableOffline(contentUrl);
+    offlineStatusConsumer = new OfflineStatusConsumer();
+    streamOfflineMonitor.addOfflineStatusConsumer(contentUrl, offlineStatusConsumer);
     contentLoggingData =
-        createContentLoggingData(content.getBasicLoggingMetadata(), representationData, position);
+        new StreamContentLoggingData(
+            position, content.getBasicLoggingMetadata(), representationData, availableOffline);
     actionParser =
         actionParserFactory.build(
             () ->
@@ -138,7 +143,7 @@
             actionParser,
             actionManager,
             basicLoggingApi,
-            contentLoggingData,
+            () -> contentLoggingData,
             modelProvider.getSessionId(),
             contextMenuManager,
             clusterPendingDismissHelper,
@@ -147,10 +152,6 @@
             tooltipApi);
     this.swipeAction = swipeAction;
     this.streamOfflineMonitor = streamOfflineMonitor;
-    contentUrl = representationData.getUri();
-    availableOffline = streamOfflineMonitor.isAvailableOffline(contentUrl);
-    offlineStatusConsumer = new OfflineStatusConsumer();
-    streamOfflineMonitor.addOfflineStatusConsumer(contentUrl, offlineStatusConsumer);
     this.contentChangedListener = contentChangedListener;
     this.viewLoggingUpdater = viewLoggingUpdater;
     loggingListener = new ResettableOneShotVisibilityLoggingListener(this);
@@ -310,14 +311,6 @@
     }
   }
 
-  private ContentLoggingData createContentLoggingData(
-      /*@UnderInitialization*/ ContentDriver this,
-      BasicLoggingMetadata basicLoggingMetadata,
-      RepresentationData representationData,
-      int position) {
-    return new StreamContentLoggingData(position, basicLoggingMetadata, representationData);
-  }
-
   @VisibleForTesting
   StreamActionApi createStreamActionApi(
       /*@UnknownInitialization*/ ContentDriver this,
@@ -325,7 +318,7 @@
       ActionParser actionParser,
       ActionManager actionManager,
       BasicLoggingApi basicLoggingApi,
-      ContentLoggingData contentLoggingData,
+      Supplier<ContentLoggingData> contentLoggingData,
       /*@Nullable*/ String sessionId,
       ContextMenuManager contextMenuManager,
       ClusterPendingDismissHelper clusterPendingDismissHelper,
@@ -371,6 +364,7 @@
       }
 
       availableOffline = offlineStatus;
+      contentLoggingData = contentLoggingData.createWithOfflineStatus(offlineStatus);
       maybeRebind();
     }
   }
diff --git a/src/main/java/com/google/android/libraries/feed/sharedstream/logging/StreamContentLoggingData.java b/src/main/java/com/google/android/libraries/feed/sharedstream/logging/StreamContentLoggingData.java
index febd58e..bab34eb 100644
--- a/src/main/java/com/google/android/libraries/feed/sharedstream/logging/StreamContentLoggingData.java
+++ b/src/main/java/com/google/android/libraries/feed/sharedstream/logging/StreamContentLoggingData.java
@@ -18,6 +18,7 @@
 import com.google.search.now.feed.client.StreamDataProto.ClientBasicLoggingMetadata;
 import com.google.search.now.ui.stream.StreamStructureProto.BasicLoggingMetadata;
 import com.google.search.now.ui.stream.StreamStructureProto.RepresentationData;
+import java.util.Objects;
 
 /** Implementation of {@link ContentLoggingData} to capture content data when logging events. */
 public class StreamContentLoggingData implements ContentLoggingData {
@@ -27,19 +28,37 @@
   private final long timeContentBecameAvailable;
   private final float score;
   private final String representationUri;
+  private final boolean isAvailableOffline;
 
   public StreamContentLoggingData(
       int positionInStream,
       BasicLoggingMetadata basicLoggingMetadata,
-      RepresentationData representationData) {
-    this.positionInStream = positionInStream;
-    this.publishedTimeSeconds = representationData.getPublishedTimeSeconds();
-    this.timeContentBecameAvailable =
+      RepresentationData representationData,
+      boolean availableOffline) {
+    this(
+        positionInStream,
+        representationData.getPublishedTimeSeconds(),
         basicLoggingMetadata
             .getExtension(ClientBasicLoggingMetadata.clientBasicLoggingMetadata)
-            .getAvailabilityTimeSeconds();
-    this.score = basicLoggingMetadata.getScore();
-    this.representationUri = representationData.getUri();
+            .getAvailabilityTimeSeconds(),
+        basicLoggingMetadata.getScore(),
+        representationData.getUri(),
+        availableOffline);
+  }
+
+  private StreamContentLoggingData(
+      int positionInStream,
+      long publishedTimeSeconds,
+      long timeContentBecameAvailable,
+      float score,
+      String representationUri,
+      boolean isAvailableOffline) {
+    this.positionInStream = positionInStream;
+    this.publishedTimeSeconds = publishedTimeSeconds;
+    this.timeContentBecameAvailable = timeContentBecameAvailable;
+    this.score = score;
+    this.representationUri = representationUri;
+    this.isAvailableOffline = isAvailableOffline;
   }
 
   @Override
@@ -68,6 +87,22 @@
   }
 
   @Override
+  public boolean isAvailableOffline() {
+    return isAvailableOffline;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(
+        positionInStream,
+        publishedTimeSeconds,
+        timeContentBecameAvailable,
+        score,
+        representationUri,
+        isAvailableOffline);
+  }
+
+  @Override
   public boolean equals(/*@Nullable*/ Object o) {
     if (this == o) {
       return true;
@@ -90,19 +125,11 @@
     if (Float.compare(that.score, score) != 0) {
       return false;
     }
-    return representationUri != null
-        ? representationUri.equals(that.representationUri)
-        : that.representationUri == null;
-  }
+    if (isAvailableOffline != that.isAvailableOffline) {
+      return false;
+    }
 
-  @Override
-  public int hashCode() {
-    int result = positionInStream;
-    result = 31 * result + (int) (publishedTimeSeconds ^ (publishedTimeSeconds >>> 32));
-    result = 31 * result + (int) (timeContentBecameAvailable ^ (timeContentBecameAvailable >>> 32));
-    result = 31 * result + (score != +0.0f ? Float.floatToIntBits(score) : 0);
-    result = 31 * result + (representationUri != null ? representationUri.hashCode() : 0);
-    return result;
+    return Objects.equals(representationUri, that.representationUri);
   }
 
   @Override
@@ -121,4 +148,24 @@
         + '\''
         + '}';
   }
+
+  /**
+   * Returns a {@link StreamContentLoggingData} instance with {@code this} as a basis, but with the
+   * given offline status.
+   *
+   * <p>Will not create a new instance if unneeded.
+   */
+  public StreamContentLoggingData createWithOfflineStatus(boolean offlineStatus) {
+    if (offlineStatus == this.isAvailableOffline) {
+      return this;
+    }
+
+    return new StreamContentLoggingData(
+        positionInStream,
+        publishedTimeSeconds,
+        timeContentBecameAvailable,
+        score,
+        representationUri,
+        offlineStatus);
+  }
 }
diff --git a/src/test/java/com/google/android/libraries/feed/basicstream/internal/actions/BUILD b/src/test/java/com/google/android/libraries/feed/basicstream/internal/actions/BUILD
index ae8da16..b2cd092 100644
--- a/src/test/java/com/google/android/libraries/feed/basicstream/internal/actions/BUILD
+++ b/src/test/java/com/google/android/libraries/feed/basicstream/internal/actions/BUILD
@@ -20,12 +20,14 @@
         "//src/main/java/com/google/android/libraries/feed/basicstream/internal/pendingdismiss",
         "//src/main/java/com/google/android/libraries/feed/common/functional",
         "//src/main/java/com/google/android/libraries/feed/sharedstream/contextmenumanager",
+        "//src/main/java/com/google/android/libraries/feed/sharedstream/logging",
         "//src/main/java/com/google/android/libraries/feed/sharedstream/pendingdismiss",
         "//src/main/java/com/google/android/libraries/feed/sharedstream/publicapi/menumeasurer",
         "//src/main/java/com/google/android/libraries/feed/testing/sharedstream/contextmenumanager",
         "//src/main/proto/com/google/android/libraries/feed/api/internal/proto:client_feed_java_proto_lite",
         "//src/main/proto/search/now/ui/action:feed_action_java_proto_lite",
         "//src/main/proto/search/now/ui/action:feed_action_payload_java_proto_lite",
+        "//src/main/proto/search/now/ui/stream:stream_java_proto_lite",
         "//src/main/proto/search/now/wire/feed:feed_java_proto_lite",
         "//third_party:robolectric",
         "@com_google_protobuf_javalite//:protobuf_java_lite",
diff --git a/src/test/java/com/google/android/libraries/feed/basicstream/internal/actions/StreamActionApiImplTest.java b/src/test/java/com/google/android/libraries/feed/basicstream/internal/actions/StreamActionApiImplTest.java
index 53c651d..c74122a 100644
--- a/src/test/java/com/google/android/libraries/feed/basicstream/internal/actions/StreamActionApiImplTest.java
+++ b/src/test/java/com/google/android/libraries/feed/basicstream/internal/actions/StreamActionApiImplTest.java
@@ -42,6 +42,7 @@
 import com.google.android.libraries.feed.api.internal.actionparser.ActionSource;
 import com.google.android.libraries.feed.basicstream.internal.pendingdismiss.ClusterPendingDismissHelper;
 import com.google.android.libraries.feed.common.functional.Consumer;
+import com.google.android.libraries.feed.sharedstream.logging.StreamContentLoggingData;
 import com.google.android.libraries.feed.sharedstream.pendingdismiss.PendingDismissCallback;
 import com.google.android.libraries.feed.testing.sharedstream.contextmenumanager.FakeContextMenuManager;
 import com.google.common.collect.ImmutableList;
@@ -55,6 +56,8 @@
 import com.google.search.now.ui.action.FeedActionProto.OpenContextMenuData;
 import com.google.search.now.ui.action.FeedActionProto.OpenUrlData;
 import com.google.search.now.ui.action.FeedActionProto.UndoAction;
+import com.google.search.now.ui.stream.StreamStructureProto.BasicLoggingMetadata;
+import com.google.search.now.ui.stream.StreamStructureProto.RepresentationData;
 import com.google.search.now.wire.feed.ActionPayloadProto.ActionPayload;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -143,12 +146,12 @@
   @Mock private ActionParser actionParser;
   @Mock private ActionManager actionManager;
   @Mock private BasicLoggingApi basicLoggingApi;
-  @Mock private ContentLoggingData contentLoggingData;
   @Mock private ClusterPendingDismissHelper clusterPendingDismissHelper;
   @Mock private ViewElementActionHandler viewElementActionHandler;
   @Mock private TooltipApi tooltipApi;
 
   @Captor private ArgumentCaptor<Consumer<String>> consumerCaptor;
+  private ContentLoggingData contentLoggingData;
   private FakeContextMenuManager contextMenuManager;
   private StreamActionApiImpl streamActionApi;
   private View view;
@@ -158,6 +161,12 @@
     initMocks(this);
 
     Context context = Robolectric.buildActivity(Activity.class).get();
+    contentLoggingData =
+        new StreamContentLoggingData(
+            0,
+            BasicLoggingMetadata.getDefaultInstance(),
+            RepresentationData.getDefaultInstance(),
+            /* availableOffline= */ false);
     view = new View(context);
     contextMenuManager = new FakeContextMenuManager();
     streamActionApi =
@@ -166,7 +175,7 @@
             actionParser,
             actionManager,
             basicLoggingApi,
-            contentLoggingData,
+            () -> contentLoggingData,
             contextMenuManager,
             SESSION_ID,
             clusterPendingDismissHelper,
@@ -254,6 +263,22 @@
   }
 
   @Test
+  public void testOnElementClick_logsElementClicked_retrievesLoggingDataLazily() {
+    contentLoggingData =
+        new StreamContentLoggingData(
+            1,
+            BasicLoggingMetadata.getDefaultInstance(),
+            RepresentationData.getDefaultInstance(),
+            /* availableOffline= */ true);
+
+    streamActionApi.onElementClick(ElementType.INTEREST_HEADER.getNumber());
+
+    // Should use the new ContentLoggingData defined, not the old one.
+    verify(basicLoggingApi)
+        .onVisualElementClicked(contentLoggingData, ElementType.INTEREST_HEADER.getNumber());
+  }
+
+  @Test
   public void testOnElementView() {
     streamActionApi.onElementView(ElementType.INTEREST_HEADER.getNumber());
 
diff --git a/src/test/java/com/google/android/libraries/feed/basicstream/internal/drivers/BUILD b/src/test/java/com/google/android/libraries/feed/basicstream/internal/drivers/BUILD
index 535cc19..4eee8c3 100644
--- a/src/test/java/com/google/android/libraries/feed/basicstream/internal/drivers/BUILD
+++ b/src/test/java/com/google/android/libraries/feed/basicstream/internal/drivers/BUILD
@@ -104,8 +104,8 @@
         "//src/main/java/com/google/android/libraries/feed/common/time/testing",
         "//src/main/java/com/google/android/libraries/feed/sharedstream/contextmenumanager",
         "//src/main/java/com/google/android/libraries/feed/sharedstream/logging",
-        "//src/main/java/com/google/android/libraries/feed/sharedstream/offlinemonitor",
         "//src/main/java/com/google/android/libraries/feed/sharedstream/publicapi/menumeasurer",
+        "//src/main/java/com/google/android/libraries/feed/testing/sharedstream/offlinemonitor",
         "//src/main/proto/com/google/android/libraries/feed/api/internal/proto:client_feed_java_proto_lite",
         "//src/main/proto/search/now/ui/action:feed_action_java_proto_lite",
         "//src/main/proto/search/now/ui/action:feed_action_payload_java_proto_lite",
diff --git a/src/test/java/com/google/android/libraries/feed/basicstream/internal/drivers/ContentDriverTest.java b/src/test/java/com/google/android/libraries/feed/basicstream/internal/drivers/ContentDriverTest.java
index f58798e..c60f8ad 100644
--- a/src/test/java/com/google/android/libraries/feed/basicstream/internal/drivers/ContentDriverTest.java
+++ b/src/test/java/com/google/android/libraries/feed/basicstream/internal/drivers/ContentDriverTest.java
@@ -48,13 +48,12 @@
 import com.google.android.libraries.feed.basicstream.internal.viewholders.PietViewHolder;
 import com.google.android.libraries.feed.basicstream.internal.viewloggingupdater.ViewLoggingUpdater;
 import com.google.android.libraries.feed.common.concurrent.testing.FakeMainThreadRunner;
-import com.google.android.libraries.feed.common.functional.Consumer;
 import com.google.android.libraries.feed.common.functional.Supplier;
 import com.google.android.libraries.feed.common.time.testing.FakeClock;
 import com.google.android.libraries.feed.sharedstream.contextmenumanager.ContextMenuManager;
 import com.google.android.libraries.feed.sharedstream.logging.LoggingListener;
 import com.google.android.libraries.feed.sharedstream.logging.StreamContentLoggingData;
-import com.google.android.libraries.feed.sharedstream.offlinemonitor.StreamOfflineMonitor;
+import com.google.android.libraries.feed.testing.sharedstream.offlinemonitor.FakeStreamOfflineMonitor;
 import com.google.common.collect.ImmutableList;
 import com.google.search.now.feed.client.StreamDataProto.StreamFeature;
 import com.google.search.now.feed.client.StreamDataProto.StreamSharedState;
@@ -79,7 +78,6 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatchers;
-import org.mockito.Captor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.Mockito;
@@ -97,8 +95,9 @@
       BasicLoggingMetadata.newBuilder().setScore(40).build();
   private static final RepresentationData REPRESENTATION_DATA =
       RepresentationData.newBuilder().setUri(CONTENT_URL).setPublishedTimeSeconds(10).build();
-  private static final ContentLoggingData CONTENT_LOGGING_DATA =
-      new StreamContentLoggingData(POSITION, BASIC_LOGGING_METADATA, REPRESENTATION_DATA);
+  private static final StreamContentLoggingData CONTENT_LOGGING_DATA =
+      new StreamContentLoggingData(
+          POSITION, BASIC_LOGGING_METADATA, REPRESENTATION_DATA, /*availableOffline= */ false);
   private static final OfflineMetadata OFFLINE_METADATA =
       OfflineMetadata.newBuilder().setTitle(CONTENT_TITLE).build();
 
@@ -161,13 +160,12 @@
   @Mock private ClusterPendingDismissHelper clusterPendingDismissHelper;
   @Mock private PietViewHolder pietViewHolder;
   @Mock private StreamActionApiImpl streamActionApi;
-  @Mock private StreamOfflineMonitor streamOfflineMonitor;
   @Mock private ContentChangedListener contentChangedListener;
   @Mock private ActionParser actionParser;
   @Mock private ContextMenuManager contextMenuManager;
   @Mock private TooltipApi tooltipApi;
 
-  @Captor private ArgumentCaptor<Consumer<Boolean>> offlineConsumerCaptor;
+  private FakeStreamOfflineMonitor streamOfflineMonitor;
   private ContentDriver contentDriver;
   private final FakeClock clock = new FakeClock();
   private final FakeMainThreadRunner mainThreadRunner = FakeMainThreadRunner.create(clock);
@@ -186,6 +184,7 @@
     when(actionParserFactory.build(ArgumentMatchers.<Supplier<ContentMetadata>>any()))
         .thenReturn(actionParser);
     viewLoggingUpdater = new ViewLoggingUpdater();
+    streamOfflineMonitor = FakeStreamOfflineMonitor.create();
     contentDriver =
         new ContentDriver(
             actionApi,
@@ -210,7 +209,7 @@
               ActionParser actionParser,
               ActionManager actionManager,
               BasicLoggingApi basicLoggingApi,
-              ContentLoggingData contentLoggingData,
+              Supplier<ContentLoggingData> contentLoggingData,
               String sessionId,
               ContextMenuManager contextMenuManager,
               ClusterPendingDismissHelper clusterPendingDismissHelper,
@@ -315,6 +314,30 @@
   }
 
   @Test
+  public void testOnContentClicked_offlineStatusChanges_logsContentClicked_withNewOfflineStatus() {
+    contentDriver.bind(pietViewHolder);
+
+    ArgumentCaptor<LoggingListener> visibilityListenerCaptor =
+        ArgumentCaptor.forClass(LoggingListener.class);
+
+    verify(pietViewHolder)
+        .bind(
+            any(Frame.class),
+            anyListOf(PietSharedState.class),
+            any(StreamActionApi.class),
+            any(FeedActionPayload.class),
+            visibilityListenerCaptor.capture(),
+            any(ActionParser.class));
+    LoggingListener listener = visibilityListenerCaptor.getValue();
+
+    streamOfflineMonitor.setOfflineStatus(CONTENT_URL, true);
+
+    listener.onContentClicked();
+
+    verify(basicLoggingApi).onContentClicked(CONTENT_LOGGING_DATA.createWithOfflineStatus(true));
+  }
+
+  @Test
   public void testOnContentClicked_visibilityListenerLogsContentClicked() {
     contentDriver.bind(pietViewHolder);
 
@@ -433,9 +456,6 @@
 
   @Test
   public void testOfflineStatusChange() {
-    reset(streamOfflineMonitor);
-
-    when(streamOfflineMonitor.isAvailableOffline(CONTENT_URL)).thenReturn(false);
     contentDriver =
         new ContentDriver(
             actionApi,
@@ -457,12 +477,9 @@
 
     contentDriver.bind(pietViewHolder);
 
-    verify(streamOfflineMonitor)
-        .addOfflineStatusConsumer(eq(CONTENT_URL), offlineConsumerCaptor.capture());
-
     reset(pietViewHolder);
 
-    offlineConsumerCaptor.getValue().accept(true);
+    streamOfflineMonitor.setOfflineStatus(CONTENT_URL, true);
 
     verify(contentChangedListener).onContentChanged();
     InOrder inOrder = Mockito.inOrder(pietViewHolder);
@@ -480,9 +497,8 @@
 
   @Test
   public void testOfflineStatusChange_noOpWithoutUpdate() {
-    reset(streamOfflineMonitor);
+    streamOfflineMonitor = FakeStreamOfflineMonitor.create();
 
-    when(streamOfflineMonitor.isAvailableOffline(CONTENT_URL)).thenReturn(true);
     contentDriver =
         new ContentDriver(
             actionApi,
@@ -502,14 +518,12 @@
             viewLoggingUpdater,
             tooltipApi);
 
+    streamOfflineMonitor.setOfflineStatus(CONTENT_URL, true);
     contentDriver.bind(pietViewHolder);
 
-    verify(streamOfflineMonitor)
-        .addOfflineStatusConsumer(eq(CONTENT_URL), offlineConsumerCaptor.capture());
-
     reset(pietViewHolder);
 
-    offlineConsumerCaptor.getValue().accept(true);
+    streamOfflineMonitor.setOfflineStatus(CONTENT_URL, true);
 
     verifyNoMoreInteractions(pietViewHolder, contentChangedListener);
   }
@@ -549,6 +563,26 @@
   }
 
   @Test
+  public void testOnElementView_logsElementViewed_usingOfflineStatusWhenLogging() {
+    contentDriver.onElementView(ElementType.INTEREST_HEADER.getNumber());
+
+    // When the view first happens, the offline status is false.
+    clock.advance(VIEW_MIN_TIME_MS / 2);
+
+    // When waiting to log the view, the offline status switches to true
+    streamOfflineMonitor.setOfflineStatus(CONTENT_URL, true);
+
+    // Advance so that the event is logged
+    clock.advance(VIEW_MIN_TIME_MS / 2);
+
+    // Should be logged with offline status true.
+    verify(basicLoggingApi)
+        .onVisualElementViewed(
+            eq(CONTENT_LOGGING_DATA.createWithOfflineStatus(true)),
+            eq(ElementType.INTEREST_HEADER.getNumber()));
+  }
+
+  @Test
   public void testOnElementView_logsElementViewed_beforeTimeout() {
     contentDriver.onElementView(ElementType.INTEREST_HEADER.getNumber());
     clock.advance(VIEW_MIN_TIME_MS - 1);