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 Presenter to RecyclerView.Adapter. Public to allow use by third
25 * party presenters.
26 */
27public class ItemBridgeAdapter extends RecyclerView.Adapter {
28    private static final String TAG = "ItemBridgeAdapter";
29    private static final boolean DEBUG = false;
30
31    /**
32     * Interface for listening to view holder 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 presenter into another view.
51     * The wrapper must be 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    public class ViewHolder extends RecyclerView.ViewHolder {
85        final Presenter mPresenter;
86        final Presenter.ViewHolder mHolder;
87        final OnFocusChangeListener mFocusChangeListener = new OnFocusChangeListener();
88        Object mItem;
89        Object mExtraObject;
90
91        /**
92         * Get {@link Presenter}.
93         */
94        public final Presenter getPresenter() {
95            return mPresenter;
96        }
97
98        /**
99         * Get {@link Presenter.ViewHolder}.
100         */
101        public final Presenter.ViewHolder getViewHolder() {
102            return mHolder;
103        }
104
105        /**
106         * Get currently bound object.
107         */
108        public final Object getItem() {
109            return mItem;
110        }
111
112        /**
113         * Get extra object associated with the view.  Developer can attach
114         * any customized UI object in addition to {@link Presenter.ViewHolder}.
115         * A typical use case is attaching an animator object.
116         */
117        public final Object getExtraObject() {
118            return mExtraObject;
119        }
120
121        /**
122         * Set extra object associated with the view.  Developer can attach
123         * any customized UI object in addition to {@link Presenter.ViewHolder}.
124         * A typical use case is attaching an animator object.
125         */
126        public void setExtraObject(Object object) {
127            mExtraObject = object;
128        }
129
130        ViewHolder(Presenter presenter, View view, Presenter.ViewHolder holder) {
131            super(view);
132            mPresenter = presenter;
133            mHolder = holder;
134        }
135    }
136
137    private ObjectAdapter.DataObserver mDataObserver = new ObjectAdapter.DataObserver() {
138        @Override
139        public void onChanged() {
140            ItemBridgeAdapter.this.notifyDataSetChanged();
141        }
142        @Override
143        public void onItemRangeChanged(int positionStart, int itemCount) {
144            ItemBridgeAdapter.this.notifyItemRangeChanged(positionStart, itemCount);
145        }
146        @Override
147        public void onItemRangeInserted(int positionStart, int itemCount) {
148            ItemBridgeAdapter.this.notifyItemRangeInserted(positionStart, itemCount);
149        }
150        @Override
151        public void onItemRangeRemoved(int positionStart, int itemCount) {
152            ItemBridgeAdapter.this.notifyItemRangeRemoved(positionStart, itemCount);
153        }
154    };
155
156    public ItemBridgeAdapter(ObjectAdapter adapter, PresenterSelector presenterSelector) {
157        setAdapter(adapter);
158        mPresenterSelector = presenterSelector;
159    }
160
161    public ItemBridgeAdapter(ObjectAdapter adapter) {
162        this(adapter, null);
163    }
164
165    public ItemBridgeAdapter() {
166    }
167
168    public void setAdapter(ObjectAdapter adapter) {
169        if (mAdapter != null) {
170            mAdapter.unregisterObserver(mDataObserver);
171        }
172        mAdapter = adapter;
173        if (mAdapter == null) {
174            return;
175        }
176
177        mAdapter.registerObserver(mDataObserver);
178        if (hasStableIds() != mAdapter.hasStableIds()) {
179            setHasStableIds(mAdapter.hasStableIds());
180        }
181    }
182
183    public void setWrapper(Wrapper wrapper) {
184        mWrapper = wrapper;
185    }
186
187    public Wrapper getWrapper() {
188        return mWrapper;
189    }
190
191    void setFocusHighlight(FocusHighlightHandler listener) {
192        mFocusHighlight = listener;
193        if (DEBUG) Log.v(TAG, "setFocusHighlight " + mFocusHighlight);
194    }
195
196    public void clear() {
197        setAdapter(null);
198    }
199
200    public void setPresenterMapper(ArrayList<Presenter> presenters) {
201        mPresenters = presenters;
202    }
203
204    public ArrayList<Presenter> getPresenterMapper() {
205        return mPresenters;
206    }
207
208    @Override
209    public int getItemCount() {
210        return mAdapter.size();
211    }
212
213    @Override
214    public int getItemViewType(int position) {
215        PresenterSelector presenterSelector = mPresenterSelector != null ?
216                mPresenterSelector : mAdapter.getPresenterSelector();
217        Object item = mAdapter.get(position);
218        Presenter presenter = presenterSelector.getPresenter(item);
219        int type = mPresenters.indexOf(presenter);
220        if (type < 0) {
221            mPresenters.add(presenter);
222            type = mPresenters.indexOf(presenter);
223            if (DEBUG) Log.v(TAG, "getItemViewType added presenter " + presenter + " type " + type);
224            if (mAdapterListener != null) {
225                mAdapterListener.onAddPresenter(presenter, type);
226            }
227        }
228        return type;
229    }
230
231    /**
232     * {@link View.OnFocusChangeListener} that assigned in
233     * {@link Presenter#onCreateViewHolder(ViewGroup)} may be chained, user should never change
234     * {@link View.OnFocusChangeListener} after that.
235     */
236    @Override
237    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
238        if (DEBUG) Log.v(TAG, "onCreateViewHolder viewType " + viewType);
239        Presenter presenter = mPresenters.get(viewType);
240        Presenter.ViewHolder presenterVh;
241        View view;
242        if (mWrapper != null) {
243            view = mWrapper.createWrapper(parent);
244            presenterVh = presenter.onCreateViewHolder(parent);
245            mWrapper.wrap(view, presenterVh.view);
246        } else {
247            presenterVh = presenter.onCreateViewHolder(parent);
248            view = presenterVh.view;
249        }
250        ViewHolder viewHolder = new ViewHolder(presenter, view, presenterVh);
251        if (mAdapterListener != null) {
252            mAdapterListener.onCreate(viewHolder);
253        }
254        View presenterView = viewHolder.mHolder.view;
255        if (presenterView != null) {
256            viewHolder.mFocusChangeListener.mChainedListener = presenterView.getOnFocusChangeListener();
257            presenterView.setOnFocusChangeListener(viewHolder.mFocusChangeListener);
258        }
259        if (mFocusHighlight != null) {
260            mFocusHighlight.onInitializeView(view);
261        }
262        return viewHolder;
263    }
264
265    public void setAdapterListener(AdapterListener listener) {
266        mAdapterListener = listener;
267    }
268
269    @Override
270    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
271        if (DEBUG) Log.v(TAG, "onBindViewHolder position " + position);
272        ViewHolder viewHolder = (ViewHolder) holder;
273        viewHolder.mItem = mAdapter.get(position);
274
275        viewHolder.mPresenter.onBindViewHolder(viewHolder.mHolder, viewHolder.mItem);
276
277        if (mAdapterListener != null) {
278            mAdapterListener.onBind(viewHolder);
279        }
280    }
281
282    @Override
283    public void onViewRecycled(RecyclerView.ViewHolder holder) {
284        ViewHolder viewHolder = (ViewHolder) holder;
285        viewHolder.mPresenter.onUnbindViewHolder(viewHolder.mHolder);
286
287        viewHolder.mItem = null;
288
289        if (mAdapterListener != null) {
290            mAdapterListener.onUnbind(viewHolder);
291        }
292    }
293
294    @Override
295    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
296        ViewHolder viewHolder = (ViewHolder) holder;
297        if (mAdapterListener != null) {
298            mAdapterListener.onAttachedToWindow(viewHolder);
299        }
300        viewHolder.mPresenter.onViewAttachedToWindow(viewHolder.mHolder);
301    }
302
303    @Override
304    public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
305        ViewHolder viewHolder = (ViewHolder) holder;
306        viewHolder.mPresenter.onViewDetachedFromWindow(viewHolder.mHolder);
307        if (mAdapterListener != null) {
308            mAdapterListener.onDetachedFromWindow(viewHolder);
309        }
310    }
311
312    @Override
313    public long getItemId(int position) {
314        return mAdapter.getId(position);
315    }
316
317}
318