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