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.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.isDisplayingAtLeast; 20f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport static com.google.common.base.Preconditions.checkArgument; 21f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 22f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport com.google.common.base.Optional; 23f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport com.google.common.collect.Lists; 24f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport com.google.common.collect.Range; 25f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 26f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport android.os.Build; 27f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport android.view.View; 28f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport android.widget.AbsListView; 29f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport android.widget.Adapter; 30f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport android.widget.AdapterView; 31f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport android.widget.AdapterViewAnimator; 32f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport android.widget.AdapterViewFlipper; 33f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 34f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport java.util.List; 35f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 36f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev/** 37f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * Implementations of {@link AdapterViewProtocol} for standard SDK Widgets. 38f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * 39f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev */ 40f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevpublic final class AdapterViewProtocols { 41f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 42f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev /** 43f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * Consider views which have over this percentage of their area visible to the user 44f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * to be fully rendered. 45f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev */ 46f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev private static final int FULLY_RENDERED_PERCENTAGE_CUTOFF = 90; 47f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 48f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev private AdapterViewProtocols() {} 49f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 50f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev private static final AdapterViewProtocol STANDARD_PROTOCOL = new StandardAdapterViewProtocol(); 51f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 52f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev /** 53f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * Creates an implementation of AdapterViewProtocol that can work with AdapterViews that do not 54f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * break method contracts on AdapterView. 55f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * 56f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev */ 57f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev public static AdapterViewProtocol standardProtocol() { 58f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev return STANDARD_PROTOCOL; 59f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 60f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 61f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev // TODO(user): expandablelistview protocols 62f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 63f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev private static final class StandardAdapterViewProtocol implements AdapterViewProtocol { 64f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev @Override 65f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev public Iterable<AdaptedData> getDataInAdapterView(AdapterView<? extends Adapter> adapterView) { 66f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev List<AdaptedData> datas = Lists.newArrayList(); 67f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev for (int i = 0; i < adapterView.getCount(); i++) { 68f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev datas.add( 69f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev new AdaptedData.Builder() 70f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev .withData(adapterView.getItemAtPosition(i)) 71f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev .withOpaqueToken(i) 72f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev .build()); 73f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 74f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev return datas; 75f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 76f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 77f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev @Override 78f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev public Optional<AdaptedData> getDataRenderedByView(AdapterView<? extends Adapter> adapterView, 79f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev View descendantView) { 80f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev if (adapterView == descendantView.getParent()) { 81f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev int position = adapterView.getPositionForView(descendantView); 82f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev if (position != AdapterView.INVALID_POSITION) { 83f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev return Optional.of(new AdaptedData.Builder() 84f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev .withData(adapterView.getItemAtPosition(position)) 85f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev .withOpaqueToken(Integer.valueOf(position)) 86f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev .build()); 87f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 88f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 89f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev return Optional.absent(); 90f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 91f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 92f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev @Override 93f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev public void makeDataRenderedWithinAdapterView( 94f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev AdapterView<? extends Adapter> adapterView, AdaptedData data) { 95f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev checkArgument(data.opaqueToken instanceof Integer, "Not my data: %s", data); 96f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev int position = ((Integer) data.opaqueToken).intValue(); 97f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 98f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev boolean moved = false; 99f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev // set selection should always work, we can give a little better experience if per subtype 100f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev // though. 101f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev if (Build.VERSION.SDK_INT > 7) { 102f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev if (adapterView instanceof AbsListView) { 103f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev if (Build.VERSION.SDK_INT > 10) { 104f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev ((AbsListView) adapterView).smoothScrollToPositionFromTop(position, 105f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev adapterView.getPaddingTop(), 0); 106f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } else { 107f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev ((AbsListView) adapterView).smoothScrollToPosition(position); 108f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 109f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev moved = true; 110f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 111f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev if (Build.VERSION.SDK_INT > 10) { 112f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev if (adapterView instanceof AdapterViewAnimator) { 113f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev if (adapterView instanceof AdapterViewFlipper) { 114f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev ((AdapterViewFlipper) adapterView).stopFlipping(); 115f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 116f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev ((AdapterViewAnimator) adapterView).setDisplayedChild(position); 117f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev moved = true; 118f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 119f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 120f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 121f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev if (!moved) { 122f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev adapterView.setSelection(position); 123f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 124f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 125f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 126f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev @SuppressWarnings("deprecation") 127f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev @Override 128f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev public boolean isDataRenderedWithinAdapterView( 129f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev AdapterView<? extends Adapter> adapterView, AdaptedData adaptedData) { 130f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev checkArgument(adaptedData.opaqueToken instanceof Integer, "Not my data: %s", adaptedData); 131f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev int dataPosition = ((Integer) adaptedData.opaqueToken).intValue(); 132f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 133f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev if (Range.closed(adapterView.getFirstVisiblePosition(), adapterView.getLastVisiblePosition()) 134f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev .contains(dataPosition)) { 135f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev if (adapterView.getFirstVisiblePosition() == adapterView.getLastVisiblePosition()) { 136f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev // thats a huge element. 137f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev return true; 138f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } else { 139f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev return isElementFullyRendered(adapterView, 140f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev dataPosition - adapterView.getFirstVisiblePosition()); 141f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 142f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } else { 143f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev return false; 144f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 145f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 146f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 147f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev private boolean isElementFullyRendered(AdapterView<? extends Adapter> adapterView, 148f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev int childAt) { 149f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev View element = adapterView.getChildAt(childAt); 150f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev // Occassionally we'll have to fight with smooth scrolling logic on our definition of when 151f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev // there is extra scrolling to be done. In particular if the element is the first or last 152f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev // element of the list, the smooth scroller may decide that no work needs to be done to scroll 153f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev // to the element if a certain percentage of it is on screen. Ugh. Sigh. Yuck. 154f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 155f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev return isDisplayingAtLeast(FULLY_RENDERED_PERCENTAGE_CUTOFF).matches(element); 156f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 157f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 158f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev} 159