1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14package android.support.v17.leanback.widget;
15
16import android.database.Observable;
17
18/**
19 * Base class adapter to be used in leanback activities.  Provides access to a data model and is
20 * decoupled from the presentation of the items via {@link PresenterSelector}.
21 */
22public abstract class ObjectAdapter {
23
24    /** Indicates that an id has not been set. */
25    public static final int NO_ID = -1;
26
27    /**
28     * A DataObserver can be notified when an ObjectAdapter's underlying data
29     * changes. Separate methods provide notifications about different types of
30     * changes.
31     */
32    public static abstract class DataObserver {
33        /**
34         * Called whenever the ObjectAdapter's data has changed in some manner
35         * outside of the set of changes covered by the other range-based change
36         * notification methods.
37         */
38        public void onChanged() {
39        }
40
41        /**
42         * Called when a range of items in the ObjectAdapter has changed. The
43         * basic ordering and structure of the ObjectAdapter has not changed.
44         *
45         * @param positionStart The position of the first item that changed.
46         * @param itemCount     The number of items changed.
47         */
48        public void onItemRangeChanged(int positionStart, int itemCount) {
49            onChanged();
50        }
51
52        /**
53         * Called when a range of items in the ObjectAdapter has changed. The
54         * basic ordering and structure of the ObjectAdapter has not changed.
55         *
56         * @param positionStart The position of the first item that changed.
57         * @param itemCount     The number of items changed.
58         * @param payload       Optional parameter, use null to identify a "full" update.
59         */
60        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
61            onChanged();
62        }
63
64        /**
65         * Called when a range of items is inserted into the ObjectAdapter.
66         *
67         * @param positionStart The position of the first inserted item.
68         * @param itemCount     The number of items inserted.
69         */
70        public void onItemRangeInserted(int positionStart, int itemCount) {
71            onChanged();
72        }
73
74        /**
75         * Called when an item is moved from one position to another position
76         *
77         * @param fromPosition Previous position of the item.
78         * @param toPosition   New position of the item.
79         */
80        public void onItemMoved(int fromPosition, int toPosition) {
81            onChanged();
82        }
83
84        /**
85         * Called when a range of items is removed from the ObjectAdapter.
86         *
87         * @param positionStart The position of the first removed item.
88         * @param itemCount     The number of items removed.
89         */
90        public void onItemRangeRemoved(int positionStart, int itemCount) {
91            onChanged();
92        }
93    }
94
95    private static final class DataObservable extends Observable<DataObserver> {
96
97        DataObservable() {
98        }
99
100        public void notifyChanged() {
101            for (int i = mObservers.size() - 1; i >= 0; i--) {
102                mObservers.get(i).onChanged();
103            }
104        }
105
106        public void notifyItemRangeChanged(int positionStart, int itemCount) {
107            for (int i = mObservers.size() - 1; i >= 0; i--) {
108                mObservers.get(i).onItemRangeChanged(positionStart, itemCount);
109            }
110        }
111
112        public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
113            for (int i = mObservers.size() - 1; i >= 0; i--) {
114                mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
115            }
116        }
117
118        public void notifyItemRangeInserted(int positionStart, int itemCount) {
119            for (int i = mObservers.size() - 1; i >= 0; i--) {
120                mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
121            }
122        }
123
124        public void notifyItemRangeRemoved(int positionStart, int itemCount) {
125            for (int i = mObservers.size() - 1; i >= 0; i--) {
126                mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
127            }
128        }
129
130        public void notifyItemMoved(int positionStart, int toPosition) {
131            for (int i = mObservers.size() - 1; i >= 0; i--) {
132                mObservers.get(i).onItemMoved(positionStart, toPosition);
133            }
134        }
135    }
136
137    private final DataObservable mObservable = new DataObservable();
138    private boolean mHasStableIds;
139    private PresenterSelector mPresenterSelector;
140
141    /**
142     * Constructs an adapter with the given {@link PresenterSelector}.
143     */
144    public ObjectAdapter(PresenterSelector presenterSelector) {
145        setPresenterSelector(presenterSelector);
146    }
147
148    /**
149     * Constructs an adapter that uses the given {@link Presenter} for all items.
150     */
151    public ObjectAdapter(Presenter presenter) {
152        setPresenterSelector(new SinglePresenterSelector(presenter));
153    }
154
155    /**
156     * Constructs an adapter.
157     */
158    public ObjectAdapter() {
159    }
160
161    /**
162     * Sets the presenter selector.  May not be null.
163     */
164    public final void setPresenterSelector(PresenterSelector presenterSelector) {
165        if (presenterSelector == null) {
166            throw new IllegalArgumentException("Presenter selector must not be null");
167        }
168        final boolean update = (mPresenterSelector != null);
169        final boolean selectorChanged = update && mPresenterSelector != presenterSelector;
170
171        mPresenterSelector = presenterSelector;
172
173        if (selectorChanged) {
174            onPresenterSelectorChanged();
175        }
176        if (update) {
177            notifyChanged();
178        }
179    }
180
181    /**
182     * Called when {@link #setPresenterSelector(PresenterSelector)} is called
183     * and the PresenterSelector differs from the previous one.
184     */
185    protected void onPresenterSelectorChanged() {
186    }
187
188    /**
189     * Returns the presenter selector for this ObjectAdapter.
190     */
191    public final PresenterSelector getPresenterSelector() {
192        return mPresenterSelector;
193    }
194
195    /**
196     * Registers a DataObserver for data change notifications.
197     */
198    public final void registerObserver(DataObserver observer) {
199        mObservable.registerObserver(observer);
200    }
201
202    /**
203     * Unregisters a DataObserver for data change notifications.
204     */
205    public final void unregisterObserver(DataObserver observer) {
206        mObservable.unregisterObserver(observer);
207    }
208
209    /**
210     * Unregisters all DataObservers for this ObjectAdapter.
211     */
212    public final void unregisterAllObservers() {
213        mObservable.unregisterAll();
214    }
215
216    /**
217     * Notifies UI that some items has changed.
218     *
219     * @param positionStart Starting position of the changed items.
220     * @param itemCount     Total number of items that changed.
221     */
222    public final void notifyItemRangeChanged(int positionStart, int itemCount) {
223        mObservable.notifyItemRangeChanged(positionStart, itemCount);
224    }
225
226    /**
227     * Notifies UI that some items has changed.
228     *
229     * @param positionStart Starting position of the changed items.
230     * @param itemCount     Total number of items that changed.
231     * @param payload       Optional parameter, use null to identify a "full" update.
232     */
233    public final void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
234        mObservable.notifyItemRangeChanged(positionStart, itemCount, payload);
235    }
236
237    /**
238     * Notifies UI that new items has been inserted.
239     *
240     * @param positionStart Position where new items has been inserted.
241     * @param itemCount     Count of the new items has been inserted.
242     */
243    final protected void notifyItemRangeInserted(int positionStart, int itemCount) {
244        mObservable.notifyItemRangeInserted(positionStart, itemCount);
245    }
246
247    /**
248     * Notifies UI that some items that has been removed.
249     *
250     * @param positionStart Starting position of the removed items.
251     * @param itemCount     Total number of items that has been removed.
252     */
253    final protected void notifyItemRangeRemoved(int positionStart, int itemCount) {
254        mObservable.notifyItemRangeRemoved(positionStart, itemCount);
255    }
256
257    /**
258     * Notifies UI that item at fromPosition has been moved to toPosition.
259     *
260     * @param fromPosition Previous position of the item.
261     * @param toPosition   New position of the item.
262     */
263    protected final void notifyItemMoved(int fromPosition, int toPosition) {
264        mObservable.notifyItemMoved(fromPosition, toPosition);
265    }
266
267    /**
268     * Notifies UI that the underlying data has changed.
269     */
270    final protected void notifyChanged() {
271        mObservable.notifyChanged();
272    }
273
274    /**
275     * Returns true if the item ids are stable across changes to the
276     * underlying data.  When this is true, clients of the ObjectAdapter can use
277     * {@link #getId(int)} to correlate Objects across changes.
278     */
279    public final boolean hasStableIds() {
280        return mHasStableIds;
281    }
282
283    /**
284     * Sets whether the item ids are stable across changes to the underlying
285     * data.
286     */
287    public final void setHasStableIds(boolean hasStableIds) {
288        boolean changed = mHasStableIds != hasStableIds;
289        mHasStableIds = hasStableIds;
290
291        if (changed) {
292            onHasStableIdsChanged();
293        }
294    }
295
296    /**
297     * Called when {@link #setHasStableIds(boolean)} is called and the status
298     * of stable ids has changed.
299     */
300    protected void onHasStableIdsChanged() {
301    }
302
303    /**
304     * Returns the {@link Presenter} for the given item from the adapter.
305     */
306    public final Presenter getPresenter(Object item) {
307        if (mPresenterSelector == null) {
308            throw new IllegalStateException("Presenter selector must not be null");
309        }
310        return mPresenterSelector.getPresenter(item);
311    }
312
313    /**
314     * Returns the number of items in the adapter.
315     */
316    public abstract int size();
317
318    /**
319     * Returns the item for the given position.
320     */
321    public abstract Object get(int position);
322
323    /**
324     * Returns the id for the given position.
325     */
326    public long getId(int position) {
327        return NO_ID;
328    }
329
330    /**
331     * Returns true if the adapter pairs each underlying data change with a call to notify and
332     * false otherwise.
333     */
334    public boolean isImmediateNotifySupported() {
335        return false;
336    }
337}
338