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  */
14 package android.support.v17.leanback.widget;
15 
16 import 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  */
22 public 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          */
onChanged()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          */
onItemRangeChanged(int positionStart, int itemCount)48         public void onItemRangeChanged(int positionStart, int itemCount) {
49             onChanged();
50         }
51 
52         /**
53          * Called when a range of items is inserted into the ObjectAdapter.
54          *
55          * @param positionStart The position of the first inserted item.
56          * @param itemCount The number of items inserted.
57          */
onItemRangeInserted(int positionStart, int itemCount)58         public void onItemRangeInserted(int positionStart, int itemCount) {
59             onChanged();
60         }
61 
62         /**
63          * Called when a range of items is removed from the ObjectAdapter.
64          *
65          * @param positionStart The position of the first removed item.
66          * @param itemCount The number of items removed.
67          */
onItemRangeRemoved(int positionStart, int itemCount)68         public void onItemRangeRemoved(int positionStart, int itemCount) {
69             onChanged();
70         }
71     }
72 
73     private static final class DataObservable extends Observable<DataObserver> {
74 
notifyChanged()75         public void notifyChanged() {
76             for (int i = mObservers.size() - 1; i >= 0; i--) {
77                 mObservers.get(i).onChanged();
78             }
79         }
80 
notifyItemRangeChanged(int positionStart, int itemCount)81         public void notifyItemRangeChanged(int positionStart, int itemCount) {
82             for (int i = mObservers.size() - 1; i >= 0; i--) {
83                 mObservers.get(i).onItemRangeChanged(positionStart, itemCount);
84             }
85         }
86 
notifyItemRangeInserted(int positionStart, int itemCount)87         public void notifyItemRangeInserted(int positionStart, int itemCount) {
88             for (int i = mObservers.size() - 1; i >= 0; i--) {
89                 mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
90             }
91         }
92 
notifyItemRangeRemoved(int positionStart, int itemCount)93         public void notifyItemRangeRemoved(int positionStart, int itemCount) {
94             for (int i = mObservers.size() - 1; i >= 0; i--) {
95                 mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
96             }
97         }
98     }
99 
100     private final DataObservable mObservable = new DataObservable();
101     private boolean mHasStableIds;
102     private PresenterSelector mPresenterSelector;
103 
104     /**
105      * Constructs an adapter with the given {@link PresenterSelector}.
106      */
ObjectAdapter(PresenterSelector presenterSelector)107     public ObjectAdapter(PresenterSelector presenterSelector) {
108         setPresenterSelector(presenterSelector);
109     }
110 
111     /**
112      * Constructs an adapter that uses the given {@link Presenter} for all items.
113      */
ObjectAdapter(Presenter presenter)114     public ObjectAdapter(Presenter presenter) {
115         setPresenterSelector(new SinglePresenterSelector(presenter));
116     }
117 
118     /**
119      * Constructs an adapter.
120      */
ObjectAdapter()121     public ObjectAdapter() {
122     }
123 
124     /**
125      * Sets the presenter selector.  May not be null.
126      */
setPresenterSelector(PresenterSelector presenterSelector)127     public final void setPresenterSelector(PresenterSelector presenterSelector) {
128         if (presenterSelector == null) {
129             throw new IllegalArgumentException("Presenter selector must not be null");
130         }
131         final boolean update = (mPresenterSelector != null);
132         final boolean selectorChanged = update && mPresenterSelector != presenterSelector;
133 
134         mPresenterSelector = presenterSelector;
135 
136         if (selectorChanged) {
137             onPresenterSelectorChanged();
138         }
139         if (update) {
140             notifyChanged();
141         }
142     }
143 
144     /**
145      * Called when {@link #setPresenterSelector(PresenterSelector)} is called
146      * and the PresenterSelector differs from the previous one.
147      */
onPresenterSelectorChanged()148     protected void onPresenterSelectorChanged() {
149     }
150 
151     /**
152      * Returns the presenter selector for this ObjectAdapter.
153      */
getPresenterSelector()154     public final PresenterSelector getPresenterSelector() {
155         return mPresenterSelector;
156     }
157 
158     /**
159      * Registers a DataObserver for data change notifications.
160      */
registerObserver(DataObserver observer)161     public final void registerObserver(DataObserver observer) {
162         mObservable.registerObserver(observer);
163     }
164 
165     /**
166      * Unregisters a DataObserver for data change notifications.
167      */
unregisterObserver(DataObserver observer)168     public final void unregisterObserver(DataObserver observer) {
169         mObservable.unregisterObserver(observer);
170     }
171 
172     /**
173      * Unregisters all DataObservers for this ObjectAdapter.
174      */
unregisterAllObservers()175     public final void unregisterAllObservers() {
176         mObservable.unregisterAll();
177     }
178 
notifyItemRangeChanged(int positionStart, int itemCount)179     final protected void notifyItemRangeChanged(int positionStart, int itemCount) {
180         mObservable.notifyItemRangeChanged(positionStart, itemCount);
181     }
182 
notifyItemRangeInserted(int positionStart, int itemCount)183     final protected void notifyItemRangeInserted(int positionStart, int itemCount) {
184         mObservable.notifyItemRangeInserted(positionStart, itemCount);
185     }
186 
notifyItemRangeRemoved(int positionStart, int itemCount)187     final protected void notifyItemRangeRemoved(int positionStart, int itemCount) {
188         mObservable.notifyItemRangeRemoved(positionStart, itemCount);
189     }
190 
notifyChanged()191     final protected void notifyChanged() {
192         mObservable.notifyChanged();
193     }
194 
195     /**
196      * Returns true if the item ids are stable across changes to the
197      * underlying data.  When this is true, clients of the ObjectAdapter can use
198      * {@link #getId(int)} to correlate Objects across changes.
199      */
hasStableIds()200     public final boolean hasStableIds() {
201         return mHasStableIds;
202     }
203 
204     /**
205      * Sets whether the item ids are stable across changes to the underlying
206      * data.
207      */
setHasStableIds(boolean hasStableIds)208     public final void setHasStableIds(boolean hasStableIds) {
209         boolean changed = mHasStableIds != hasStableIds;
210         mHasStableIds = hasStableIds;
211 
212         if (changed) {
213             onHasStableIdsChanged();
214         }
215     }
216 
217     /**
218      * Called when {@link #setHasStableIds(boolean)} is called and the status
219      * of stable ids has changed.
220      */
onHasStableIdsChanged()221     protected void onHasStableIdsChanged() {
222     }
223 
224     /**
225      * Returns the {@link Presenter} for the given item from the adapter.
226      */
getPresenter(Object item)227     public final Presenter getPresenter(Object item) {
228         if (mPresenterSelector == null) {
229             throw new IllegalStateException("Presenter selector must not be null");
230         }
231         return mPresenterSelector.getPresenter(item);
232     }
233 
234     /**
235      * Returns the number of items in the adapter.
236      */
size()237     public abstract int size();
238 
239     /**
240      * Returns the item for the given position.
241      */
get(int position)242     public abstract Object get(int position);
243 
244     /**
245      * Returns the id for the given position.
246      */
getId(int position)247     public long getId(int position) {
248         return NO_ID;
249     }
250 }
251