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