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.content.Context;
17import android.support.v17.leanback.R;
18import android.util.SparseArray;
19import android.view.LayoutInflater;
20import android.view.View;
21import android.view.ViewGroup;
22import android.widget.LinearLayout;
23
24/**
25 * A presenter that assumes a LinearLayout container for a series
26 * of control buttons backed by objects of type {@link Action}.
27 *
28 * Different layouts may be passed to the presenter constructor.
29 * The layout must contain a view with id control_bar.
30 */
31class ControlBarPresenter extends Presenter {
32
33    private static final int MAX_CONTROLS = 7;
34
35    /**
36     * The data type expected by this presenter.
37     */
38    static class BoundData {
39        /**
40         * Adapter containing objects of type {@link Action}.
41         */
42        ObjectAdapter adapter;
43
44        /**
45         * The presenter to be used for the adapter objects.
46         */
47        Presenter presenter;
48    }
49
50    /**
51     * Listener for control selected events.
52     */
53    interface OnControlSelectedListener {
54        void onControlSelected(Presenter.ViewHolder controlViewHolder, Object item,
55                BoundData data);
56    }
57
58    /**
59     * Listener for control clicked events.
60     */
61    interface OnControlClickedListener {
62        void onControlClicked(Presenter.ViewHolder controlViewHolder, Object item,
63                BoundData data);
64    }
65
66    class ViewHolder extends Presenter.ViewHolder {
67        ObjectAdapter mAdapter;
68        BoundData mData;
69        Presenter mPresenter;
70        ControlBar mControlBar;
71        View mControlsContainer;
72        SparseArray<Presenter.ViewHolder> mViewHolders =
73                new SparseArray<Presenter.ViewHolder>();
74        ObjectAdapter.DataObserver mDataObserver;
75
76        /**
77         * Constructor for the ViewHolder.
78         */
79        ViewHolder(View rootView) {
80            super(rootView);
81            mControlsContainer = rootView.findViewById(R.id.controls_container);
82            mControlBar = (ControlBar) rootView.findViewById(R.id.control_bar);
83            if (mControlBar == null) {
84                throw new IllegalStateException("Couldn't find control_bar");
85            }
86            mControlBar.setOnChildFocusedListener(new ControlBar.OnChildFocusedListener() {
87                @Override
88                public void onChildFocusedListener(View child, View focused) {
89                    if (mOnControlSelectedListener == null) {
90                        return;
91                    }
92                    for (int position = 0; position < mViewHolders.size(); position++) {
93                        if (mViewHolders.get(position).view == child) {
94                            mOnControlSelectedListener.onControlSelected(
95                                    mViewHolders.get(position),
96                                    getDisplayedAdapter().get(position), mData);
97                            break;
98                        }
99                    }
100                }
101            });
102            mDataObserver = new ObjectAdapter.DataObserver() {
103                @Override
104                public void onChanged() {
105                    if (mAdapter == getDisplayedAdapter()) {
106                        showControls(mPresenter);
107                    }
108                }
109                @Override
110                public void onItemRangeChanged(int positionStart, int itemCount) {
111                    if (mAdapter == getDisplayedAdapter()) {
112                        for (int i = 0; i < itemCount; i++) {
113                            bindControlToAction(positionStart + i, mPresenter);
114                        }
115                    }
116                }
117            };
118        }
119
120        int getChildMarginFromCenter(Context context, int numControls) {
121            // Includes margin between icons plus two times half the icon width.
122            return getChildMarginDefault(context) + getControlIconWidth(context);
123        }
124
125        void showControls(Presenter presenter) {
126            ObjectAdapter adapter = getDisplayedAdapter();
127            int adapterSize = adapter == null ? 0 : adapter.size();
128            // Shrink the number of attached views
129            View focusedView = mControlBar.getFocusedChild();
130            if (focusedView != null && adapterSize > 0 &&
131                    mControlBar.indexOfChild(focusedView) >= adapterSize) {
132                mControlBar.getChildAt(adapter.size() - 1).requestFocus();
133            }
134            for (int i = mControlBar.getChildCount() - 1; i >= adapterSize; i--) {
135                mControlBar.removeViewAt(i);
136            }
137            for (int position = 0; position < adapterSize && position < MAX_CONTROLS;
138                    position++) {
139                bindControlToAction(position, adapter, presenter);
140            }
141            mControlBar.setChildMarginFromCenter(
142                    getChildMarginFromCenter(mControlBar.getContext(), adapterSize));
143        }
144
145        void bindControlToAction(int position, Presenter presenter) {
146            bindControlToAction(position, getDisplayedAdapter(), presenter);
147        }
148
149        private void bindControlToAction(final int position,
150                ObjectAdapter adapter, Presenter presenter) {
151            Presenter.ViewHolder vh = mViewHolders.get(position);
152            Object item = adapter.get(position);
153            if (vh == null) {
154                vh = presenter.onCreateViewHolder(mControlBar);
155                mViewHolders.put(position, vh);
156
157                final Presenter.ViewHolder itemViewHolder = vh;
158                presenter.setOnClickListener(vh, new View.OnClickListener() {
159                    @Override
160                    public void onClick(View v) {
161                        Object item = getDisplayedAdapter().get(position);
162                        if (mOnControlClickedListener != null) {
163                            mOnControlClickedListener.onControlClicked(itemViewHolder, item,
164                                    mData);
165                        }
166                    }
167                });
168            }
169            if (vh.view.getParent() == null) {
170                mControlBar.addView(vh.view);
171            }
172            presenter.onBindViewHolder(vh, item);
173        }
174
175        /**
176         * Returns the adapter currently bound to the displayed controls.
177         * May be overridden in a subclass.
178         */
179        ObjectAdapter getDisplayedAdapter() {
180            return mAdapter;
181        }
182    }
183
184    private OnControlClickedListener mOnControlClickedListener;
185    private OnControlSelectedListener mOnControlSelectedListener;
186    private int mLayoutResourceId;
187    private static int sChildMarginDefault;
188    private static int sControlIconWidth;
189
190    /**
191     * Constructor for a ControlBarPresenter.
192     *
193     * @param layoutResourceId The resource id of the layout for this presenter.
194     */
195    public ControlBarPresenter(int layoutResourceId) {
196        mLayoutResourceId = layoutResourceId;
197    }
198
199    /**
200     * Returns the layout resource id.
201     */
202    public int getLayoutResourceId() {
203        return mLayoutResourceId;
204    }
205
206    /**
207     * Sets the listener for control clicked events.
208     */
209    public void setOnControlClickedListener(OnControlClickedListener listener) {
210        mOnControlClickedListener = listener;
211    }
212
213    /**
214     * Returns the listener for control clicked events.
215     */
216    public OnControlClickedListener getOnItemViewClickedListener() {
217        return mOnControlClickedListener;
218    }
219
220    /**
221     * Sets the listener for control selection.
222     */
223    public void setOnControlSelectedListener(OnControlSelectedListener listener) {
224        mOnControlSelectedListener = listener;
225    }
226
227    /**
228     * Returns the listener for control selection.
229     */
230    public OnControlSelectedListener getOnItemControlListener() {
231        return mOnControlSelectedListener;
232    }
233
234    public void setBackgroundColor(ViewHolder vh, int color) {
235        vh.mControlsContainer.setBackgroundColor(color);
236    }
237
238    @Override
239    public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
240        View v = LayoutInflater.from(parent.getContext())
241            .inflate(getLayoutResourceId(), parent, false);
242        return new ViewHolder(v);
243    }
244
245    @Override
246    public void onBindViewHolder(Presenter.ViewHolder holder, Object item) {
247        ViewHolder vh = (ViewHolder) holder;
248        BoundData data = (BoundData) item;
249        if (vh.mAdapter != data.adapter) {
250            vh.mAdapter = data.adapter;
251            if (vh.mAdapter != null) {
252                vh.mAdapter.registerObserver(vh.mDataObserver);
253            }
254        }
255        vh.mPresenter = data.presenter;
256        vh.mData = data;
257        vh.showControls(vh.mPresenter);
258    }
259
260    @Override
261    public void onUnbindViewHolder(Presenter.ViewHolder holder) {
262        ViewHolder vh = (ViewHolder) holder;
263        if (vh.mAdapter != null) {
264            vh.mAdapter.unregisterObserver(vh.mDataObserver);
265            vh.mAdapter = null;
266        }
267        vh.mData = null;
268    }
269
270    int getChildMarginDefault(Context context) {
271        if (sChildMarginDefault == 0) {
272            sChildMarginDefault = context.getResources().getDimensionPixelSize(
273                    R.dimen.lb_playback_controls_child_margin_default);
274        }
275        return sChildMarginDefault;
276    }
277
278    int getControlIconWidth(Context context) {
279        if (sControlIconWidth == 0) {
280            sControlIconWidth = context.getResources().getDimensionPixelSize(
281                    R.dimen.lb_control_icon_width);
282        }
283        return sControlIconWidth;
284    }
285}
286