1 /* 2 * Copyright (C) 2014 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.example.android.leanback; 18 19 import android.app.Activity; 20 import android.app.FragmentManager; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.res.Configuration; 24 import android.graphics.drawable.Drawable; 25 import android.os.Bundle; 26 import android.support.v17.leanback.app.GuidedStepFragment; 27 import android.support.v17.leanback.widget.GuidanceStylist; 28 import android.support.v17.leanback.widget.GuidanceStylist.Guidance; 29 import android.support.v17.leanback.widget.GuidedAction; 30 import android.support.v17.leanback.widget.GuidedActionsStylist; 31 import android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder; 32 import android.support.v17.leanback.widget.GuidedDatePickerAction; 33 import android.text.InputType; 34 import android.text.TextUtils; 35 import android.util.Log; 36 import android.view.ViewGroup; 37 import android.view.ViewTreeObserver.OnGlobalLayoutListener; 38 import android.view.inputmethod.EditorInfo; 39 40 import java.util.ArrayList; 41 import java.util.Calendar; 42 import java.util.List; 43 44 /** 45 * Activity that showcases different aspects of GuidedStepFragments. 46 */ 47 public class GuidedStepActivity extends Activity { 48 49 private static final int BACK = 2; 50 51 private static final int FIRST_NAME = 3; 52 private static final int LAST_NAME = 4; 53 private static final int PASSWORD = 5; 54 private static final int PAYMENT = 6; 55 private static final int NEW_PAYMENT = 7; 56 private static final int PAYMENT_EXPIRE = 8; 57 58 private static final long RADIO_ID_BASE = 0; 59 private static final long CHECKBOX_ID_BASE = 100; 60 61 private static final long DEFAULT_OPTION = RADIO_ID_BASE; 62 63 private static final String[] OPTION_NAMES = { "Option A", "Option B", "Option C" }; 64 private static final String[] OPTION_DESCRIPTIONS = { "Here's one thing you can do", 65 "Here's another thing you can do", "Here's one more thing you can do" }; 66 67 private static final String TAG = GuidedStepActivity.class.getSimpleName(); 68 69 @Override onCreate(Bundle savedInstanceState)70 protected void onCreate(Bundle savedInstanceState) { 71 Log.v(TAG, "onCreate"); 72 super.onCreate(savedInstanceState); 73 setContentView(R.layout.guided_step_activity); 74 if (savedInstanceState == null) { 75 GuidedStepFragment.addAsRoot(this, new FirstStepFragment(), R.id.lb_guidedstep_host); 76 } 77 } 78 79 @Override onConfigurationChanged(Configuration newConfig)80 public void onConfigurationChanged(Configuration newConfig) { 81 Log.v(TAG, "onConfigurationChanged"); 82 super.onConfigurationChanged(newConfig); 83 } 84 85 @Override onSaveInstanceState(Bundle outState)86 protected void onSaveInstanceState(Bundle outState) { 87 Log.v(TAG, "onSaveInstanceState"); 88 super.onSaveInstanceState(outState); 89 } 90 91 @Override onRestoreInstanceState(Bundle savedInstanceState)92 protected void onRestoreInstanceState(Bundle savedInstanceState) { 93 Log.v(TAG, "onRestoreInstanceState"); 94 super.onRestoreInstanceState(savedInstanceState); 95 } 96 addAction(List<GuidedAction> actions, long id, String title, String desc)97 private static void addAction(List<GuidedAction> actions, long id, String title, String desc) { 98 actions.add(new GuidedAction.Builder() 99 .id(id) 100 .title(title) 101 .description(desc) 102 .build()); 103 } 104 addAction(List<GuidedAction> actions, long id, String title, String desc, List<GuidedAction> subActions)105 private static void addAction(List<GuidedAction> actions, long id, String title, String desc, 106 List<GuidedAction> subActions) { 107 actions.add(new GuidedAction.Builder() 108 .id(id) 109 .title(title) 110 .description(desc) 111 .subActions(subActions) 112 .build()); 113 } 114 addEditableAction(Context context, List<GuidedAction> actions, long id, String title, String desc)115 private static void addEditableAction(Context context, List<GuidedAction> actions, 116 long id, String title, String desc) { 117 actions.add(new GuidedAction.Builder(context) 118 .id(id) 119 .title(title) 120 .description(desc) 121 .editable(true) 122 .icon(R.drawable.lb_ic_search_mic) 123 .build()); 124 } 125 addEditableAction(List<GuidedAction> actions, long id, String title, String editTitle, String desc)126 private static void addEditableAction(List<GuidedAction> actions, long id, String title, 127 String editTitle, String desc) { 128 actions.add(new GuidedAction.Builder() 129 .id(id) 130 .title(title) 131 .editTitle(editTitle) 132 .description(desc) 133 .editable(true) 134 .build()); 135 } 136 addEditableAction(List<GuidedAction> actions, long id, String title, String editTitle, int editInputType, String desc, String editDesc)137 private static void addEditableAction(List<GuidedAction> actions, long id, String title, 138 String editTitle, int editInputType, String desc, String editDesc) { 139 actions.add(new GuidedAction.Builder() 140 .id(id) 141 .title(title) 142 .editTitle(editTitle) 143 .editInputType(editInputType) 144 .description(desc) 145 .editDescription(editDesc) 146 .editable(true) 147 .build()); 148 } 149 addDatePickerAction(List<GuidedAction> actions, long id, String title)150 private static void addDatePickerAction(List<GuidedAction> actions, long id, String title) { 151 actions.add(new GuidedDatePickerAction.Builder(null) 152 .id(id) 153 .title(title) 154 .datePickerFormat("MY") 155 .build()); 156 } 157 addEditableDescriptionAction(List<GuidedAction> actions, long id, String title, String desc, String editDescription, int descriptionEditInputType)158 private static void addEditableDescriptionAction(List<GuidedAction> actions, long id, 159 String title, String desc, String editDescription, int descriptionEditInputType) { 160 actions.add(new GuidedAction.Builder() 161 .id(id) 162 .title(title) 163 .description(desc) 164 .editDescription(editDescription) 165 .descriptionEditInputType(descriptionEditInputType) 166 .descriptionEditable(true) 167 .build()); 168 } 169 addCheckedAction(List<GuidedAction> actions, long id, Context context, String title, String desc, int checkSetId)170 private static void addCheckedAction(List<GuidedAction> actions, long id, Context context, 171 String title, String desc, int checkSetId) { 172 actions.add(new GuidedAction.Builder() 173 .id(id) 174 .title(title) 175 .description(desc) 176 .checkSetId(checkSetId) 177 .build()); 178 } 179 180 public static class FirstStepFragment extends GuidedStepFragment { 181 182 @Override onProvideTheme()183 public int onProvideTheme() { 184 return R.style.Theme_Example_Leanback_GuidedStep_First; 185 } 186 187 @Override onCreateGuidance(Bundle savedInstanceState)188 public Guidance onCreateGuidance(Bundle savedInstanceState) { 189 String title = getString(R.string.guidedstep_first_title); 190 String breadcrumb = getString(R.string.guidedstep_first_breadcrumb); 191 String description = getString(R.string.guidedstep_first_description); 192 Drawable icon = getActivity().getResources().getDrawable(R.drawable.ic_main_icon); 193 return new Guidance(title, description, breadcrumb, icon); 194 } 195 196 @Override onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState)197 public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) { 198 Context context = getActivity(); 199 actions.add(new GuidedAction.Builder(context) 200 .clickAction(GuidedAction.ACTION_ID_CONTINUE) 201 .description("Let's do it") 202 .build()); 203 actions.add(new GuidedAction.Builder(context) 204 .clickAction(GuidedAction.ACTION_ID_CANCEL) 205 .description("Never mind") 206 .build()); 207 } 208 209 @Override onGuidedActionClicked(GuidedAction action)210 public void onGuidedActionClicked(GuidedAction action) { 211 FragmentManager fm = getFragmentManager(); 212 if (action.getId() == GuidedAction.ACTION_ID_CONTINUE) { 213 GuidedStepFragment.add(fm, new SecondStepFragment(), R.id.lb_guidedstep_host); 214 } else if (action.getId() == GuidedAction.ACTION_ID_CANCEL){ 215 finishGuidedStepFragments(); 216 } 217 } 218 } 219 220 static ArrayList<String> sCards = new ArrayList<String>(); 221 static int sSelectedCard = -1; 222 static { 223 sCards.add("Visa-1234"); 224 sCards.add("Master-4321"); 225 } 226 227 public static class NewPaymentStepFragment extends GuidedStepFragment { 228 229 @Override onCreateGuidance(Bundle savedInstanceState)230 public Guidance onCreateGuidance(Bundle savedInstanceState) { 231 String title = getString(R.string.guidedstep_newpayment_title); 232 String breadcrumb = getString(R.string.guidedstep_newpayment_breadcrumb); 233 String description = getString(R.string.guidedstep_newpayment_description); 234 Drawable icon = getActivity().getResources().getDrawable(R.drawable.ic_main_icon); 235 return new Guidance(title, description, breadcrumb, icon); 236 } 237 238 @Override onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState)239 public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) { 240 addEditableAction(actions, NEW_PAYMENT, "Input credit card number", "", 241 InputType.TYPE_CLASS_NUMBER, 242 "Input credit card number", "Input credit card number"); 243 addDatePickerAction(actions, PAYMENT_EXPIRE, "Exp:"); 244 } 245 246 @Override onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState)247 public void onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) { 248 Context context = getActivity(); 249 actions.add(new GuidedAction.Builder(context).clickAction(GuidedAction.ACTION_ID_OK) 250 .build()); 251 actions.get(actions.size() - 1).setEnabled(false); 252 } 253 254 @Override onGuidedActionClicked(GuidedAction action)255 public void onGuidedActionClicked(GuidedAction action) { 256 if (action.getId() == GuidedAction.ACTION_ID_OK) { 257 CharSequence desc = findActionById(NEW_PAYMENT).getDescription(); 258 String cardNumber = desc.subSequence(desc.length() - 4, desc.length()).toString(); 259 String card; 260 if ((Integer.parseInt(cardNumber) & 1) == 0) { 261 card = "Visa "+cardNumber; 262 } else { 263 card = "Master "+cardNumber; 264 } 265 sSelectedCard = sCards.size(); 266 sCards.add(card); 267 popBackStackToGuidedStepFragment(NewPaymentStepFragment.class, 268 FragmentManager.POP_BACK_STACK_INCLUSIVE); 269 } 270 } 271 272 @Override onGuidedActionEditedAndProceed(GuidedAction action)273 public long onGuidedActionEditedAndProceed(GuidedAction action) { 274 if (action.getId() == NEW_PAYMENT) { 275 CharSequence editTitle = action.getEditTitle(); 276 if (isCardNumberValid(editTitle)) { 277 editTitle = editTitle.subSequence(editTitle.length() - 4, editTitle.length()); 278 action.setDescription("Visa XXXX-XXXX-XXXX-" + editTitle); 279 updateOkButton(isExpDateValid(findActionById(PAYMENT_EXPIRE))); 280 return GuidedAction.ACTION_ID_NEXT; 281 } else if (editTitle.length() == 0) { 282 action.setDescription("Input credit card number"); 283 updateOkButton(false); 284 return GuidedAction.ACTION_ID_CURRENT; 285 } else { 286 action.setDescription("Error credit card number"); 287 updateOkButton(false); 288 return GuidedAction.ACTION_ID_CURRENT; 289 } 290 } else if (action.getId() == PAYMENT_EXPIRE) { 291 updateOkButton(isExpDateValid(action) && 292 isCardNumberValid(findActionById(NEW_PAYMENT).getEditTitle())); 293 } 294 return GuidedAction.ACTION_ID_NEXT; 295 } 296 isCardNumberValid(CharSequence number)297 boolean isCardNumberValid(CharSequence number) { 298 return TextUtils.isDigitsOnly(number) && number.length() == 16; 299 } 300 isExpDateValid(GuidedAction action)301 boolean isExpDateValid(GuidedAction action) { 302 long date = ((GuidedDatePickerAction) action).getDate(); 303 Calendar c = Calendar.getInstance(); 304 c.setTimeInMillis(date); 305 return Calendar.getInstance().before(c); 306 } 307 updateOkButton(boolean enabled)308 void updateOkButton(boolean enabled) { 309 findButtonActionById(GuidedAction.ACTION_ID_OK).setEnabled(enabled); 310 notifyButtonActionChanged(findButtonActionPositionById(GuidedAction.ACTION_ID_OK)); 311 } 312 } 313 314 public static class SecondStepFragment extends GuidedStepFragment { 315 onCreateActionsStylist()316 public GuidedActionsStylist onCreateActionsStylist() { 317 return new GuidedActionsStylist() { 318 protected void setupImeOptions(GuidedActionsStylist.ViewHolder vh, 319 GuidedAction action) { 320 if (action.getId() == PASSWORD) { 321 vh.getEditableDescriptionView().setImeActionLabel("Confirm!", 322 EditorInfo.IME_ACTION_DONE); 323 } else { 324 super.setupImeOptions(vh, action); 325 } 326 } 327 }; 328 } 329 330 @Override 331 public Guidance onCreateGuidance(Bundle savedInstanceState) { 332 String title = getString(R.string.guidedstep_second_title); 333 String breadcrumb = getString(R.string.guidedstep_second_breadcrumb); 334 String description = getString(R.string.guidedstep_second_description); 335 Drawable icon = getActivity().getResources().getDrawable(R.drawable.ic_main_icon); 336 return new Guidance(title, description, breadcrumb, icon); 337 } 338 339 @Override 340 public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) { 341 addEditableAction(getContext(), actions, FIRST_NAME, "Pat", "Your first name"); 342 addEditableAction(getContext(), actions, LAST_NAME, "Smith", "Your last name"); 343 List<GuidedAction> subActions = new ArrayList<GuidedAction>(); 344 addAction(actions, PAYMENT, "Select Payment", "", subActions); 345 addEditableDescriptionAction(actions, PASSWORD, "Password", "", "", 346 InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); 347 } 348 349 @Override 350 public void onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) { 351 actions.add(new GuidedAction.Builder(getActivity()) 352 .clickAction(GuidedAction.ACTION_ID_CONTINUE) 353 .description("Continue") 354 .build()); 355 } 356 357 @Override 358 public void onGuidedActionClicked(GuidedAction action) { 359 if (action.getId() == GuidedAction.ACTION_ID_CONTINUE) { 360 FragmentManager fm = getFragmentManager(); 361 GuidedStepFragment.add(fm, new ThirdStepFragment(), R.id.lb_guidedstep_host); 362 } 363 } 364 365 @Override 366 public long onGuidedActionEditedAndProceed(GuidedAction action) { 367 if (action.getId() == PASSWORD) { 368 CharSequence password = action.getEditDescription(); 369 if (password.length() > 0) { 370 if (isPaymentValid()) { 371 updateContinue(true); 372 return GuidedAction.ACTION_ID_NEXT; 373 } else { 374 updateContinue(false); 375 return GuidedAction.ACTION_ID_CURRENT; 376 } 377 } else { 378 updateContinue(false); 379 return GuidedAction.ACTION_ID_CURRENT; 380 } 381 } 382 return GuidedAction.ACTION_ID_NEXT; 383 } 384 385 @Override 386 public boolean onSubGuidedActionClicked(GuidedAction action) { 387 if (action.isChecked()) { 388 String payment = action.getTitle().toString(); 389 for (int i = 0; i < sCards.size(); i++) { 390 if (payment.equals(sCards.get(i))) { 391 sSelectedCard = i; 392 findActionById(PAYMENT).setDescription(payment); 393 notifyActionChanged(findActionPositionById(PAYMENT)); 394 updateContinue(isPasswordValid()); 395 break; 396 } 397 } 398 return true; 399 } else { 400 FragmentManager fm = getFragmentManager(); 401 GuidedStepFragment.add(fm, new NewPaymentStepFragment(), R.id.lb_guidedstep_host); 402 return false; 403 } 404 } 405 406 @Override 407 public void onResume() { 408 super.onResume(); 409 // when resumed, update sub actions list and selected index from data model. 410 GuidedAction payments = findActionById(PAYMENT); 411 payments.getSubActions().clear(); 412 for (int i = 0; i < sCards.size(); i++) { 413 addCheckedAction(payments.getSubActions(), -1, getActivity(), sCards.get(i), "", 414 GuidedAction.DEFAULT_CHECK_SET_ID); 415 if (i == sSelectedCard) { 416 payments.getSubActions().get(i).setChecked(true); 417 } 418 } 419 addAction(payments.getSubActions(), NEW_PAYMENT, "Add New Card", ""); 420 if (sSelectedCard != -1) { 421 payments.setDescription(sCards.get(sSelectedCard)); 422 } 423 notifyActionChanged(findActionPositionById(PAYMENT)); 424 updateContinue(isPasswordValid() && isPaymentValid()); 425 } 426 427 boolean isPaymentValid() { 428 CharSequence paymentType = findActionById(PAYMENT).getDescription(); 429 return (paymentType.length() >= 4 && 430 paymentType.subSequence(0, 4).toString().equals("Visa")) || 431 (paymentType.length() >= 6 && 432 paymentType.subSequence(0, 6).toString().equals("Master")); 433 } 434 435 boolean isPasswordValid() { 436 return findActionById(PASSWORD).getEditDescription().length() > 0; 437 } 438 439 void updateContinue(boolean enabled) { 440 findButtonActionById(GuidedAction.ACTION_ID_CONTINUE).setEnabled(enabled); 441 notifyButtonActionChanged(findButtonActionPositionById( 442 GuidedAction.ACTION_ID_CONTINUE)); 443 } 444 } 445 446 public static class ThirdStepFragment extends GuidedStepFragment { 447 448 private long mSelectedOption = DEFAULT_OPTION; 449 450 @Override 451 public Guidance onCreateGuidance(Bundle savedInstanceState) { 452 String title = getString(R.string.guidedstep_third_title); 453 String breadcrumb = getString(R.string.guidedstep_third_breadcrumb); 454 String description = getString(R.string.guidedstep_third_description); 455 Drawable icon = getActivity().getResources().getDrawable(R.drawable.ic_main_icon); 456 return new Guidance(title, description, breadcrumb, icon); 457 } 458 459 @Override 460 public GuidanceStylist onCreateGuidanceStylist() { 461 return new GuidanceStylist() { 462 @Override 463 public int onProvideLayoutId() { 464 return R.layout.guidedstep_second_guidance; 465 } 466 }; 467 } 468 469 @Override 470 public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) { 471 String desc = "The description can be quite long as well. " + 472 "Just be sure to set multilineDescription to true in the GuidedAction."; 473 actions.add(new GuidedAction.Builder() 474 .title("Note that Guided Actions can have titles that are quite long.") 475 .description(desc) 476 .multilineDescription(true) 477 .infoOnly(true) 478 .enabled(true) 479 .focusable(false) 480 .build()); 481 for (int i = 0; i < OPTION_NAMES.length; i++) { 482 addCheckedAction(actions, RADIO_ID_BASE + i, getActivity(), OPTION_NAMES[i], 483 OPTION_DESCRIPTIONS[i], GuidedAction.DEFAULT_CHECK_SET_ID); 484 if (i == DEFAULT_OPTION) { 485 actions.get(actions.size() -1).setChecked(true); 486 } 487 } 488 for (int i = 0; i < OPTION_NAMES.length; i++) { 489 addCheckedAction(actions, CHECKBOX_ID_BASE + i, getActivity(), OPTION_NAMES[i], 490 OPTION_DESCRIPTIONS[i], GuidedAction.CHECKBOX_CHECK_SET_ID); 491 } 492 } 493 494 @Override 495 public void onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) { 496 actions.add(new GuidedAction.Builder(getActivity()) 497 .clickAction(GuidedAction.ACTION_ID_CONTINUE) 498 .build()); 499 } 500 501 @Override 502 public void onGuidedActionClicked(GuidedAction action) { 503 if (action.getId() == GuidedAction.ACTION_ID_CONTINUE) { 504 FragmentManager fm = getFragmentManager(); 505 FourthStepFragment f = new FourthStepFragment(); 506 Bundle arguments = new Bundle(); 507 arguments.putLong(FourthStepFragment.EXTRA_OPTION, mSelectedOption); 508 f.setArguments(arguments); 509 GuidedStepFragment.add(fm, f, R.id.lb_guidedstep_host); 510 } else if (action.getCheckSetId() == GuidedAction.DEFAULT_CHECK_SET_ID) { 511 mSelectedOption = action.getId(); 512 } 513 } 514 515 } 516 517 public static class FourthStepFragment extends GuidedStepFragment { 518 public static final String EXTRA_OPTION = "extra_option"; 519 520 public FourthStepFragment() { 521 } 522 523 public long getOption() { 524 Bundle b = getArguments(); 525 if (b == null) return 0; 526 return b.getLong(EXTRA_OPTION, 0); 527 } 528 529 @Override 530 public Guidance onCreateGuidance(Bundle savedInstanceState) { 531 String title = getString(R.string.guidedstep_fourth_title); 532 String breadcrumb = getString(R.string.guidedstep_fourth_breadcrumb); 533 String description = "You chose: " + OPTION_NAMES[(int) getOption()]; 534 Drawable icon = getActivity().getResources().getDrawable(R.drawable.ic_main_icon); 535 return new Guidance(title, description, breadcrumb, icon); 536 } 537 538 @Override 539 public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) { 540 actions.add(new GuidedAction.Builder(getActivity()) 541 .clickAction(GuidedAction.ACTION_ID_FINISH) 542 .description("All Done...") 543 .build()); 544 addAction(actions, BACK, "Start Over", "Let's try this again..."); 545 } 546 547 @Override 548 public void onGuidedActionClicked(GuidedAction action) { 549 if (action.getId() == GuidedAction.ACTION_ID_FINISH) { 550 finishGuidedStepFragments(); 551 } else if (action.getId() == BACK) { 552 // pop 4, 3, 2 553 popBackStackToGuidedStepFragment(SecondStepFragment.class, 554 FragmentManager.POP_BACK_STACK_INCLUSIVE); 555 } 556 } 557 558 } 559 560 } 561