• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.tv.ui.sidepanel;
18 
19 import android.app.Fragment;
20 import android.content.Context;
21 import android.graphics.drawable.RippleDrawable;
22 import android.os.Bundle;
23 import android.support.v17.leanback.widget.VerticalGridView;
24 import android.support.v7.widget.RecyclerView;
25 import android.view.KeyEvent;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.FrameLayout;
30 import android.widget.TextView;
31 import com.android.tv.MainActivity;
32 import com.android.tv.R;
33 import com.android.tv.TvSingletons;
34 import com.android.tv.analytics.HasTrackerLabel;
35 import com.android.tv.analytics.Tracker;
36 import com.android.tv.common.util.DurationTimer;
37 import com.android.tv.common.util.SystemProperties;
38 import com.android.tv.data.ChannelDataManager;
39 import com.android.tv.data.ProgramDataManager;
40 import com.android.tv.util.ViewCache;
41 import java.util.List;
42 
43 public abstract class SideFragment<T extends Item> extends Fragment implements HasTrackerLabel {
44     public static final int INVALID_POSITION = -1;
45 
46     private static final int PRELOAD_VIEW_SIZE = 7;
47     private static final int[] PRELOAD_VIEW_IDS = {
48         R.layout.option_item_radio_button,
49         R.layout.option_item_channel_lock,
50         R.layout.option_item_check_box,
51         R.layout.option_item_channel_check,
52         R.layout.option_item_action
53     };
54 
55     private static RecyclerView.RecycledViewPool sRecycledViewPool =
56             new RecyclerView.RecycledViewPool();
57 
58     private VerticalGridView mListView;
59     private ItemAdapter mAdapter;
60     private SideFragmentListener mListener;
61     private ChannelDataManager mChannelDataManager;
62     private ProgramDataManager mProgramDataManager;
63     private Tracker mTracker;
64     private final DurationTimer mSidePanelDurationTimer = new DurationTimer();
65 
66     private final int mHideKey;
67     private final int mDebugHideKey;
68 
SideFragment()69     public SideFragment() {
70         this(KeyEvent.KEYCODE_UNKNOWN, KeyEvent.KEYCODE_UNKNOWN);
71     }
72 
73     /**
74      * @param hideKey the KeyCode used to hide the fragment
75      * @param debugHideKey the KeyCode used to hide the fragment if {@link
76      *     SystemProperties#USE_DEBUG_KEYS}.
77      */
SideFragment(int hideKey, int debugHideKey)78     public SideFragment(int hideKey, int debugHideKey) {
79         mHideKey = hideKey;
80         mDebugHideKey = debugHideKey;
81     }
82 
83     @Override
onAttach(Context context)84     public void onAttach(Context context) {
85         super.onAttach(context);
86         mChannelDataManager = getMainActivity().getChannelDataManager();
87         mProgramDataManager = getMainActivity().getProgramDataManager();
88         mTracker = TvSingletons.getSingletons(context).getTracker();
89     }
90 
91     @Override
onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)92     public View onCreateView(
93             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
94         View view =
95                 ViewCache.getInstance()
96                         .getOrCreateView(inflater, getFragmentLayoutResourceId(), container);
97 
98         TextView textView = (TextView) view.findViewById(R.id.side_panel_title);
99         textView.setText(getTitle());
100 
101         mListView = (VerticalGridView) view.findViewById(R.id.side_panel_list);
102         mListView.setRecycledViewPool(sRecycledViewPool);
103 
104         mAdapter = new ItemAdapter(inflater, getItemList());
105         mListView.setAdapter(mAdapter);
106         mListView.requestFocus();
107 
108         return view;
109     }
110 
111     @Override
onResume()112     public void onResume() {
113         super.onResume();
114         mTracker.sendShowSidePanel(this);
115         mTracker.sendScreenView(this.getTrackerLabel());
116         mSidePanelDurationTimer.start();
117     }
118 
119     @Override
onPause()120     public void onPause() {
121         mTracker.sendHideSidePanel(this, mSidePanelDurationTimer.reset());
122         super.onPause();
123     }
124 
125     @Override
onDetach()126     public void onDetach() {
127         mTracker = null;
128         super.onDetach();
129     }
130 
isHideKeyForThisPanel(int keyCode)131     public final boolean isHideKeyForThisPanel(int keyCode) {
132         boolean debugKeysEnabled = SystemProperties.USE_DEBUG_KEYS.getValue();
133         return mHideKey != KeyEvent.KEYCODE_UNKNOWN
134                 && (mHideKey == keyCode || (debugKeysEnabled && mDebugHideKey == keyCode));
135     }
136 
137     @Override
onDestroyView()138     public void onDestroyView() {
139         super.onDestroyView();
140         mListView.swapAdapter(null, true);
141         if (mListener != null) {
142             mListener.onSideFragmentViewDestroyed();
143         }
144     }
145 
setListener(SideFragmentListener listener)146     public final void setListener(SideFragmentListener listener) {
147         mListener = listener;
148     }
149 
setSelectedPosition(int position)150     protected void setSelectedPosition(int position) {
151         mListView.setSelectedPosition(position);
152     }
153 
getSelectedPosition()154     protected int getSelectedPosition() {
155         return mListView.getSelectedPosition();
156     }
157 
setItems(List<T> items)158     public void setItems(List<T> items) {
159         mAdapter.reset(items);
160     }
161 
closeFragment()162     protected void closeFragment() {
163         getMainActivity().getOverlayManager().getSideFragmentManager().popSideFragment();
164     }
165 
getMainActivity()166     protected MainActivity getMainActivity() {
167         return (MainActivity) getActivity();
168     }
169 
getChannelDataManager()170     protected ChannelDataManager getChannelDataManager() {
171         return mChannelDataManager;
172     }
173 
getProgramDataManager()174     protected ProgramDataManager getProgramDataManager() {
175         return mProgramDataManager;
176     }
177 
notifyDataSetChanged()178     protected void notifyDataSetChanged() {
179         mAdapter.notifyDataSetChanged();
180     }
181 
182     /*
183      * HACK: The following methods bypass the updating mechanism of RecyclerView.Adapter and
184      * directly updates each item. This works around a bug in the base libraries where calling
185      * Adapter.notifyItemsChanged() causes the VerticalGridView to lose track of displayed item
186      * position.
187      */
188 
notifyItemChanged(int position)189     protected void notifyItemChanged(int position) {
190         notifyItemChanged(mAdapter.getItem(position));
191     }
192 
notifyItemChanged(Item item)193     protected void notifyItemChanged(Item item) {
194         item.notifyUpdated();
195     }
196 
197     /** Notifies all items of ItemAdapter has changed without structural changes. */
notifyItemsChanged()198     protected void notifyItemsChanged() {
199         notifyItemsChanged(0, mAdapter.getItemCount());
200     }
201 
202     /**
203      * Notifies some items of ItemAdapter has changed starting from position <code>positionStart
204      * </code> to the end without structural changes.
205      */
notifyItemsChanged(int positionStart)206     protected void notifyItemsChanged(int positionStart) {
207         notifyItemsChanged(positionStart, mAdapter.getItemCount() - positionStart);
208     }
209 
notifyItemsChanged(int positionStart, int itemCount)210     protected void notifyItemsChanged(int positionStart, int itemCount) {
211         while (itemCount-- != 0) {
212             notifyItemChanged(positionStart++);
213         }
214     }
215 
216     /*
217      * END HACK
218      */
219 
getFragmentLayoutResourceId()220     protected int getFragmentLayoutResourceId() {
221         return R.layout.option_fragment;
222     }
223 
getTitle()224     protected abstract String getTitle();
225 
226     @Override
getTrackerLabel()227     public abstract String getTrackerLabel();
228 
getItemList()229     protected abstract List<T> getItemList();
230 
231     public interface SideFragmentListener {
onSideFragmentViewDestroyed()232         void onSideFragmentViewDestroyed();
233     }
234 
235     /** Preloads the item views. */
preloadItemViews(Context context)236     public static void preloadItemViews(Context context) {
237         ViewCache.getInstance()
238                 .putView(context, R.layout.option_fragment, new FrameLayout(context), 1);
239         VerticalGridView fakeParent = new VerticalGridView(context);
240         for (int id : PRELOAD_VIEW_IDS) {
241             sRecycledViewPool.setMaxRecycledViews(id, PRELOAD_VIEW_SIZE);
242             ViewCache.getInstance().putView(context, id, fakeParent, PRELOAD_VIEW_SIZE);
243         }
244     }
245 
246     /** Releases the recycled view pool. */
releaseRecycledViewPool()247     public static void releaseRecycledViewPool() {
248         sRecycledViewPool.clear();
249     }
250 
251     private static class ItemAdapter<T extends Item> extends RecyclerView.Adapter<ViewHolder> {
252         private final LayoutInflater mLayoutInflater;
253         private List<T> mItems;
254 
ItemAdapter(LayoutInflater layoutInflater, List<T> items)255         private ItemAdapter(LayoutInflater layoutInflater, List<T> items) {
256             mLayoutInflater = layoutInflater;
257             mItems = items;
258         }
259 
reset(List<T> items)260         private void reset(List<T> items) {
261             mItems = items;
262             notifyDataSetChanged();
263         }
264 
265         @Override
onCreateViewHolder(ViewGroup parent, int viewType)266         public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
267             View view = ViewCache.getInstance().getOrCreateView(mLayoutInflater, viewType, parent);
268             return new ViewHolder(view);
269         }
270 
271         @Override
onBindViewHolder(ViewHolder holder, int position)272         public void onBindViewHolder(ViewHolder holder, int position) {
273             holder.onBind(this, getItem(position));
274         }
275 
276         @Override
onViewRecycled(ViewHolder holder)277         public void onViewRecycled(ViewHolder holder) {
278             holder.onUnbind();
279         }
280 
281         @Override
getItemViewType(int position)282         public int getItemViewType(int position) {
283             return getItem(position).getResourceId();
284         }
285 
286         @Override
getItemCount()287         public int getItemCount() {
288             return mItems == null ? 0 : mItems.size();
289         }
290 
getItem(int position)291         private T getItem(int position) {
292             return mItems.get(position);
293         }
294 
clearRadioGroup(T item)295         private void clearRadioGroup(T item) {
296             int position = mItems.indexOf(item);
297             for (int i = position - 1; i >= 0; --i) {
298                 if ((item = mItems.get(i)) instanceof RadioButtonItem) {
299                     ((RadioButtonItem) item).setChecked(false);
300                 } else {
301                     break;
302                 }
303             }
304             for (int i = position + 1; i < mItems.size(); ++i) {
305                 if ((item = mItems.get(i)) instanceof RadioButtonItem) {
306                     ((RadioButtonItem) item).setChecked(false);
307                 } else {
308                     break;
309                 }
310             }
311         }
312     }
313 
314     private static class ViewHolder extends RecyclerView.ViewHolder
315             implements View.OnClickListener, View.OnFocusChangeListener {
316         private ItemAdapter mAdapter;
317         public Item mItem;
318 
ViewHolder(View view)319         private ViewHolder(View view) {
320             super(view);
321             itemView.setOnClickListener(this);
322             itemView.setOnFocusChangeListener(this);
323         }
324 
onBind(ItemAdapter adapter, Item item)325         public void onBind(ItemAdapter adapter, Item item) {
326             mAdapter = adapter;
327             mItem = item;
328             mItem.onBind(itemView);
329             mItem.onUpdate();
330         }
331 
onUnbind()332         public void onUnbind() {
333             mItem.onUnbind();
334             mItem = null;
335             mAdapter = null;
336         }
337 
338         @Override
onClick(View view)339         public void onClick(View view) {
340             if (mItem instanceof RadioButtonItem) {
341                 mAdapter.clearRadioGroup(mItem);
342             }
343             if (view.getBackground() instanceof RippleDrawable) {
344                 view.postDelayed(
345                         () -> {
346                             if (mItem != null) {
347                                 mItem.onSelected();
348                             }
349                         },
350                         view.getResources().getInteger(R.integer.side_panel_ripple_anim_duration));
351             } else {
352                 mItem.onSelected();
353             }
354         }
355 
356         @Override
onFocusChange(View view, boolean focusGained)357         public void onFocusChange(View view, boolean focusGained) {
358             if (focusGained) {
359                 mItem.onFocused();
360             }
361         }
362     }
363 }
364