private List removeEventRegistration()

in src/main/java/com/google/firebase/database/core/SyncTree.java [519:617]


  private List<Event> removeEventRegistration(
      @NotNull final QuerySpec query,
      @Nullable final EventRegistration eventRegistration,
      @Nullable final DatabaseError cancelError) {
    return persistenceManager.runInTransaction(
        new Callable<List<Event>>() {
          @Override
          public List<Event> call() {
            // Find the syncPoint first. Then deal with whether or not it has matching listeners
            Path path = query.getPath();
            SyncPoint maybeSyncPoint = syncPointTree.get(path);
            List<Event> cancelEvents = new ArrayList<>();
            // A removal on a default query affects all queries at that location. A removal on an
            // indexed query, even one without other query constraints, does *not* affect all
            // queries at that location. So this check must be for 'default', and not
            // loadsAllData().
            if (maybeSyncPoint != null
                && (query.isDefault() || maybeSyncPoint.viewExistsForQuery(query))) {
              // @type {{removed: !Array.<!fb.api.Query>, events: !Array.<!fb.core.view.Event>}}

              Pair<List<QuerySpec>, List<Event>> removedAndEvents =
                  maybeSyncPoint.removeEventRegistration(query, eventRegistration, cancelError);
              if (maybeSyncPoint.isEmpty()) {
                syncPointTree = syncPointTree.remove(path);
              }
              List<QuerySpec> removed = removedAndEvents.getFirst();
              cancelEvents = removedAndEvents.getSecond();
              // We may have just removed one of many listeners and can short-circuit this whole
              // process. We may also not have removed a default listener, in which case all of the
              // descendant listeners should already be properly set up.
              //
              // Since indexed queries can shadow if they don't have other query constraints, check
              // for loadsAllData(), instead of isDefault().
              boolean removingDefault = false;
              for (QuerySpec queryRemoved : removed) {
                persistenceManager.setQueryInactive(query);
                removingDefault = removingDefault || queryRemoved.loadsAllData();
              }
              ImmutableTree<SyncPoint> currentTree = syncPointTree;
              boolean covered =
                  currentTree.getValue() != null && currentTree.getValue().hasCompleteView();
              for (ChildKey component : path) {
                currentTree = currentTree.getChild(component);
                covered =
                    covered
                        || (currentTree.getValue() != null
                            && currentTree.getValue().hasCompleteView());
                if (covered || currentTree.isEmpty()) {
                  break;
                }
              }

              if (removingDefault && !covered) {
                ImmutableTree<SyncPoint> subtree = syncPointTree.subtree(path);
                // There are potentially child listeners. Determine what if any listens we need to
                // send before executing the removal.
                if (!subtree.isEmpty()) {
                  // We need to fold over our subtree and collect the listeners to send
                  List<View> newViews = collectDistinctViewsForSubTree(subtree);

                  // Ok, we've collected all the listens we need. Set them up.
                  for (View view : newViews) {
                    ListenContainer container = new ListenContainer(view);
                    QuerySpec newQuery = view.getQuery();
                    listenProvider.startListening(
                        queryForListening(newQuery), container.tag, container, container);
                  }
                } else {
                  // There's nothing below us, so nothing we need to start listening on
                }
              }
              // If we removed anything and we're not covered by a higher up listen, we need to stop
              // listening on this query. The above block has us covered in terms of making sure
              // we're set up on listens lower in the tree.
              // Also, note that if we have a cancelError, it's already been removed at the provider
              // level.
              if (!covered && !removed.isEmpty() && cancelError == null) {
                // If we removed a default, then we weren't listening on any of the other queries
                // here. Just cancel the one default. Otherwise, we need to iterate through and
                // cancel each individual query
                if (removingDefault) {
                  listenProvider.stopListening(queryForListening(query), null);
                } else {
                  for (QuerySpec queryToRemove : removed) {
                    Tag tag = tagForQuery(queryToRemove);
                    assert tag != null;
                    listenProvider.stopListening(queryForListening(queryToRemove), tag);
                  }
                }
              }
              // Now, clear all of the tags we're tracking for the removed listens
              removeTags(removed);
            } else {
              // No-op, this listener must've been already removed
            }
            return cancelEvents;
          }
        });
  }