Home | History | Annotate | Download | only in onboarding
      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 
     34 import com.android.tv.ApplicationSingletons;
     35 import com.android.tv.R;
     36 import com.android.tv.TvApplication;
     37 import com.android.tv.common.ui.setup.SetupGuidedStepFragment;
     38 import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
     39 import com.android.tv.data.ChannelDataManager;
     40 import com.android.tv.data.TvInputNewComparator;
     41 import com.android.tv.tuner.TunerInputController;
     42 import com.android.tv.ui.GuidedActionsStylistWithDivider;
     43 import com.android.tv.util.SetupUtils;
     44 import com.android.tv.util.TvInputManagerHelper;
     45 
     46 import java.util.ArrayList;
     47 import java.util.Collections;
     48 import java.util.List;
     49 
     50 /**
     51  * A fragment for channel source info/setup.
     52  */
     53 public 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