1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.support.v17.leanback.widget;
18 
19 import android.support.v17.leanback.tests.R;
20 import android.support.v7.widget.RecyclerView;
21 import android.support.v17.leanback.widget.BaseGridView;
22 import android.support.v17.leanback.widget.OnChildSelectedListener;
23 import android.app.Activity;
24 import android.content.Intent;
25 import android.graphics.Color;
26 import android.os.Bundle;
27 import android.util.Log;
28 import android.util.SparseArray;
29 import android.view.View;
30 import android.view.View.OnClickListener;
31 import android.view.View.OnFocusChangeListener;
32 import android.view.ViewGroup;
33 import android.widget.ImageView;
34 import android.widget.TextView;
35 
36 import java.util.ArrayList;
37 
38 /**
39  * @hide from javadoc
40  */
41 public class GridActivity extends Activity {
42 
43     private static final String TAG = "GridActivity";
44 
45     interface AdapterListener {
onBind(RecyclerView.ViewHolder vh, int position)46         void onBind(RecyclerView.ViewHolder vh, int position);
47     }
48 
49     public static final String EXTRA_LAYOUT_RESOURCE_ID = "layoutResourceId";
50     public static final String EXTRA_NUM_ITEMS = "numItems";
51     public static final String EXTRA_ITEMS = "items";
52     public static final String EXTRA_ITEMS_FOCUSABLE = "itemsFocusable";
53     public static final String EXTRA_STAGGERED = "staggered";
54     public static final String EXTRA_REQUEST_LAYOUT_ONFOCUS = "requestLayoutOnFocus";
55     public static final String EXTRA_REQUEST_FOCUS_ONLAYOUT = "requstFocusOnLayout";
56     public static final String EXTRA_CHILD_LAYOUT_ID = "childLayoutId";
57     public static final String EXTRA_SECONDARY_SIZE_ZERO = "secondarySizeZero";
58     public static final String EXTRA_UPDATE_SIZE = "updateSize";
59     public static final String EXTRA_LAYOUT_MARGINS = "layoutMargins";
60     public static final String EXTRA_NINEPATCH_SHADOW = "NINEPATCH_SHADOW";
61 
62     /**
63      * Class that implements GridWidgetTest.ViewTypeProvider for creating different
64      * view types for each position.
65      */
66     public static final String EXTRA_VIEWTYPEPROVIDER_CLASS = "viewtype_class";
67     /**
68      * Class that implements GridWidgetTest.ItemAlignmentFacetProvider for creating different
69      * ItemAlignmentFacet for each ViewHolder.
70      */
71     public static final String EXTRA_ITEMALIGNMENTPROVIDER_CLASS = "itemalignment_class";
72     /**
73      * Class that implements GridWidgetTest.ItemAlignmentFacetProvider for creating different
74      * ItemAlignmentFacet for a given viewType.
75      */
76     public static final String EXTRA_ITEMALIGNMENTPROVIDER_VIEWTYPE_CLASS =
77             "itemalignment_viewtype_class";
78     public static final String SELECT_ACTION = "android.test.leanback.widget.SELECT";
79 
80     static final int DEFAULT_NUM_ITEMS = 100;
81     static final boolean DEFAULT_STAGGERED = true;
82     static final boolean DEFAULT_REQUEST_LAYOUT_ONFOCUS = false;
83     static final boolean DEFAULT_REQUEST_FOCUS_ONLAYOUT = false;
84 
85     private static final boolean DEBUG = false;
86 
87     int mLayoutId;
88     int mOrientation;
89     int mNumItems;
90     int mChildLayout;
91     boolean mStaggered;
92     boolean mRequestLayoutOnFocus;
93     boolean mRequestFocusOnLayout;
94     boolean mSecondarySizeZero;
95     GridWidgetTest.ViewTypeProvider mViewTypeProvider;
96     GridWidgetTest.ItemAlignmentFacetProvider mAlignmentProvider;
97     GridWidgetTest.ItemAlignmentFacetProvider mAlignmentViewTypeProvider;
98     AdapterListener mAdapterListener;
99     boolean mUpdateSize = true;
100 
101     int[] mGridViewLayoutSize;
102     BaseGridView mGridView;
103     int[] mItemLengths;
104     boolean[] mItemFocusables;
105     int[] mLayoutMargins;
106     int mNinePatchShadow;
107 
108     private int mBoundCount;
109 
createView()110     private View createView() {
111 
112         View view = getLayoutInflater().inflate(mLayoutId, null, false);
113         mGridView = (BaseGridView) view.findViewById(R.id.gridview);
114         mOrientation = mGridView instanceof HorizontalGridView ? BaseGridView.HORIZONTAL :
115                 BaseGridView.VERTICAL;
116         mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_BOTH_EDGE);
117         mGridView.setWindowAlignmentOffsetPercent(35);
118         mGridView.setOnChildSelectedListener(new OnChildSelectedListener() {
119             @Override
120             public void onChildSelected(ViewGroup parent, View view, int position, long id) {
121                 if (DEBUG) Log.d(TAG, "onChildSelected position=" + position +  " id="+id);
122             }
123         });
124         if (mNinePatchShadow != 0) {
125             mGridView.setLayoutMode(ViewGroup.LAYOUT_MODE_OPTICAL_BOUNDS);
126         }
127         return view;
128     }
129 
130     @Override
onCreate(Bundle savedInstanceState)131     protected void onCreate(Bundle savedInstanceState) {
132         Intent intent = getIntent();
133 
134         mLayoutId = intent.getIntExtra(EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_grid);
135         mChildLayout = intent.getIntExtra(EXTRA_CHILD_LAYOUT_ID, -1);
136         mStaggered = intent.getBooleanExtra(EXTRA_STAGGERED, DEFAULT_STAGGERED);
137         mRequestLayoutOnFocus = intent.getBooleanExtra(EXTRA_REQUEST_LAYOUT_ONFOCUS,
138                 DEFAULT_REQUEST_LAYOUT_ONFOCUS);
139         mRequestFocusOnLayout = intent.getBooleanExtra(EXTRA_REQUEST_FOCUS_ONLAYOUT,
140                 DEFAULT_REQUEST_FOCUS_ONLAYOUT);
141         mUpdateSize = intent.getBooleanExtra(EXTRA_UPDATE_SIZE, true);
142         mSecondarySizeZero = intent.getBooleanExtra(EXTRA_SECONDARY_SIZE_ZERO, false);
143         mItemLengths = intent.getIntArrayExtra(EXTRA_ITEMS);
144         mItemFocusables = intent.getBooleanArrayExtra(EXTRA_ITEMS_FOCUSABLE);
145         mLayoutMargins = intent.getIntArrayExtra(EXTRA_LAYOUT_MARGINS);
146         String alignmentClass = intent.getStringExtra(EXTRA_ITEMALIGNMENTPROVIDER_CLASS);
147         String alignmentViewTypeClass =
148                 intent.getStringExtra(EXTRA_ITEMALIGNMENTPROVIDER_VIEWTYPE_CLASS);
149         String viewTypeClass = intent.getStringExtra(EXTRA_VIEWTYPEPROVIDER_CLASS);
150         mNinePatchShadow = intent.getIntExtra(EXTRA_NINEPATCH_SHADOW, 0);
151         try {
152             if (alignmentClass != null) {
153                 mAlignmentProvider = (GridWidgetTest.ItemAlignmentFacetProvider)
154                         Class.forName(alignmentClass).newInstance();
155             }
156             if (alignmentViewTypeClass != null) {
157                 mAlignmentViewTypeProvider = (GridWidgetTest.ItemAlignmentFacetProvider)
158                         Class.forName(alignmentViewTypeClass).newInstance();
159             }
160             if (viewTypeClass != null) {
161                 mViewTypeProvider = (GridWidgetTest.ViewTypeProvider)
162                         Class.forName(viewTypeClass).newInstance();
163             }
164         } catch (ClassNotFoundException ex) {
165             throw new RuntimeException(ex);
166         } catch (InstantiationException ex) {
167             throw new RuntimeException(ex);
168         } catch (IllegalAccessException ex) {
169             throw new RuntimeException(ex);
170         }
171 
172         super.onCreate(savedInstanceState);
173 
174         if (DEBUG) Log.v(TAG, "onCreate " + this);
175 
176         RecyclerView.Adapter adapter = new MyAdapter();
177 
178         View view = createView();
179         if (mItemLengths == null) {
180             mNumItems = intent.getIntExtra(EXTRA_NUM_ITEMS, DEFAULT_NUM_ITEMS);
181             mItemLengths = new int[mNumItems];
182             for (int i = 0; i < mItemLengths.length; i++) {
183                 if (mOrientation == BaseGridView.HORIZONTAL) {
184                     mItemLengths[i] = mStaggered ? (int)(Math.random() * 180) + 180 : 240;
185                 } else {
186                     mItemLengths[i] = mStaggered ? (int)(Math.random() * 120) + 120 : 160;
187                 }
188             }
189         } else {
190             mNumItems = mItemLengths.length;
191         }
192 
193         mGridView.setAdapter(adapter);
194         setContentView(view);
195     }
196 
rebindToNewAdapter()197     void rebindToNewAdapter() {
198         mGridView.setAdapter(new MyAdapter());
199     }
200 
201     @Override
onNewIntent(Intent intent)202     protected void onNewIntent(Intent intent) {
203         if (DEBUG) Log.v(TAG, "onNewIntent " + intent+ " "+this);
204         if (intent.getAction().equals(SELECT_ACTION)) {
205             int position = intent.getIntExtra("SELECT_POSITION", -1);
206             if (position >= 0) {
207                 mGridView.setSelectedPosition(position);
208             }
209         }
210         super.onNewIntent(intent);
211     }
212 
213     private OnFocusChangeListener mItemFocusChangeListener = new OnFocusChangeListener() {
214 
215         @Override
216         public void onFocusChange(View v, boolean hasFocus) {
217             if (hasFocus) {
218                 v.setBackgroundColor(Color.YELLOW);
219             } else {
220                 v.setBackgroundColor(Color.LTGRAY);
221             }
222             if (mRequestLayoutOnFocus) {
223                 RecyclerView.ViewHolder vh = mGridView.getChildViewHolder(v);
224                 int position = vh.getAdapterPosition();
225                 updateSize(v, position);
226             }
227         }
228     };
229 
230     private OnFocusChangeListener mSubItemFocusChangeListener = new OnFocusChangeListener() {
231 
232         @Override
233         public void onFocusChange(View v, boolean hasFocus) {
234             if (hasFocus) {
235                 v.setBackgroundColor(Color.YELLOW);
236             } else {
237                 v.setBackgroundColor(Color.LTGRAY);
238             }
239         }
240     };
241 
resetBoundCount()242     void resetBoundCount() {
243         mBoundCount = 0;
244     }
245 
getBoundCount()246     int getBoundCount() {
247        return mBoundCount;
248     }
249 
swap(int index1, int index2)250     void swap(int index1, int index2) {
251         if (index1 == index2) {
252             return;
253         } else if (index1 > index2) {
254             int index = index1;
255             index1 = index2;
256             index2 = index;
257         }
258         int value = mItemLengths[index1];
259         mItemLengths[index1] = mItemLengths[index2];
260         mItemLengths[index2] = value;
261         mGridView.getAdapter().notifyItemMoved(index1, index2);
262         mGridView.getAdapter().notifyItemMoved(index2 - 1, index1);
263     }
264 
changeArraySize(int length)265     void changeArraySize(int length) {
266         mNumItems = length;
267         mGridView.getAdapter().notifyDataSetChanged();
268     }
269 
removeItems(int index, int length)270     int[] removeItems(int index, int length) {
271         int[] removed = new int[length];
272         System.arraycopy(mItemLengths, index, removed, 0, length);
273         System.arraycopy(mItemLengths, index + length, mItemLengths, index,
274                 mNumItems - index - length);
275         mNumItems -= length;
276         if (mGridView.getAdapter() != null) {
277             mGridView.getAdapter().notifyItemRangeRemoved(index, length);
278         }
279         return removed;
280     }
281 
attachToNewAdapter(int[] items)282     void attachToNewAdapter(int[] items) {
283         mItemLengths = items;
284         mNumItems = items.length;
285         mGridView.setAdapter(new MyAdapter());
286     }
287 
288 
addItems(int index, int[] items)289     void addItems(int index, int[] items) {
290         int length = items.length;
291         if (mItemLengths.length < mNumItems + length) {
292             int[] array = new int[mNumItems + length];
293             System.arraycopy(mItemLengths, 0, array, 0, mNumItems);
294             mItemLengths = array;
295         }
296         System.arraycopy(mItemLengths, index, mItemLengths, index + length, mNumItems - index);
297         System.arraycopy(items, 0, mItemLengths, index, length);
298         mNumItems += length;
299         if (mGridView.getAdapter() != null) {
300             mGridView.getAdapter().notifyItemRangeInserted(index, length);
301         }
302     }
303 
304     class MyAdapter extends RecyclerView.Adapter implements FacetProviderAdapter {
305 
306         @Override
getItemViewType(int position)307         public int getItemViewType(int position) {
308             if (mViewTypeProvider != null) {
309                 return mViewTypeProvider.getViewType(position);
310             }
311             return 0;
312         }
313 
314         @Override
getFacetProvider(int viewType)315         public FacetProvider getFacetProvider(int viewType) {
316             final Object alignmentFacet = mAlignmentViewTypeProvider != null?
317                 mAlignmentViewTypeProvider.getItemAlignmentFacet(viewType) : null;
318             if (alignmentFacet != null) {
319                 return new FacetProvider() {
320                     @Override
321                     public Object getFacet(Class facetClass) {
322                         if (facetClass.equals(ItemAlignmentFacet.class)) {
323                             return alignmentFacet;
324                         }
325                         return null;
326                     }
327                 };
328             }
329             return null;
330         }
331 
332         @Override
333         public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
334             if (DEBUG) Log.v(TAG, "createViewHolder " + viewType);
335             View itemView;
336             if (mChildLayout != -1) {
337                 final View view = getLayoutInflater().inflate(mChildLayout, parent, false);
338                 ArrayList<View> focusables = new ArrayList<View>();
339                 view.addFocusables(focusables, View.FOCUS_UP);
340                 for (int i = 0; i < focusables.size(); i++) {
341                     View f = focusables.get(i);
342                     f.setBackgroundColor(Color.LTGRAY);
343                     f.setOnFocusChangeListener(new OnFocusChangeListener() {
344                         @Override
345                         public void onFocusChange(View v, boolean hasFocus) {
346                             if (hasFocus) {
347                                 v.setBackgroundColor(Color.YELLOW);
348                             } else {
349                                 v.setBackgroundColor(Color.LTGRAY);
350                             }
351                             if (mRequestLayoutOnFocus) {
352                                 if (v == view) {
353                                     RecyclerView.ViewHolder vh = mGridView.getChildViewHolder(v);
354                                     int position = vh.getAdapterPosition();
355                                     updateSize(v, position);
356                                 }
357                                 view.requestLayout();
358                             }
359                         }
360                     });
361                 }
362                 itemView = view;
363             } else {
364                 TextView textView = new TextView(parent.getContext()) {
365                     @Override
366                     protected void onLayout(boolean change, int left, int top, int right,
367                             int bottom) {
368                         super.onLayout(change, left, top, right, bottom);
369                         if (mRequestFocusOnLayout) {
370                             if (hasFocus()) {
371                                 clearFocus();
372                                 requestFocus();
373                             }
374                         }
375                     }
376                 };
377                 textView.setTextColor(Color.BLACK);
378                 textView.setOnFocusChangeListener(mItemFocusChangeListener);
379                 itemView = textView;
380             }
381             if (mLayoutMargins != null) {
382                 ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams)
383                         itemView.getLayoutParams();
384                 if (lp == null) {
385                     lp = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
386                             ViewGroup.LayoutParams.WRAP_CONTENT);
387                 }
388                 lp.leftMargin = mLayoutMargins[0];
389                 lp.topMargin = mLayoutMargins[1];
390                 lp.rightMargin = mLayoutMargins[2];
391                 lp.bottomMargin = mLayoutMargins[3];
392                 itemView.setLayoutParams(lp);
393             }
394             if (mNinePatchShadow != 0) {
395                 ViewGroup viewGroup = (ViewGroup) itemView;
396                 View shadow = new View(viewGroup.getContext());
397                 shadow.setBackgroundResource(mNinePatchShadow);
398                 viewGroup.addView(shadow);
399                 viewGroup.setLayoutMode(ViewGroup.LAYOUT_MODE_OPTICAL_BOUNDS);
400             }
401             return new ViewHolder(itemView);
402         }
403 
404         @Override
405         public void onBindViewHolder(RecyclerView.ViewHolder baseHolder, int position) {
406             if (DEBUG) Log.v(TAG, "bindViewHolder " + position + " " + baseHolder);
407             mBoundCount++;
408             ViewHolder holder = (ViewHolder) baseHolder;
409             if (mAlignmentProvider != null) {
410                 holder.mItemAlignment = mAlignmentProvider.getItemAlignmentFacet(position);
411             } else {
412                 holder.mItemAlignment = null;
413             }
414             if (mChildLayout == -1) {
415                 ((TextView) holder.itemView).setText("Item "+mItemLengths[position]
416                         + " type=" + getItemViewType(position));
417                 boolean focusable = true;
418                 if (mItemFocusables != null) {
419                     focusable = mItemFocusables[position];
420                 }
421                 ((TextView) holder.itemView).setFocusable(focusable);
422                 ((TextView) holder.itemView).setFocusableInTouchMode(focusable);
423                 holder.itemView.setBackgroundColor(Color.LTGRAY);
424             } else {
425                 if (holder.itemView instanceof TextView) {
426                     ((TextView) holder.itemView).setText("Item "+mItemLengths[position]
427                             + " type=" + getItemViewType(position));
428                 }
429             }
430             updateSize(holder.itemView, position);
431             if (mAdapterListener != null) {
432                 mAdapterListener.onBind(baseHolder, position);
433             }
434         }
435 
436         @Override
437         public int getItemCount() {
438             return mNumItems;
439         }
440 
441     }
442 
443     void updateSize(View view, int position) {
444         if (!mUpdateSize) {
445             return;
446         }
447         ViewGroup.LayoutParams p = view.getLayoutParams();
448         if (p == null) {
449             p = new ViewGroup.LayoutParams(0, 0);
450         }
451         if (mOrientation == BaseGridView.HORIZONTAL) {
452             p.width = mItemLengths[position] + (mRequestLayoutOnFocus && view.hasFocus() ? 1 : 0);
453             p.height = mSecondarySizeZero ? 0 : 80;
454         } else {
455             p.width = mSecondarySizeZero ? 0 : 240;
456             p.height = mItemLengths[position] + (mRequestLayoutOnFocus && view.hasFocus() ? 1 : 0);
457         }
458         view.setLayoutParams(p);
459     }
460 
461     static class ViewHolder extends RecyclerView.ViewHolder implements FacetProvider {
462 
463         ItemAlignmentFacet mItemAlignment;
464         public ViewHolder(View v) {
465             super(v);
466         }
467 
468         @Override
469         public Object getFacet(Class facetClass) {
470             if (facetClass.equals(ItemAlignmentFacet.class)) {
471                 return mItemAlignment;
472             }
473             return null;
474         }
475     }
476 }
477