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 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
     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
     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
     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
     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
    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             singletons
    201                     .getTunerInputController()
    202                     .executeNetworkTunerDiscoveryAsyncTask(getContext());
    203         }
    204 
    205         @Override
    206         public void onDestroy() {
    207             super.onDestroy();
    208             mChannelDataManager.removeListener(mChannelDataManagerListener);
    209             mInputManager.removeCallback(mInputCallback);
    210         }
    211 
    212         @NonNull
    213         @Override
    214         public Guidance onCreateGuidance(Bundle savedInstanceState) {
    215             String title = getString(R.string.setup_sources_text);
    216             String description = getString(R.string.setup_sources_description);
    217             return new Guidance(title, description, null, null);
    218         }
    219 
    220         @Override
    221         public GuidedActionsStylist onCreateActionsStylist() {
    222             return new SetupSourceGuidedActionsStylist();
    223         }
    224 
    225         @Override
    226         public void onCreateActions(
    227                 @NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
    228             createActionsInternal(actions);
    229         }
    230 
    231         private void buildInputs() {
    232             List<TvInputInfo> oldInputs = mInputs;
    233             mInputs = mInputManager.getTvInputInfos(true, true);
    234             // Get newly installed input ID.
    235             if (oldInputs != null) {
    236                 List<TvInputInfo> newList = new ArrayList<>(mInputs);
    237                 for (TvInputInfo input : oldInputs) {
    238                     newList.remove(input);
    239                 }
    240                 if (newList.size() > 0 && mSetupUtils.isNewInput(newList.get(0).getId())) {
    241                     mNewlyAddedInputId = newList.get(0).getId();
    242                 } else {
    243                     mNewlyAddedInputId = null;
    244                 }
    245             }
    246             Collections.sort(mInputs, new TvInputNewComparator(mSetupUtils, mInputManager));
    247             mKnownInputStartIndex = 0;
    248             mDoneInputStartIndex = 0;
    249             for (TvInputInfo input : mInputs) {
    250                 if (mSetupUtils.isNewInput(input.getId())) {
    251                     mSetupUtils.markAsKnownInput(input.getId());
    252                     ++mKnownInputStartIndex;
    253                 }
    254                 if (!mSetupUtils.isSetupDone(input.getId())) {
    255                     ++mDoneInputStartIndex;
    256                 }
    257             }
    258         }
    259 
    260         private void updateActions() {
    261             List<GuidedAction> actions = new ArrayList<>();
    262             createActionsInternal(actions);
    263             setActions(actions);
    264         }
    265 
    266         private void createActionsInternal(List<GuidedAction> actions) {
    267             int newPosition = -1;
    268             int position = 0;
    269             if (mDoneInputStartIndex > 0) {
    270                 // Need a "New" category
    271                 actions.add(
    272                         new GuidedAction.Builder(getActivity())
    273                                 .id(ACTION_HEADER)
    274                                 .title(null)
    275                                 .description(getString(R.string.setup_category_new))
    276                                 .focusable(false)
    277                                 .infoOnly(true)
    278                                 .build());
    279             }
    280             for (int i = 0; i < mInputs.size(); ++i) {
    281                 if (i == mDoneInputStartIndex) {
    282                     ++position;
    283                     actions.add(
    284                             new GuidedAction.Builder(getActivity())
    285                                     .id(ACTION_HEADER)
    286                                     .title(null)
    287                                     .description(getString(R.string.setup_category_done))
    288                                     .focusable(false)
    289                                     .infoOnly(true)
    290                                     .build());
    291                 }
    292                 TvInputInfo input = mInputs.get(i);
    293                 String inputId = input.getId();
    294                 String description;
    295                 int channelCount = mChannelDataManager.getChannelCountForInput(inputId);
    296                 if (mSetupUtils.isSetupDone(inputId) || channelCount > 0) {
    297                     if (channelCount == 0) {
    298                         description = getString(R.string.setup_input_no_channels);
    299                     } else {
    300                         description =
    301                                 getResources()
    302                                         .getQuantityString(
    303                                                 R.plurals.setup_input_channels,
    304                                                 channelCount,
    305                                                 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(
    317                         new GuidedAction.Builder(getActivity())
    318                                 .id(ACTION_INPUT_START + i)
    319                                 .title(input.loadLabel(getActivity()).toString())
    320                                 .description(description)
    321                                 .build());
    322             }
    323             if (mInputs.size() > 0) {
    324                 // Divider
    325                 ++position;
    326                 actions.add(GuidedActionsStylistWithDivider.createDividerAction(getContext()));
    327             }
    328             // online store action
    329             ++position;
    330             actions.add(
    331                     new GuidedAction.Builder(getActivity())
    332                             .id(ACTION_ONLINE_STORE)
    333                             .title(getString(R.string.setup_store_action_title))
    334                             .description(getString(R.string.setup_store_action_description))
    335                             .icon(R.drawable.ic_store)
    336                             .build());
    337 
    338             if (newPosition != -1) {
    339                 VerticalGridView gridView = getGuidedActionsStylist().getActionsGridView();
    340                 gridView.setSelectedPosition(newPosition);
    341             }
    342         }
    343 
    344         @Override
    345         protected String getActionCategory() {
    346             return ACTION_CATEGORY;
    347         }
    348 
    349         @Override
    350         public void onGuidedActionClicked(GuidedAction action) {
    351             if (action.getId() == ACTION_ONLINE_STORE) {
    352                 mParentFragment.onActionClick(ACTION_CATEGORY, (int) action.getId());
    353                 return;
    354             }
    355             int index = (int) action.getId() - ACTION_INPUT_START;
    356             if (index >= 0) {
    357                 TvInputInfo input = mInputs.get(index);
    358                 Bundle params = new Bundle();
    359                 params.putString(ACTION_PARAM_KEY_INPUT_ID, input.getId());
    360                 mParentFragment.onActionClick(ACTION_CATEGORY, ACTION_SETUP_INPUT, params);
    361             }
    362         }
    363 
    364         void executePendingAction() {
    365             switch (mPendingAction) {
    366                 case PENDING_ACTION_INPUT_CHANGED:
    367                     buildInputs();
    368                     // Fall through
    369                 case PENDING_ACTION_CHANNEL_CHANGED:
    370                     updateActions();
    371                     break;
    372                 default: // fall out
    373             }
    374             mPendingAction = PENDING_ACTION_NONE;
    375         }
    376 
    377         private class SetupSourceGuidedActionsStylist extends GuidedActionsStylistWithDivider {
    378             private static final float ALPHA_CATEGORY = 1.0f;
    379             private static final float ALPHA_INPUT_DESCRIPTION = 0.5f;
    380 
    381             @Override
    382             public void onBindViewHolder(ViewHolder vh, GuidedAction action) {
    383                 super.onBindViewHolder(vh, action);
    384                 TextView descriptionView = vh.getDescriptionView();
    385                 if (descriptionView != null) {
    386                     if (action.getId() == ACTION_HEADER) {
    387                         descriptionView.setAlpha(ALPHA_CATEGORY);
    388                         descriptionView.setTextColor(
    389                                 getResources().getColor(R.color.setup_category, null));
    390                         descriptionView.setTypeface(
    391                                 Typeface.create(getString(R.string.condensed_font), 0));
    392                     } else {
    393                         descriptionView.setAlpha(ALPHA_INPUT_DESCRIPTION);
    394                         descriptionView.setTextColor(
    395                                 getResources()
    396                                         .getColor(R.color.common_setup_input_description, null));
    397                         descriptionView.setTypeface(Typeface.create(getString(R.string.font), 0));
    398                     }
    399                 }
    400                 setAccessibilityDelegate(vh, action);
    401             }
    402         }
    403     }
    404 }
    405