| // Copyright 2018 The Feed Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.android.libraries.feed.basicstream.internal; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.never; |
| import static org.mockito.Mockito.reset; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| import static org.mockito.MockitoAnnotations.initMocks; |
| |
| import android.app.Activity; |
| import android.content.Context; |
| import android.support.v7.widget.RecyclerView; |
| import android.support.v7.widget.RecyclerView.ViewHolder; |
| import android.view.ViewGroup; |
| import android.widget.FrameLayout; |
| import android.widget.FrameLayout.LayoutParams; |
| import android.widget.LinearLayout; |
| import com.google.android.libraries.feed.api.client.stream.Header; |
| import com.google.android.libraries.feed.api.client.stream.Stream.ContentChangedListener; |
| import com.google.android.libraries.feed.api.host.config.Configuration; |
| import com.google.android.libraries.feed.api.host.stream.CardConfiguration; |
| import com.google.android.libraries.feed.basicstream.internal.drivers.ContentDriver; |
| import com.google.android.libraries.feed.basicstream.internal.drivers.HeaderDriver; |
| import com.google.android.libraries.feed.basicstream.internal.drivers.LeafFeatureDriver; |
| import com.google.android.libraries.feed.basicstream.internal.drivers.StreamDriver; |
| import com.google.android.libraries.feed.basicstream.internal.viewholders.ContinuationViewHolder; |
| import com.google.android.libraries.feed.basicstream.internal.viewholders.FeedViewHolder; |
| import com.google.android.libraries.feed.basicstream.internal.viewholders.NoContentViewHolder; |
| import com.google.android.libraries.feed.basicstream.internal.viewholders.PietViewHolder; |
| import com.google.android.libraries.feed.basicstream.internal.viewholders.ViewHolderType; |
| import com.google.android.libraries.feed.basicstream.internal.viewholders.ZeroStateViewHolder; |
| import com.google.android.libraries.feed.common.functional.Supplier; |
| import com.google.android.libraries.feed.piet.FrameAdapter; |
| import com.google.android.libraries.feed.piet.PietManager; |
| import com.google.android.libraries.feed.piet.host.ActionHandler; |
| import com.google.android.libraries.feed.piet.host.EventLogger; |
| import com.google.android.libraries.feed.sharedstream.deepestcontenttracker.DeepestContentTracker; |
| import com.google.android.libraries.feed.sharedstream.piet.PietEventLogger; |
| import com.google.android.libraries.feed.sharedstream.publicapi.scroll.ScrollObservable; |
| import com.google.android.libraries.feed.sharedstream.ui.R; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Lists; |
| import java.util.Collections; |
| import java.util.List; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.ArgumentMatchers; |
| import org.mockito.InOrder; |
| import org.mockito.Mock; |
| import org.mockito.Mockito; |
| import org.robolectric.Robolectric; |
| import org.robolectric.RobolectricTestRunner; |
| |
| /** Tests for {@link StreamRecyclerViewAdapter}. */ |
| @RunWith(RobolectricTestRunner.class) |
| public class StreamRecyclerViewAdapterTest { |
| |
| private static final int HEADER_COUNT = 2; |
| private static final long FEATURE_DRIVER_1_ID = 123; |
| private static final long FEATURE_DRIVER_2_ID = 321; |
| private static final String INITIAL_CONTENT_ID = "INITIAL_CONTENT_ID"; |
| private static final String FEATURE_DRIVER_1_CONTENT_ID = "FEATURE_DRIVER_1_CONTENT_ID"; |
| private static final String FEATURE_DRIVER_2_CONTENT_ID = "FEATURE_DRIVER_2_CONTENT_ID"; |
| private static final Configuration CONFIGURATION = new Configuration.Builder().build(); |
| |
| @Mock private CardConfiguration cardConfiguration; |
| @Mock private ContentChangedListener contentChangedListener; |
| @Mock private PietManager pietManager; |
| @Mock private FrameAdapter frameAdapter; |
| @Mock private StreamDriver driver; |
| @Mock private ContentDriver initialFeatureDriver; |
| @Mock private ContentDriver featureDriver1; |
| @Mock private ContentDriver featureDriver2; |
| @Mock private HeaderDriver headerDriver1; |
| @Mock private HeaderDriver headerDriver2; |
| @Mock private DeepestContentTracker deepestContentTracker; |
| @Mock private Header header1; |
| @Mock private Header header2; |
| @Mock private ScrollObservable scrollObservable; |
| @Mock private PietEventLogger pietEventLogger; |
| |
| private Context context; |
| private LinearLayout frameContainer; |
| private StreamRecyclerViewAdapter streamRecyclerViewAdapter; |
| private List<Header> headers; |
| private StreamAdapterObserver streamAdapterObserver; |
| |
| @Before |
| public void setUp() { |
| initMocks(this); |
| |
| context = Robolectric.buildActivity(Activity.class).get(); |
| context.setTheme(R.style.Light); |
| frameContainer = new LinearLayout(context); |
| |
| when(pietManager.createPietFrameAdapter( |
| ArgumentMatchers.<Supplier<ViewGroup>>any(), |
| any(ActionHandler.class), |
| any(EventLogger.class), |
| eq(context))) |
| .thenReturn(frameAdapter); |
| when(frameAdapter.getFrameContainer()).thenReturn(frameContainer); |
| |
| when(featureDriver1.itemId()).thenReturn(FEATURE_DRIVER_1_ID); |
| when(featureDriver2.itemId()).thenReturn(FEATURE_DRIVER_2_ID); |
| when(initialFeatureDriver.getContentId()).thenReturn(INITIAL_CONTENT_ID); |
| |
| headers = ImmutableList.of(header1, header2); |
| |
| when(featureDriver1.getContentId()).thenReturn(FEATURE_DRIVER_1_CONTENT_ID); |
| when(featureDriver2.getContentId()).thenReturn(FEATURE_DRIVER_2_CONTENT_ID); |
| when(headerDriver1.getHeader()).thenReturn(header1); |
| when(headerDriver2.getHeader()).thenReturn(header2); |
| |
| streamRecyclerViewAdapter = |
| new StreamRecyclerViewAdapter( |
| context, |
| new RecyclerView(context), |
| cardConfiguration, |
| pietManager, |
| deepestContentTracker, |
| contentChangedListener, |
| scrollObservable, |
| CONFIGURATION, |
| pietEventLogger); |
| streamRecyclerViewAdapter.setHeaders(headers); |
| |
| when(driver.getLeafFeatureDrivers()).thenReturn(Lists.newArrayList(initialFeatureDriver)); |
| streamRecyclerViewAdapter.setDriver(driver); |
| |
| streamAdapterObserver = new StreamAdapterObserver(); |
| streamRecyclerViewAdapter.registerAdapterDataObserver(streamAdapterObserver); |
| } |
| |
| @Test |
| public void testCreateViewHolderPiet() { |
| FrameLayout parent = new FrameLayout(context); |
| ViewHolder viewHolder = |
| streamRecyclerViewAdapter.onCreateViewHolder(parent, ViewHolderType.TYPE_CARD); |
| |
| FrameLayout cardView = getCardView(viewHolder); |
| assertThat(cardView.getChildAt(0)).isEqualTo(frameContainer); |
| } |
| |
| @Test |
| public void testCreateViewHolderContinuation() { |
| FrameLayout parent = new FrameLayout(context); |
| ViewHolder viewHolder = |
| streamRecyclerViewAdapter.onCreateViewHolder(parent, ViewHolderType.TYPE_CONTINUATION); |
| FrameLayout viewHolderFrameLayout = getCardView(viewHolder); |
| |
| assertThat(viewHolder).isInstanceOf(ContinuationViewHolder.class); |
| assertThat(viewHolderFrameLayout.getLayoutParams().height).isEqualTo(LayoutParams.WRAP_CONTENT); |
| assertThat(viewHolderFrameLayout.getLayoutParams().width).isEqualTo(LayoutParams.MATCH_PARENT); |
| } |
| |
| @Test |
| public void testCreateViewHolderNoContent() { |
| FrameLayout parent = new FrameLayout(context); |
| ViewHolder viewHolder = |
| streamRecyclerViewAdapter.onCreateViewHolder(parent, ViewHolderType.TYPE_NO_CONTENT); |
| FrameLayout viewHolderFrameLayout = getCardView(viewHolder); |
| |
| assertThat(viewHolder).isInstanceOf(NoContentViewHolder.class); |
| assertThat(viewHolderFrameLayout.getLayoutParams().height).isEqualTo(LayoutParams.WRAP_CONTENT); |
| assertThat(viewHolderFrameLayout.getLayoutParams().width).isEqualTo(LayoutParams.MATCH_PARENT); |
| } |
| |
| @Test |
| public void testCreateViewHolderZeroState() { |
| FrameLayout parent = new FrameLayout(context); |
| ViewHolder viewHolder = |
| streamRecyclerViewAdapter.onCreateViewHolder(parent, ViewHolderType.TYPE_ZERO_STATE); |
| FrameLayout viewHolderFrameLayout = getCardView(viewHolder); |
| |
| assertThat(viewHolder).isInstanceOf(ZeroStateViewHolder.class); |
| assertThat(viewHolderFrameLayout.getLayoutParams().height).isEqualTo(LayoutParams.WRAP_CONTENT); |
| assertThat(viewHolderFrameLayout.getLayoutParams().width).isEqualTo(LayoutParams.MATCH_PARENT); |
| } |
| |
| @Test |
| public void testOnBindViewHolder() { |
| FrameLayout parent = new FrameLayout(context); |
| FeedViewHolder viewHolder = |
| streamRecyclerViewAdapter.onCreateViewHolder(parent, ViewHolderType.TYPE_CARD); |
| |
| streamRecyclerViewAdapter.onBindViewHolder(viewHolder, getContentBindingIndex(0)); |
| |
| verify(initialFeatureDriver).bind(viewHolder); |
| } |
| |
| @Test |
| public void testOnViewRecycled() { |
| PietViewHolder viewHolder = mock(PietViewHolder.class); |
| |
| streamRecyclerViewAdapter.onBindViewHolder(viewHolder, getContentBindingIndex(0)); |
| |
| // Make sure the content model is bound |
| verify(initialFeatureDriver).bind(viewHolder); |
| |
| streamRecyclerViewAdapter.onViewRecycled(viewHolder); |
| |
| verify(initialFeatureDriver).unbind(); |
| } |
| |
| @Test |
| public void testSetDriver_initialContentModels() { |
| // streamRecyclerViewAdapter.setDriver(driver) is called in setup() |
| assertThat(streamRecyclerViewAdapter.getLeafFeatureDrivers()) |
| .containsExactly(initialFeatureDriver); |
| } |
| |
| @Test |
| public void testSetDriver_newDriver() { |
| StreamDriver newDriver = mock(StreamDriver.class); |
| List<LeafFeatureDriver> newFeatureDrivers = Lists.newArrayList(featureDriver1, featureDriver2); |
| |
| when(newDriver.getLeafFeatureDrivers()).thenReturn(newFeatureDrivers); |
| |
| streamRecyclerViewAdapter.setDriver(newDriver); |
| |
| verify(driver).setStreamContentListener(null); |
| assertThat(streamRecyclerViewAdapter.getItemId(getContentBindingIndex(0))) |
| .isEqualTo(FEATURE_DRIVER_1_ID); |
| assertThat(streamRecyclerViewAdapter.getItemId(getContentBindingIndex(1))) |
| .isEqualTo(FEATURE_DRIVER_2_ID); |
| |
| InOrder inOrder = Mockito.inOrder(newDriver); |
| |
| // getLeafFeatureDrivers() needs to be called before setting the listener, otherwise the adapter |
| // could be notified of a child and then given it in getLeafFeatureDrivers(). |
| inOrder.verify(newDriver).getLeafFeatureDrivers(); |
| inOrder.verify(newDriver).setStreamContentListener(streamRecyclerViewAdapter); |
| inOrder.verify(newDriver).maybeRestoreScroll(); |
| } |
| |
| @Test |
| public void testNotifyContentsAdded_endOfList() { |
| streamRecyclerViewAdapter.notifyContentsAdded( |
| 1, Lists.newArrayList(featureDriver1, featureDriver2)); |
| assertThat(streamRecyclerViewAdapter.getLeafFeatureDrivers()) |
| .containsAtLeast(initialFeatureDriver, featureDriver1, featureDriver2); |
| } |
| |
| @Test |
| public void testNotifyContentsAdded_startOfList() { |
| streamRecyclerViewAdapter.notifyContentsAdded( |
| 0, Lists.newArrayList(featureDriver1, featureDriver2)); |
| assertThat(streamRecyclerViewAdapter.getLeafFeatureDrivers()) |
| .containsAtLeast(featureDriver1, featureDriver2, initialFeatureDriver); |
| } |
| |
| @Test |
| public void testNotifyContentsRemoved() { |
| streamRecyclerViewAdapter.notifyContentRemoved(0); |
| assertThat(streamRecyclerViewAdapter.getLeafFeatureDrivers()).isEmpty(); |
| verify(deepestContentTracker).removeContentId(0); |
| } |
| |
| @Test |
| public void testNotifyContentsAdded_notifiesListener() { |
| streamRecyclerViewAdapter.setShown(false); |
| streamRecyclerViewAdapter.notifyContentsAdded( |
| 0, Lists.newArrayList(featureDriver1, featureDriver2)); |
| |
| assertThat(streamAdapterObserver.insertedStart).isEqualTo(headers.size()); |
| assertThat(streamAdapterObserver.insertedCount).isEqualTo(2); |
| verify(contentChangedListener).onContentChanged(); |
| } |
| |
| @Test |
| public void testNotifyContentsAdded_doesNotNotifiesContentChangedListener_ifShownTrue() { |
| streamRecyclerViewAdapter.setShown(true); |
| streamRecyclerViewAdapter.notifyContentsAdded( |
| 0, Lists.newArrayList(featureDriver1, featureDriver2)); |
| |
| verify(contentChangedListener, never()).onContentChanged(); |
| } |
| |
| @Test |
| public void testNotifyContentsAdded_streamVisibleFalse() { |
| streamRecyclerViewAdapter.setStreamContentVisible(false); |
| streamAdapterObserver.reset(); |
| |
| streamRecyclerViewAdapter.notifyContentsAdded( |
| 0, Lists.newArrayList(featureDriver1, featureDriver2)); |
| assertThat(streamAdapterObserver.insertedStart).isEqualTo(0); |
| assertThat(streamAdapterObserver.insertedCount).isEqualTo(0); |
| } |
| |
| @Test |
| public void testNotifyContentsRemoved_notifiesListener() { |
| streamRecyclerViewAdapter.setShown(false); |
| streamRecyclerViewAdapter.notifyContentRemoved(0); |
| |
| assertThat(streamAdapterObserver.removedStart).isEqualTo(headers.size()); |
| assertThat(streamAdapterObserver.removedCount).isEqualTo(1); |
| verify(contentChangedListener).onContentChanged(); |
| } |
| |
| @Test |
| public void testNotifyContentsRemoved_doesNotNotifiesContentChangedListener_ifShownTrue() { |
| streamRecyclerViewAdapter.setShown(true); |
| streamRecyclerViewAdapter.notifyContentRemoved(0); |
| |
| verify(contentChangedListener, never()).onContentChanged(); |
| } |
| |
| @Test |
| public void testNotifyContentsRemoved_streamVisibleFalse() { |
| streamRecyclerViewAdapter.setStreamContentVisible(false); |
| streamAdapterObserver.reset(); |
| |
| streamRecyclerViewAdapter.notifyContentRemoved(0); |
| assertThat(streamAdapterObserver.removedStart).isEqualTo(0); |
| assertThat(streamAdapterObserver.removedCount).isEqualTo(0); |
| } |
| |
| @Test |
| public void testNotifyContentsCleared_notifiesListener() { |
| streamRecyclerViewAdapter.setShown(false); |
| streamRecyclerViewAdapter.notifyContentsCleared(); |
| |
| assertThat(streamAdapterObserver.removedStart).isEqualTo(headers.size()); |
| assertThat(streamAdapterObserver.removedCount).isEqualTo(1); |
| verify(contentChangedListener).onContentChanged(); |
| } |
| |
| @Test |
| public void testNotifyContentsCleared_doesNotNotifiesContentChangedListener_ifShownTrue() { |
| streamRecyclerViewAdapter.setShown(true); |
| streamRecyclerViewAdapter.notifyContentsCleared(); |
| |
| verify(contentChangedListener, never()).onContentChanged(); |
| } |
| |
| @Test |
| public void testNotifyContentsCleared_streamVisibleFalse() { |
| streamRecyclerViewAdapter.setStreamContentVisible(false); |
| streamAdapterObserver.reset(); |
| |
| streamRecyclerViewAdapter.notifyContentsCleared(); |
| assertThat(streamAdapterObserver.removedStart).isEqualTo(0); |
| assertThat(streamAdapterObserver.removedCount).isEqualTo(0); |
| } |
| |
| @Test |
| public void testOnDestroy() { |
| streamRecyclerViewAdapter = |
| new StreamRecyclerViewAdapter( |
| context, |
| new RecyclerView(context), |
| cardConfiguration, |
| pietManager, |
| deepestContentTracker, |
| contentChangedListener, |
| scrollObservable, |
| CONFIGURATION, |
| pietEventLogger) { |
| @Override |
| HeaderDriver createHeaderDriver(Header header) { |
| if (header == headers.get(0)) { |
| return headerDriver1; |
| } |
| return headerDriver2; |
| } |
| }; |
| streamRecyclerViewAdapter.setHeaders(headers); |
| streamRecyclerViewAdapter.onDestroy(); |
| |
| verify(headerDriver1).unbind(); |
| verify(headerDriver1).onDestroy(); |
| verify(headerDriver2).unbind(); |
| verify(headerDriver2).onDestroy(); |
| } |
| |
| @Test |
| public void testSetHeaders_destroysPreviousHeaderDrivers() { |
| streamRecyclerViewAdapter = |
| new StreamRecyclerViewAdapter( |
| context, |
| new RecyclerView(context), |
| cardConfiguration, |
| pietManager, |
| deepestContentTracker, |
| contentChangedListener, |
| scrollObservable, |
| CONFIGURATION, |
| pietEventLogger) { |
| @Override |
| HeaderDriver createHeaderDriver(Header header) { |
| if (header == headers.get(0)) { |
| return headerDriver1; |
| } |
| return headerDriver2; |
| } |
| }; |
| streamRecyclerViewAdapter.setHeaders(headers); |
| |
| streamRecyclerViewAdapter.setHeaders(Collections.emptyList()); |
| |
| verify(headerDriver1).onDestroy(); |
| verify(headerDriver2).onDestroy(); |
| } |
| |
| @Test |
| public void testGetItemCount() { |
| assertThat(streamRecyclerViewAdapter.getItemCount()).isEqualTo(headers.size() + 1); |
| } |
| |
| @Test |
| public void testGetItemCount_streamVisibleFalse() { |
| streamRecyclerViewAdapter.setStreamContentVisible(false); |
| assertThat(streamRecyclerViewAdapter.getItemCount()).isEqualTo(headers.size()); |
| } |
| |
| @Test |
| public void setStreamContentVisible_hidesContent() { |
| streamRecyclerViewAdapter.setStreamContentVisible(false); |
| assertThat(streamAdapterObserver.removedStart).isEqualTo(headers.size()); |
| assertThat(streamAdapterObserver.removedCount).isEqualTo(1); |
| } |
| |
| @Test |
| public void setStreamContentVisible_hidesContent_doubleCall() { |
| streamRecyclerViewAdapter.setStreamContentVisible(false); |
| |
| streamAdapterObserver.reset(); |
| streamRecyclerViewAdapter.setStreamContentVisible(false); |
| |
| // Nothing additional should get called |
| assertThat(streamAdapterObserver.removedStart).isEqualTo(0); |
| assertThat(streamAdapterObserver.removedCount).isEqualTo(0); |
| } |
| |
| @Test |
| public void setStreamContentVisible_showsContent() { |
| streamRecyclerViewAdapter.setStreamContentVisible(false); |
| streamAdapterObserver.reset(); |
| |
| streamRecyclerViewAdapter.setStreamContentVisible(true); |
| assertThat(streamAdapterObserver.insertedStart).isEqualTo(headers.size()); |
| assertThat(streamAdapterObserver.insertedCount).isEqualTo(1); |
| } |
| |
| @Test |
| public void setStreamContentVisible_showsContent_doubleCall() { |
| streamRecyclerViewAdapter.setStreamContentVisible(false); |
| streamAdapterObserver.reset(); |
| |
| streamRecyclerViewAdapter.setStreamContentVisible(true); |
| |
| streamAdapterObserver.reset(); |
| streamRecyclerViewAdapter.setStreamContentVisible(true); |
| |
| // Nothing additional should get called |
| assertThat(streamAdapterObserver.insertedStart).isEqualTo(0); |
| assertThat(streamAdapterObserver.insertedCount).isEqualTo(0); |
| } |
| |
| @Test |
| public void testOnBindViewHolder_updatesDeepestViewedContent() { |
| PietViewHolder viewHolder = mock(PietViewHolder.class); |
| |
| streamRecyclerViewAdapter.onBindViewHolder(viewHolder, getContentBindingIndex(0)); |
| |
| verify(deepestContentTracker).updateDeepestContentTracker(0, INITIAL_CONTENT_ID); |
| } |
| |
| @Test |
| public void testDismissHeader() { |
| streamRecyclerViewAdapter = |
| new StreamRecyclerViewAdapter( |
| context, |
| new RecyclerView(context), |
| cardConfiguration, |
| pietManager, |
| deepestContentTracker, |
| contentChangedListener, |
| scrollObservable, |
| CONFIGURATION, |
| pietEventLogger) { |
| @Override |
| HeaderDriver createHeaderDriver(Header header) { |
| if (header == headers.get(0)) { |
| return headerDriver1; |
| } |
| return headerDriver2; |
| } |
| }; |
| streamRecyclerViewAdapter.setHeaders(headers); |
| assertThat(streamRecyclerViewAdapter.getItemCount()).isEqualTo(2); |
| |
| streamRecyclerViewAdapter.dismissHeader(header1); |
| |
| assertThat(streamRecyclerViewAdapter.getItemCount()).isEqualTo(1); |
| verify(header1).onDismissed(); |
| verify(headerDriver1).onDestroy(); |
| } |
| |
| @Test |
| public void testDismissHeader_whenNoHeaders() { |
| streamRecyclerViewAdapter.setHeaders(Collections.emptyList()); |
| reset(header1); |
| assertThat(streamRecyclerViewAdapter.getItemCount()).isEqualTo(1); |
| |
| streamRecyclerViewAdapter.dismissHeader(header1); |
| |
| assertThat(streamRecyclerViewAdapter.getItemCount()).isEqualTo(1); |
| verify(header1, never()).onDismissed(); |
| } |
| |
| @Test |
| public void testRebind() { |
| PietViewHolder viewHolder = mock(PietViewHolder.class); |
| streamRecyclerViewAdapter.onBindViewHolder(viewHolder, getContentBindingIndex(0)); |
| streamRecyclerViewAdapter.rebind(); |
| verify(initialFeatureDriver).maybeRebind(); |
| } |
| |
| private FrameLayout getCardView(ViewHolder viewHolder) { |
| return (FrameLayout) viewHolder.itemView; |
| } |
| |
| private int getContentBindingIndex(int index) { |
| return HEADER_COUNT + index; |
| } |
| |
| private class StreamAdapterObserver extends RecyclerView.AdapterDataObserver { |
| |
| private int insertedStart; |
| private int insertedCount; |
| private int removedStart; |
| private int removedCount; |
| |
| @Override |
| public void onItemRangeInserted(int positionStart, int itemCount) { |
| insertedStart = positionStart; |
| insertedCount = itemCount; |
| } |
| |
| @Override |
| public void onItemRangeRemoved(int positionStart, int itemCount) { |
| removedStart = positionStart; |
| removedCount = itemCount; |
| } |
| |
| public void reset() { |
| insertedStart = 0; |
| insertedCount = 0; |
| removedStart = 0; |
| removedCount = 0; |
| } |
| } |
| } |