1package android.support.v17.leanback.app;
2
3import android.support.v17.leanback.widget.ObjectAdapter;
4import android.support.v17.leanback.widget.Row;
5
6/**
7 * Wrapper class for {@link ObjectAdapter} used by {@link BrowseFragment} to initialize
8 * {@link RowsFragment}. We use invisible rows to represent
9 * {@link android.support.v17.leanback.widget.DividerRow},
10 * {@link android.support.v17.leanback.widget.SectionRow} and
11 * {@link android.support.v17.leanback.widget.PageRow} in RowsFragment. In case we have an
12 * invisible row at the end of a RowsFragment, it creates a jumping effect as the layout manager
13 * thinks there are items even though they're invisible. This class takes care of filtering out
14 * the invisible rows at the end. In case the data inside the adapter changes, it adjusts the
15 * bounds to reflect the latest data.
16 */
17class ListRowDataAdapter extends ObjectAdapter {
18    public static final int ON_ITEM_RANGE_CHANGED = 2;
19    public static final int ON_ITEM_RANGE_INSERTED = 4;
20    public static final int ON_ITEM_RANGE_REMOVED = 8;
21    public static final int ON_CHANGED = 16;
22
23    private final ObjectAdapter mAdapter;
24    int mLastVisibleRowIndex;
25
26    public ListRowDataAdapter(ObjectAdapter adapter) {
27        super(adapter.getPresenterSelector());
28        this.mAdapter = adapter;
29        initialize();
30
31        // If an user implements its own ObjectAdapter, notification corresponding to data
32        // updates can be batched e.g. remove, add might be followed by notifyRemove, notifyAdd.
33        // But underlying data would have changed during the notifyRemove call by the previous add
34        // operation. To handle this case, we use QueueBasedDataObserver which forces
35        // recyclerview to do a full data refresh after each update operation.
36        if (adapter.isImmediateNotifySupported()) {
37            mAdapter.registerObserver(new SimpleDataObserver());
38        } else {
39            mAdapter.registerObserver(new QueueBasedDataObserver());
40        }
41    }
42
43    void initialize() {
44        mLastVisibleRowIndex = -1;
45        int i = mAdapter.size() - 1;
46        while (i >= 0) {
47            Row item = (Row) mAdapter.get(i);
48            if (item.isRenderedAsRowView()) {
49                mLastVisibleRowIndex = i;
50                break;
51            }
52            i--;
53        }
54    }
55
56    @Override
57    public int size() {
58        return mLastVisibleRowIndex + 1;
59    }
60
61    @Override
62    public Object get(int index) {
63        return mAdapter.get(index);
64    }
65
66    void doNotify(int eventType, int positionStart, int itemCount) {
67        switch (eventType) {
68            case ON_ITEM_RANGE_CHANGED:
69                notifyItemRangeChanged(positionStart, itemCount);
70                break;
71            case ON_ITEM_RANGE_INSERTED:
72                notifyItemRangeInserted(positionStart, itemCount);
73                break;
74            case ON_ITEM_RANGE_REMOVED:
75                notifyItemRangeRemoved(positionStart, itemCount);
76                break;
77            case ON_CHANGED:
78                notifyChanged();
79                break;
80            default:
81                throw new IllegalArgumentException("Invalid event type " + eventType);
82        }
83    }
84
85    private class SimpleDataObserver extends DataObserver {
86
87        SimpleDataObserver() {
88        }
89
90        @Override
91        public void onItemRangeChanged(int positionStart, int itemCount) {
92            if (positionStart <= mLastVisibleRowIndex) {
93                onEventFired(ON_ITEM_RANGE_CHANGED, positionStart,
94                        Math.min(itemCount, mLastVisibleRowIndex - positionStart + 1));
95            }
96        }
97
98        @Override
99        public void onItemRangeInserted(int positionStart, int itemCount) {
100            if (positionStart <= mLastVisibleRowIndex) {
101                mLastVisibleRowIndex += itemCount;
102                onEventFired(ON_ITEM_RANGE_INSERTED, positionStart, itemCount);
103                return;
104            }
105
106            int lastVisibleRowIndex = mLastVisibleRowIndex;
107            initialize();
108            if (mLastVisibleRowIndex > lastVisibleRowIndex) {
109                int totalItems = mLastVisibleRowIndex - lastVisibleRowIndex;
110                onEventFired(ON_ITEM_RANGE_INSERTED, lastVisibleRowIndex + 1, totalItems);
111            }
112        }
113
114        @Override
115        public void onItemRangeRemoved(int positionStart, int itemCount) {
116            if (positionStart + itemCount - 1 < mLastVisibleRowIndex) {
117                mLastVisibleRowIndex -= itemCount;
118                onEventFired(ON_ITEM_RANGE_REMOVED, positionStart, itemCount);
119                return;
120            }
121
122            int lastVisibleRowIndex = mLastVisibleRowIndex;
123            initialize();
124            int totalItems = lastVisibleRowIndex - mLastVisibleRowIndex;
125            if (totalItems > 0) {
126                onEventFired(ON_ITEM_RANGE_REMOVED,
127                        Math.min(mLastVisibleRowIndex + 1, positionStart),
128                        totalItems);
129            }
130        }
131
132        @Override
133        public void onChanged() {
134            initialize();
135            onEventFired(ON_CHANGED, -1, -1);
136        }
137
138        protected void onEventFired(int eventType, int positionStart, int itemCount) {
139            doNotify(eventType, positionStart, itemCount);
140        }
141    }
142
143
144    /**
145     * When using custom {@link ObjectAdapter}, it's possible that the user may make multiple
146     * changes to the underlying data at once. The notifications about those updates may be
147     * batched and the underlying data would have changed to reflect latest updates as opposed
148     * to intermediate changes. In order to force RecyclerView to refresh the view with access
149     * only to the final data, we call notifyChange().
150     */
151    private class QueueBasedDataObserver extends DataObserver {
152
153        QueueBasedDataObserver() {
154        }
155
156        @Override
157        public void onChanged() {
158            initialize();
159            notifyChanged();
160        }
161    }
162}
163