Report an internal error when the root feature is not properly bound.

PiperOrigin-RevId: 258617660
Change-Id: I65c3aae9ce6998a9d207cd7ca659bee00ba6b4a9
diff --git a/src/main/java/com/google/android/libraries/feed/api/client/scope/StreamScopeBuilder.java b/src/main/java/com/google/android/libraries/feed/api/client/scope/StreamScopeBuilder.java
index a84e3a7..18f8496 100644
--- a/src/main/java/com/google/android/libraries/feed/api/client/scope/StreamScopeBuilder.java
+++ b/src/main/java/com/google/android/libraries/feed/api/client/scope/StreamScopeBuilder.java
@@ -165,7 +165,13 @@
     if (modelProviderFactory == null) {
       modelProviderFactory =
           new FeedModelProviderFactory(
-              feedSessionManager, threadUtils, timingUtils, taskQueue, mainThreadRunner, config);
+              feedSessionManager,
+              threadUtils,
+              timingUtils,
+              taskQueue,
+              mainThreadRunner,
+              config,
+              basicLoggingApi);
     }
     if (actionParserFactory == null) {
       actionParserFactory = new FeedActionParserFactory(protocolAdapter, basicLoggingApi);
diff --git a/src/main/java/com/google/android/libraries/feed/api/host/logging/InternalFeedError.java b/src/main/java/com/google/android/libraries/feed/api/host/logging/InternalFeedError.java
index e566d22..583402e 100644
--- a/src/main/java/com/google/android/libraries/feed/api/host/logging/InternalFeedError.java
+++ b/src/main/java/com/google/android/libraries/feed/api/host/logging/InternalFeedError.java
@@ -41,6 +41,7 @@
   InternalFeedError.ITEM_NOT_PARSED,
   InternalFeedError.STORAGE_MISS_BEYOND_THRESHOLD,
   InternalFeedError.CONTENT_MUTATION_FAILED,
+  InternalFeedError.ROOT_NOT_BOUND_TO_FEATURE,
   InternalFeedError.NEXT_VALUE
 })
 public @interface InternalFeedError {
@@ -102,6 +103,9 @@
   /** Represents a failed content mutation in FeedSessionManager. */
   int CONTENT_MUTATION_FAILED = 16;
 
+  /** Represents a root feature that is not bound as expected. */
+  int ROOT_NOT_BOUND_TO_FEATURE = 17;
+
   /** The next value that should be used when adding additional values to the IntDef. */
-  int NEXT_VALUE = 17;
+  int NEXT_VALUE = 18;
 }
diff --git a/src/main/java/com/google/android/libraries/feed/common/testing/InfraIntegrationScope.java b/src/main/java/com/google/android/libraries/feed/common/testing/InfraIntegrationScope.java
index 0697bbb..d39afed 100644
--- a/src/main/java/com/google/android/libraries/feed/common/testing/InfraIntegrationScope.java
+++ b/src/main/java/com/google/android/libraries/feed/common/testing/InfraIntegrationScope.java
@@ -152,7 +152,8 @@
             timingUtils,
             taskQueue,
             fakeMainThreadRunner,
-            configuration);
+            configuration,
+            fakeBasicLoggingApi);
     requestManager = new RequestManagerImpl(fakeFeedRequestManager, feedSessionManager);
   }
 
diff --git a/src/main/java/com/google/android/libraries/feed/feedmodelprovider/FeedModelProvider.java b/src/main/java/com/google/android/libraries/feed/feedmodelprovider/FeedModelProvider.java
index 6bb79a8..745a59f 100644
--- a/src/main/java/com/google/android/libraries/feed/feedmodelprovider/FeedModelProvider.java
+++ b/src/main/java/com/google/android/libraries/feed/feedmodelprovider/FeedModelProvider.java
@@ -18,6 +18,8 @@
 import com.google.android.libraries.feed.api.common.MutationContext;
 import com.google.android.libraries.feed.api.host.config.Configuration;
 import com.google.android.libraries.feed.api.host.config.Configuration.ConfigKey;
+import com.google.android.libraries.feed.api.host.logging.BasicLoggingApi;
+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.Task;
 import com.google.android.libraries.feed.api.internal.common.ThreadUtils;
@@ -127,6 +129,7 @@
   private final MainThreadRunner mainThreadRunner;
   private final ModelChildBinder modelChildBinder;
   private final TimingUtils timingUtils;
+  private final BasicLoggingApi basicLoggingApi;
 
   /*@Nullable*/ private final Predicate<StreamStructure> filterPredicate;
   /*@Nullable*/ private RemoveTrackingFactory<?> removeTrackingFactory;
@@ -135,7 +138,7 @@
   private final int pageSize;
   private final int minPageSize;
 
-  /*@Nullable*/ @VisibleForTesting String sessionId;
+  @VisibleForTesting /*@Nullable*/ String sessionId;
 
   @GuardedBy("lock")
   private boolean delayedTriggerRefresh = false;
@@ -151,7 +154,8 @@
       TaskQueue taskQueue,
       MainThreadRunner mainThreadRunner,
       /*@Nullable*/ Predicate<StreamStructure> filterPredicate,
-      Configuration config) {
+      Configuration config,
+      BasicLoggingApi basicLoggingApi) {
     this.feedSessionManager = feedSessionManager;
     this.threadUtils = threadUtils;
     this.timingUtils = timingUtils;
@@ -162,6 +166,7 @@
     this.pageSize = (int) config.getValueOrDefault(ConfigKey.NON_CACHED_PAGE_SIZE, 0L);
     this.minPageSize = (int) config.getValueOrDefault(ConfigKey.NON_CACHED_MIN_PAGE_SIZE, 0L);
     this.filterPredicate = filterPredicate;
+    this.basicLoggingApi = basicLoggingApi;
 
     CursorProvider cursorProvider =
         parentId -> {
@@ -185,15 +190,17 @@
   /*@Nullable*/
   public ModelFeature getRootFeature() {
     synchronized (lock) {
-      if (root == null) {
+      UpdatableModelChild localRoot = root;
+      if (localRoot == null) {
         Logger.i(TAG, "Found Empty Stream");
         return null;
       }
-      if (root.getType() != Type.FEATURE) {
-        Logger.e(TAG, "Root is bound to the wrong type %s", root.getType());
+      if (localRoot.getType() != Type.FEATURE) {
+        basicLoggingApi.onInternalError(InternalFeedError.ROOT_NOT_BOUND_TO_FEATURE);
+        Logger.e(TAG, "Root is bound to the wrong type %s", localRoot.getType());
         return null;
       }
-      return root.getModelFeature();
+      return localRoot.getModelFeature();
     }
   }
 
diff --git a/src/main/java/com/google/android/libraries/feed/feedmodelprovider/FeedModelProviderFactory.java b/src/main/java/com/google/android/libraries/feed/feedmodelprovider/FeedModelProviderFactory.java
index 236dc41..799b898 100644
--- a/src/main/java/com/google/android/libraries/feed/feedmodelprovider/FeedModelProviderFactory.java
+++ b/src/main/java/com/google/android/libraries/feed/feedmodelprovider/FeedModelProviderFactory.java
@@ -15,6 +15,7 @@
 package com.google.android.libraries.feed.feedmodelprovider;
 
 import com.google.android.libraries.feed.api.host.config.Configuration;
+import com.google.android.libraries.feed.api.host.logging.BasicLoggingApi;
 import com.google.android.libraries.feed.api.internal.common.ThreadUtils;
 import com.google.android.libraries.feed.api.internal.modelprovider.ModelProvider;
 import com.google.android.libraries.feed.api.internal.modelprovider.ModelProvider.ViewDepthProvider;
@@ -38,6 +39,7 @@
   private final TaskQueue taskQueue;
   private final MainThreadRunner mainThreadRunner;
   private final Configuration config;
+  private final BasicLoggingApi basicLoggingApi;
 
   public FeedModelProviderFactory(
       FeedSessionManager feedSessionManager,
@@ -45,13 +47,15 @@
       TimingUtils timingUtils,
       TaskQueue taskQueue,
       MainThreadRunner mainThreadRunner,
-      Configuration config) {
+      Configuration config,
+      BasicLoggingApi basicLoggingApi) {
     this.feedSessionManager = feedSessionManager;
     this.threadUtils = threadUtils;
     this.timingUtils = timingUtils;
     this.taskQueue = taskQueue;
     this.mainThreadRunner = mainThreadRunner;
     this.config = config;
+    this.basicLoggingApi = basicLoggingApi;
   }
 
   @Override
@@ -64,7 +68,8 @@
             taskQueue,
             mainThreadRunner,
             null,
-            config);
+            config,
+            basicLoggingApi);
     feedSessionManager.getExistingSession(sessionId, modelProvider, uiContext);
     return modelProvider;
   }
@@ -88,7 +93,8 @@
             taskQueue,
             mainThreadRunner,
             filterPredicate,
-            config);
+            config,
+            basicLoggingApi);
     feedSessionManager.getNewSession(
         modelProvider, modelProvider.getViewDepthProvider(viewDepthProvider), uiContext);
     return modelProvider;
diff --git a/src/test/java/com/google/android/libraries/feed/feedmodelprovider/BUILD b/src/test/java/com/google/android/libraries/feed/feedmodelprovider/BUILD
index 9dad351..a1e4eeb 100644
--- a/src/test/java/com/google/android/libraries/feed/feedmodelprovider/BUILD
+++ b/src/test/java/com/google/android/libraries/feed/feedmodelprovider/BUILD
@@ -17,6 +17,7 @@
         "//src/main/java/com/google/android/libraries/feed/common/concurrent/testing",
         "//src/main/java/com/google/android/libraries/feed/common/time",
         "//src/main/java/com/google/android/libraries/feed/feedmodelprovider",
+        "//src/main/java/com/google/android/libraries/feed/testing/host/logging",
         "//src/main/proto/com/google/android/libraries/feed/api/internal/proto:client_feed_java_proto_lite",
         "//third_party:robolectric",
         "@com_google_protobuf_javalite//:protobuf_java_lite",
@@ -46,6 +47,7 @@
         "//src/main/java/com/google/android/libraries/feed/common/time",
         "//src/main/java/com/google/android/libraries/feed/feedmodelprovider",
         "//src/main/java/com/google/android/libraries/feed/feedmodelprovider/internal",
+        "//src/main/java/com/google/android/libraries/feed/testing/host/logging",
         "//src/main/proto/com/google/android/libraries/feed/api/internal/proto:client_feed_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",
diff --git a/src/test/java/com/google/android/libraries/feed/feedmodelprovider/FeedModelProviderFactoryTest.java b/src/test/java/com/google/android/libraries/feed/feedmodelprovider/FeedModelProviderFactoryTest.java
index c909ff1..07a5e3e 100644
--- a/src/test/java/com/google/android/libraries/feed/feedmodelprovider/FeedModelProviderFactoryTest.java
+++ b/src/test/java/com/google/android/libraries/feed/feedmodelprovider/FeedModelProviderFactoryTest.java
@@ -26,6 +26,7 @@
 import com.google.android.libraries.feed.common.concurrent.TaskQueue;
 import com.google.android.libraries.feed.common.concurrent.testing.FakeMainThreadRunner;
 import com.google.android.libraries.feed.common.time.TimingUtils;
+import com.google.android.libraries.feed.testing.host.logging.FakeBasicLoggingApi;
 import com.google.search.now.feed.client.StreamDataProto.UiContext;
 import org.junit.Before;
 import org.junit.Test;
@@ -37,6 +38,7 @@
 @RunWith(RobolectricTestRunner.class)
 public class FeedModelProviderFactoryTest {
 
+  private final FakeBasicLoggingApi fakeBasicLoggingApi = new FakeBasicLoggingApi();
   @Mock private ThreadUtils threadUtils;
   @Mock private TimingUtils timingUtils;
   @Mock private FeedSessionManager feedSessionManager;
@@ -60,7 +62,13 @@
   public void testModelProviderFactory() {
     FeedModelProviderFactory factory =
         new FeedModelProviderFactory(
-            feedSessionManager, threadUtils, timingUtils, taskQueue, mainThreadRunner, config);
+            feedSessionManager,
+            threadUtils,
+            timingUtils,
+            taskQueue,
+            mainThreadRunner,
+            config,
+            fakeBasicLoggingApi);
     assertThat(factory.createNew(null, UiContext.getDefaultInstance())).isNotNull();
   }
 }
diff --git a/src/test/java/com/google/android/libraries/feed/feedmodelprovider/FeedModelProviderTest.java b/src/test/java/com/google/android/libraries/feed/feedmodelprovider/FeedModelProviderTest.java
index f1ceacd..5eac740 100644
--- a/src/test/java/com/google/android/libraries/feed/feedmodelprovider/FeedModelProviderTest.java
+++ b/src/test/java/com/google/android/libraries/feed/feedmodelprovider/FeedModelProviderTest.java
@@ -27,6 +27,7 @@
 
 import com.google.android.libraries.feed.api.host.config.Configuration;
 import com.google.android.libraries.feed.api.host.config.Configuration.ConfigKey;
+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.internal.common.PayloadWithId;
 import com.google.android.libraries.feed.api.internal.common.ThreadUtils;
@@ -55,6 +56,7 @@
 import com.google.android.libraries.feed.feedmodelprovider.FeedModelProvider.TokenTracking;
 import com.google.android.libraries.feed.feedmodelprovider.internal.UpdatableModelChild;
 import com.google.android.libraries.feed.feedmodelprovider.internal.UpdatableModelToken;
+import com.google.android.libraries.feed.testing.host.logging.FakeBasicLoggingApi;
 import com.google.protobuf.ByteString;
 import com.google.search.now.feed.client.StreamDataProto.StreamFeature;
 import com.google.search.now.feed.client.StreamDataProto.StreamPayload;
@@ -83,6 +85,7 @@
 
   private final ContentIdGenerators idGenerators = new ContentIdGenerators();
   private final String rootContentId = idGenerators.createRootContentId(0);
+  private final FakeBasicLoggingApi fakeBasicLoggingApi = new FakeBasicLoggingApi();
 
   @Mock private TaskQueue taskQueue;
   @Mock private FeedSessionManager feedSessionManager;
@@ -631,6 +634,18 @@
   }
 
   @Test
+  public void testGetEmptyRoot() {
+    FeedModelProvider modelProvider = createFeedModelProviderWithConfig();
+    ModelMutation mutation = getRootedModelMutator(modelProvider);
+    childBindings.clear();
+    mutation.commit();
+
+    assertThat(modelProvider.getRootFeature()).isNull();
+    assertThat(fakeBasicLoggingApi.lastInternalError)
+        .isEqualTo(InternalFeedError.ROOT_NOT_BOUND_TO_FEATURE);
+  }
+
+  @Test
   public void testGetAllRootChildren() {
     FeedModelProvider modelProvider = createFeedModelProviderWithConfig();
     int featureCnt = 3;
@@ -703,7 +718,8 @@
         taskQueue,
         fakeMainThreadRunner,
         null,
-        config);
+        config,
+        fakeBasicLoggingApi);
   }
 
   private ModelMutation createTopLevelFeatures(ModelProvider modelProvider, int featureCount) {
diff --git a/src/test/java/com/google/android/libraries/feed/feedsessionmanager/FeedSessionManagerImplTest.java b/src/test/java/com/google/android/libraries/feed/feedsessionmanager/FeedSessionManagerImplTest.java
index 4b40168..985be3d 100644
--- a/src/test/java/com/google/android/libraries/feed/feedsessionmanager/FeedSessionManagerImplTest.java
+++ b/src/test/java/com/google/android/libraries/feed/feedsessionmanager/FeedSessionManagerImplTest.java
@@ -798,7 +798,8 @@
             timingUtils,
             fakeTaskQueue,
             fakeMainThreadRunner,
-            configuration);
+            configuration,
+            fakeBasicLoggingApi);
     if (sessionId == null) {
       return modelProviderFactory.createNew(/* viewDepthProvider= */ null, uiContext);
     } else {