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