1f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev/*
2f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * Copyright (C) 2014 The Android Open Source Project
3f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev *
4f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * Licensed under the Apache License, Version 2.0 (the "License");
5f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * you may not use this file except in compliance with the License.
6f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * You may obtain a copy of the License at
7f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev *
8f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev *   http://www.apache.org/licenses/LICENSE-2.0
9f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev *
10f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * Unless required by applicable law or agreed to in writing, software
11f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * distributed under the License is distributed on an "AS IS" BASIS,
12f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * See the License for the specific language governing permissions and
14f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * limitations under the License.
15f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev */
16f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
17f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevpackage com.google.android.apps.common.testing.ui.espresso.action;
18f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
19f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport static com.google.common.base.Preconditions.checkNotNull;
20f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport com.google.common.base.Optional;
21f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
22f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport android.view.View;
23f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport android.widget.Adapter;
24f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport android.widget.AdapterView;
25f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
26f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport javax.annotation.Nullable;
27f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
28f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev/**
29f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * A sadly necessary layer of indirection to interact with AdapterViews.
30f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * <p>
31f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * Generally any subclass should respect the contracts and behaviors of its superclass. Otherwise
32f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * it becomes impossible to work generically with objects that all claim to share a supertype - you
33f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * need special cases to perform the same operation 'owned' by the supertype for each sub-type. The
34f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * 'is - a' relationship is broken.
35f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * </p>
36f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev *
37f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * <p>
38f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * Android breaks the Liskov substitution principal with ExpandableListView - you can't use
39f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * getAdapter(), getItemAtPosition(), and other methods common to AdapterViews on an
40f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * ExpandableListView because an ExpandableListView isn't an adapterView - they just share a lot of
41f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * code.
42f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * </p>
43f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev *
44f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * <p>
45f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * This interface exists to work around this wart (which sadly is copied in other projects too) and
46f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * lets the implementor translate Espresso's needs and manipulations of the AdapterView into calls
47f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * that make sense for the given subtype and context.
48f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * </p>
49f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev *
50f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * <p><i>
51f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * If you have to implement this to talk to widgets your own project defines - I'm sorry.
52f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * </i><p>
53f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev *
54f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev */
55f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevpublic interface AdapterViewProtocol {
56f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
57f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  /**
58f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * Returns all data this AdapterViewProtocol can find within the given AdapterView.
59f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   *
60f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * <p>
61f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * Any AdaptedData returned by this method can be passed to makeDataRenderedWithinView and the
62f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * implementation should make the AdapterView bring that data item onto the screen.
63f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * </p>
64f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   *
65f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * @param adapterView the AdapterView we want to interrogate the contents of.
66f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * @return an {@link Iterable} of AdaptedDatas representing all data the implementation sees in
67f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   *         this view
68f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * @throws IllegalArgumentException if the implementation doesn't know how to manipulate the given
69f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   *         adapter view.
70f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   */
71f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  Iterable<AdaptedData> getDataInAdapterView(AdapterView<? extends Adapter> adapterView);
72f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
73f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  /**
74f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * Returns the data object this particular view is rendering if possible.
75f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   *
76f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * <p>
77f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * Implementations are expected to create a relationship between the data in the AdapterView and
78f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * the descendant views of the AdapterView that obeys the following conditions:
79f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * </p>
80f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   *
81f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * <ul>
82f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * <li>For each descendant view there exists either 0 or 1 data objects it is rendering.</li>
83f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * <li>For each data object the AdapterView there exists either 0 or 1 descendant views which
84f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   *   claim to be rendering it.</li>
85f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * </ul>
86f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   *
87f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * <p> For example - if a PersonObject is rendered into: </p>
88f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * <code>
89f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * LinearLayout
90f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   *   ImageView picture
91f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   *   TextView firstName
92f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   *   TextView lastName
93f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * </code>
94f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   *
95f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * <p>
96f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * It would be expected that getDataRenderedByView(adapter, LinearLayout) would return the
97f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * PersonObject. If it were called instead with the TextView or ImageView it would return
98f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * Object.absent().
99f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * </p>
100f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   *
101f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * @param adapterView the adapterview hosting the data.
102f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * @param descendantView a view which is a child, grand-child, or deeper descendant of adapterView
103f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * @return an optional data object the descendant view is rendering.
104f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * @throws IllegalArgumentException if this protocol cannot interrogate this class of adapterView
105f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   */
106f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  Optional<AdaptedData> getDataRenderedByView(
107f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      AdapterView<? extends Adapter> adapterView, View descendantView);
108f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
109f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  /**
110f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * Requests that a particular piece of data held in this AdapterView is actually rendered by it.
111f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   *
112f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * <p>
113f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * After calling this method it expected that there will exist some descendant view of adapterView
114f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * for which calling getDataRenderedByView(adapterView, descView).get() == data.data is true.
115f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * <p>
116f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   *
117f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * </p>
118f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * Note: this need not happen immediately. EG: an implementor handling ListView may call
119f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * listView.smoothScrollToPosition(data.opaqueToken) - which kicks off an animated scroll over
120f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * the list to the given position. The animation may be in progress after this call returns. The
121f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * only guarantee is that eventually - with no further interaction necessary - this data item
122f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * will be rendered as a child or deeper descendant of this AdapterView.
123f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * </p>
124f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   *
125f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * @param adapterView the adapterView hosting the data.
126f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * @param data an AdaptedData instance retrieved by a prior call to getDataInAdapterView
127f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * @throws IllegalArgumentException if this protocol cannot manipulate adapterView or if data is
128f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   *   not owned by this AdapterViewProtocol.
129f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   */
130f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  void makeDataRenderedWithinAdapterView(
131f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      AdapterView<? extends Adapter> adapterView, AdaptedData data);
132f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
133f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
134f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  /**
135f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * Indicates whether or not there now exists a descendant view within adapterView that
136f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * is rendering this data.
137f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   *
138f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * @param adapterView the AdapterView hosting this data.
139f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * @param adaptedData the data we are checking the display state for.
140f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * @return true if the data is rendered by a view in the adapterView, false otherwise.
141f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   */
142f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  boolean isDataRenderedWithinAdapterView(
143f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      AdapterView<? extends Adapter> adapterView, AdaptedData adaptedData);
144f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
145f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
146f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  /**
147f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * A holder that associates a data object from an AdapterView with a token the
148f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * AdapterViewProtocol can use to force that data object to be rendered as a child or deeper
149f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   * descendant of the adapter view.
150f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev   */
151f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  public static class AdaptedData {
152f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
153f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    /**
154f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev     * One of the objects the AdapterView is exposing to the user.
155f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev     */
156f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    @Nullable
157f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    public final Object data;
158f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
159f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    /**
160f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev     * A token the implementor of AdapterViewProtocol can use to force the adapterView to display
161f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev     * this data object as a child or deeper descendant in it. Equal opaqueToken point to the same
162f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev     * data object on the AdapterView.
163f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev     */
164f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    public final Object opaqueToken;
165f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
166f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    @Override
167f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    public String toString() {
168f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      return String.format("Data: %s (class: %s) token: %s", data,
169f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev          null == data ? null : data.getClass(), opaqueToken);
170f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    }
171f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
172f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    private AdaptedData(Object data, Object opaqueToken) {
173f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      this.data = data;
174f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      this.opaqueToken = checkNotNull(opaqueToken);
175f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    }
176f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
177f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    public static class Builder {
178f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      private Object data;
179f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      private Object opaqueToken;
180f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
181f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      public Builder withData(@Nullable Object data) {
182f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        this.data = data;
183f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        return this;
184f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      }
185f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
186f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      public Builder withOpaqueToken(@Nullable Object opaqueToken) {
187f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        this.opaqueToken = opaqueToken;
188f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        return this;
189f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      }
190f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev
191f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      public AdaptedData build() {
192f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev        return new AdaptedData(data, opaqueToken);
193f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev      }
194f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev    }
195f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev  }
196f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev}
197