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        SparseArray<Presenter.ViewHolder> mViewHolders =
72                new SparseArray<Presenter.ViewHolder>();
73        ObjectAdapter.DataObserver mDataObserver;
74
75        /**
76         * Constructor for the ViewHolder.
77         */
78        ViewHolder(View rootView) {
79            super(rootView);
80            mControlBar = (ControlBar) rootView.findViewById(R.id.control_bar);
81            if (mControlBar == null) {
82                throw new IllegalStateException("Couldn't find control_bar");
83            }
84            mControlBar.setOnChildFocusedListener(new ControlBar.OnChildFocusedListener() {
85                @Override
86                public void onChildFocusedListener(View child, View focused) {
87                    if (mOnControlSelectedListener == null) {
88                        return;
89                    }
90                    for (int position = 0; position < mViewHolders.size(); position++) {
91                        if (mViewHolders.get(position).view == child) {
92                            mOnControlSelectedListener.onControlSelected(
93                                    mViewHolders.get(position),
94                                    getDisplayedAdapter().get(position), mData);
95                            break;
96                        }
97                    }
98                }
99            });
100            mDataObserver = new ObjectAdapter.DataObserver() {
101                @Override
102                public void onChanged() {
103                    if (mAdapter == getDisplayedAdapter()) {
104                        showControls(mPresenter);
105                    }
106                }
107                @Override
108                public void onItemRangeChanged(int positionStart, int itemCount) {
109                    if (mAdapter == getDisplayedAdapter()) {
110                        for (int i = 0; i < itemCount; i++) {
111                            bindControlToAction(positionStart + i, mPresenter);
112                        }
113                    }
114                }
115            };
116        }
117
118        int getChildMarginFromCenter(Context context, int numControls) {
119            // Includes margin between icons plus two times half the icon width.
120            return getChildMarginDefault(context) + getControlIconWidth(context);
121        }
122
123        void showControls(Presenter presenter) {
124            ObjectAdapter adapter = getDisplayedAdapter();
125            int adapterSize = adapter == null ? 0 : adapter.size();
126            // Shrink the number of attached views
127            View focusedView = mControlBar.getFocusedChild();
128            if (focusedView != null && adapterSize > 0 &&
129                    mControlBar.indexOfChild(focusedView) >= adapterSize) {
130                mControlBar.getChildAt(adapter.size() - 1).requestFocus();
131            }
132            for (int i = mControlBar.getChildCount() - 1; i >= adapterSize; i--) {
133                mControlBar.removeViewAt(i);
134            }
135            for (int position = 0; position < adapterSize && position < MAX_CONTROLS;
136                    position++) {
137                bindControlToAction(position, adapter, presenter);
138            }
139            mControlBar.setChildMarginFromCenter(
140                    getChildMarginFromCenter(mControlBar.getContext(), adapterSize));
141        }
142
143        void bindControlToAction(int position, Presenter presenter) {
144            bindControlToAction(position, getDisplayedAdapter(), presenter);
145        }
146
147        private void bindControlToAction(final int position,
148                ObjectAdapter adapter, Presenter presenter) {
149            Presenter.ViewHolder vh = mViewHolders.get(position);
150            Object item = adapter.get(position);
151            if (vh == null) {
152                vh = presenter.onCreateViewHolder(mControlBar);
153                mViewHolders.put(position, vh);
154
155                final Presenter.ViewHolder itemViewHolder = vh;
156                presenter.setOnClickListener(vh, new View.OnClickListener() {
157                    @Override
158                    public void onClick(View v) {
159                        Object item = getDisplayedAdapter().get(position);
160                        if (mOnControlClickedListener != null) {
161                            mOnControlClickedListener.onControlClicked(itemViewHolder, item,
162                                    mData);
163                        }
164                    }
165                });
166            }
167            if (vh.view.getParent() == null) {
168                mControlBar.addView(vh.view);
169            }
170            presenter.onBindViewHolder(vh, item);
171        }
172
173        /**
174         * Returns the adapter currently bound to the displayed controls.
175         * May be overridden in a subclass.
176         */
177        ObjectAdapter getDisplayedAdapter() {
178            return mAdapter;
179        }
180    }
181
182    private OnControlClickedListener mOnControlClickedListener;
183    private OnControlSelectedListener mOnControlSelectedListener;
184    private int mLayoutResourceId;
185    private static int sChildMarginDefault;
186    private static int sControlIconWidth;
187
188    /**
189     * Constructor for a ControlBarPresenter.
190     *
191     * @param layoutResourceId The resource id of the layout for this presenter.
192     */
193    public ControlBarPresenter(int layoutResourceId) {
194        mLayoutResourceId = layoutResourceId;
195    }
196
197    /**
198     * Returns the layout resource id.
199     */
200    public int getLayoutResourceId() {
201        return mLayoutResourceId;
202    }
203
204    /**
205     * Sets the listener for control clicked events.
206     */
207    public void setOnControlClickedListener(OnControlClickedListener listener) {
208        mOnControlClickedListener = listener;
209    }
210
211    /**
212     * Returns the listener for control clicked events.
213     */
214    public OnControlClickedListener getOnItemViewClickedListener() {
215        return mOnControlClickedListener;
216    }
217
218    /**
219     * Sets the listener for control selection.
220     */
221    public void setOnControlSelectedListener(OnControlSelectedListener listener) {
222        mOnControlSelectedListener = listener;
223    }
224
225    /**
226     * Returns the listener for control selection.
227     */
228    public OnControlSelectedListener getOnItemControlListener() {
229        return mOnControlSelectedListener;
230    }
231
232    @Override
233    public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
234        View v = LayoutInflater.from(parent.getContext())
235            .inflate(getLayoutResourceId(), parent, false);
236        return new ViewHolder(v);
237    }
238
239    @Override
240    public void onBindViewHolder(Presenter.ViewHolder holder, Object item) {
241        ViewHolder vh = (ViewHolder) holder;
242        BoundData data = (BoundData) item;
243        if (vh.mAdapter != data.adapter) {
244            vh.mAdapter = data.adapter;
245            if (vh.mAdapter != null) {
246                vh.mAdapter.registerObserver(vh.mDataObserver);
247            }
248        }
249        vh.mPresenter = data.presenter;
250        vh.mData = data;
251        vh.showControls(vh.mPresenter);
252    }
253
254    @Override
255    public void onUnbindViewHolder(Presenter.ViewHolder holder) {
256        ViewHolder vh = (ViewHolder) holder;
257        if (vh.mAdapter != null) {
258            vh.mAdapter.unregisterObserver(vh.mDataObserver);
259            vh.mAdapter = null;
260        }
261        vh.mData = null;
262    }
263
264    int getChildMarginDefault(Context context) {
265        if (sChildMarginDefault == 0) {
266            sChildMarginDefault = context.getResources().getDimensionPixelSize(
267                    R.dimen.lb_playback_controls_child_margin_default);
268        }
269        return sChildMarginDefault;
270    }
271
272    int getControlIconWidth(Context context) {
273        if (sControlIconWidth == 0) {
274            sControlIconWidth = context.getResources().getDimensionPixelSize(
275                    R.dimen.lb_control_icon_width);
276        }
277        return sControlIconWidth;
278    }
279}
280