content: /*
   * Copyright (C) 2017 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.orchestrator.listeners;
  
  import static androidx.test.orchestrator.junit.BundleJUnitUtils.getDescription;
  import static androidx.test.orchestrator.junit.BundleJUnitUtils.getFailure;
  import static androidx.test.orchestrator.junit.BundleJUnitUtils.getResult;
  
  import android.app.Instrumentation;
  import android.os.Bundle;
  import android.util.Log;
  import androidx.test.orchestrator.junit.ParcelableDescription;
  import androidx.test.orchestrator.junit.ParcelableFailure;
  import java.util.ArrayList;
  import java.util.List;
  
  /** Container class for all orchestration listeners */
  public final class OrchestrationListenerManager {
  
    private static final String TAG = "ListenerManager";
  
    /** Message types sent from the remote instrumentation */
    public enum TestEvent {
      TEST_RUN_STARTED,
      TEST_RUN_FINISHED,
      TEST_STARTED,
      TEST_FINISHED,
      TEST_FAILURE,
      TEST_ASSUMPTION_FAILURE,
      TEST_IGNORED
    }
  
    public static final String KEY_TEST_EVENT = "TestEvent";
  
    private final List<OrchestrationRunListener> listeners = new ArrayList<>();
    private final Instrumentation instrumentation;
  
    private boolean markTerminationAsFailure = false;
    private ParcelableDescription lastDescription;
  
    public OrchestrationListenerManager(Instrumentation instrumentation) {
      if (null == instrumentation) {
        throw new IllegalArgumentException("Instrumentation must not be null");
      }
  
      this.instrumentation = instrumentation;
    }
  
    public void addListener(OrchestrationRunListener listener) {
      listener.setInstrumentation(instrumentation);
      listeners.add(listener);
    }
  
    /** To be called after test collection, before the first test begins. */
    public void orchestrationRunStarted(int testCount) {
      for (OrchestrationRunListener listener : listeners) {
        listener.orchestrationRunStarted(testCount);
      }
    }
  
    /** To be called when the test process begins */
    public void testProcessStarted(ParcelableDescription description) {
      lastDescription = description;
      markTerminationAsFailure = true;
    }
  
    /** To be called when the test process terminates, with the result from standard out. */
    public void testProcessFinished(String outputFile) {
      if (markTerminationAsFailure) {
        for (OrchestrationRunListener listener : listeners) {
          listener.testFailure(
              new ParcelableFailure(
                  lastDescription,
                  new Throwable(
                      "Test instrumentation process crashed. Check " + outputFile + " for details")));
          listener.testFinished(lastDescription);
        }
      }
    }
  
    /**
     * Takes a test message and parses it out for all the listeners.
     *
     * @param bundle A bundle containing a key describing the type of message, and a bundle with the
     *     appropriate parcelable imitation of aJ Unit object.
     */
    public void handleNotification(Bundle bundle) {
      bundle.setClassLoader(getClass().getClassLoader());
      cacheStatus(bundle);
      for (OrchestrationRunListener listener : listeners) {
        handleNotificationForListener(listener, bundle);
      }
    }
  
    private void cacheStatus(Bundle bundle) {
      if (getDescription(bundle) != null) {
        lastDescription = getDescription(bundle);
      }
  
      TestEvent status = TestEvent.valueOf(bundle.getString(KEY_TEST_EVENT));
      switch (status) {
        case TEST_RUN_STARTED:
          // Likely already set true in testProcessStarted(), but no reason to not set again.
          markTerminationAsFailure = true;
          break;
        case TEST_FAILURE:
          // After failure, no need to report further failures if process crashes
          markTerminationAsFailure = false;
          break;
        case TEST_RUN_FINISHED:
          // It's now ok to terminate safely.
          markTerminationAsFailure = false;
          break;
        default:
          // We only care about three cases.
      }
    }
  
    private void handleNotificationForListener(OrchestrationRunListener listener, Bundle bundle) {
  
      TestEvent status = TestEvent.valueOf(bundle.getString(KEY_TEST_EVENT));
  
      switch (status) {
        case TEST_RUN_STARTED:
          listener.testRunStarted(getDescription(bundle));
          break;
  
        case TEST_STARTED:
          listener.testStarted(getDescription(bundle));
          break;
  
        case TEST_FINISHED:
          listener.testFinished(getDescription(bundle));
          break;
  
        case TEST_FAILURE:
          listener.testFailure(getFailure(bundle));
          break;
  
        case TEST_ASSUMPTION_FAILURE:
          listener.testAssumptionFailure(getFailure(bundle));
          break;
  
        case TEST_IGNORED:
          listener.testIgnored(getDescription(bundle));
          break;
  
        case TEST_RUN_FINISHED:
          listener.testRunFinished(getResult(bundle));
          break;
  
        default:
          Log.e(TAG, "Unknown notification type");
      }
    }
  }
  