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 androidx.leanback.widget;
15
16import android.util.Log;
17import android.view.View;
18import android.view.ViewGroup;
19
20import androidx.recyclerview.widget.RecyclerView;
21
22import java.util.ArrayList;
23import java.util.List;
24
25/**
26 * Bridge from {@link Presenter} to {@link RecyclerView.Adapter}. Public to allow use by third
27 * party Presenters.
28 */
29public class ItemBridgeAdapter extends RecyclerView.Adapter implements FacetProviderAdapter {
30    static final String TAG = "ItemBridgeAdapter";
31    static final boolean DEBUG = false;
32
33    /**
34     * Interface for listening to ViewHolder operations.
35     */
36    public static class AdapterListener {
37        public void onAddPresenter(Presenter presenter, int type) {
38        }
39
40        public void onCreate(ViewHolder viewHolder) {
41        }
42
43        public void onBind(ViewHolder viewHolder) {
44        }
45
46        public void onBind(ViewHolder viewHolder, List payloads) {
47            onBind(viewHolder);
48        }
49
50        public void onUnbind(ViewHolder viewHolder) {
51        }
52
53        public void onAttachedToWindow(ViewHolder viewHolder) {
54        }
55
56        public void onDetachedFromWindow(ViewHolder viewHolder) {
57        }
58    }
59
60    /**
61     * Interface for wrapping a view created by a Presenter into another view.
62     * The wrapper must be the immediate parent of the wrapped view.
63     */
64    public static abstract class Wrapper {
65        public abstract View createWrapper(View root);
66
67        public abstract void wrap(View wrapper, View wrapped);
68    }
69
70    private ObjectAdapter mAdapter;
71    Wrapper mWrapper;
72    private PresenterSelector mPresenterSelector;
73    FocusHighlightHandler mFocusHighlight;
74    private AdapterListener mAdapterListener;
75    private ArrayList<Presenter> mPresenters = new ArrayList<Presenter>();
76
77    final class OnFocusChangeListener implements View.OnFocusChangeListener {
78        View.OnFocusChangeListener mChainedListener;
79
80        @Override
81        public void onFocusChange(View view, boolean hasFocus) {
82            if (DEBUG) {
83                Log.v(TAG, "onFocusChange " + hasFocus + " " + view
84                        + " mFocusHighlight" + mFocusHighlight);
85            }
86            if (mWrapper != null) {
87                view = (View) view.getParent();
88            }
89            if (mFocusHighlight != null) {
90                mFocusHighlight.onItemFocused(view, hasFocus);
91            }
92            if (mChainedListener != null) {
93                mChainedListener.onFocusChange(view, hasFocus);
94            }
95        }
96    }
97
98    /**
99     * ViewHolder for the ItemBridgeAdapter.
100     */
101    public class ViewHolder extends RecyclerView.ViewHolder implements FacetProvider {
102        final Presenter mPresenter;
103        final Presenter.ViewHolder mHolder;
104        final OnFocusChangeListener mFocusChangeListener = new OnFocusChangeListener();
105        Object mItem;
106        Object mExtraObject;
107
108        /**
109         * Get {@link Presenter}.
110         */
111        public final Presenter getPresenter() {
112            return mPresenter;
113        }
114
115        /**
116         * Get {@link Presenter.ViewHolder}.
117         */
118        public final Presenter.ViewHolder getViewHolder() {
119            return mHolder;
120        }
121
122        /**
123         * Get currently bound object.
124         */
125        public final Object getItem() {
126            return mItem;
127        }
128
129        /**
130         * Get extra object associated with the view.  Developer can attach
131         * any customized UI object in addition to {@link Presenter.ViewHolder}.
132         * A typical use case is attaching an animator object.
133         */
134        public final Object getExtraObject() {
135            return mExtraObject;
136        }
137
138        /**
139         * Set extra object associated with the view.  Developer can attach
140         * any customized UI object in addition to {@link Presenter.ViewHolder}.
141         * A typical use case is attaching an animator object.
142         */
143        public void setExtraObject(Object object) {
144            mExtraObject = object;
145        }
146
147        @Override
148        public Object getFacet(Class<?> facetClass) {
149            return mHolder.getFacet(facetClass);
150        }
151
152        ViewHolder(Presenter presenter, View view, Presenter.ViewHolder holder) {
153            super(view);
154            mPresenter = presenter;
155            mHolder = holder;
156        }
157    }
158
159    private ObjectAdapter.DataObserver mDataObserver = new ObjectAdapter.DataObserver() {
160        @Override
161        public void onChanged() {
162            ItemBridgeAdapter.this.notifyDataSetChanged();
163        }
164
165        @Override
166        public void onItemRangeChanged(int positionStart, int itemCount) {
167            ItemBridgeAdapter.this.notifyItemRangeChanged(positionStart, itemCount);
168        }
169
170        @Override
171        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
172            ItemBridgeAdapter.this.notifyItemRangeChanged(positionStart, itemCount, payload);
173        }
174
175        @Override
176        public void onItemRangeInserted(int positionStart, int itemCount) {
177            ItemBridgeAdapter.this.notifyItemRangeInserted(positionStart, itemCount);
178        }
179
180        @Override
181        public void onItemRangeRemoved(int positionStart, int itemCount) {
182            ItemBridgeAdapter.this.notifyItemRangeRemoved(positionStart, itemCount);
183        }
184
185        @Override
186        public void onItemMoved(int fromPosition, int toPosition) {
187            ItemBridgeAdapter.this.notifyItemMoved(fromPosition, toPosition);
188        }
189    };
190
191    public ItemBridgeAdapter(ObjectAdapter adapter, PresenterSelector presenterSelector) {
192        setAdapter(adapter);
193        mPresenterSelector = presenterSelector;
194    }
195
196    public ItemBridgeAdapter(ObjectAdapter adapter) {
197        this(adapter, null);
198    }
199
200    public ItemBridgeAdapter() {
201    }
202
203    /**
204     * Sets the {@link ObjectAdapter}.
205     */
206    public void setAdapter(ObjectAdapter adapter) {
207        if (adapter == mAdapter) {
208            return;
209        }
210        if (mAdapter != null) {
211            mAdapter.unregisterObserver(mDataObserver);
212        }
213        mAdapter = adapter;
214        if (mAdapter == null) {
215            notifyDataSetChanged();
216            return;
217        }
218
219        mAdapter.registerObserver(mDataObserver);
220        if (hasStableIds() != mAdapter.hasStableIds()) {
221            setHasStableIds(mAdapter.hasStableIds());
222        }
223        notifyDataSetChanged();
224    }
225
226    /**
227     * Changes Presenter that creates and binds the view.
228     *
229     * @param presenterSelector Presenter that creates and binds the view.
230     */
231    public void setPresenter(PresenterSelector presenterSelector) {
232        mPresenterSelector = presenterSelector;
233        notifyDataSetChanged();
234    }
235
236    /**
237     * Sets the {@link Wrapper}.
238     */
239    public void setWrapper(Wrapper wrapper) {
240        mWrapper = wrapper;
241    }
242
243    /**
244     * Returns the {@link Wrapper}.
245     */
246    public Wrapper getWrapper() {
247        return mWrapper;
248    }
249
250    void setFocusHighlight(FocusHighlightHandler listener) {
251        mFocusHighlight = listener;
252        if (DEBUG) Log.v(TAG, "setFocusHighlight " + mFocusHighlight);
253    }
254
255    /**
256     * Clears the adapter.
257     */
258    public void clear() {
259        setAdapter(null);
260    }
261
262    /**
263     * Sets the presenter mapper array.
264     */
265    public void setPresenterMapper(ArrayList<Presenter> presenters) {
266        mPresenters = presenters;
267    }
268
269    /**
270     * Returns the presenter mapper array.
271     */
272    public ArrayList<Presenter> getPresenterMapper() {
273        return mPresenters;
274    }
275
276    @Override
277    public int getItemCount() {
278        return mAdapter != null ? mAdapter.size() : 0;
279    }
280
281    @Override
282    public int getItemViewType(int position) {
283        PresenterSelector presenterSelector = mPresenterSelector != null
284                ? mPresenterSelector : mAdapter.getPresenterSelector();
285        Object item = mAdapter.get(position);
286        Presenter presenter = presenterSelector.getPresenter(item);
287        int type = mPresenters.indexOf(presenter);
288        if (type < 0) {
289            mPresenters.add(presenter);
290            type = mPresenters.indexOf(presenter);
291            if (DEBUG) Log.v(TAG, "getItemViewType added presenter " + presenter + " type " + type);
292            onAddPresenter(presenter, type);
293            if (mAdapterListener != null) {
294                mAdapterListener.onAddPresenter(presenter, type);
295            }
296        }
297        return type;
298    }
299
300    /**
301     * Called when presenter is added to Adapter.
302     */
303    protected void onAddPresenter(Presenter presenter, int type) {
304    }
305
306    /**
307     * Called when ViewHolder is created.
308     */
309    protected void onCreate(ViewHolder viewHolder) {
310    }
311
312    /**
313     * Called when ViewHolder has been bound to data.
314     */
315    protected void onBind(ViewHolder viewHolder) {
316    }
317
318    /**
319     * Called when ViewHolder has been unbound from data.
320     */
321    protected void onUnbind(ViewHolder viewHolder) {
322    }
323
324    /**
325     * Called when ViewHolder has been attached to window.
326     */
327    protected void onAttachedToWindow(ViewHolder viewHolder) {
328    }
329
330    /**
331     * Called when ViewHolder has been detached from window.
332     */
333    protected void onDetachedFromWindow(ViewHolder viewHolder) {
334    }
335
336    /**
337     * {@link View.OnFocusChangeListener} that assigned in
338     * {@link Presenter#onCreateViewHolder(ViewGroup)} may be chained, user should never change
339     * {@link View.OnFocusChangeListener} after that.
340     */
341    @Override
342    public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
343        if (DEBUG) Log.v(TAG, "onCreateViewHolder viewType " + viewType);
344        Presenter presenter = mPresenters.get(viewType);
345        Presenter.ViewHolder presenterVh;
346        View view;
347        if (mWrapper != null) {
348            view = mWrapper.createWrapper(parent);
349            presenterVh = presenter.onCreateViewHolder(parent);
350            mWrapper.wrap(view, presenterVh.view);
351        } else {
352            presenterVh = presenter.onCreateViewHolder(parent);
353            view = presenterVh.view;
354        }
355        ViewHolder viewHolder = new ViewHolder(presenter, view, presenterVh);
356        onCreate(viewHolder);
357        if (mAdapterListener != null) {
358            mAdapterListener.onCreate(viewHolder);
359        }
360        View presenterView = viewHolder.mHolder.view;
361        if (presenterView != null) {
362            viewHolder.mFocusChangeListener.mChainedListener =
363                    presenterView.getOnFocusChangeListener();
364            presenterView.setOnFocusChangeListener(viewHolder.mFocusChangeListener);
365        }
366        if (mFocusHighlight != null) {
367            mFocusHighlight.onInitializeView(view);
368        }
369        return viewHolder;
370    }
371
372    /**
373     * Sets the AdapterListener.
374     */
375    public void setAdapterListener(AdapterListener listener) {
376        mAdapterListener = listener;
377    }
378
379    @Override
380    public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
381        if (DEBUG) Log.v(TAG, "onBindViewHolder position " + position);
382        ViewHolder viewHolder = (ViewHolder) holder;
383        viewHolder.mItem = mAdapter.get(position);
384
385        viewHolder.mPresenter.onBindViewHolder(viewHolder.mHolder, viewHolder.mItem);
386
387        onBind(viewHolder);
388        if (mAdapterListener != null) {
389            mAdapterListener.onBind(viewHolder);
390        }
391    }
392
393    @Override
394    public final  void onBindViewHolder(RecyclerView.ViewHolder holder, int position,
395            List payloads) {
396        if (DEBUG) Log.v(TAG, "onBindViewHolder position " + position);
397        ViewHolder viewHolder = (ViewHolder) holder;
398        viewHolder.mItem = mAdapter.get(position);
399
400        viewHolder.mPresenter.onBindViewHolder(viewHolder.mHolder, viewHolder.mItem, payloads);
401
402        onBind(viewHolder);
403        if (mAdapterListener != null) {
404            mAdapterListener.onBind(viewHolder, payloads);
405        }
406    }
407
408    @Override
409    public final void onViewRecycled(RecyclerView.ViewHolder holder) {
410        ViewHolder viewHolder = (ViewHolder) holder;
411        viewHolder.mPresenter.onUnbindViewHolder(viewHolder.mHolder);
412        onUnbind(viewHolder);
413        if (mAdapterListener != null) {
414            mAdapterListener.onUnbind(viewHolder);
415        }
416        viewHolder.mItem = null;
417    }
418
419    @Override
420    public final boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) {
421        onViewRecycled(holder);
422        return false;
423    }
424
425    @Override
426    public final void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
427        ViewHolder viewHolder = (ViewHolder) holder;
428        onAttachedToWindow(viewHolder);
429        if (mAdapterListener != null) {
430            mAdapterListener.onAttachedToWindow(viewHolder);
431        }
432        viewHolder.mPresenter.onViewAttachedToWindow(viewHolder.mHolder);
433    }
434
435    @Override
436    public final void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
437        ViewHolder viewHolder = (ViewHolder) holder;
438        viewHolder.mPresenter.onViewDetachedFromWindow(viewHolder.mHolder);
439        onDetachedFromWindow(viewHolder);
440        if (mAdapterListener != null) {
441            mAdapterListener.onDetachedFromWindow(viewHolder);
442        }
443    }
444
445    @Override
446    public long getItemId(int position) {
447        return mAdapter.getId(position);
448    }
449
450    @Override
451    public FacetProvider getFacetProvider(int type) {
452        return mPresenters.get(type);
453    }
454}
455