• 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.onboarding;
18 
19 import android.content.Context;
20 import android.graphics.Typeface;
21 import android.media.tv.TvInputInfo;
22 import android.media.tv.TvInputManager.TvInputCallback;
23 import android.os.Bundle;
24 import android.support.annotation.NonNull;
25 import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
26 import android.support.v17.leanback.widget.GuidedAction;
27 import android.support.v17.leanback.widget.GuidedActionsStylist;
28 import android.support.v17.leanback.widget.VerticalGridView;
29 import android.view.LayoutInflater;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.widget.TextView;
33 import com.android.tv.R;
34 import com.android.tv.TvSingletons;
35 import com.android.tv.common.ui.setup.SetupGuidedStepFragment;
36 import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
37 import com.android.tv.data.ChannelDataManager;
38 import com.android.tv.data.TvInputNewComparator;
39 import com.android.tv.ui.GuidedActionsStylistWithDivider;
40 import com.android.tv.util.SetupUtils;
41 import com.android.tv.util.TvInputManagerHelper;
42 import java.util.ArrayList;
43 import java.util.Collections;
44 import java.util.List;
45 
46 /** A fragment for channel source info/setup. */
47 public class SetupSourcesFragment extends SetupMultiPaneFragment {
48     /** The action category for the actions which is fired from this fragment. */
49     public static final String ACTION_CATEGORY = "com.android.tv.onboarding.SetupSourcesFragment";
50     /** An action to open the merchant collection. */
51     public static final int ACTION_ONLINE_STORE = 1;
52     /**
53      * An action to show the setup activity of TV input.
54      *
55      * <p>This action is not added to the action list. This is sent outside of the fragment. Use
56      * {@link #ACTION_PARAM_KEY_INPUT_ID} to get the input ID from the parameter.
57      */
58     public static final int ACTION_SETUP_INPUT = 2;
59 
60     /**
61      * The key for the action parameter which contains the TV input ID. It's used for the action
62      * {@link #ACTION_SETUP_INPUT}.
63      */
64     public static final String ACTION_PARAM_KEY_INPUT_ID = "input_id";
65 
66     private static final String SETUP_TRACKER_LABEL = "Setup fragment";
67 
68     @Override
onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)69     public View onCreateView(
70             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
71         View view = super.onCreateView(inflater, container, savedInstanceState);
72         TvSingletons.getSingletons(getActivity()).getTracker().sendScreenView(SETUP_TRACKER_LABEL);
73         return view;
74     }
75 
76     @Override
onEnterTransitionEnd()77     protected void onEnterTransitionEnd() {
78         SetupGuidedStepFragment f = getContentFragment();
79         if (f instanceof ContentFragment) {
80             // If the enter transition is canceled quickly, the child fragment can be null because
81             // the fragment is added asynchronously.
82             ((ContentFragment) f).executePendingAction();
83         }
84     }
85 
86     @Override
onCreateContentFragment()87     protected SetupGuidedStepFragment onCreateContentFragment() {
88         SetupGuidedStepFragment f = new ContentFragment();
89         Bundle arguments = new Bundle();
90         arguments.putBoolean(SetupGuidedStepFragment.KEY_THREE_PANE, true);
91         f.setArguments(arguments);
92         return f;
93     }
94 
95     @Override
getActionCategory()96     protected String getActionCategory() {
97         return ACTION_CATEGORY;
98     }
99 
100     public static class ContentFragment extends SetupGuidedStepFragment {
101         // ACTION_ONLINE_STORE is defined in the outer class.
102         private static final int ACTION_HEADER = 3;
103         private static final int ACTION_INPUT_START = 4;
104 
105         private static final int PENDING_ACTION_NONE = 0;
106         private static final int PENDING_ACTION_INPUT_CHANGED = 1;
107         private static final int PENDING_ACTION_CHANNEL_CHANGED = 2;
108 
109         private TvInputManagerHelper mInputManager;
110         private ChannelDataManager mChannelDataManager;
111         private SetupUtils mSetupUtils;
112         private List<TvInputInfo> mInputs;
113         private int mKnownInputStartIndex;
114         private int mDoneInputStartIndex;
115 
116         private SetupSourcesFragment mParentFragment;
117 
118         private String mNewlyAddedInputId;
119 
120         private int mPendingAction = PENDING_ACTION_NONE;
121 
122         private final TvInputCallback mInputCallback =
123                 new TvInputCallback() {
124                     @Override
125                     public void onInputAdded(String inputId) {
126                         handleInputChanged();
127                     }
128 
129                     @Override
130                     public void onInputRemoved(String inputId) {
131                         handleInputChanged();
132                     }
133 
134                     @Override
135                     public void onInputUpdated(String inputId) {
136                         handleInputChanged();
137                     }
138 
139                     @Override
140                     public void onTvInputInfoUpdated(TvInputInfo inputInfo) {
141                         handleInputChanged();
142                     }
143 
144                     private void handleInputChanged() {
145                         // The actions created while enter transition is running will not be
146                         // included in the
147                         // fragment transition.
148                         if (mParentFragment.isEnterTransitionRunning()) {
149                             mPendingAction = PENDING_ACTION_INPUT_CHANGED;
150                             return;
151                         }
152                         buildInputs();
153                         updateActions();
154                     }
155                 };
156 
157         private final ChannelDataManager.Listener mChannelDataManagerListener =
158                 new ChannelDataManager.Listener() {
159                     @Override
160                     public void onLoadFinished() {
161                         handleChannelChanged();
162                     }
163 
164                     @Override
165                     public void onChannelListUpdated() {
166                         handleChannelChanged();
167                     }
168 
169                     @Override
170                     public void onChannelBrowsableChanged() {
171                         handleChannelChanged();
172                     }
173 
174                     private void handleChannelChanged() {
175                         // The actions created while enter transition is running will not be
176                         // included in the
177                         // fragment transition.
178                         if (mParentFragment.isEnterTransitionRunning()) {
179                             if (mPendingAction != PENDING_ACTION_INPUT_CHANGED) {
180                                 mPendingAction = PENDING_ACTION_CHANNEL_CHANGED;
181                             }
182                             return;
183                         }
184                         updateActions();
185                     }
186                 };
187 
188         @Override
onCreate(Bundle savedInstanceState)189         public void onCreate(Bundle savedInstanceState) {
190             Context context = getActivity();
191             TvSingletons singletons = TvSingletons.getSingletons(context);
192             mInputManager = singletons.getTvInputManagerHelper();
193             mChannelDataManager = singletons.getChannelDataManager();
194             mSetupUtils = singletons.getSetupUtils();
195             buildInputs();
196             mInputManager.addCallback(mInputCallback);
197             mChannelDataManager.addListener(mChannelDataManagerListener);
198             super.onCreate(savedInstanceState);
199             mParentFragment = (SetupSourcesFragment) getParentFragment();
200             if (singletons.getBuiltInTunerManager().isPresent()) {
201                 singletons
202                         .getBuiltInTunerManager()
203                         .get()
204                         .getTunerInputController()
205                         .executeNetworkTunerDiscoveryAsyncTask(getContext());
206             }
207         }
208 
209         @Override
onDestroy()210         public void onDestroy() {
211             super.onDestroy();
212             mChannelDataManager.removeListener(mChannelDataManagerListener);
213             mInputManager.removeCallback(mInputCallback);
214         }
215 
216         @NonNull
217         @Override
onCreateGuidance(Bundle savedInstanceState)218         public Guidance onCreateGuidance(Bundle savedInstanceState) {
219             String title = getString(R.string.setup_sources_text);
220             String description = getString(R.string.setup_sources_description);
221             return new Guidance(title, description, null, null);
222         }
223 
224         @Override
onCreateActionsStylist()225         public GuidedActionsStylist onCreateActionsStylist() {
226             return new SetupSourceGuidedActionsStylist();
227         }
228 
229         @Override
onCreateActions( @onNull List<GuidedAction> actions, Bundle savedInstanceState)230         public void onCreateActions(
231                 @NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
232             createActionsInternal(actions);
233         }
234 
buildInputs()235         private void buildInputs() {
236             List<TvInputInfo> oldInputs = mInputs;
237             mInputs = mInputManager.getTvInputInfos(true, true);
238             // Get newly installed input ID.
239             if (oldInputs != null) {
240                 List<TvInputInfo> newList = new ArrayList<>(mInputs);
241                 for (TvInputInfo input : oldInputs) {
242                     newList.remove(input);
243                 }
244                 if (newList.size() > 0 && mSetupUtils.isNewInput(newList.get(0).getId())) {
245                     mNewlyAddedInputId = newList.get(0).getId();
246                 } else {
247                     mNewlyAddedInputId = null;
248                 }
249             }
250             Collections.sort(mInputs, new TvInputNewComparator(mSetupUtils, mInputManager));
251             mKnownInputStartIndex = 0;
252             mDoneInputStartIndex = 0;
253             for (TvInputInfo input : mInputs) {
254                 if (mSetupUtils.isNewInput(input.getId())) {
255                     mSetupUtils.markAsKnownInput(input.getId());
256                     ++mKnownInputStartIndex;
257                 }
258                 if (!mSetupUtils.isSetupDone(input.getId())) {
259                     ++mDoneInputStartIndex;
260                 }
261             }
262         }
263 
updateActions()264         private void updateActions() {
265             List<GuidedAction> actions = new ArrayList<>();
266             createActionsInternal(actions);
267             setActions(actions);
268         }
269 
createActionsInternal(List<GuidedAction> actions)270         private void createActionsInternal(List<GuidedAction> actions) {
271             int newPosition = -1;
272             int position = 0;
273             if (mDoneInputStartIndex > 0) {
274                 // Need a "New" category
275                 actions.add(
276                         new GuidedAction.Builder(getActivity())
277                                 .id(ACTION_HEADER)
278                                 .title(null)
279                                 .description(getString(R.string.setup_category_new))
280                                 .focusable(false)
281                                 .infoOnly(true)
282                                 .build());
283             }
284             for (int i = 0; i < mInputs.size(); ++i) {
285                 if (i == mDoneInputStartIndex) {
286                     ++position;
287                     actions.add(
288                             new GuidedAction.Builder(getActivity())
289                                     .id(ACTION_HEADER)
290                                     .title(null)
291                                     .description(getString(R.string.setup_category_done))
292                                     .focusable(false)
293                                     .infoOnly(true)
294                                     .build());
295                 }
296                 TvInputInfo input = mInputs.get(i);
297                 String inputId = input.getId();
298                 String description;
299                 int channelCount = mChannelDataManager.getChannelCountForInput(inputId);
300                 if (mSetupUtils.isSetupDone(inputId) || channelCount > 0) {
301                     if (channelCount == 0) {
302                         description = getString(R.string.setup_input_no_channels);
303                     } else {
304                         description =
305                                 getResources()
306                                         .getQuantityString(
307                                                 R.plurals.setup_input_channels,
308                                                 channelCount,
309                                                 channelCount);
310                     }
311                 } else if (i >= mKnownInputStartIndex) {
312                     description = getString(R.string.setup_input_setup_now);
313                 } else {
314                     description = getString(R.string.setup_input_new);
315                 }
316                 ++position;
317                 if (input.getId().equals(mNewlyAddedInputId)) {
318                     newPosition = position;
319                 }
320                 actions.add(
321                         new GuidedAction.Builder(getActivity())
322                                 .id(ACTION_INPUT_START + i)
323                                 .title(input.loadLabel(getActivity()).toString())
324                                 .description(description)
325                                 .build());
326             }
327             if (mInputs.size() > 0) {
328                 // Divider
329                 ++position;
330                 actions.add(GuidedActionsStylistWithDivider.createDividerAction(getContext()));
331             }
332             // online store action
333             ++position;
334             actions.add(
335                     new GuidedAction.Builder(getActivity())
336                             .id(ACTION_ONLINE_STORE)
337                             .title(getString(R.string.setup_store_action_title))
338                             .description(getString(R.string.setup_store_action_description))
339                             .icon(R.drawable.ic_app_store)
340                             .build());
341 
342             if (newPosition != -1) {
343                 VerticalGridView gridView = getGuidedActionsStylist().getActionsGridView();
344                 gridView.setSelectedPosition(newPosition);
345             }
346         }
347 
348         @Override
getActionCategory()349         protected String getActionCategory() {
350             return ACTION_CATEGORY;
351         }
352 
353         @Override
onGuidedActionClicked(GuidedAction action)354         public void onGuidedActionClicked(GuidedAction action) {
355             if (action.getId() == ACTION_ONLINE_STORE) {
356                 mParentFragment.onActionClick(ACTION_CATEGORY, (int) action.getId());
357                 return;
358             }
359             int index = (int) action.getId() - ACTION_INPUT_START;
360             if (index >= 0) {
361                 TvInputInfo input = mInputs.get(index);
362                 Bundle params = new Bundle();
363                 params.putString(ACTION_PARAM_KEY_INPUT_ID, input.getId());
364                 mParentFragment.onActionClick(ACTION_CATEGORY, ACTION_SETUP_INPUT, params);
365             }
366         }
367 
executePendingAction()368         void executePendingAction() {
369             switch (mPendingAction) {
370                 case PENDING_ACTION_INPUT_CHANGED:
371                     buildInputs();
372                     // Fall through
373                 case PENDING_ACTION_CHANNEL_CHANGED:
374                     updateActions();
375                     break;
376                 default: // fall out
377             }
378             mPendingAction = PENDING_ACTION_NONE;
379         }
380 
381         private class SetupSourceGuidedActionsStylist extends GuidedActionsStylistWithDivider {
382             private static final float ALPHA_CATEGORY = 1.0f;
383             private static final float ALPHA_INPUT_DESCRIPTION = 0.5f;
384 
385             @Override
onBindViewHolder(ViewHolder vh, GuidedAction action)386             public void onBindViewHolder(ViewHolder vh, GuidedAction action) {
387                 super.onBindViewHolder(vh, action);
388                 TextView descriptionView = vh.getDescriptionView();
389                 if (descriptionView != null) {
390                     if (action.getId() == ACTION_HEADER) {
391                         descriptionView.setAlpha(ALPHA_CATEGORY);
392                         descriptionView.setTextColor(
393                                 getResources().getColor(R.color.setup_category, null));
394                         descriptionView.setTypeface(
395                                 Typeface.create(getString(R.string.condensed_font), 0));
396                     } else {
397                         descriptionView.setAlpha(ALPHA_INPUT_DESCRIPTION);
398                         descriptionView.setTextColor(
399                                 getResources()
400                                         .getColor(R.color.common_setup_input_description, null));
401                         descriptionView.setTypeface(Typeface.create(getString(R.string.font), 0));
402                     }
403                 }
404                 setAccessibilityDelegate(vh, action);
405             }
406         }
407     }
408 }
409