gt:     for (AnalyticsListener listener : listeners) {
context: /*
   * Copyright (C) 2018 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package com.google.android.exoplayer2.analytics;
  
  import android.graphics.SurfaceTexture;
  import android.view.Surface;
  import androidx.annotation.Nullable;
  import com.google.android.exoplayer2.C;
  import com.google.android.exoplayer2.ExoPlaybackException;
  import com.google.android.exoplayer2.Format;
  import com.google.android.exoplayer2.PlaybackParameters;
  import com.google.android.exoplayer2.Player;
  import com.google.android.exoplayer2.Player.PlaybackSuppressionReason;
  import com.google.android.exoplayer2.Timeline;
  import com.google.android.exoplayer2.Timeline.Period;
  import com.google.android.exoplayer2.Timeline.Window;
  import com.google.android.exoplayer2.analytics.AnalyticsListener.EventTime;
  import com.google.android.exoplayer2.audio.AudioAttributes;
  import com.google.android.exoplayer2.audio.AudioListener;
  import com.google.android.exoplayer2.audio.AudioRendererEventListener;
  import com.google.android.exoplayer2.decoder.DecoderCounters;
  import com.google.android.exoplayer2.drm.DefaultDrmSessionEventListener;
  import com.google.android.exoplayer2.metadata.Metadata;
  import com.google.android.exoplayer2.metadata.MetadataOutput;
  import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
  import com.google.android.exoplayer2.source.MediaSourceEventListener;
  import com.google.android.exoplayer2.source.TrackGroupArray;
  import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
  import com.google.android.exoplayer2.upstream.BandwidthMeter;
  import com.google.android.exoplayer2.util.Assertions;
  import com.google.android.exoplayer2.util.Clock;
  import com.google.android.exoplayer2.video.VideoListener;
  import com.google.android.exoplayer2.video.VideoRendererEventListener;
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Set;
  import java.util.concurrent.CopyOnWriteArraySet;
  import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
  import org.checkerframework.checker.nullness.qual.RequiresNonNull;
  
  /**
   * Data collector which is able to forward analytics events to {@link AnalyticsListener}s by
   * listening to all available ExoPlayer listeners.
   */
  public class AnalyticsCollector
      implements Player.EventListener,
          MetadataOutput,
          AudioRendererEventListener,
          VideoRendererEventListener,
          MediaSourceEventListener,
          BandwidthMeter.EventListener,
          DefaultDrmSessionEventListener,
          VideoListener,
          AudioListener {
  
    private final CopyOnWriteArraySet<AnalyticsListener> listeners;
    private final Clock clock;
    private final Window window;
    private final MediaPeriodQueueTracker mediaPeriodQueueTracker;
  
    private @MonotonicNonNull Player player;
  
    /**
     * Creates an analytics collector.
     *
     * @param clock A {@link Clock} used to generate timestamps.
     */
    public AnalyticsCollector(Clock clock) {
      this.clock = Assertions.checkNotNull(clock);
      listeners = new CopyOnWriteArraySet<>();
      mediaPeriodQueueTracker = new MediaPeriodQueueTracker();
      window = new Window();
    }
  
    /**
     * Adds a listener for analytics events.
     *
     * @param listener The listener to add.
     */
    public void addListener(AnalyticsListener listener) {
      listeners.add(listener);
    }
  
    /**
     * Removes a previously added analytics event listener.
     *
     * @param listener The listener to remove.
     */
    public void removeListener(AnalyticsListener listener) {
      listeners.remove(listener);
    }
  
    /**
     * Sets the player for which data will be collected. Must only be called if no player has been set
     * yet or the current player is idle.
     *
     * @param player The {@link Player} for which data will be collected.
     */
    public void setPlayer(Player player) {
      Assertions.checkState(
          this.player == null || mediaPeriodQueueTracker.mediaPeriodInfoQueue.isEmpty());
      this.player = Assertions.checkNotNull(player);
    }
  
    // External events.
  
    /**
     * Notify analytics collector that a seek operation will start. Should be called before the player
     * adjusts its state and position to the seek.
     */
    public final void notifySeekStarted() {
      if (!mediaPeriodQueueTracker.isSeeking()) {
        EventTime eventTime = generatePlayingMediaPeriodEventTime();
        mediaPeriodQueueTracker.onSeekStarted();
        for (AnalyticsListener listener : listeners) {
          listener.onSeekStarted(eventTime);
        }
      }
    }
  
    /**
     * Resets the analytics collector for a new media source. Should be called before the player is
     * prepared with a new media source.
     */
    public final void resetForNewMediaSource() {
      // Copying the list is needed because onMediaPeriodReleased will modify the list.
      List<MediaPeriodInfo> mediaPeriodInfos =
          new ArrayList<>(mediaPeriodQueueTracker.mediaPeriodInfoQueue);
      for (MediaPeriodInfo mediaPeriodInfo : mediaPeriodInfos) {
        onMediaPeriodReleased(mediaPeriodInfo.windowIndex, mediaPeriodInfo.mediaPeriodId);
      }
    }
  
    // MetadataOutput implementation.
  
    @Override
    public final void onMetadata(Metadata metadata) {
      EventTime eventTime = generatePlayingMediaPeriodEventTime();
      for (AnalyticsListener listener : listeners) {
        listener.onMetadata(eventTime, metadata);
      }
    }
  
    // AudioRendererEventListener implementation.
  
    @Override
    public final void onAudioEnabled(DecoderCounters counters) {
      // The renderers are only enabled after we changed the playing media period.
      EventTime eventTime = generatePlayingMediaPeriodEventTime();
      for (AnalyticsListener listener : listeners) {
        listener.onDecoderEnabled(eventTime, C.TRACK_TYPE_AUDIO, counters);
      }
    }
  
    @Override
    public final void onAudioDecoderInitialized(
        String decoderName, long initializedTimestampMs, long initializationDurationMs) {
      EventTime eventTime = generateReadingMediaPeriodEventTime();
      for (AnalyticsListener listener : listeners) {
        listener.onDecoderInitialized(
            eventTime, C.TRACK_TYPE_AUDIO, decoderName, initializationDurationMs);
      }
    }
  
    @Override
    public final void onAudioInputFormatChanged(Format format) {
      EventTime eventTime = generateReadingMediaPeriodEventTime();
      for (AnalyticsListener listener : listeners) {
        listener.onDecoderInputFormatChanged(eventTime, C.TRACK_TYPE_AUDIO, format);
      }
    }
  
    @Override
    public final void onAudioSinkUnderrun(
        int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
      EventTime eventTime = generateReadingMediaPeriodEventTime();
      for (AnalyticsListener listener : listeners) {
        listener.onAudioUnderrun(eventTime, bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
      }
    }
  
    @Override
    public final void onAudioDisabled(DecoderCounters counters) {
      // The renderers are disabled after we changed the playing media period on the playback thread
      // but before this change is reported to the app thread.
      EventTime eventTime = generateLastReportedPlayingMediaPeriodEventTime();
      for (AnalyticsListener listener : listeners) {
        listener.onDecoderDisabled(eventTime, C.TRACK_TYPE_AUDIO, counters);
      }
    }
  
    // AudioListener implementation.
  
    @Override
    public final void onAudioSessionId(int audioSessionId) {
      EventTime eventTime = generateReadingMediaPeriodEventTime();
      for (AnalyticsListener listener : listeners) {
        listener.onAudioSessionId(eventTime, audioSessionId);
      }
    }
  
    @Override
    public void onAudioAttributesChanged(AudioAttributes audioAttributes) {
      EventTime eventTime = generateReadingMediaPeriodEventTime();
      for (AnalyticsListener listener : listeners) {
        listener.onAudioAttributesChanged(eventTime, audioAttributes);
      }
    }
  
    @Override
    public void onVolumeChanged(float audioVolume) {
      EventTime eventTime = generateReadingMediaPeriodEventTime();
      for (AnalyticsListener listener : listeners) {
        listener.onVolumeChanged(eventTime, audioVolume);
      }
    }
  
    // VideoRendererEventListener implementation.
  
    @Override
    public final void onVideoEnabled(DecoderCounters counters) {
      // The renderers are only enabled after we changed the playing media period.
      EventTime eventTime = generatePlayingMediaPeriodEventTime();
      for (AnalyticsListener listener : listeners) {
        listener.onDecoderEnabled(eventTime, C.TRACK_TYPE_VIDEO, counters);
      }
    }
  
    @Override
    public final void onVideoDecoderInitialized(
        String decoderName, long initializedTimestampMs, long initializationDurationMs) {
      EventTime eventTime = generateReadingMediaPeriodEventTime();
      for (AnalyticsListener listener : listeners) {
        listener.onDecoderInitialized(
            eventTime, C.TRACK_TYPE_VIDEO, decoderName, initializationDurationMs);
      }
    }
  
    @Override
    public final void onVideoInputFormatChanged(Format format) {
      EventTime eventTime = generateReadingMediaPeriodEventTime();
      for (AnalyticsListener listener : listeners) {
        listener.onDecoderInputFormatChanged(eventTime, C.TRACK_TYPE_VIDEO, format);
      }
    }
  
    @Override
    public final void onDroppedFrames(int count, long elapsedMs) {
      EventTime eventTime = generateLastReportedPlayingMediaPeriodEventTime();
      for (AnalyticsListener listener : listeners) {
        listener.onDroppedVideoFrames(eventTime, count, elapsedMs);
      }
    }
  
    @Override
    public final void onVideoDisabled(DecoderCounters counters) {
      // The renderers are disabled after we changed the playing media period on the playback thread
      // but before this change is reported to the app thread.
      EventTime eventTime = generateLastReportedPlayingMediaPeriodEventTime();
      for (AnalyticsListener listener : listeners) {
        listener.onDecoderDisabled(eventTime, C.TRACK_TYPE_VIDEO, counters);
      }
    }
  
    @Override
    public final void onRenderedFirstFrame(@Nullable Surface surface) {
      EventTime eventTime = generateReadingMediaPeriodEventTime();
      for (AnalyticsListener listener : listeners) {
        listener.onRenderedFirstFrame(eventTime, surface);
      }
    }
  
    // VideoListener implementation.
  
    @Override
    public final void onRenderedFirstFrame() {
      // Do nothing. Already reported in VideoRendererEventListener.onRenderedFirstFrame.
    }
  
    @Override
    public final void onVideoSizeChanged(
        int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
      EventTime eventTime = generateReadingMediaPeriodEventTime();
      for (AnalyticsListener listener : listeners) {
        listener.onVideoSizeChanged(
            eventTime, width, height, unappliedRotationDegrees, pixelWidthHeightRatio);
      }
    }
  
    @Override
    public void onSurfaceSizeChanged(int width, int height) {
      EventTime eventTime = generateReadingMediaPeriodEventTime();
      for (AnalyticsListener listener : listeners) {
        listener.onSurfaceSizeChanged(eventTime, width, height);
      }
    }
  
    @Override
    public boolean onSurfaceDestroyed(SurfaceTexture surfaceTexture) {
      return false;
    }
  
    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
  
    }
  
    // MediaSourceEventListener implementation.
  
    @Override
    public final void onMediaPeriodCreated(int windowIndex, MediaPeriodId mediaPeriodId) {
      mediaPeriodQueueTracker.onMediaPeriodCreated(windowIndex, mediaPeriodId);
      EventTime eventTime = generateMediaPeriodEventTime(windowIndex, mediaPeriodId);
  