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