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