public final class

InteractionResponse

extends java.lang.Object

implements EspressoRemoteMessage.To<MessageLite>

 java.lang.Object

↳androidx.test.espresso.remote.InteractionResponse

Gradle dependencies

compile group: 'androidx.test.espresso', name: 'espresso-remote', version: '3.6.1'

  • groupId: androidx.test.espresso
  • artifactId: espresso-remote
  • version: 3.6.1

Artifact androidx.test.espresso:espresso-remote:3.6.1 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.test.espresso:espresso-remote com.android.support.test.espresso:espresso-remote

Androidx class mapping:

androidx.test.espresso.remote.InteractionResponse android.support.test.espresso.remote.InteractionResponse

Overview

Encapsulates a UiInteraction.InteractionResultProto request. Takes care of all the proto packing and unpacking.

Summary

Methods
public InteractionResponse.RemoteErrorgetRemoteError()

public InteractionResponse.StatusgetStatus()

public booleanhasRemoteError()

public MessageLitetoProto()

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Methods

public boolean hasRemoteError()

Returns:

true if a InteractionResponse.RemoteError has been set on this InteractionResponse

public MessageLite toProto()

public InteractionResponse.Status getStatus()

Returns:

the InteractionResponse.Status of this InteractionResponse

public InteractionResponse.RemoteError getRemoteError()

Returns:

the UiInteraction.Error of this InteractionResponse

Source

/*
 * Copyright (C) 2016 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.espresso.remote;

import static androidx.test.espresso.remote.InteractionResponse.Status.Ok;
import static androidx.test.internal.util.Checks.checkNotNull;
import static androidx.test.internal.util.Checks.checkState;

import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.test.espresso.proto.UiInteraction.Error;
import androidx.test.espresso.proto.UiInteraction.InteractionResultProto;
import androidx.test.espresso.remote.EspressoRemoteMessage.To;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.MessageLite;
import java.util.Arrays;
import java.util.IllegalFormatException;
import java.util.Locale;

/**
 * Encapsulates a {@link InteractionResultProto} request. Takes care of all the proto packing and
 * unpacking.
 */
public final class InteractionResponse implements To<MessageLite> {

  /**
   * This field is used to create an instance of {@link InteractionResponse} from its unwrapped
   * proto message.
   */
  private static final EspressoRemoteMessage.From<InteractionResponse, InteractionResultProto>
      FROM =
          new EspressoRemoteMessage.From<InteractionResponse, InteractionResultProto>() {
            @Override
            public InteractionResponse fromProto(InteractionResultProto interactionResultProto) {
              InteractionResultProto resultProto = interactionResultProto;
              // Get status code
              boolean status = resultProto.getOk();

              // Get remote error if any
              RemoteError remoteError = null;
              if (resultProto.hasErrorMsg()) {
                Error errorMsg = resultProto.getErrorMsg();
                int errorCode = errorMsg.getCode();
                String errorDescription = errorMsg.getDescription();
                if (RemoteError.isWellKnownError(errorCode)) {
                  remoteError =
                      new RemoteError(
                          errorCode,
                          RemoteError.getWellKnownFormattedErrorDescription(
                              errorCode, errorDescription));
                } else {
                  remoteError = new RemoteError(errorCode, errorDescription);
                }
              }
              return new InteractionResponse.Builder()
                  .setStatus(status ? Status.Ok : Status.Error)
                  .setRemoteError(remoteError)
                  .build();
            }
          };

  private final Status status;
  @Nullable private final RemoteError remoteError;

  @VisibleForTesting
  InteractionResponse(Status status, RemoteError remoteError) {
    this.status = status;
    this.remoteError = remoteError;
  }

  private InteractionResponse(Builder builder) {
    this(builder.status, builder.remoteError);
  }

  /** @return true if a {@link RemoteError} has been set on this {@link InteractionResponse} */
  public boolean hasRemoteError() {
    return remoteError != null;
  }

  /** {@inheritDoc} */
  @Override
  @SuppressWarnings("unchecked") // safe covariant cast
  public MessageLite toProto() {
    InteractionResultProto.Builder builder =
        InteractionResultProto.newBuilder().setOk(Ok == status);
    if (remoteError != null) {
      builder.setErrorMsg(
          Error.newBuilder()
              .setCode(remoteError.getCode())
              .setDescription(remoteError.getDescription()));
    }
    return builder.build();
  }

  /** @return the {@link Status} of this {@link InteractionResponse} */
  public Status getStatus() {
    return status;
  }

  /** @return the {@link Error} of this {@link InteractionResponse} */
  public RemoteError getRemoteError() {
    return remoteError;
  }

  /**
   * The status of this interaction response. Can either be {@link Status#Ok} or {@link
   * Status#Error}
   */
  public enum Status {
    Ok,
    Error
  }

  // TODO(b/31122396): parse this from a proto Enum representing wire protocol error codes and their
  // default description.
  public static final class RemoteError {
    private static final String TAG = "RemoteError";

    public static final int REMOTE_ESPRESSO_ERROR_CODE = 0;
    public static final int REMOTE_PROTOCOL_ERROR_CODE = 1;

    private static final RemoteError REMOTE_ESPRESSO_ERROR =
        new RemoteError(
            REMOTE_ESPRESSO_ERROR_CODE,
            "The following remote Espresso exception with " + "error code [%s] occurred:\n%s");
    private static final RemoteError REMOTE_PROTOCOL_ERROR =
        new RemoteError(
            REMOTE_PROTOCOL_ERROR_CODE,
            "The following remote protocol Espresso "
                + "exception with error code [%s] occurred:\n%s");

    // Reverse-lookup for getting a RemoteError from an error code
    private static final SparseArray<RemoteError> lookup = new SparseArray<>();

    static {
      for (RemoteError e : Arrays.asList(REMOTE_ESPRESSO_ERROR, REMOTE_PROTOCOL_ERROR)) {
        lookup.put(e.getCode(), e);
      }
    }

    private final int code;
    private final String description;

    RemoteError(int code, String description) {
      this.code = code;
      this.description = description;
    }

    /** @return the description of this {@link RemoteError} instance. */
    public String getDescription() {
      return description;
    }

    /** @return the error code of this {@link RemoteError} instance. */
    public int getCode() {
      return code;
    }

    @Override
    public String toString() {
      return code + ": " + description;
    }

    private static String formatDescription(
        String description, int errorCode, String detailedError) {
      checkState(!TextUtils.isEmpty(description), "description cannot be empty!");
      if (detailedError != null) {
        try {
          description = String.format(Locale.ROOT, description, errorCode, detailedError);
        } catch (IllegalFormatException ife) {
          Log.w(TAG, "Cannot format remote error description: " + description);
        }
      }
      return description;
    }

    private static boolean isWellKnownError(int errorCode) {
      return lookup.get(errorCode) != null;
    }

    private static RemoteError getWellKnownError(int errorCode) {
      return lookup.get(errorCode);
    }

    /**
     * Formats a well known Espresso remote error description with a detailed error message.
     *
     * <p>This method allows to provide a formatted error message using the specified format error
     * description and the detailed error message passed as the second parameter. In order to use
     * this feature the well known error description must define a string format in the form of:
     *
     * <pre>{@code
     * new RemoteError(1, "Some remote error occured: [%s]");
     *
     * }</pre>
     *
     * @param errorCode the error code to use as a key for the lookup
     * @param detailedError the detailed error to add to the error description
     * @return formatted error message
     */
    static String getWellKnownFormattedErrorDescription(int errorCode, String detailedError) {
      checkState(isWellKnownError(errorCode));
      RemoteError remoteError = getWellKnownError(errorCode);
      return formatDescription(remoteError.getDescription(), errorCode, detailedError);
    }
  }

  /** Builder for {@link InteractionResponse} */
  public static class Builder {
    private Status status;
    @Nullable private RemoteError remoteError;
    private byte[] interactionResultProtoByteArray;

    public Builder setStatus(@NonNull Status status) {
      this.status = checkNotNull(status);
      return this;
    }

    /**
     * Set the {@link RemoteError} for this {@link InteractionResponse}
     *
     * @param remoteError the remote error to set
     * @return fluent interface {@link Builder}
     */
    public Builder setRemoteError(@Nullable RemoteError remoteError) {
      this.remoteError = remoteError;
      return this;
    }

    /**
     * Set the result proto as a byte array. This byte array will be parsed into an {@link
     * InteractionResultProto}. Providing an invalid byte byte array will result in a {@link
     * RemoteProtocolException} when the {@link #build()} method is called!
     *
     * @param protoByteArray the proto byte array to set
     * @return fluent interface {@link Builder}
     */
    public Builder setResultProto(@NonNull byte[] protoByteArray) {
      this.interactionResultProtoByteArray =
          checkNotNull(protoByteArray, "protoByteArray cannot be" + "null!");
      return this;
    }

    /**
     * Builds an {@link InteractionResponse} object.
     *
     * @throws RemoteProtocolException when conflicting properties are set. You can either set an
     *     {@link Status} with an optional {@link RemoteError} error in case that the status is
     *     {@link Status#Error} or set the proto byte array not both. Setting both values would
     *     result in an override, therefore setting both properties will result in an exception.
     * @throws RemoteProtocolException when the provided proto byte array cannot be parsed into a
     *     protocol buffer of type {@link InteractionResultProto}
     * @return an {@link InteractionResponse} object.
     */
    public InteractionResponse build() {
      if (status != null || remoteError != null) {
        if (interactionResultProtoByteArray != null) {
          throw new RemoteProtocolException(
              "Instances can either be create from an status and optional remote error or an"
                  + " interaction result proto byte array but not both!");
        }
      }

      if (interactionResultProtoByteArray != null) {
        InteractionResultProto interactionResultProto;
        try {
          interactionResultProto =
              InteractionResultProto.parseFrom(interactionResultProtoByteArray);
        } catch (InvalidProtocolBufferException ipbe) {
          throw new RemoteProtocolException("Cannot parse interactionResultProto", ipbe);
        }
        return InteractionResponse.FROM.fromProto(interactionResultProto);
      }

      return new InteractionResponse(this);
    }
  }
}