public final class

GenericRemoteMessage

extends java.lang.Object

implements EspressoRemoteMessage.To<MessageLite>

 java.lang.Object

↳androidx.test.espresso.remote.GenericRemoteMessage

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.GenericRemoteMessage android.support.test.espresso.remote.GenericRemoteMessage

Overview

Generic implementation of the EspressoRemoteMessage interface, which uses reflection for proto message serialization and deserialization.

Every Espresso matcher, view action or view assertion needs to support proto serialization to participate in any kind of cross process UI interaction using Espresso Remote.

Each serializable type T needs to provide two things. First a proto message declaration of type T. Second an EspressoRemoteMessage class which implements the EspressoRemoteMessage.To and EspressoRemoteMessage.From interfaces to serialize/deserialization any state into their proto message equivalent.

This GenericRemoteMessage class is special type of EspressoRemoteMessage, which implements the the EspressoRemoteMessage.To and EspressoRemoteMessage.From interfaces and uses reflection to perform serialisation of an object of type T into its proto message representation.

Espresso Remote uses a global RemoteDescriptorRegistry for RemoteDescriptor lookups. Where a RemoteDescriptor is a descriptor object which contains all the necessary meta information for proto serialization.

Usage:

For a type Foo with fields bar and baz:

 public class Foo {
   private final String bar;
   private final int baz;

   protected Foo(String bar, int baz) { // set fields }
 }
 

Note: A protected constructor with serializable fields in declared order is required for proto deserialization.

Create a corresponding proto definition, FooProto.proto for Foo:

 message FooProto {
   bytes bar = 1; // Name needs to match Foo#bar
   bytes baz = 2; // Name needs to match Foo#baz
 }
 

The proto type definitions used with GenericRemoteMessage need to be of type byte and the type names must match the variable name of the serializable fields in Foo.java.

The last step is to create a RemoteDescriptor using a RemoteDescriptor.Builder, to configure all the meta data required for generic serialization. Finally register the descriptor with the RemoteDescriptorRegistry. A typical descriptor builder looks like this:

 new RemoteDescriptor.Builder()
   .setInstanceType(Foo.class)
   .setInstanceFieldDescriptors(
     FieldDescriptor.of(String.class, "bar")),
     FieldDescriptor.of(int.class, 32))
   .setRemoteType(GenericRemoteMessage.class)
   .setProtoType(FooProto.class)
   .build();
 

First set the instance type, Foo.class. Then specify the serializable fields using a FieldDescriptor. Where a FieldDescriptor represents the name and the type of a reflective field of Foo. Any fields described by the field properties and passed to RemoteDescriptor.Builder.setInstanceFieldDescriptors(FieldDescriptor...) will be serialised into FooProto.proto. Next specify that type Foo will use for it's serialization entry point, in this case GenericRemoteMessage and lastly set the proto message class.

Note: The declared field properties order, must match the protected constructor of Foo, which takes the serializable fields in declared order!

Summary

Fields
public static final EspressoRemoteMessage.From<java.lang.Object, MessageLite>FROM

This is used to create and deserialize a proto message into an instance type

Constructors
publicGenericRemoteMessage(java.lang.Object instance)

Creates a new GenericRemoteMessage.

Methods
public MessageLitetoProto()

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

Fields

public static final EspressoRemoteMessage.From<java.lang.Object, MessageLite> FROM

This is used to create and deserialize a proto message into an instance type

Constructors

public GenericRemoteMessage(java.lang.Object instance)

Creates a new GenericRemoteMessage.

This constructor is called reflectively and should not be used.

Parameters:

instance: the object that needs to be serialized into a proto

Methods

public MessageLite toProto()

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.internal.util.Checks.checkNotNull;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.google.protobuf.MessageLite;

/**
 * Generic implementation of the {@link EspressoRemoteMessage} interface, which uses reflection for
 * proto message serialization and deserialization.
 *
 * <p>Every Espresso matcher, view action or view assertion needs to support proto serialization to
 * participate in any kind of cross process UI interaction using Espresso Remote.
 *
 * <p>Each serializable type T needs to provide two things. First a proto message declaration of
 * type T. Second an {@link EspressoRemoteMessage} class which implements the {@link
 * EspressoRemoteMessage.To} and {@link EspressoRemoteMessage.From} interfaces to
 * serialize/deserialization any state into their proto message equivalent.
 *
 * <p>This {@link GenericRemoteMessage} class is special type of {@link EspressoRemoteMessage},
 * which implements the the {@link EspressoRemoteMessage.To} and {@link EspressoRemoteMessage.From}
 * interfaces and uses reflection to perform serialisation of an object of type T into its proto
 * message representation.
 *
 * <p>Espresso Remote uses a global {@link RemoteDescriptorRegistry} for {@link RemoteDescriptor}
 * lookups. Where a {@link RemoteDescriptor} is a descriptor object which contains all the necessary
 * meta information for proto serialization.
 *
 * <p>Usage:
 *
 * <p>For a type {@code Foo} with fields {@code bar} and {@code baz}:
 *
 * <pre>{@code
 * public class Foo {
 *   private final String bar;
 *   private final int baz;
 *
 *   protected Foo(String bar, int baz) { // set fields }
 * }
 * }</pre>
 *
 * <p>Note: A protected constructor with serializable fields in declared order is required for proto
 * deserialization.
 *
 * <p>Create a corresponding proto definition, {@code FooProto.proto} for {@code Foo}:
 *
 * <pre>{@code
 * message FooProto {
 *   bytes bar = 1; // Name needs to match Foo#bar
 *   bytes baz = 2; // Name needs to match Foo#baz
 * }
 * }</pre>
 *
 * <p>The proto type definitions used with {@link GenericRemoteMessage} need to be of type {@code
 * byte} and the type names must match the variable name of the serializable fields in {@code
 * Foo.java}.
 *
 * <p>The last step is to create a {@link RemoteDescriptor} using a {@link
 * RemoteDescriptor.Builder}, to configure all the meta data required for generic serialization.
 * Finally register the descriptor with the {@link RemoteDescriptorRegistry}. A typical descriptor
 * builder looks like this:
 *
 * <pre>{@code
 * new RemoteDescriptor.Builder()
 *   .setInstanceType(Foo.class)
 *   .setInstanceFieldDescriptors(
 *     FieldDescriptor.of(String.class, "bar")),
 *     FieldDescriptor.of(int.class, 32))
 *   .setRemoteType(GenericRemoteMessage.class)
 *   .setProtoType(FooProto.class)
 *   .build();
 * }</pre>
 *
 * <p>First set the instance type, {@code Foo.class}. Then specify the serializable fields using a
 * {@link FieldDescriptor}. Where a {@link FieldDescriptor} represents the name and the type of a
 * reflective field of {@code Foo}. Any fields described by the field properties and passed to
 * {@link RemoteDescriptor.Builder#setInstanceFieldDescriptors(FieldDescriptor...)} will be
 * serialised into {@code FooProto.proto}. Next specify that type {@code Foo} will use for it's
 * serialization entry point, in this case {@link GenericRemoteMessage} and lastly set the proto
 * message class.
 *
 * <p>Note: The declared field properties order, must match the protected constructor of {@code
 * Foo}, which takes the serializable fields in declared order!
 *
 * @throws RemoteProtocolException if a {@link FieldDescriptor}s field name does not match any field
 *     in the declared type or proto message.
 * @throws RemoteProtocolException if type cannot be serialised or dematerialised
 */
public final class GenericRemoteMessage implements EspressoRemoteMessage.To<MessageLite> {

  private final RemoteMessageSerializer remoteMessageSerializer;

  /**
   * Creates a new {@link GenericRemoteMessage}.
   *
   * <p>This constructor is called reflectively and should not be used.
   *
   * @param instance the object that needs to be serialized into a proto
   */
  public GenericRemoteMessage(@NonNull Object instance) {
    this(
        checkNotNull(instance, "instance cannot be null!"), RemoteDescriptorRegistry.getInstance());
  }

  @VisibleForTesting
  GenericRemoteMessage(Object instance, RemoteDescriptorRegistry remoteDescriptorRegistry) {
    this(new RemoteMessageSerializer(instance, remoteDescriptorRegistry));
  }

  private GenericRemoteMessage(RemoteMessageSerializer remoteMessageSerializer) {
    this.remoteMessageSerializer = remoteMessageSerializer;
  }

  /** {@inheritDoc} */
  @Override
  public MessageLite toProto() {
    return remoteMessageSerializer.toProto();
  }

  private static RemoteDescriptorRegistry remoteDescriptorRegistry =
      RemoteDescriptorRegistry.getInstance();

  /** This is used to create and deserialize a proto message into an instance type */
  public static final EspressoRemoteMessage.From<Object, MessageLite> FROM =
      new EspressoRemoteMessage.From<Object, MessageLite>() {
        /** {@inheritDoc} */
        @Override
        public Object fromProto(@NonNull MessageLite messageLite) {
          checkNotNull(messageLite, "messageLite cannot be null!");
          return new RemoteMessageDeserializer(remoteDescriptorRegistry).fromProto(messageLite);
        }
      };

  /**
   * Overrides the default {@link RemoteDescriptorRegistry} to use a custom registry for testing.
   *
   * @param remoteDescriptorRegistry the remote descriptor registry to use for proto serialization
   */
  @VisibleForTesting
  static void setRemoteDescriptorRegistry(RemoteDescriptorRegistry remoteDescriptorRegistry) {
    GenericRemoteMessage.remoteDescriptorRegistry = remoteDescriptorRegistry;
  }
}