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;
22
23/**
24 * A presenter that assumes a LinearLayout container for a series
25 * of control buttons backed by objects of type {@link Action}.
26 *
27 * Different layouts may be passed to the presenter constructor.
28 * The layout must contain a view with id control_bar.
29 */
30class ControlBarPresenter extends Presenter {
31
32    static final int MAX_CONTROLS = 7;
33
34    /**
35     * The data type expected by this presenter.
36     */
37    static class BoundData {
38        /**
39         * Adapter containing objects of type {@link Action}.
40         */
41        ObjectAdapter adapter;
42
43        /**
44         * The presenter to be used for the adapter objects.
45         */
46        Presenter presenter;
47    }
48
49    /**
50     * Listener for control selected events.
51     */
52    interface OnControlSelectedListener {
53        void onControlSelected(Presenter.ViewHolder controlViewHolder, Object item,
54                BoundData data);
55    }
56
57    /**
58     * Listener for control clicked events.
59     */
60    interface OnControlClickedListener {
61        void onControlClicked(Presenter.ViewHolder controlViewHolder, Object item,
62                BoundData data);
63    }
64
65    class ViewHolder extends Presenter.ViewHolder {
66        ObjectAdapter mAdapter;
67        BoundData mData;
68        Presenter mPresenter;
69        ControlBar mControlBar;
70        View mControlsContainer;
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            mControlsContainer = rootView.findViewById(R.id.controls_container);
81            mControlBar = (ControlBar) rootView.findViewById(R.id.control_bar);
82            if (mControlBar == null) {
83                throw new IllegalStateException("Couldn't find control_bar");
84            }
85            mControlBar.setDefaultFocusToMiddle(mDefaultFocusToMiddle);
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    OnControlClickedListener mOnControlClickedListener;
185    OnControlSelectedListener mOnControlSelectedListener;
186    private int mLayoutResourceId;
187    private static int sChildMarginDefault;
188    private static int sControlIconWidth;
189    boolean mDefaultFocusToMiddle = true;
190
191    /**
192     * Constructor for a ControlBarPresenter.
193     *
194     * @param layoutResourceId The resource id of the layout for this presenter.
195     */
196    public ControlBarPresenter(int layoutResourceId) {
197        mLayoutResourceId = layoutResourceId;
198    }
199
200    /**
201     * Returns the layout resource id.
202     */
203    public int getLayoutResourceId() {
204        return mLayoutResourceId;
205    }
206
207    /**
208     * Sets the listener for control clicked events.
209     */
210    public void setOnControlClickedListener(OnControlClickedListener listener) {
211        mOnControlClickedListener = listener;
212    }
213
214    /**
215     * Returns the listener for control clicked events.
216     */
217    public OnControlClickedListener getOnItemViewClickedListener() {
218        return mOnControlClickedListener;
219    }
220
221    /**
222     * Sets the listener for control selection.
223     */
224    public void setOnControlSelectedListener(OnControlSelectedListener listener) {
225        mOnControlSelectedListener = listener;
226    }
227
228    /**
229     * Returns the listener for control selection.
230     */
231    public OnControlSelectedListener getOnItemControlListener() {
232        return mOnControlSelectedListener;
233    }
234
235    public void setBackgroundColor(ViewHolder vh, int color) {
236        vh.mControlsContainer.setBackgroundColor(color);
237    }
238
239    @Override
240    public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
241        View v = LayoutInflater.from(parent.getContext())
242            .inflate(getLayoutResourceId(), parent, false);
243        return new ViewHolder(v);
244    }
245
246    @Override
247    public void onBindViewHolder(Presenter.ViewHolder holder, Object item) {
248        ViewHolder vh = (ViewHolder) holder;
249        BoundData data = (BoundData) item;
250        if (vh.mAdapter != data.adapter) {
251            vh.mAdapter = data.adapter;
252            if (vh.mAdapter != null) {
253                vh.mAdapter.registerObserver(vh.mDataObserver);
254            }
255        }
256        vh.mPresenter = data.presenter;
257        vh.mData = data;
258        vh.showControls(vh.mPresenter);
259    }
260
261    @Override
262    public void onUnbindViewHolder(Presenter.ViewHolder holder) {
263        ViewHolder vh = (ViewHolder) holder;
264        if (vh.mAdapter != null) {
265            vh.mAdapter.unregisterObserver(vh.mDataObserver);
266            vh.mAdapter = null;
267        }
268        vh.mData = null;
269    }
270
271    int getChildMarginDefault(Context context) {
272        if (sChildMarginDefault == 0) {
273            sChildMarginDefault = context.getResources().getDimensionPixelSize(
274                    R.dimen.lb_playback_controls_child_margin_default);
275        }
276        return sChildMarginDefault;
277    }
278
279    int getControlIconWidth(Context context) {
280        if (sControlIconWidth == 0) {
281            sControlIconWidth = context.getResources().getDimensionPixelSize(
282                    R.dimen.lb_control_icon_width);
283        }
284        return sControlIconWidth;
285    }
286
287    /**
288     * @param defaultFocusToMiddle True for middle item, false for 0.
289     */
290    void setDefaultFocusToMiddle(boolean defaultFocusToMiddle) {
291        mDefaultFocusToMiddle = defaultFocusToMiddle;
292    }
293
294}
295