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