Home | History | Annotate | Download | only in app
      1 // CHECKSTYLE:OFF Generated code
      2 /* This file is auto-generated from RowsSupportFragmentTest.java.  DO NOT MODIFY. */
      3 
      4 /*
      5  * Copyright (C) 2016 The Android Open Source Project
      6  *
      7  * Licensed under the Apache License, Version 2.0 (the "License");
      8  * you may not use this file except in compliance with the License.
      9  * You may obtain a copy of the License at
     10  *
     11  *      http://www.apache.org/licenses/LICENSE-2.0
     12  *
     13  * Unless required by applicable law or agreed to in writing, software
     14  * distributed under the License is distributed on an "AS IS" BASIS,
     15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     16  * See the License for the specific language governing permissions and
     17  * limitations under the License.
     18  */
     19 package androidx.leanback.app;
     20 
     21 import static org.junit.Assert.assertEquals;
     22 import static org.junit.Assert.assertFalse;
     23 import static org.junit.Assert.assertNotNull;
     24 import static org.junit.Assert.assertNotSame;
     25 import static org.junit.Assert.assertNull;
     26 import static org.junit.Assert.assertSame;
     27 import static org.junit.Assert.assertTrue;
     28 
     29 import android.app.Fragment;
     30 import android.graphics.Rect;
     31 import android.os.Build;
     32 import android.os.Bundle;
     33 import android.os.Handler;
     34 import android.os.SystemClock;
     35 import android.support.test.InstrumentationRegistry;
     36 import android.support.test.filters.LargeTest;
     37 import android.support.test.filters.SdkSuppress;
     38 import android.support.test.runner.AndroidJUnit4;
     39 import android.view.KeyEvent;
     40 import android.view.LayoutInflater;
     41 import android.view.View;
     42 import android.view.ViewGroup;
     43 import android.widget.TextView;
     44 
     45 import androidx.leanback.test.R;
     46 import androidx.leanback.testutils.PollingCheck;
     47 import androidx.leanback.widget.ArrayObjectAdapter;
     48 import androidx.leanback.widget.HeaderItem;
     49 import androidx.leanback.widget.HorizontalGridView;
     50 import androidx.leanback.widget.ItemBridgeAdapter;
     51 import androidx.leanback.widget.ListRow;
     52 import androidx.leanback.widget.ListRowPresenter;
     53 import androidx.leanback.widget.ObjectAdapter;
     54 import androidx.leanback.widget.OnItemViewClickedListener;
     55 import androidx.leanback.widget.PageRow;
     56 import androidx.leanback.widget.Presenter;
     57 import androidx.leanback.widget.Row;
     58 import androidx.leanback.widget.RowPresenter;
     59 import androidx.leanback.widget.SinglePresenterSelector;
     60 import androidx.leanback.widget.VerticalGridView;
     61 
     62 import org.junit.Test;
     63 import org.junit.runner.RunWith;
     64 
     65 import java.lang.ref.WeakReference;
     66 import java.util.ArrayList;
     67 import java.util.List;
     68 
     69 @LargeTest
     70 @RunWith(AndroidJUnit4.class)
     71 public class RowsFragmentTest extends SingleFragmentTestBase {
     72 
     73     static final StringPresenter sCardPresenter = new StringPresenter();
     74 
     75     static void loadData(ArrayObjectAdapter adapter, int numRows, int repeatPerRow) {
     76         for (int i = 0; i < numRows; ++i) {
     77             ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(sCardPresenter);
     78             int index = 0;
     79             for (int j = 0; j < repeatPerRow; ++j) {
     80                 listRowAdapter.add("Hello world-" + (index++));
     81                 listRowAdapter.add("This is a test-" + (index++));
     82                 listRowAdapter.add("Android TV-" + (index++));
     83                 listRowAdapter.add("Leanback-" + (index++));
     84                 listRowAdapter.add("Hello world-" + (index++));
     85                 listRowAdapter.add("Android TV-" + (index++));
     86                 listRowAdapter.add("Leanback-" + (index++));
     87                 listRowAdapter.add("GuidedStepFragment-" + (index++));
     88             }
     89             HeaderItem header = new HeaderItem(i, "Row " + i);
     90             adapter.add(new ListRow(header, listRowAdapter));
     91         }
     92     }
     93 
     94     static Bundle saveActivityState(final SingleFragmentTestActivity activity) {
     95         final Bundle[] savedState = new Bundle[1];
     96         // save activity state
     97         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
     98             @Override
     99             public void run() {
    100                 savedState[0] = activity.performSaveInstanceState();
    101             }
    102         });
    103         return savedState[0];
    104     }
    105 
    106     static void waitForHeaderTransition(final F_Base fragment) {
    107         // Wait header transition finishes
    108         SystemClock.sleep(100);
    109         PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
    110             @Override
    111             public boolean canProceed() {
    112                 return !fragment.isInHeadersTransition();
    113             }
    114         });
    115     }
    116 
    117     static void selectAndWaitFragmentAnimation(final F_Base fragment, final int row,
    118             final int item) {
    119         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    120             @Override
    121             public void run() {
    122                 fragment.setSelectedPosition(row, true,
    123                         new ListRowPresenter.SelectItemViewHolderTask(item));
    124             }
    125         });
    126         // Wait header transition finishes and scrolling stops
    127         SystemClock.sleep(100);
    128         PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
    129             @Override
    130             public boolean canProceed() {
    131                 return !fragment.isInHeadersTransition()
    132                         && !fragment.getHeadersFragment().isScrolling();
    133             }
    134         });
    135     }
    136 
    137     public static class F_defaultAlignment extends RowsFragment {
    138         @Override
    139         public void onCreate(Bundle savedInstanceState) {
    140             super.onCreate(savedInstanceState);
    141             ListRowPresenter lrp = new ListRowPresenter();
    142             ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
    143             setAdapter(adapter);
    144             loadData(adapter, 10, 1);
    145         }
    146     }
    147 
    148     @Test
    149     public void defaultAlignment() throws Throwable {
    150         SingleFragmentTestActivity activity = launchAndWaitActivity(F_defaultAlignment.class, 1000);
    151 
    152         final Rect rect = new Rect();
    153 
    154         final VerticalGridView gridView = ((RowsFragment) activity.getTestFragment())
    155                 .getVerticalGridView();
    156         View row0 = gridView.findViewHolderForAdapterPosition(0).itemView;
    157         rect.set(0, 0, row0.getWidth(), row0.getHeight());
    158         gridView.offsetDescendantRectToMyCoords(row0, rect);
    159         assertEquals("First row is initially aligned to top of screen", 0, rect.top);
    160 
    161         sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
    162         waitForScrollIdle(gridView);
    163         View row1 = gridView.findViewHolderForAdapterPosition(1).itemView;
    164         PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(row1));
    165 
    166         rect.set(0, 0, row1.getWidth(), row1.getHeight());
    167         gridView.offsetDescendantRectToMyCoords(row1, rect);
    168         assertTrue("Second row should not be aligned to top of screen", rect.top > 0);
    169     }
    170 
    171     public static class F_selectBeforeSetAdapter extends RowsFragment {
    172         @Override
    173         public void onCreate(Bundle savedInstanceState) {
    174             super.onCreate(savedInstanceState);
    175             setSelectedPosition(7, false);
    176             new Handler().postDelayed(new Runnable() {
    177                 @Override
    178                 public void run() {
    179                     getVerticalGridView().requestLayout();
    180                 }
    181             }, 100);
    182             new Handler().postDelayed(new Runnable() {
    183                 @Override
    184                 public void run() {
    185                     ListRowPresenter lrp = new ListRowPresenter();
    186                     ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
    187                     setAdapter(adapter);
    188                     loadData(adapter, 10, 1);
    189                 }
    190             }, 1000);
    191         }
    192     }
    193 
    194     @Test
    195     public void selectBeforeSetAdapter() throws InterruptedException {
    196         SingleFragmentTestActivity activity =
    197                 launchAndWaitActivity(F_selectBeforeSetAdapter.class, 2000);
    198 
    199         final VerticalGridView gridView = ((RowsFragment) activity.getTestFragment())
    200                 .getVerticalGridView();
    201         assertEquals(7, gridView.getSelectedPosition());
    202         assertNotNull(gridView.findViewHolderForAdapterPosition(7));
    203     }
    204 
    205     public static class F_selectBeforeAddData extends RowsFragment {
    206         @Override
    207         public void onCreate(Bundle savedInstanceState) {
    208             super.onCreate(savedInstanceState);
    209             ListRowPresenter lrp = new ListRowPresenter();
    210             final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
    211             setAdapter(adapter);
    212             setSelectedPosition(7, false);
    213             new Handler().postDelayed(new Runnable() {
    214                 @Override
    215                 public void run() {
    216                     getVerticalGridView().requestLayout();
    217                 }
    218             }, 100);
    219             new Handler().postDelayed(new Runnable() {
    220                 @Override
    221                 public void run() {
    222                     loadData(adapter, 10, 1);
    223                 }
    224             }, 1000);
    225         }
    226     }
    227 
    228     @Test
    229     public void selectBeforeAddData() throws InterruptedException {
    230         SingleFragmentTestActivity activity =
    231                 launchAndWaitActivity(F_selectBeforeAddData.class, 2000);
    232 
    233         final VerticalGridView gridView = ((RowsFragment) activity.getTestFragment())
    234                 .getVerticalGridView();
    235         assertEquals(7, gridView.getSelectedPosition());
    236         assertNotNull(gridView.findViewHolderForAdapterPosition(7));
    237     }
    238 
    239     public static class F_selectAfterAddData extends RowsFragment {
    240         @Override
    241         public void onCreate(Bundle savedInstanceState) {
    242             super.onCreate(savedInstanceState);
    243             ListRowPresenter lrp = new ListRowPresenter();
    244             final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
    245             setAdapter(adapter);
    246             loadData(adapter, 10, 1);
    247             new Handler().postDelayed(new Runnable() {
    248                 @Override
    249                 public void run() {
    250                     setSelectedPosition(7, false);
    251                 }
    252             }, 1000);
    253         }
    254     }
    255 
    256     @Test
    257     public void selectAfterAddData() throws InterruptedException {
    258         SingleFragmentTestActivity activity =
    259                 launchAndWaitActivity(F_selectAfterAddData.class, 2000);
    260 
    261         final VerticalGridView gridView = ((RowsFragment) activity.getTestFragment())
    262                 .getVerticalGridView();
    263         assertEquals(7, gridView.getSelectedPosition());
    264         assertNotNull(gridView.findViewHolderForAdapterPosition(7));
    265     }
    266 
    267     static WeakReference<F_restoreSelection> sLastF_restoreSelection;
    268 
    269     public static class F_restoreSelection extends RowsFragment {
    270         public F_restoreSelection() {
    271             sLastF_restoreSelection = new WeakReference<F_restoreSelection>(this);
    272         }
    273 
    274         @Override
    275         public void onCreate(Bundle savedInstanceState) {
    276             super.onCreate(savedInstanceState);
    277             ListRowPresenter lrp = new ListRowPresenter();
    278             final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
    279             setAdapter(adapter);
    280             loadData(adapter, 10, 1);
    281             if (savedInstanceState == null) {
    282                 setSelectedPosition(7, false);
    283             }
    284         }
    285     }
    286 
    287     @Test
    288     public void restoreSelection() {
    289         final SingleFragmentTestActivity activity =
    290                 launchAndWaitActivity(F_restoreSelection.class, 1000);
    291 
    292         InstrumentationRegistry.getInstrumentation().runOnMainSync(
    293                 new Runnable() {
    294                     @Override
    295                     public void run() {
    296                         activity.recreate();
    297                     }
    298                 }
    299         );
    300         SystemClock.sleep(1000);
    301 
    302         // mActivity is invalid after recreate(), a new Activity instance is created
    303         // but we could get Fragment from static variable.
    304         RowsFragment fragment = sLastF_restoreSelection.get();
    305         final VerticalGridView gridView = fragment.getVerticalGridView();
    306         assertEquals(7, gridView.getSelectedPosition());
    307         assertNotNull(gridView.findViewHolderForAdapterPosition(7));
    308 
    309     }
    310 
    311     public static class F_ListRowWithOnClick extends RowsFragment {
    312         Presenter.ViewHolder mLastClickedItemViewHolder;
    313         @Override
    314         public void onCreate(Bundle savedInstanceState) {
    315             super.onCreate(savedInstanceState);
    316             setOnItemViewClickedListener(new OnItemViewClickedListener() {
    317                 @Override
    318                 public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
    319                         RowPresenter.ViewHolder rowViewHolder, Row row) {
    320                     mLastClickedItemViewHolder = itemViewHolder;
    321                 }
    322             });
    323             ListRowPresenter lrp = new ListRowPresenter();
    324             ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
    325             setAdapter(adapter);
    326             loadData(adapter, 10, 1);
    327         }
    328     }
    329 
    330     @Test
    331     public void prefetchChildItemsBeforeAttach() throws Throwable {
    332         SingleFragmentTestActivity activity =
    333                 launchAndWaitActivity(F_ListRowWithOnClick.class, 1000);
    334 
    335         F_ListRowWithOnClick fragment = (F_ListRowWithOnClick) activity.getTestFragment();
    336         final VerticalGridView gridView = fragment.getVerticalGridView();
    337         View lastRow = gridView.getChildAt(gridView.getChildCount() - 1);
    338         final int lastRowPos = gridView.getChildAdapterPosition(lastRow);
    339         InstrumentationRegistry.getInstrumentation().runOnMainSync(
    340                 new Runnable() {
    341                     public void run() {
    342                         gridView.setSelectedPositionSmooth(lastRowPos);
    343                     }
    344                 }
    345         );
    346         waitForScrollIdle(gridView);
    347         ItemBridgeAdapter.ViewHolder prefetchedBridgeVh = (ItemBridgeAdapter.ViewHolder)
    348                 gridView.findViewHolderForAdapterPosition(lastRowPos + 1);
    349         RowPresenter prefetchedRowPresenter = (RowPresenter) prefetchedBridgeVh.getPresenter();
    350         final ListRowPresenter.ViewHolder prefetchedListRowVh = (ListRowPresenter.ViewHolder)
    351                 prefetchedRowPresenter.getRowViewHolder(prefetchedBridgeVh.getViewHolder());
    352 
    353         fragment.mLastClickedItemViewHolder = null;
    354         InstrumentationRegistry.getInstrumentation().runOnMainSync(
    355                 new Runnable() {
    356                     public void run() {
    357                         prefetchedListRowVh.getItemViewHolder(0).view.performClick();
    358                     }
    359                 }
    360         );
    361         assertSame(prefetchedListRowVh.getItemViewHolder(0), fragment.mLastClickedItemViewHolder);
    362     }
    363 
    364     @Test
    365     public void changeHasStableIdToTrueAfterViewCreated() throws InterruptedException {
    366         SingleFragmentTestActivity activity =
    367                 launchAndWaitActivity(RowsFragment.class, 2000);
    368         final RowsFragment fragment = (RowsFragment) activity.getTestFragment();
    369 
    370         InstrumentationRegistry.getInstrumentation().runOnMainSync(
    371                 new Runnable() {
    372                     public void run() {
    373                         ObjectAdapter adapter = new ObjectAdapter() {
    374                             @Override
    375                             public int size() {
    376                                 return 0;
    377                             }
    378 
    379                             @Override
    380                             public Object get(int position) {
    381                                 return null;
    382                             }
    383 
    384                             @Override
    385                             public long getId(int position) {
    386                                 return 1;
    387                             }
    388                         };
    389                         adapter.setHasStableIds(true);
    390                         fragment.setAdapter(adapter);
    391                     }
    392                 }
    393         );
    394     }
    395 
    396     static class StableIdAdapter extends ObjectAdapter {
    397         ArrayList<Integer> mList = new ArrayList();
    398 
    399         @Override
    400         public long getId(int position) {
    401             return mList.get(position).longValue();
    402         }
    403 
    404         @Override
    405         public Object get(int position) {
    406             return mList.get(position);
    407         }
    408 
    409         @Override
    410         public int size() {
    411             return mList.size();
    412         }
    413     }
    414 
    415     public static class F_rowNotifyItemRangeChange extends BrowseFragment {
    416 
    417         @Override
    418         public void onCreate(Bundle savedInstanceState) {
    419             super.onCreate(savedInstanceState);
    420             ListRowPresenter lrp = new ListRowPresenter();
    421             final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
    422             for (int i = 0; i < 2; i++) {
    423                 StableIdAdapter listRowAdapter = new StableIdAdapter();
    424                 listRowAdapter.setHasStableIds(true);
    425                 listRowAdapter.setPresenterSelector(
    426                         new SinglePresenterSelector(sCardPresenter));
    427                 int index = 0;
    428                 listRowAdapter.mList.add(index++);
    429                 listRowAdapter.mList.add(index++);
    430                 listRowAdapter.mList.add(index++);
    431                 HeaderItem header = new HeaderItem(i, "Row " + i);
    432                 adapter.add(new ListRow(header, listRowAdapter));
    433             }
    434             setAdapter(adapter);
    435             new Handler().postDelayed(new Runnable() {
    436                 @Override
    437                 public void run() {
    438                     StableIdAdapter rowAdapter = (StableIdAdapter)
    439                             ((ListRow) adapter.get(1)).getAdapter();
    440                     rowAdapter.notifyItemRangeChanged(0, 3);
    441                 }
    442             }, 500);
    443         }
    444     }
    445 
    446     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
    447     @Test
    448     public void rowNotifyItemRangeChange() throws InterruptedException {
    449         SingleFragmentTestActivity activity = launchAndWaitActivity(
    450                 RowsFragmentTest.F_rowNotifyItemRangeChange.class, 2000);
    451 
    452         VerticalGridView verticalGridView = ((BrowseFragment) activity.getTestFragment())
    453                 .getRowsFragment().getVerticalGridView();
    454         for (int i = 0; i < verticalGridView.getChildCount(); i++) {
    455             HorizontalGridView horizontalGridView = verticalGridView.getChildAt(i)
    456                     .findViewById(R.id.row_content);
    457             for (int j = 0; j < horizontalGridView.getChildCount(); j++) {
    458                 assertEquals(horizontalGridView.getPaddingTop(),
    459                         horizontalGridView.getChildAt(j).getTop());
    460             }
    461         }
    462     }
    463 
    464     public static class F_rowNotifyItemRangeChangeWithTransition extends BrowseFragment {
    465 
    466         @Override
    467         public void onCreate(Bundle savedInstanceState) {
    468             super.onCreate(savedInstanceState);
    469             ListRowPresenter lrp = new ListRowPresenter();
    470             prepareEntranceTransition();
    471             final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
    472             for (int i = 0; i < 2; i++) {
    473                 StableIdAdapter listRowAdapter = new StableIdAdapter();
    474                 listRowAdapter.setHasStableIds(true);
    475                 listRowAdapter.setPresenterSelector(
    476                         new SinglePresenterSelector(sCardPresenter));
    477                 int index = 0;
    478                 listRowAdapter.mList.add(index++);
    479                 listRowAdapter.mList.add(index++);
    480                 listRowAdapter.mList.add(index++);
    481                 HeaderItem header = new HeaderItem(i, "Row " + i);
    482                 adapter.add(new ListRow(header, listRowAdapter));
    483             }
    484             setAdapter(adapter);
    485             new Handler().postDelayed(new Runnable() {
    486                 @Override
    487                 public void run() {
    488                     StableIdAdapter rowAdapter = (StableIdAdapter)
    489                             ((ListRow) adapter.get(1)).getAdapter();
    490                     rowAdapter.notifyItemRangeChanged(0, 3);
    491                 }
    492             }, 500);
    493             new Handler().postDelayed(new Runnable() {
    494                 @Override
    495                 public void run() {
    496                     startEntranceTransition();
    497                 }
    498             }, 520);
    499         }
    500     }
    501 
    502     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
    503     @Test
    504     public void rowNotifyItemRangeChangeWithTransition() throws InterruptedException {
    505         SingleFragmentTestActivity activity = launchAndWaitActivity(
    506                         RowsFragmentTest.F_rowNotifyItemRangeChangeWithTransition.class, 3000);
    507 
    508         VerticalGridView verticalGridView = ((BrowseFragment) activity.getTestFragment())
    509                 .getRowsFragment().getVerticalGridView();
    510         for (int i = 0; i < verticalGridView.getChildCount(); i++) {
    511             HorizontalGridView horizontalGridView = verticalGridView.getChildAt(i)
    512                     .findViewById(R.id.row_content);
    513             for (int j = 0; j < horizontalGridView.getChildCount(); j++) {
    514                 assertEquals(horizontalGridView.getPaddingTop(),
    515                         horizontalGridView.getChildAt(j).getTop());
    516                 assertEquals(0, horizontalGridView.getChildAt(j).getTranslationY(), 0.1f);
    517             }
    518         }
    519     }
    520 
    521     public static class F_Base extends BrowseFragment {
    522 
    523         List<Long> mEntranceTransitionStartTS = new ArrayList();
    524         List<Long> mEntranceTransitionEndTS = new ArrayList();
    525 
    526         @Override
    527         protected void onEntranceTransitionStart() {
    528             super.onEntranceTransitionStart();
    529             mEntranceTransitionStartTS.add(SystemClock.uptimeMillis());
    530         }
    531 
    532         @Override
    533         protected void onEntranceTransitionEnd() {
    534             super.onEntranceTransitionEnd();
    535             mEntranceTransitionEndTS.add(SystemClock.uptimeMillis());
    536         }
    537 
    538         public void assertExecutedEntranceTransition() {
    539             assertEquals(1, mEntranceTransitionStartTS.size());
    540             assertEquals(1, mEntranceTransitionEndTS.size());
    541             assertTrue(mEntranceTransitionEndTS.get(0) - mEntranceTransitionStartTS.get(0) > 100);
    542         }
    543 
    544         public void assertNoEntranceTransition() {
    545             assertEquals(0, mEntranceTransitionStartTS.size());
    546             assertEquals(0, mEntranceTransitionEndTS.size());
    547         }
    548 
    549         /**
    550          * Util to wait PageFragment swapped.
    551          */
    552         Fragment waitPageFragment(final Class pageFragmentClass) {
    553             PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
    554                 @Override
    555                 public boolean canProceed() {
    556                     return pageFragmentClass.isInstance(getMainFragment())
    557                             && getMainFragment().getView() != null;
    558                 }
    559             });
    560             return getMainFragment();
    561         }
    562 
    563         /**
    564          * Wait until a fragment for non-page Row is created. Does not apply to the case a
    565          * RowsFragment is created on a PageRow.
    566          */
    567         RowsFragment waitRowsFragment() {
    568             PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
    569                 @Override
    570                 public boolean canProceed() {
    571                     return mMainFragmentListRowDataAdapter != null
    572                             && getMainFragment() instanceof RowsFragment
    573                             && !(getMainFragment() instanceof SampleRowsFragment);
    574                 }
    575             });
    576             return (RowsFragment) getMainFragment();
    577         }
    578     }
    579 
    580     static ObjectAdapter createListRowAdapter() {
    581         StableIdAdapter listRowAdapter = new StableIdAdapter();
    582         listRowAdapter.setHasStableIds(false);
    583         listRowAdapter.setPresenterSelector(
    584                 new SinglePresenterSelector(sCardPresenter));
    585         int index = 0;
    586         listRowAdapter.mList.add(index++);
    587         listRowAdapter.mList.add(index++);
    588         listRowAdapter.mList.add(index++);
    589         return listRowAdapter;
    590     }
    591 
    592     /**
    593      * Create BrowseFragmentAdapter with 3 ListRows
    594      */
    595     static ArrayObjectAdapter createListRowsAdapter() {
    596         ListRowPresenter lrp = new ListRowPresenter();
    597         final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
    598         for (int i = 0; i < 3; i++) {
    599             ObjectAdapter listRowAdapter = createListRowAdapter();
    600             HeaderItem header = new HeaderItem(i, "Row " + i);
    601             adapter.add(new ListRow(header, listRowAdapter));
    602         }
    603         return adapter;
    604     }
    605 
    606     /**
    607      * A typical BrowseFragment with multiple rows that start entrance transition
    608      */
    609     public static class F_standard extends F_Base {
    610         @Override
    611         public void onCreate(Bundle savedInstanceState) {
    612             super.onCreate(savedInstanceState);
    613             if (savedInstanceState == null) {
    614                 prepareEntranceTransition();
    615             }
    616             new Handler().postDelayed(new Runnable() {
    617                 @Override
    618                 public void run() {
    619                     setAdapter(createListRowsAdapter());
    620                     startEntranceTransition();
    621                 }
    622             }, 100);
    623         }
    624     }
    625 
    626     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
    627     @Test
    628     public void browseFragmentSetNullAdapter() throws InterruptedException {
    629         final SingleFragmentTestActivity activity = launchAndWaitActivity(
    630                 RowsFragmentTest.F_standard.class, 2000);
    631         final F_standard fragment = ((F_standard) activity.getTestFragment());
    632         fragment.assertExecutedEntranceTransition();
    633 
    634         final ObjectAdapter adapter1 = fragment.getAdapter();
    635         ListRowDataAdapter wrappedAdapter = fragment.mMainFragmentListRowDataAdapter;
    636         assertTrue(adapter1.hasObserver());
    637         assertTrue(wrappedAdapter.hasObserver());
    638         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    639             @Override
    640             public void run() {
    641                 fragment.setAdapter(null);
    642             }
    643         });
    644         // adapter should no longer has observer and there is no reference to adapter from
    645         // BrowseFragment.
    646         assertFalse(adapter1.hasObserver());
    647         assertFalse(wrappedAdapter.hasObserver());
    648         assertNull(fragment.getAdapter());
    649         assertNull(fragment.mMainFragmentListRowDataAdapter);
    650         // RowsFragment is still there
    651         assertTrue(fragment.mMainFragment instanceof RowsFragment);
    652         assertNotNull(fragment.mMainFragmentRowsAdapter);
    653         assertNotNull(fragment.mMainFragmentAdapter);
    654 
    655         // initialize to same adapter
    656         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    657             @Override
    658             public void run() {
    659                 fragment.setAdapter(adapter1);
    660             }
    661         });
    662         assertTrue(adapter1.hasObserver());
    663         assertNotSame(wrappedAdapter, fragment.mMainFragmentListRowDataAdapter);
    664     }
    665 
    666     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
    667     @Test
    668     public void browseFragmentChangeAdapter() throws InterruptedException {
    669         final SingleFragmentTestActivity activity = launchAndWaitActivity(
    670                 RowsFragmentTest.F_standard.class, 2000);
    671         final F_standard fragment = ((F_standard) activity.getTestFragment());
    672         fragment.assertExecutedEntranceTransition();
    673 
    674         final ObjectAdapter adapter1 = fragment.getAdapter();
    675         ListRowDataAdapter wrappedAdapter = fragment.mMainFragmentListRowDataAdapter;
    676         assertTrue(adapter1.hasObserver());
    677         assertTrue(wrappedAdapter.hasObserver());
    678         final ObjectAdapter adapter2 = createListRowsAdapter();
    679         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    680             @Override
    681             public void run() {
    682                 fragment.setAdapter(adapter2);
    683             }
    684         });
    685         // adapter1 should no longer has observer and adapter2 will have observer
    686         assertFalse(adapter1.hasObserver());
    687         assertFalse(wrappedAdapter.hasObserver());
    688         assertSame(adapter2, fragment.getAdapter());
    689         assertNotSame(wrappedAdapter, fragment.mMainFragmentListRowDataAdapter);
    690         assertTrue(adapter2.hasObserver());
    691         assertTrue(fragment.mMainFragmentListRowDataAdapter.hasObserver());
    692     }
    693 
    694     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
    695     @Test
    696     public void browseFragmentChangeAdapterToPage() throws InterruptedException {
    697         final SingleFragmentTestActivity activity = launchAndWaitActivity(
    698                 RowsFragmentTest.F_standard.class, 2000);
    699         final F_standard fragment = ((F_standard) activity.getTestFragment());
    700         fragment.assertExecutedEntranceTransition();
    701 
    702         final ObjectAdapter adapter1 = fragment.getAdapter();
    703         ListRowDataAdapter wrappedAdapter = fragment.mMainFragmentListRowDataAdapter;
    704         assertTrue(adapter1.hasObserver());
    705         assertTrue(wrappedAdapter.hasObserver());
    706         final ObjectAdapter adapter2 = create2PageRow3ListRow();
    707         fragment.getMainFragmentRegistry().registerFragment(MyPageRow.class,
    708                 new MyFragmentFactory());
    709         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    710             @Override
    711             public void run() {
    712                 fragment.setAdapter(adapter2);
    713             }
    714         });
    715         fragment.waitPageFragment(SampleRowsFragment.class);
    716         // adapter1 should no longer has observer and adapter2 will have observer
    717         assertFalse(adapter1.hasObserver());
    718         assertFalse(wrappedAdapter.hasObserver());
    719         assertSame(adapter2, fragment.getAdapter());
    720         assertNull(fragment.mMainFragmentListRowDataAdapter);
    721         assertTrue(adapter2.hasObserver());
    722     }
    723 
    724     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
    725     @Test
    726     public void browseFragmentNotifyDataChangeListRowToPage() throws InterruptedException {
    727         final SingleFragmentTestActivity activity = launchAndWaitActivity(
    728                 RowsFragmentTest.F_standard.class, 2000);
    729         final F_standard fragment = ((F_standard) activity.getTestFragment());
    730         fragment.assertExecutedEntranceTransition();
    731 
    732         final ArrayObjectAdapter adapter1 = (ArrayObjectAdapter) fragment.getAdapter();
    733         ListRowDataAdapter wrappedAdapter = fragment.mMainFragmentListRowDataAdapter;
    734         assertTrue(adapter1.hasObserver());
    735         assertTrue(wrappedAdapter.hasObserver());
    736 
    737         fragment.getMainFragmentRegistry().registerFragment(MyPageRow.class,
    738                 new MyFragmentFactory());
    739         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    740             @Override
    741             public void run() {
    742                 adapter1.removeItems(0, 1);
    743                 adapter1.add(0, new MyPageRow(0));
    744             }
    745         });
    746         fragment.waitPageFragment(SampleRowsFragment.class);
    747         assertTrue(adapter1.hasObserver());
    748         assertNull(fragment.mMainFragmentListRowDataAdapter);
    749     }
    750 
    751     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
    752     @Test
    753     public void browseFragmentNotifyItemChangeListRowToPage() throws InterruptedException {
    754         final SingleFragmentTestActivity activity = launchAndWaitActivity(
    755                 RowsFragmentTest.F_standard.class, 2000);
    756         final F_standard fragment = ((F_standard) activity.getTestFragment());
    757         fragment.assertExecutedEntranceTransition();
    758 
    759         final ArrayObjectAdapter adapter1 = (ArrayObjectAdapter) fragment.getAdapter();
    760         ListRowDataAdapter wrappedAdapter = fragment.mMainFragmentListRowDataAdapter;
    761         assertTrue(adapter1.hasObserver());
    762         assertTrue(wrappedAdapter.hasObserver());
    763 
    764         fragment.getMainFragmentRegistry().registerFragment(MyPageRow.class,
    765                 new MyFragmentFactory());
    766         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    767             @Override
    768             public void run() {
    769                 adapter1.replace(0, new MyPageRow(0));
    770             }
    771         });
    772         fragment.waitPageFragment(SampleRowsFragment.class);
    773         assertTrue(adapter1.hasObserver());
    774         assertNull(fragment.mMainFragmentListRowDataAdapter);
    775     }
    776 
    777     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
    778     @Test
    779     public void browseFragmentNotifyDataChangeListRowToListRow() throws InterruptedException {
    780         final SingleFragmentTestActivity activity = launchAndWaitActivity(
    781                 RowsFragmentTest.F_standard.class, 2000);
    782         final F_standard fragment = ((F_standard) activity.getTestFragment());
    783         fragment.assertExecutedEntranceTransition();
    784 
    785         final ArrayObjectAdapter adapter1 = (ArrayObjectAdapter) fragment.getAdapter();
    786         ListRowDataAdapter wrappedAdapter = fragment.mMainFragmentListRowDataAdapter;
    787         assertTrue(adapter1.hasObserver());
    788         assertTrue(wrappedAdapter.hasObserver());
    789 
    790         fragment.getMainFragmentRegistry().registerFragment(MyPageRow.class,
    791                 new MyFragmentFactory());
    792         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    793             @Override
    794             public void run() {
    795                 ObjectAdapter listRowAdapter = createListRowAdapter();
    796                 HeaderItem header = new HeaderItem(0, "Row 0 changed");
    797                 adapter1.removeItems(0, 1);
    798                 adapter1.add(0, new ListRow(header, listRowAdapter));
    799             }
    800         });
    801         assertTrue(adapter1.hasObserver());
    802         assertTrue(wrappedAdapter.hasObserver());
    803         assertSame(wrappedAdapter, fragment.mMainFragmentListRowDataAdapter);
    804     }
    805 
    806     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
    807     @Test
    808     public void browseFragmentNotifyItemChangeListRowToListRow() throws InterruptedException {
    809         final SingleFragmentTestActivity activity = launchAndWaitActivity(
    810                 RowsFragmentTest.F_standard.class, 2000);
    811         final F_standard fragment = ((F_standard) activity.getTestFragment());
    812         fragment.assertExecutedEntranceTransition();
    813 
    814         final ArrayObjectAdapter adapter1 = (ArrayObjectAdapter) fragment.getAdapter();
    815         ListRowDataAdapter wrappedAdapter = fragment.mMainFragmentListRowDataAdapter;
    816         assertTrue(adapter1.hasObserver());
    817         assertTrue(wrappedAdapter.hasObserver());
    818 
    819         fragment.getMainFragmentRegistry().registerFragment(MyPageRow.class,
    820                 new MyFragmentFactory());
    821         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    822             @Override
    823             public void run() {
    824                 ObjectAdapter listRowAdapter = createListRowAdapter();
    825                 HeaderItem header = new HeaderItem(0, "Row 0 changed");
    826                 adapter1.replace(0, new ListRow(header, listRowAdapter));
    827             }
    828         });
    829         assertTrue(adapter1.hasObserver());
    830         assertTrue(wrappedAdapter.hasObserver());
    831         assertSame(wrappedAdapter, fragment.mMainFragmentListRowDataAdapter);
    832     }
    833 
    834     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
    835     @Test
    836     public void browseFragmentChangeAdapterPageToPage() throws InterruptedException {
    837         final SingleFragmentTestActivity activity = launchAndWaitActivity(
    838                 RowsFragmentTest.F_2PageRow3ListRow.class, 2000);
    839         final F_2PageRow3ListRow fragment = ((F_2PageRow3ListRow) activity.getTestFragment());
    840         fragment.assertExecutedEntranceTransition();
    841 
    842         final ObjectAdapter adapter1 = fragment.getAdapter();
    843         assertNull(fragment.mMainFragmentListRowDataAdapter);
    844         assertTrue(adapter1.hasObserver());
    845         final ObjectAdapter adapter2 = create2PageRow3ListRow();
    846         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    847             @Override
    848             public void run() {
    849                 fragment.setAdapter(adapter2);
    850             }
    851         });
    852         fragment.waitPageFragment(SampleRowsFragment.class);
    853         // adapter1 should no longer has observer and adapter2 will have observer
    854         assertFalse(adapter1.hasObserver());
    855         assertSame(adapter2, fragment.getAdapter());
    856         assertNull(fragment.mMainFragmentListRowDataAdapter);
    857         assertTrue(adapter2.hasObserver());
    858     }
    859 
    860     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
    861     @Test
    862     public void browseFragmentNotifyChangePageToPage() throws InterruptedException {
    863         final SingleFragmentTestActivity activity = launchAndWaitActivity(
    864                 RowsFragmentTest.F_2PageRow3ListRow.class, 2000);
    865         final F_2PageRow3ListRow fragment = ((F_2PageRow3ListRow) activity.getTestFragment());
    866         fragment.assertExecutedEntranceTransition();
    867 
    868         final ArrayObjectAdapter adapter1 = (ArrayObjectAdapter) fragment.getAdapter();
    869         assertNull(fragment.mMainFragmentListRowDataAdapter);
    870         assertTrue(adapter1.hasObserver());
    871         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    872             @Override
    873             public void run() {
    874                 adapter1.removeItems(0, 1);
    875                 adapter1.add(0, new MyPageRow(1));
    876             }
    877         });
    878         fragment.waitPageFragment(SampleFragment.class);
    879         // adapter1 should no longer has observer and adapter2 will have observer
    880         assertTrue(adapter1.hasObserver());
    881         assertNull(fragment.mMainFragmentListRowDataAdapter);
    882     }
    883 
    884     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
    885     @Test
    886     public void browseFragmentNotifyItemChangePageToPage() throws InterruptedException {
    887         final SingleFragmentTestActivity activity = launchAndWaitActivity(
    888                 RowsFragmentTest.F_2PageRow3ListRow.class, 2000);
    889         final F_2PageRow3ListRow fragment = ((F_2PageRow3ListRow) activity.getTestFragment());
    890         fragment.assertExecutedEntranceTransition();
    891 
    892         final ArrayObjectAdapter adapter1 = (ArrayObjectAdapter) fragment.getAdapter();
    893         assertNull(fragment.mMainFragmentListRowDataAdapter);
    894         assertTrue(adapter1.hasObserver());
    895         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    896             @Override
    897             public void run() {
    898                 adapter1.replace(0, new MyPageRow(1));
    899             }
    900         });
    901         fragment.waitPageFragment(SampleFragment.class);
    902         // adapter1 should no longer has observer and adapter2 will have observer
    903         assertTrue(adapter1.hasObserver());
    904         assertNull(fragment.mMainFragmentListRowDataAdapter);
    905     }
    906 
    907     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
    908     @Test
    909     public void browseFragmentChangeAdapterPageToListRow() throws InterruptedException {
    910         final SingleFragmentTestActivity activity = launchAndWaitActivity(
    911                 RowsFragmentTest.F_2PageRow3ListRow.class, 2000);
    912         final F_2PageRow3ListRow fragment = ((F_2PageRow3ListRow) activity.getTestFragment());
    913         fragment.assertExecutedEntranceTransition();
    914 
    915         final ObjectAdapter adapter1 = fragment.getAdapter();
    916         assertNull(fragment.mMainFragmentListRowDataAdapter);
    917         assertTrue(adapter1.hasObserver());
    918         final ObjectAdapter adapter2 = createListRowsAdapter();
    919         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    920             @Override
    921             public void run() {
    922                 fragment.setAdapter(adapter2);
    923             }
    924         });
    925         fragment.waitRowsFragment();
    926         // adapter1 should no longer has observer and adapter2 will have observer
    927         assertFalse(adapter1.hasObserver());
    928         assertSame(adapter2, fragment.getAdapter());
    929         assertTrue(adapter2.hasObserver());
    930         assertTrue(fragment.mMainFragmentListRowDataAdapter.hasObserver());
    931     }
    932 
    933     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
    934     @Test
    935     public void browseFragmentNotifyDataChangePageToListRow() throws InterruptedException {
    936         final SingleFragmentTestActivity activity = launchAndWaitActivity(
    937                 RowsFragmentTest.F_2PageRow3ListRow.class, 2000);
    938         final F_2PageRow3ListRow fragment = ((F_2PageRow3ListRow) activity.getTestFragment());
    939         fragment.assertExecutedEntranceTransition();
    940 
    941         final ArrayObjectAdapter adapter1 = (ArrayObjectAdapter) fragment.getAdapter();
    942         assertNull(fragment.mMainFragmentListRowDataAdapter);
    943         assertTrue(adapter1.hasObserver());
    944         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    945             @Override
    946             public void run() {
    947                 ObjectAdapter listRowAdapter = createListRowAdapter();
    948                 HeaderItem header = new HeaderItem(0, "Row 0 changed");
    949                 adapter1.removeItems(0, 1);
    950                 adapter1.add(0, new ListRow(header, listRowAdapter));
    951             }
    952         });
    953         fragment.waitRowsFragment();
    954         assertTrue(adapter1.hasObserver());
    955         assertTrue(fragment.mMainFragmentListRowDataAdapter.hasObserver());
    956     }
    957 
    958     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
    959     @Test
    960     public void browseFragmentNotifyItemChangePageToListRow() throws InterruptedException {
    961         final SingleFragmentTestActivity activity = launchAndWaitActivity(
    962                 RowsFragmentTest.F_2PageRow3ListRow.class, 2000);
    963         final F_2PageRow3ListRow fragment = ((F_2PageRow3ListRow) activity.getTestFragment());
    964         fragment.assertExecutedEntranceTransition();
    965 
    966         final ArrayObjectAdapter adapter1 = (ArrayObjectAdapter) fragment.getAdapter();
    967         assertNull(fragment.mMainFragmentListRowDataAdapter);
    968         assertTrue(adapter1.hasObserver());
    969         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    970             @Override
    971             public void run() {
    972                 ObjectAdapter listRowAdapter = createListRowAdapter();
    973                 HeaderItem header = new HeaderItem(0, "Row 0 changed");
    974                 adapter1.replace(0, new ListRow(header, listRowAdapter));
    975             }
    976         });
    977         fragment.waitRowsFragment();
    978         assertTrue(adapter1.hasObserver());
    979         assertTrue(fragment.mMainFragmentListRowDataAdapter.hasObserver());
    980     }
    981 
    982     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
    983     @Test
    984     public void browseFragmentRestore() throws InterruptedException {
    985         final SingleFragmentTestActivity activity = launchAndWaitActivity(
    986                 RowsFragmentTest.F_standard.class, 2000);
    987         final F_standard fragment = ((F_standard) activity.getTestFragment());
    988         fragment.assertExecutedEntranceTransition();
    989 
    990         // select item 2 on row 1
    991         selectAndWaitFragmentAnimation(fragment, 1, 2);
    992         // save activity to state
    993         Bundle savedState = saveActivityState(activity);
    994         activity.finish();
    995 
    996         // recreate activity with saved state
    997         SingleFragmentTestActivity activity2 = launchAndWaitActivity(
    998                 RowsFragmentTest.F_standard.class,
    999                 new Options().savedInstance(savedState), 2000);
   1000         final F_standard fragment2 = ((F_standard) activity2.getTestFragment());
   1001         // validate restored activity selected row and selected item
   1002         fragment2.assertNoEntranceTransition();
   1003         assertEquals(1, fragment2.getSelectedPosition());
   1004         assertEquals(2, ((ListRowPresenter.ViewHolder) fragment2.getSelectedRowViewHolder())
   1005                 .getSelectedPosition());
   1006         activity2.finish();
   1007     }
   1008 
   1009     public static class MyPageRow extends PageRow {
   1010         public int type;
   1011         public MyPageRow(int type) {
   1012             super(new HeaderItem(100 + type, "page type " + type));
   1013             this.type = type;
   1014         }
   1015     }
   1016 
   1017     /**
   1018      * A RowsFragment that is a separate page in BrowseFragment.
   1019      */
   1020     public static class SampleRowsFragment extends RowsFragment {
   1021         public SampleRowsFragment() {
   1022             // simulates late data loading:
   1023             new Handler().postDelayed(new Runnable() {
   1024                 @Override
   1025                 public void run() {
   1026                     setAdapter(createListRowsAdapter());
   1027                     if (getMainFragmentAdapter() != null) {
   1028                         getMainFragmentAdapter().getFragmentHost()
   1029                                 .notifyDataReady(getMainFragmentAdapter());
   1030                     }
   1031                 }
   1032             }, 500);
   1033         }
   1034     }
   1035 
   1036     /**
   1037      * A custom Fragment that is a separate page in BrowseFragment.
   1038      */
   1039     public static class SampleFragment extends Fragment implements
   1040             BrowseFragment.MainFragmentAdapterProvider {
   1041 
   1042         public static class PageFragmentAdapterImpl extends
   1043                 BrowseFragment.MainFragmentAdapter<SampleFragment> {
   1044 
   1045             public PageFragmentAdapterImpl(SampleFragment fragment) {
   1046                 super(fragment);
   1047                 setScalingEnabled(true);
   1048             }
   1049 
   1050             @Override
   1051             public void setEntranceTransitionState(boolean state) {
   1052                 getFragment().setEntranceTransitionState(state);
   1053             }
   1054         }
   1055 
   1056         final PageFragmentAdapterImpl mMainFragmentAdapter = new PageFragmentAdapterImpl(this);
   1057 
   1058         void setEntranceTransitionState(boolean state) {
   1059             final View view = getView();
   1060             int visibility = state ? View.VISIBLE : View.INVISIBLE;
   1061             view.findViewById(R.id.tv1).setVisibility(visibility);
   1062             view.findViewById(R.id.tv2).setVisibility(visibility);
   1063             view.findViewById(R.id.tv3).setVisibility(visibility);
   1064         }
   1065 
   1066         @Override
   1067         public View onCreateView(
   1068                 final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
   1069             View view = inflater.inflate(R.layout.page_fragment, container, false);
   1070             return view;
   1071         }
   1072 
   1073         @Override
   1074         public void onViewCreated(View view, Bundle savedInstanceState) {
   1075             // static layout has view and data ready immediately
   1076             mMainFragmentAdapter.getFragmentHost().notifyViewCreated(mMainFragmentAdapter);
   1077             mMainFragmentAdapter.getFragmentHost().notifyDataReady(mMainFragmentAdapter);
   1078         }
   1079 
   1080         @Override
   1081         public BrowseFragment.MainFragmentAdapter getMainFragmentAdapter() {
   1082             return mMainFragmentAdapter;
   1083         }
   1084     }
   1085 
   1086     /**
   1087      * Create BrowseFragmentAdapter with 3 ListRows and 2 PageRows
   1088      */
   1089     private static ArrayObjectAdapter create3ListRow2PageRowAdapter() {
   1090         ListRowPresenter lrp = new ListRowPresenter();
   1091         final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
   1092         for (int i = 0; i < 3; i++) {
   1093             StableIdAdapter listRowAdapter = new StableIdAdapter();
   1094             listRowAdapter.setHasStableIds(false);
   1095             listRowAdapter.setPresenterSelector(
   1096                     new SinglePresenterSelector(sCardPresenter));
   1097             int index = 0;
   1098             listRowAdapter.mList.add(index++);
   1099             listRowAdapter.mList.add(index++);
   1100             listRowAdapter.mList.add(index++);
   1101             HeaderItem header = new HeaderItem(i, "Row " + i);
   1102             adapter.add(new ListRow(header, listRowAdapter));
   1103         }
   1104         adapter.add(new MyPageRow(0));
   1105         adapter.add(new MyPageRow(1));
   1106         return adapter;
   1107     }
   1108 
   1109     /**
   1110      * Create BrowseFragmentAdapter with 2 PageRows then 3 ListRow
   1111      */
   1112     private static ArrayObjectAdapter create2PageRow3ListRow() {
   1113         ListRowPresenter lrp = new ListRowPresenter();
   1114         final ArrayObjectAdapter adapter = new ArrayObjectAdapter(lrp);
   1115         adapter.add(new MyPageRow(0));
   1116         adapter.add(new MyPageRow(1));
   1117         for (int i = 0; i < 3; i++) {
   1118             StableIdAdapter listRowAdapter = new StableIdAdapter();
   1119             listRowAdapter.setHasStableIds(false);
   1120             listRowAdapter.setPresenterSelector(
   1121                     new SinglePresenterSelector(sCardPresenter));
   1122             int index = 0;
   1123             listRowAdapter.mList.add(index++);
   1124             listRowAdapter.mList.add(index++);
   1125             listRowAdapter.mList.add(index++);
   1126             HeaderItem header = new HeaderItem(i, "Row " + i);
   1127             adapter.add(new ListRow(header, listRowAdapter));
   1128         }
   1129         return adapter;
   1130     }
   1131 
   1132     static class MyFragmentFactory extends BrowseFragment.FragmentFactory {
   1133         @Override
   1134         public Fragment createFragment(Object rowObj) {
   1135             MyPageRow row = (MyPageRow) rowObj;
   1136             if (row.type == 0) {
   1137                 return new SampleRowsFragment();
   1138             } else if (row.type == 1) {
   1139                 return new SampleFragment();
   1140             }
   1141             return null;
   1142         }
   1143     }
   1144 
   1145     /**
   1146      * A BrowseFragment with three ListRows, one SampleRowsFragment and one SampleFragment.
   1147      */
   1148     public static class F_3ListRow2PageRow extends F_Base {
   1149         public F_3ListRow2PageRow() {
   1150             getMainFragmentRegistry().registerFragment(MyPageRow.class, new MyFragmentFactory());
   1151         }
   1152         @Override
   1153         public void onCreate(Bundle savedInstanceState) {
   1154             super.onCreate(savedInstanceState);
   1155             if (savedInstanceState == null) {
   1156                 prepareEntranceTransition();
   1157             }
   1158             new Handler().postDelayed(new Runnable() {
   1159                 @Override
   1160                 public void run() {
   1161                     setAdapter(create3ListRow2PageRowAdapter());
   1162                     startEntranceTransition();
   1163                 }
   1164             }, 100);
   1165         }
   1166     }
   1167 
   1168     /**
   1169      * A BrowseFragment with three ListRows, one SampleRowsFragment and one SampleFragment.
   1170      */
   1171     public static class F_2PageRow3ListRow extends F_Base {
   1172         public F_2PageRow3ListRow() {
   1173             getMainFragmentRegistry().registerFragment(MyPageRow.class, new MyFragmentFactory());
   1174         }
   1175         @Override
   1176         public void onCreate(Bundle savedInstanceState) {
   1177             super.onCreate(savedInstanceState);
   1178             if (savedInstanceState == null) {
   1179                 prepareEntranceTransition();
   1180             }
   1181             new Handler().postDelayed(new Runnable() {
   1182                 @Override
   1183                 public void run() {
   1184                     setAdapter(create2PageRow3ListRow());
   1185                     startEntranceTransition();
   1186                 }
   1187             }, 100);
   1188         }
   1189     }
   1190 
   1191     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
   1192     @Test
   1193     public void mixedBrowseFragmentRestoreToListRow() throws Throwable {
   1194         final SingleFragmentTestActivity activity = launchAndWaitActivity(
   1195                 RowsFragmentTest.F_3ListRow2PageRow.class, 2000);
   1196         final F_3ListRow2PageRow fragment = ((F_3ListRow2PageRow) activity.getTestFragment());
   1197         fragment.assertExecutedEntranceTransition();
   1198 
   1199         // select item 2 on row 1.
   1200         selectAndWaitFragmentAnimation(fragment, 1, 2);
   1201         Bundle savedState = saveActivityState(activity);
   1202         activity.finish();
   1203 
   1204         // start a new activity with the state
   1205         SingleFragmentTestActivity activity2 = launchAndWaitActivity(
   1206                 RowsFragmentTest.F_standard.class,
   1207                 new Options().savedInstance(savedState), 2000);
   1208         final F_3ListRow2PageRow fragment2 = ((F_3ListRow2PageRow) activity2.getTestFragment());
   1209         assertFalse(fragment2.isShowingHeaders());
   1210         fragment2.assertNoEntranceTransition();
   1211         assertEquals(1, fragment2.getSelectedPosition());
   1212         assertEquals(2, ((ListRowPresenter.ViewHolder) fragment2.getSelectedRowViewHolder())
   1213                 .getSelectedPosition());
   1214         activity2.finish();
   1215     }
   1216 
   1217     void mixedBrowseFragmentRestoreToSampleRowsFragment(final boolean hideFastLane)
   1218             throws Throwable {
   1219         final SingleFragmentTestActivity activity = launchAndWaitActivity(
   1220                 RowsFragmentTest.F_3ListRow2PageRow.class, 2000);
   1221         final F_3ListRow2PageRow fragment = ((F_3ListRow2PageRow) activity.getTestFragment());
   1222         fragment.assertExecutedEntranceTransition();
   1223 
   1224         // select row 3 which is mapped to SampleRowsFragment.
   1225         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
   1226             @Override
   1227             public void run() {
   1228                 fragment.setSelectedPosition(3, true);
   1229             }
   1230         });
   1231         // Wait SampleRowsFragment being created
   1232         final SampleRowsFragment mainFragment = (SampleRowsFragment) fragment.waitPageFragment(
   1233                 SampleRowsFragment.class);
   1234         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
   1235             @Override
   1236             public void run() {
   1237                 if (hideFastLane) {
   1238                     fragment.startHeadersTransition(false);
   1239                 }
   1240             }
   1241         });
   1242         // Wait header transition finishes
   1243         waitForHeaderTransition(fragment);
   1244         // Select item 1 on row 1 in SampleRowsFragment
   1245         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
   1246             @Override
   1247             public void run() {
   1248                 mainFragment.setSelectedPosition(1, true,
   1249                         new ListRowPresenter.SelectItemViewHolderTask(1));
   1250             }
   1251         });
   1252         // Save activity state
   1253         Bundle savedState = saveActivityState(activity);
   1254         activity.finish();
   1255 
   1256         SingleFragmentTestActivity activity2 = launchAndWaitActivity(
   1257                 RowsFragmentTest.F_3ListRow2PageRow.class,
   1258                 new Options().savedInstance(savedState), 2000);
   1259         final F_3ListRow2PageRow fragment2 = ((F_3ListRow2PageRow) activity2.getTestFragment());
   1260         final SampleRowsFragment mainFragment2 = (SampleRowsFragment) fragment2.waitPageFragment(
   1261                 SampleRowsFragment.class);
   1262         assertEquals(!hideFastLane, fragment2.isShowingHeaders());
   1263         fragment2.assertNoEntranceTransition();
   1264         // Validate BrowseFragment selected row 3 (mapped to SampleRowsFragment)
   1265         assertEquals(3, fragment2.getSelectedPosition());
   1266         // Validate SampleRowsFragment's selected row and selected item
   1267         assertEquals(1, mainFragment2.getSelectedPosition());
   1268         assertEquals(1, ((ListRowPresenter.ViewHolder) mainFragment2
   1269                 .findRowViewHolderByPosition(1)).getSelectedPosition());
   1270         activity2.finish();
   1271     }
   1272 
   1273     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
   1274     @Test
   1275     public void mixedBrowseFragmentRestoreToSampleRowsFragmentHideFastLane() throws Throwable {
   1276         mixedBrowseFragmentRestoreToSampleRowsFragment(true);
   1277 
   1278     }
   1279 
   1280     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
   1281     @Test
   1282     public void mixedBrowseFragmentRestoreToSampleRowsFragmentShowFastLane() throws Throwable {
   1283         mixedBrowseFragmentRestoreToSampleRowsFragment(false);
   1284     }
   1285 
   1286     void mixedBrowseFragmentRestoreToSampleFragment(final boolean hideFastLane)
   1287             throws Throwable {
   1288         final SingleFragmentTestActivity activity = launchAndWaitActivity(
   1289                 RowsFragmentTest.F_3ListRow2PageRow.class, 2000);
   1290         final F_3ListRow2PageRow fragment = ((F_3ListRow2PageRow) activity.getTestFragment());
   1291         fragment.assertExecutedEntranceTransition();
   1292 
   1293         // select row 3 which is mapped to SampleFragment.
   1294         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
   1295             @Override
   1296             public void run() {
   1297                 fragment.setSelectedPosition(4, true);
   1298             }
   1299         });
   1300         // Wait SampleFragment to be created
   1301         final SampleFragment mainFragment = (SampleFragment) fragment.waitPageFragment(
   1302                 SampleFragment.class);
   1303         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
   1304             @Override
   1305             public void run() {
   1306                 if (hideFastLane) {
   1307                     fragment.startHeadersTransition(false);
   1308                 }
   1309             }
   1310         });
   1311         waitForHeaderTransition(fragment);
   1312         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
   1313             @Override
   1314             public void run() {
   1315                 // change TextView content which should be saved in states.
   1316                 TextView t = mainFragment.getView().findViewById(R.id.tv2);
   1317                 t.setText("changed text");
   1318             }
   1319         });
   1320         // Save activity state
   1321         Bundle savedState = saveActivityState(activity);
   1322         activity.finish();
   1323 
   1324         SingleFragmentTestActivity activity2 = launchAndWaitActivity(
   1325                 RowsFragmentTest.F_3ListRow2PageRow.class,
   1326                 new Options().savedInstance(savedState), 2000);
   1327         final F_3ListRow2PageRow fragment2 = ((F_3ListRow2PageRow) activity2.getTestFragment());
   1328         final SampleFragment mainFragment2 = (SampleFragment) fragment2.waitPageFragment(
   1329                 SampleFragment.class);
   1330         assertEquals(!hideFastLane, fragment2.isShowingHeaders());
   1331         fragment2.assertNoEntranceTransition();
   1332         // Validate BrowseFragment selected row 3 (mapped to SampleFragment)
   1333         assertEquals(4, fragment2.getSelectedPosition());
   1334         // Validate SampleFragment's view states are restored
   1335         TextView t = mainFragment2.getView().findViewById(R.id.tv2);
   1336         assertEquals("changed text", t.getText().toString());
   1337         activity2.finish();
   1338     }
   1339 
   1340     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
   1341     @Test
   1342     public void mixedBrowseFragmentRestoreToSampleFragmentHideFastLane() throws Throwable {
   1343         mixedBrowseFragmentRestoreToSampleFragment(true);
   1344 
   1345     }
   1346 
   1347     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
   1348     @Test
   1349     public void mixedBrowseFragmentRestoreToSampleFragmentShowFastLane() throws Throwable {
   1350         mixedBrowseFragmentRestoreToSampleFragment(false);
   1351     }
   1352 
   1353 
   1354 }
   1355