content: /*
   * Copyright (C) 2022 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 androidx.test.platform.tracing;
  
  import static androidx.test.internal.util.Checks.checkNotNull;
  
  import android.util.Log;
  import androidx.annotation.NonNull;
  import androidx.annotation.RestrictTo;
  import androidx.test.platform.tracing.Tracer.Span;
  import com.google.errorprone.annotations.MustBeClosed;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  /**
   * {@link Tracing} is a singleton to interact with registered {@link Tracer} implementations.
   *
   * <p>Support for actual tracing libraries is done by implementing wrappers following the {@link
   * Tracer} interface. The actual {@link Tracer} implementations must be registered using {@link
   * Tracing#registerTracer(Tracer)}.
   *
   * <p>The {@link Tracing} singleton is also an entry point used to create the root span for any
   * tests by invoking the {@link #beginSpan(String)} method. The returned {@link Span} object must be
   * properly closed by invoking the {@link Span#close()} method or wrapping them in a try-resource
   * block.
   *
   * @hide
   */
  @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
  public final class Tracing {
    private static final String TAG = Tracing.class.getSimpleName();
    private static final Tracing singleton = new Tracing();
    private final List<Tracer> tracers = Collections.synchronizedList(new ArrayList<>());
  
    private Tracing() {
      registerTracer(new AndroidXTracer().enableTracing());
    }
  
    /**
     * Static getter for external access to the singleton. <br>
     * Preferred method for internal Espresso is getting this singleton via dagger injection.
     */
    @NonNull
    public static Tracing getInstance() {
      return singleton;
    }
  
    /**
     * Registers a new tracer.
     *
     * <p>Once a tracer is registered, it starts being invoked for new root {@link #beginSpan(String)}
     * calls. Existing in-flight spans do not invoke the new tracer.
     *
     * @param tracer A non-null Tracer object. A previously registered instance is ignored.
     */
    public void registerTracer(@NonNull Tracer tracer) {
      checkNotNull(tracer, "Tracer cannot be null.");
      if (tracers.contains(tracer)) {
        Log.w(TAG, "Tracer already present: " + tracer.getClass());
      } else {
        Log.i(TAG, "Tracer added: " + tracer.getClass());
        tracers.add(tracer);
      }
    }
  
    /**
     * Unregisters a tracer.
     *
     * <p>Once a tracer is unregistered, it will stop being invoked for any new root {@link
     * #beginSpan(String)} calls made or any new {@link Span#beginChildSpan(String)}. However the
     * tracer is still be called for any in-flight spans being closed.
     *
     * @param tracer A Tracer object. A null reference or non-registered instance is ignored.
     */
    public void unregisterTracer(Tracer tracer) {
      tracers.remove(tracer);
      Log.i(TAG, "Tracer removed: " + (tracer == null ? null : tracer.getClass()));
    }
  
    /**
     * Returns a new Span as a managed resource in a try{} block. {@link Span#close()} is
     * automatically called when the resource is released.
     *
     * @param name A non-null name describing this span.
     */
    @NonNull
    @MustBeClosed
    public Span beginSpan(@NonNull String name) {
      checkNotNull(name);
      Map<Tracer, Span> spans;
      synchronized (tracers) {
        spans = new HashMap<>(tracers.size());
        for (Tracer tracer : tracers) {
          spans.put(tracer, createUnmanagedSpan(tracer, name));
        }
      }
      return new TracerSpan(spans);
    }
  
    /**
     * TracerSpan is an internal implementation class of a parent span.
     *
     * <p>Each Tracer creates its own Span instance wrapping a specific trace event; TracerSpan keeps
     * track of which Tracer creates which Span. <br>
     * When creating child spans, only tracers involved in the original parent span are called if they
     * are still registered.
     */
    class TracerSpan implements Span {
      private final Map<Tracer, Span> spans;
  
      private TracerSpan(@NonNull Map<Tracer, Span> spans) {
        this.spans = spans;
      }
  
      /**
       * Creates a child span for any tracer that participated in the parent span if that tracer is
       * still registered.
       */
      @NonNull
      @Override
      public Span beginChildSpan(@NonNull String name) {
        checkNotNull(name);
        Map<Tracer, Span> childSpans;
        synchronized (tracers) {
          childSpans = new HashMap<>(tracers.size());
          for (Tracer tracer : tracers) {
            Span parentSpan = spans.get(tracer);
            if (parentSpan != null) {
              childSpans.put(tracer, createUnmanagedChildSpan(parentSpan, name));
            }
          }
        }
  
        return new TracerSpan(childSpans);
      }
  
      /** All spans are closed, even if the tracer has been unregistered in between. */
      @Override
      public void close() {
        for (Span span : spans.values()) {
          span.close();
        }
      }
    }
  
    /**
     * Internal method to create a Span that does not enforce try-resource usage. Caller <em>must</em>
     * manually call {@link Span#close()} later on the resource.
     */
    @SuppressWarnings("MustBeClosedChecker")
    private static Span createUnmanagedSpan(Tracer tracer, String name) {
      return tracer.beginSpan(name);
    }
  
    /**
     * Internal method to create a Span that does not enforce try-resource usage. Caller <em>must</em>
     * manually call {@link Span#close()} later on the resource.
     */
    @SuppressWarnings("MustBeClosedChecker")
    private static Span createUnmanagedChildSpan(Span parentSpan, String name) {
      return parentSpan.beginChildSpan(name);
    }
  }
  