GuidedStepSupportFragment.java revision a97810e4e2ec2552f8247ebdadf323dae70d9e3f
1/* This file is auto-generated from GuidedStepFragment.java. DO NOT MODIFY. */ 2 3/* 4 * Copyright (C) 2015 The Android Open Source Project 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 * in compliance with the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software distributed under the License 12 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 13 * or implied. See the License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16package android.support.v17.leanback.app; 17 18import android.animation.Animator; 19import android.animation.AnimatorSet; 20import android.support.v4.app.FragmentActivity; 21import android.support.v4.app.Fragment; 22import android.support.v4.app.FragmentManager; 23import android.support.v4.app.FragmentManager.BackStackEntry; 24import android.support.v4.app.FragmentTransaction; 25import android.content.Context; 26import android.content.res.TypedArray; 27import android.os.Build; 28import android.os.Bundle; 29import android.support.annotation.NonNull; 30import android.support.v17.leanback.transition.TransitionHelper; 31import android.support.v17.leanback.R; 32import android.support.v17.leanback.widget.GuidanceStylist; 33import android.support.v17.leanback.widget.GuidanceStylist.Guidance; 34import android.support.v17.leanback.widget.GuidedAction; 35import android.support.v17.leanback.widget.GuidedActionsStylist; 36import android.support.v17.leanback.widget.VerticalGridView; 37import android.support.v4.app.ActivityCompat; 38import android.support.v7.widget.RecyclerView; 39import android.util.AttributeSet; 40import android.util.Log; 41import android.util.TypedValue; 42import android.view.ContextThemeWrapper; 43import android.view.Gravity; 44import android.view.LayoutInflater; 45import android.view.View; 46import android.view.ViewGroup; 47import android.view.ViewTreeObserver; 48import android.widget.ImageView; 49import android.widget.RelativeLayout; 50import android.widget.LinearLayout; 51import android.widget.TextView; 52 53import java.util.ArrayList; 54import java.util.List; 55 56/** 57 * A GuidedStepSupportFragment is used to guide the user through a decision or series of decisions. 58 * It is composed of a guidance view on the left and a view on the right containing a list of 59 * possible actions. 60 * <p> 61 * <h3>Basic Usage</h3> 62 * <p> 63 * Clients of GuidedStepSupportFragment must create a custom subclass to attach to their Activities. 64 * This custom subclass provides the information necessary to construct the user interface and 65 * respond to user actions. At a minimum, subclasses should override: 66 * <ul> 67 * <li>{@link #onCreateGuidance}, to provide instructions to the user</li> 68 * <li>{@link #onCreateActions}, to provide a set of {@link GuidedAction}s the user can take</li> 69 * <li>{@link #onGuidedActionClicked}, to respond to those actions</li> 70 * </ul> 71 * <p> 72 * Clients use following helper functions to add GuidedStepSupportFragment to Activity or FragmentManager: 73 * <ul> 74 * <li>{@link #addAsRoot(FragmentActivity, GuidedStepSupportFragment, int)}, to be called during Activity onCreate, 75 * adds GuidedStepSupportFragment as the first Fragment in activity.</li> 76 * <li>{@link #add(FragmentManager, GuidedStepSupportFragment)} or {@link #add(FragmentManager, 77 * GuidedStepSupportFragment, int)}, to add GuidedStepSupportFragment on top of existing Fragments or 78 * replacing existing GuidedStepSupportFragment when moving forward to next step.</li> 79 * </ul> 80 * <h3>Theming and Stylists</h3> 81 * <p> 82 * GuidedStepSupportFragment delegates its visual styling to classes called stylists. The {@link 83 * GuidanceStylist} is responsible for the left guidance view, while the {@link 84 * GuidedActionsStylist} is responsible for the right actions view. The stylists use theme 85 * attributes to derive values associated with the presentation, such as colors, animations, etc. 86 * Most simple visual aspects of GuidanceStylist and GuidedActionsStylist can be customized 87 * via theming; see their documentation for more information. 88 * <p> 89 * GuidedStepSupportFragments must have access to an appropriate theme in order for the stylists to 90 * function properly. Specifically, the fragment must receive {@link 91 * android.support.v17.leanback.R.style#Theme_Leanback_GuidedStep}, or a theme whose parent is 92 * is set to that theme. Themes can be provided in one of three ways: 93 * <ul> 94 * <li>The simplest way is to set the theme for the host Activity to the GuidedStep theme or a 95 * theme that derives from it.</li> 96 * <li>If the Activity already has a theme and setting its parent theme is inconvenient, the 97 * existing Activity theme can have an entry added for the attribute {@link 98 * android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepTheme}. If present, 99 * this theme will be used by GuidedStepSupportFragment as an overlay to the Activity's theme.</li> 100 * <li>Finally, custom subclasses of GuidedStepSupportFragment may provide a theme through the {@link 101 * #onProvideTheme} method. This can be useful if a subclass is used across multiple 102 * Activities.</li> 103 * </ul> 104 * <p> 105 * If the theme is provided in multiple ways, the onProvideTheme override has priority, followed by 106 * the Activty's theme. (Themes whose parent theme is already set to the guided step theme do not 107 * need to set the guidedStepTheme attribute; if set, it will be ignored.) 108 * <p> 109 * If themes do not provide enough customizability, the stylists themselves may be subclassed and 110 * provided to the GuidedStepSupportFragment through the {@link #onCreateGuidanceStylist} and {@link 111 * #onCreateActionsStylist} methods. The stylists have simple hooks so that subclasses 112 * may override layout files; subclasses may also have more complex logic to determine styling. 113 * <p> 114 * <h3>Guided sequences</h3> 115 * <p> 116 * GuidedStepSupportFragments can be grouped together to provide a guided sequence. GuidedStepSupportFragments 117 * grouped as a sequence use custom animations provided by {@link GuidanceStylist} and 118 * {@link GuidedActionsStylist} (or subclasses) during transitions between steps. Clients 119 * should use {@link #add} to place subsequent GuidedFragments onto the fragment stack so that 120 * custom animations are properly configured. (Custom animations are triggered automatically when 121 * the fragment stack is subsequently popped by any normal mechanism.) 122 * <p> 123 * <i>Note: Currently GuidedStepSupportFragments grouped in this way must all be defined programmatically, 124 * rather than in XML. This restriction may be removed in the future.</i> 125 * 126 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepTheme 127 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepBackground 128 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidthWeight 129 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidthWeightTwoPanels 130 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsBackground 131 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedButtonActionsBackground 132 * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsElevation 133 * @see GuidanceStylist 134 * @see GuidanceStylist.Guidance 135 * @see GuidedAction 136 * @see GuidedActionsStylist 137 */ 138public class GuidedStepSupportFragment extends Fragment implements GuidedActionAdapter.ClickListener, 139 GuidedActionAdapter.FocusListener { 140 141 private static final String TAG_LEAN_BACK_ACTIONS_FRAGMENT = "leanBackGuidedStepSupportFragment"; 142 private static final String EXTRA_ACTION_SELECTED_INDEX = "selectedIndex"; 143 144 private static final String ENTRY_NAME_DEFAULT = "GuidedStepDefault"; 145 146 private static final String ENTRY_NAME_ENTRANCE = "GuidedStepEntrance"; 147 148 private static final boolean IS_FRAMEWORK_FRAGMENT = false; 149 150 /** 151 * Fragment argument name for UI style. The argument value is persisted in fragment state. 152 * The value is initially {@link #UI_STYLE_DEFAULT} and might be changed in one of the three 153 * helper functions: 154 * <ul> 155 * <li>{@link #addAsRoot(FragmentActivity, GuidedStepSupportFragment, int)}</li> 156 * <li>{@link #add(FragmentManager, GuidedStepSupportFragment)} or {@link #add(FragmentManager, 157 * GuidedStepSupportFragment, int)}</li> 158 * </ul> 159 * <p> 160 * Argument value can be either: 161 * <ul> 162 * <li>{@link #UI_STYLE_DEFAULT}</li> 163 * <li>{@link #UI_STYLE_ENTRANCE}</li> 164 * <li>{@link #UI_STYLE_ACTIVITY_ROOT}</li> 165 * </ul> 166 */ 167 public static final String EXTRA_UI_STYLE = "uiStyle"; 168 169 /** 170 * Default value for argument {@link #EXTRA_UI_STYLE}. The default value is assigned 171 * in GuidedStepSupportFragment constructor. This is the case that we use GuidedStepSupportFragment to 172 * replace another existing GuidedStepSupportFragment when moving forward to next step. Default 173 * behavior of this style is: 174 * <ul> 175 * <li> Enter transition slides in from END(right), exit transition slide out to START(left). 176 * </li> 177 * <li> No background, see {@link #onProvideBackgroundSupportFragment()}.</li> 178 * </ul> 179 */ 180 public static final int UI_STYLE_DEFAULT = 0; 181 182 /** 183 * One possible value of argument {@link #EXTRA_UI_STYLE}. This is the case that we show 184 * GuidedStepSupportFragment on top of other content. The default behavior of this style: 185 * <ul> 186 * <li>Enter transition slides in from two sides, exit transition is inherited from 187 * {@link #UI_STYLE_DEFAULT}. Note: Changing exit transition by UI style is not working because 188 * fragment transition asks for exit transition before UI style is restored in Fragment 189 * .onCreate().</li> 190 * <li> {@link #onProvideBackgroundSupportFragment()} will create {@link GuidedStepBackgroundSupportFragment} 191 * to covering underneath content. The activity must provide a container to host background 192 * fragment and override {@link #getContainerIdForBackground()}</li> 193 * </ul> 194 */ 195 public static final int UI_STYLE_ENTRANCE = 1; 196 197 /** 198 * One possible value of argument {@link #EXTRA_UI_STYLE}. This is the case that we show first 199 * GuidedStepSupportFragment in a separate activity. The default behavior of this style: 200 * <ul> 201 * <li> Enter transition is assigned null (will rely on activity transition), exit transition is 202 * same as {@link #UI_STYLE_DEFAULT}. Note: Changing exit transition by UI style is not working 203 * because fragment transition asks for exit transition before UI style is restored in 204 * Fragment.onCreate().</li> 205 * <li> No background, see {@link #onProvideBackgroundSupportFragment()}. 206 * </ul> 207 */ 208 public static final int UI_STYLE_ACTIVITY_ROOT = 2; 209 210 private static final String TAG = "GuidedStepSupportFragment"; 211 private static final boolean DEBUG = false; 212 213 private int mTheme; 214 private ContextThemeWrapper mThemeWrapper; 215 private GuidanceStylist mGuidanceStylist; 216 private GuidedActionsStylist mActionsStylist; 217 private GuidedActionsStylist mButtonActionsStylist; 218 private GuidedActionAdapter mAdapter; 219 private GuidedActionAdapter mButtonAdapter; 220 private List<GuidedAction> mActions = new ArrayList<GuidedAction>(); 221 private List<GuidedAction> mButtonActions = new ArrayList<GuidedAction>(); 222 private int mSelectedIndex = -1; 223 private int mButtonSelectedIndex = -1; 224 225 public GuidedStepSupportFragment() { 226 // We need to supply the theme before any potential call to onInflate in order 227 // for the defaulting to work properly. 228 mTheme = onProvideTheme(); 229 mGuidanceStylist = onCreateGuidanceStylist(); 230 mActionsStylist = onCreateActionsStylist(); 231 mButtonActionsStylist = onCreateButtonActionsStylist(); 232 onProvideFragmentTransitions(); 233 } 234 235 /** 236 * Creates the presenter used to style the guidance panel. The default implementation returns 237 * a basic GuidanceStylist. 238 * @return The GuidanceStylist used in this fragment. 239 */ 240 public GuidanceStylist onCreateGuidanceStylist() { 241 return new GuidanceStylist(); 242 } 243 244 /** 245 * Creates the presenter used to style the guided actions panel. The default implementation 246 * returns a basic GuidedActionsStylist. 247 * @return The GuidedActionsStylist used in this fragment. 248 */ 249 public GuidedActionsStylist onCreateActionsStylist() { 250 return new GuidedActionsStylist(); 251 } 252 253 /** 254 * Creates the presenter used to style a sided actions panel for button only. 255 * The default implementation returns a basic GuidedActionsStylist. 256 * @return The GuidedActionsStylist used in this fragment. 257 */ 258 public GuidedActionsStylist onCreateButtonActionsStylist() { 259 return new GuidedActionsStylist(); 260 } 261 262 /** 263 * Returns the theme used for styling the fragment. The default returns -1, indicating that the 264 * host Activity's theme should be used. 265 * @return The theme resource ID of the theme to use in this fragment, or -1 to use the 266 * host Activity's theme. 267 */ 268 public int onProvideTheme() { 269 return -1; 270 } 271 272 /** 273 * Returns the information required to provide guidance to the user. This hook is called during 274 * {@link #onCreateView}. May be overridden to return a custom subclass of {@link 275 * GuidanceStylist.Guidance} for use in a subclass of {@link GuidanceStylist}. The default 276 * returns a Guidance object with empty fields; subclasses should override. 277 * @param savedInstanceState The saved instance state from onCreateView. 278 * @return The Guidance object representing the information used to guide the user. 279 */ 280 public @NonNull Guidance onCreateGuidance(Bundle savedInstanceState) { 281 return new Guidance("", "", "", null); 282 } 283 284 /** 285 * Fills out the set of actions available to the user. This hook is called during {@link 286 * #onCreate}. The default leaves the list of actions empty; subclasses should override. 287 * @param actions A non-null, empty list ready to be populated. 288 * @param savedInstanceState The saved instance state from onCreate. 289 */ 290 public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) { 291 } 292 293 /** 294 * Fills out the set of actions shown at right available to the user. This hook is called during 295 * {@link #onCreate}. The default leaves the list of actions empty; subclasses may override. 296 * @param actions A non-null, empty list ready to be populated. 297 * @param savedInstanceState The saved instance state from onCreate. 298 */ 299 public void onCreateButtonActions(@NonNull List<GuidedAction> actions, 300 Bundle savedInstanceState) { 301 } 302 303 /** 304 * Callback invoked when an action is taken by the user. Subclasses should override in 305 * order to act on the user's decisions. 306 * @param action The chosen action. 307 */ 308 @Override 309 public void onGuidedActionClicked(GuidedAction action) { 310 } 311 312 /** 313 * Callback invoked when an action is focused (made to be the current selection) by the user. 314 */ 315 @Override 316 public void onGuidedActionFocused(GuidedAction action) { 317 } 318 319 /** 320 * Callback invoked when an action's title or description has been edited. 321 * Override {@link #onGuidedActionEditedAndProceed(GuidedAction)} instead of app wants to 322 * control the next action to focus on. 323 */ 324 public void onGuidedActionEdited(GuidedAction action) { 325 } 326 327 /** 328 * Callback invoked when an action's title or description has been edited. Default 329 * implementation calls {@link #onGuidedActionEdited(GuidedAction)} and returns 330 * {@link GuidedAction#ACTION_ID_NEXT}. 331 * 332 * @param action The action that has been edited. 333 * @return ID of the action will be focused or {@link GuidedAction#ACTION_ID_NEXT}, 334 * {@link GuidedAction#ACTION_ID_CURRENT}. 335 */ 336 public long onGuidedActionEditedAndProceed(GuidedAction action) { 337 onGuidedActionEdited(action); 338 return GuidedAction.ACTION_ID_NEXT; 339 } 340 341 /** 342 * Adds the specified GuidedStepSupportFragment to the fragment stack, replacing any existing 343 * GuidedStepSupportFragments in the stack, and configuring the fragment-to-fragment custom 344 * transitions. A backstack entry is added, so the fragment will be dismissed when BACK key 345 * is pressed. 346 * <li>If current fragment on stack is GuidedStepSupportFragment: assign {@link #UI_STYLE_DEFAULT} 347 * <li>If current fragment on stack is not GuidedStepSupportFragment: assign {@link #UI_STYLE_ENTRANCE} 348 * <p> 349 * Note: currently fragments added using this method must be created programmatically rather 350 * than via XML. 351 * @param fragmentManager The FragmentManager to be used in the transaction. 352 * @param fragment The GuidedStepSupportFragment to be inserted into the fragment stack. 353 * @return The ID returned by the call FragmentTransaction.replace. 354 */ 355 public static int add(FragmentManager fragmentManager, GuidedStepSupportFragment fragment) { 356 return add(fragmentManager, fragment, android.R.id.content); 357 } 358 359 /** 360 * Adds the specified GuidedStepSupportFragment to the fragment stack, replacing any existing 361 * GuidedStepSupportFragments in the stack, and configuring the fragment-to-fragment custom 362 * transitions. A backstack entry is added, so the fragment will be dismissed when BACK key 363 * is pressed. 364 * <li>If current fragment on stack is GuidedStepSupportFragment: assign {@link #UI_STYLE_DEFAULT} 365 * <li>If current fragment on stack is not GuidedStepSupportFragment: assign {@link #UI_STYLE_ENTRANCE} 366 * <p> 367 * Note: currently fragments added using this method must be created programmatically rather 368 * than via XML. 369 * @param fragmentManager The FragmentManager to be used in the transaction. 370 * @param fragment The GuidedStepSupportFragment to be inserted into the fragment stack. 371 * @param id The id of container to add GuidedStepSupportFragment, can be android.R.id.content. 372 * @return The ID returned by the call FragmentTransaction.replace. 373 */ 374 public static int add(FragmentManager fragmentManager, GuidedStepSupportFragment fragment, int id) { 375 boolean inGuidedStep = getCurrentGuidedStepSupportFragment(fragmentManager) != null; 376 if (IS_FRAMEWORK_FRAGMENT && Build.VERSION.SDK_INT >= 21 && Build.VERSION.SDK_INT < 23 377 && !inGuidedStep && fragment.getContainerIdForBackground() != View.NO_ID) { 378 // workaround b/22631964 for framework fragment 379 fragmentManager.beginTransaction() 380 .replace(id, new DummyFragment(), TAG_LEAN_BACK_ACTIONS_FRAGMENT) 381 .replace(fragment.getContainerIdForBackground(), new DummyFragment()) 382 .commit(); 383 } 384 FragmentTransaction ft = fragmentManager.beginTransaction(); 385 386 fragment.setUiStyle(inGuidedStep ? UI_STYLE_DEFAULT : UI_STYLE_ENTRANCE); 387 ft.addToBackStack(fragment.generateStackEntryName()); 388 initialBackground(fragment, id, ft); 389 return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit(); 390 } 391 392 /** 393 * Returns BackStackEntry name for the GuidedStepSupportFragment or empty String if no entry is 394 * associated. Note {@link #UI_STYLE_ACTIVITY_ROOT} will return empty String. The method 395 * returns undefined value if the fragment is not in FragmentManager. 396 * @return BackStackEntry name for the GuidedStepSupportFragment or empty String if no entry is 397 * associated. 398 */ 399 public String generateStackEntryName() { 400 return generateStackEntryName(getUiStyle(), getClass()); 401 } 402 403 /** 404 * Generates BackStackEntry name for GuidedStepSupportFragment class or empty String if no entry is 405 * associated. Note {@link #UI_STYLE_ACTIVITY_ROOT} is not allowed and returns empty String. 406 * @param uiStyle {@link #UI_STYLE_DEFAULT} or {@link #UI_STYLE_ENTRANCE} 407 * @return BackStackEntry name for the GuidedStepSupportFragment or empty String if no entry is 408 * associated. 409 */ 410 public static String generateStackEntryName(int uiStyle, Class guidedStepFragmentClass) { 411 if (!GuidedStepSupportFragment.class.isAssignableFrom(guidedStepFragmentClass)) { 412 return ""; 413 } 414 switch (uiStyle) { 415 case UI_STYLE_DEFAULT: 416 return ENTRY_NAME_DEFAULT + guidedStepFragmentClass.getName(); 417 case UI_STYLE_ENTRANCE: 418 return ENTRY_NAME_ENTRANCE + guidedStepFragmentClass.getName(); 419 case UI_STYLE_ACTIVITY_ROOT: 420 default: 421 return ""; 422 } 423 } 424 425 /** 426 * Returns true if the backstack represents GuidedStepSupportFragment with {@link #UI_STYLE_ENTRANCE}; 427 * false otherwise. 428 * @param backStackEntryName Name of BackStackEntry. 429 * @return True if the backstack represents GuidedStepSupportFragment with {@link #UI_STYLE_ENTRANCE}; 430 * false otherwise. 431 */ 432 public static boolean isUiStyleEntrance(String backStackEntryName) { 433 return backStackEntryName != null && backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE); 434 } 435 436 /** 437 * Returns true if the backstack represents GuidedStepSupportFragment with {@link #UI_STYLE_DEFAULT}; 438 * false otherwise. 439 * @param backStackEntryName Name of BackStackEntry. 440 * @return True if the backstack represents GuidedStepSupportFragment with {@link #UI_STYLE_DEFAULT}; 441 * false otherwise. 442 */ 443 public static boolean isUiStyleDefault(String backStackEntryName) { 444 return backStackEntryName != null && backStackEntryName.startsWith(ENTRY_NAME_DEFAULT); 445 } 446 447 /** 448 * Extract Class name from BackStackEntry name. 449 * @param backStackEntryName Name of BackStackEntry. 450 * @return Class name of GuidedStepSupportFragment. 451 */ 452 public static String getGuidedStepSupportFragmentClassName(String backStackEntryName) { 453 if (backStackEntryName.startsWith(ENTRY_NAME_DEFAULT)) { 454 return backStackEntryName.substring(ENTRY_NAME_DEFAULT.length()); 455 } else if (backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE)) { 456 return backStackEntryName.substring(ENTRY_NAME_ENTRANCE.length()); 457 } else { 458 return ""; 459 } 460 } 461 462 /** 463 * Adds the specified GuidedStepSupportFragment as content of Activity; no backstack entry is added so 464 * the activity will be dismissed when BACK key is pressed. 465 * {@link #UI_STYLE_ACTIVITY_ROOT} is assigned. 466 * 467 * Note: currently fragments added using this method must be created programmatically rather 468 * than via XML. 469 * @param activity The Activity to be used to insert GuidedstepFragment. 470 * @param fragment The GuidedStepSupportFragment to be inserted into the fragment stack. 471 * @param id The id of container to add GuidedStepSupportFragment, can be android.R.id.content. 472 * @return The ID returned by the call FragmentTransaction.replace. 473 */ 474 public static int addAsRoot(FragmentActivity activity, GuidedStepSupportFragment fragment, int id) { 475 // Workaround b/23764120: call getDecorView() to force requestFeature of ActivityTransition. 476 activity.getWindow().getDecorView(); 477 478 FragmentManager fragmentManager = activity.getSupportFragmentManager(); 479 FragmentTransaction ft = fragmentManager.beginTransaction(); 480 fragment.setUiStyle(UI_STYLE_ACTIVITY_ROOT); 481 initialBackground(fragment, id, ft); 482 return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit(); 483 } 484 485 static void initialBackground(GuidedStepSupportFragment fragment, int id, FragmentTransaction ft) { 486 if (fragment.getContainerIdForBackground() != View.NO_ID) { 487 Fragment backgroundFragment = fragment.onProvideBackgroundSupportFragment(); 488 if (backgroundFragment != null) { 489 ft.replace(fragment.getContainerIdForBackground(), backgroundFragment); 490 } 491 } 492 } 493 494 /** 495 * Returns the current GuidedStepSupportFragment on the fragment transaction stack. 496 * @return The current GuidedStepSupportFragment, if any, on the fragment transaction stack. 497 */ 498 public static GuidedStepSupportFragment getCurrentGuidedStepSupportFragment(FragmentManager fm) { 499 Fragment f = fm.findFragmentByTag(TAG_LEAN_BACK_ACTIONS_FRAGMENT); 500 if (f instanceof GuidedStepSupportFragment) { 501 return (GuidedStepSupportFragment) f; 502 } 503 return null; 504 } 505 506 /** 507 * @hide 508 */ 509 public static class DummyFragment extends Fragment { 510 511 @Override 512 public View onCreateView(LayoutInflater inflater, ViewGroup container, 513 Bundle savedInstanceState) { 514 final View v = new View(inflater.getContext()); 515 v.setVisibility(View.GONE); 516 return v; 517 } 518 } 519 520 /** 521 * Returns the GuidanceStylist that displays guidance information for the user. 522 * @return The GuidanceStylist for this fragment. 523 */ 524 public GuidanceStylist getGuidanceStylist() { 525 return mGuidanceStylist; 526 } 527 528 /** 529 * Returns the GuidedActionsStylist that displays the actions the user may take. 530 * @return The GuidedActionsStylist for this fragment. 531 */ 532 public GuidedActionsStylist getGuidedActionsStylist() { 533 return mActionsStylist; 534 } 535 536 /** 537 * Returns the list of button GuidedActions that the user may take in this fragment. 538 * @return The list of button GuidedActions for this fragment. 539 */ 540 public List<GuidedAction> getButtonActions() { 541 return mButtonActions; 542 } 543 544 /** 545 * Find button GuidedAction by Id. 546 * @param id Id of the button action to search. 547 * @return GuidedAction object or null if not found. 548 */ 549 public GuidedAction findButtonActionById(long id) { 550 int index = findButtonActionPositionById(id); 551 return index >= 0 ? mButtonActions.get(index) : null; 552 } 553 554 /** 555 * Find button GuidedAction position in array by Id. 556 * @param id Id of the button action to search. 557 * @return position of GuidedAction object in array or -1 if not found. 558 */ 559 public int findButtonActionPositionById(long id) { 560 if (mButtonActions != null) { 561 for (int i = 0; i < mButtonActions.size(); i++) { 562 GuidedAction action = mButtonActions.get(i); 563 if (mButtonActions.get(i).getId() == id) { 564 return i; 565 } 566 } 567 } 568 return -1; 569 } 570 571 /** 572 * Returns the GuidedActionsStylist that displays the button actions the user may take. 573 * @return The GuidedActionsStylist for this fragment. 574 */ 575 public GuidedActionsStylist getGuidedButtonActionsStylist() { 576 return mButtonActionsStylist; 577 } 578 579 /** 580 * Sets the list of button GuidedActions that the user may take in this fragment. 581 * @param actions The list of button GuidedActions for this fragment. 582 */ 583 public void setButtonActions(List<GuidedAction> actions) { 584 mButtonActions = actions; 585 if (mButtonAdapter != null) { 586 mButtonAdapter.setActions(mButtonActions); 587 } 588 } 589 590 /** 591 * Notify an button action has changed and update its UI. 592 * @param position Position of the button GuidedAction in array. 593 */ 594 public void notifyButtonActionChanged(int position) { 595 if (mButtonAdapter != null) { 596 mButtonAdapter.notifyItemChanged(position); 597 } 598 } 599 600 /** 601 * Returns the view corresponding to the button action at the indicated position in the list of 602 * actions for this fragment. 603 * @param position The integer position of the button action of interest. 604 * @return The View corresponding to the button action at the indicated position, or null if 605 * that action is not currently onscreen. 606 */ 607 public View getButtonActionItemView(int position) { 608 final RecyclerView.ViewHolder holder = mButtonActionsStylist.getActionsGridView() 609 .findViewHolderForPosition(position); 610 return holder == null ? null : holder.itemView; 611 } 612 613 /** 614 * Scrolls the action list to the position indicated, selecting that button action's view. 615 * @param position The integer position of the button action of interest. 616 */ 617 public void setSelectedButtonActionPosition(int position) { 618 mButtonActionsStylist.getActionsGridView().setSelectedPosition(position); 619 } 620 621 /** 622 * Returns the position if the currently selected button GuidedAction. 623 * @return position The integer position of the currently selected button action. 624 */ 625 public int getSelectedButtonActionPosition() { 626 return mButtonActionsStylist.getActionsGridView().getSelectedPosition(); 627 } 628 629 /** 630 * Returns the list of GuidedActions that the user may take in this fragment. 631 * @return The list of GuidedActions for this fragment. 632 */ 633 public List<GuidedAction> getActions() { 634 return mActions; 635 } 636 637 /** 638 * Find GuidedAction by Id. 639 * @param id Id of the action to search. 640 * @return GuidedAction object or null if not found. 641 */ 642 public GuidedAction findActionById(long id) { 643 int index = findActionPositionById(id); 644 return index >= 0 ? mActions.get(index) : null; 645 } 646 647 /** 648 * Find GuidedAction position in array by Id. 649 * @param id Id of the action to search. 650 * @return position of GuidedAction object in array or -1 if not found. 651 */ 652 public int findActionPositionById(long id) { 653 if (mActions != null) { 654 for (int i = 0; i < mActions.size(); i++) { 655 GuidedAction action = mActions.get(i); 656 if (mActions.get(i).getId() == id) { 657 return i; 658 } 659 } 660 } 661 return -1; 662 } 663 664 /** 665 * Sets the list of GuidedActions that the user may take in this fragment. 666 * @param actions The list of GuidedActions for this fragment. 667 */ 668 public void setActions(List<GuidedAction> actions) { 669 mActions = actions; 670 if (mAdapter != null) { 671 mAdapter.setActions(mActions); 672 } 673 } 674 675 /** 676 * Notify an action has changed and update its UI. 677 * @param position Position of the GuidedAction in array. 678 */ 679 public void notifyActionChanged(int position) { 680 if (mAdapter != null) { 681 mAdapter.notifyItemChanged(position); 682 } 683 } 684 685 /** 686 * Returns the view corresponding to the action at the indicated position in the list of 687 * actions for this fragment. 688 * @param position The integer position of the action of interest. 689 * @return The View corresponding to the action at the indicated position, or null if that 690 * action is not currently onscreen. 691 */ 692 public View getActionItemView(int position) { 693 final RecyclerView.ViewHolder holder = mActionsStylist.getActionsGridView() 694 .findViewHolderForPosition(position); 695 return holder == null ? null : holder.itemView; 696 } 697 698 /** 699 * Scrolls the action list to the position indicated, selecting that action's view. 700 * @param position The integer position of the action of interest. 701 */ 702 public void setSelectedActionPosition(int position) { 703 mActionsStylist.getActionsGridView().setSelectedPosition(position); 704 } 705 706 /** 707 * Returns the position if the currently selected GuidedAction. 708 * @return position The integer position of the currently selected action. 709 */ 710 public int getSelectedActionPosition() { 711 return mActionsStylist.getActionsGridView().getSelectedPosition(); 712 } 713 714 /** 715 * Called by Constructor to provide fragment transitions. Default implementation creates 716 * a short slide and fade transition in code for {@link #UI_STYLE_DEFAULT} for both enter and 717 * exit transition. When using style {@link #UI_STYLE_ENTRANCE}, enter transition is set 718 * to slide from both sides. When using style {@link #UI_STYLE_ACTIVITY_ROOT}, enter 719 * transition is set to null and you should rely on activity transition. 720 * <p> 721 * Subclass may override and set its own fragment transition. Note that because Context is not 722 * available when onProvideFragmentTransitions() is called, subclass will need use a cached 723 * static application context to load transition from xml. Because the fragment view is 724 * removed during fragment transition, in general app cannot use two Visibility transition 725 * together. Workaround is to create your own Visibility transition that controls multiple 726 * animators (e.g. slide and fade animation in one Transition class). 727 */ 728 protected void onProvideFragmentTransitions() { 729 if (Build.VERSION.SDK_INT >= 21) { 730 if (getUiStyle() == UI_STYLE_DEFAULT) { 731 Object enterTransition = TransitionHelper.createFadeAndShortSlide(Gravity.END); 732 TransitionHelper.exclude(enterTransition, R.id.action_fragment_background, true); 733 TransitionHelper.exclude(enterTransition, R.id.guided_button_actions_background, 734 true); 735 TransitionHelper.exclude(enterTransition, R.id.guidedactions_selector, true); 736 TransitionHelper.setEnterTransition(this, enterTransition); 737 Object exitTransition = TransitionHelper.createFadeAndShortSlide(Gravity.START); 738 TransitionHelper.exclude(exitTransition, R.id.action_fragment_background, true); 739 TransitionHelper.exclude(exitTransition, R.id.guided_button_actions_background, 740 true); 741 TransitionHelper.exclude(exitTransition, R.id.guidedactions_selector, true); 742 TransitionHelper.setExitTransition(this, exitTransition); 743 } else if (getUiStyle() == UI_STYLE_ENTRANCE) { 744 Object enterTransition = TransitionHelper.createFadeAndShortSlide(Gravity.END | 745 Gravity.START); 746 TransitionHelper.include(enterTransition, R.id.content_fragment); 747 TransitionHelper.include(enterTransition, R.id.action_fragment_background); 748 TransitionHelper.include(enterTransition, R.id.guided_button_actions_background); 749 TransitionHelper.include(enterTransition, R.id.guidedactions_selector); 750 TransitionHelper.include(enterTransition, R.id.guidedactions_list); 751 TransitionHelper.setEnterTransition(this, enterTransition); 752 // exit transition is unchanged, same as UI_STYLE_DEFAULT 753 } else if (getUiStyle() == UI_STYLE_ACTIVITY_ROOT) { 754 // for Activity root, we dont need enter transition, use activity transition 755 TransitionHelper.setEnterTransition(this, null); 756 // exit transition is unchanged, same as UI_STYLE_DEFAULT 757 } 758 } 759 } 760 761 /** 762 * Default implementation of background for covering content below GuidedStepSupportFragment. 763 * It uses current theme attribute guidedStepBackground which by default is read from 764 * android:windowBackground. 765 */ 766 public static class GuidedStepBackgroundSupportFragment extends Fragment { 767 public GuidedStepBackgroundSupportFragment() { 768 onProvideFragmentTransitions(); 769 } 770 771 /** 772 * Sets fragment transitions for GuidedStepBackgroundSupportFragment. Can be overridden. 773 */ 774 protected void onProvideFragmentTransitions() { 775 if (Build.VERSION.SDK_INT >= 21) { 776 Object enterTransition = TransitionHelper.createFadeTransition( 777 TransitionHelper.FADE_IN|TransitionHelper.FADE_OUT); 778 TransitionHelper.setEnterTransition(this, enterTransition); 779 } 780 } 781 782 @Override 783 public View onCreateView(LayoutInflater inflater, ViewGroup container, 784 Bundle savedInstanceState) { 785 FragmentActivity activity = getActivity(); 786 Context themedContext = null; 787 if (!isGuidedStepTheme(activity)) { 788 // Look up the guidedStepTheme in the activity's currently specified theme. If it 789 // exists, replace the theme with its value. 790 int resId = R.attr.guidedStepTheme; 791 TypedValue typedValue = new TypedValue(); 792 boolean found = activity.getTheme().resolveAttribute(resId, typedValue, true); 793 if (DEBUG) Log.v(TAG, "Found guided step theme reference? " + found); 794 if (found) { 795 ContextThemeWrapper themeWrapper = 796 new ContextThemeWrapper(activity, typedValue.resourceId); 797 if (isGuidedStepTheme(themeWrapper)) { 798 themedContext = themeWrapper; 799 } 800 } 801 if (!found) { 802 Log.e(TAG, "GuidedStepSupportFragment does not have an appropriate theme set."); 803 } 804 } 805 806 if (themedContext != null) { 807 inflater = inflater.cloneInContext(themedContext); 808 } 809 810 return inflater.inflate(R.layout.lb_guidedstep_background, container, false); 811 } 812 } 813 814 /** 815 * Creates a background fragment for {@link #UI_STYLE_ENTRANCE}, returns null for other cases. 816 * Subclass may override the default behavior, e.g. provide different backgrounds 817 * for {@link #UI_STYLE_DEFAULT}. Background fragment will be inserted in {@link 818 * #getContainerIdForBackground()}. 819 * 820 * @return fragment that will be inserted below GuidedStepSupportFragment. 821 */ 822 protected Fragment onProvideBackgroundSupportFragment() { 823 if (getUiStyle() == UI_STYLE_ENTRANCE) { 824 return new GuidedStepBackgroundSupportFragment(); 825 } 826 return null; 827 } 828 829 /** 830 * Returns container id for inserting {@link #onProvideBackgroundSupportFragment()}. The id should be 831 * different than container id for inserting GuidedStepSupportFragment. 832 * Default value is {@link View#NO_ID}. Subclass must override to host background fragment. 833 * @return container id for inserting {@link #onProvideBackgroundSupportFragment()} 834 */ 835 protected int getContainerIdForBackground() { 836 return View.NO_ID; 837 } 838 839 840 /** 841 * Set UI style to fragment arguments, UI style cannot be changed after initialization. 842 * @param style {@link #UI_STYLE_ACTIVITY_ROOT} {@link #UI_STYLE_DEFAULT} or 843 * {@link #UI_STYLE_ENTRANCE}. 844 */ 845 public void setUiStyle(int style) { 846 int oldStyle = getUiStyle(); 847 Bundle arguments = getArguments(); 848 boolean isNew = false; 849 if (arguments == null) { 850 arguments = new Bundle(); 851 isNew = true; 852 } 853 arguments.putInt(EXTRA_UI_STYLE, style); 854 // call setArgument() will validate if the fragment is already added. 855 if (isNew) { 856 setArguments(arguments); 857 } 858 if (style != oldStyle) { 859 onProvideFragmentTransitions(); 860 } 861 } 862 863 /** 864 * Read UI style from fragment arguments. 865 * 866 * @return {@link #UI_STYLE_ACTIVITY_ROOT} {@link #UI_STYLE_DEFAULT} or 867 * {@link #UI_STYLE_ENTRANCE}. 868 */ 869 public int getUiStyle() { 870 Bundle b = getArguments(); 871 if (b == null) return UI_STYLE_DEFAULT; 872 return b.getInt(EXTRA_UI_STYLE, UI_STYLE_DEFAULT); 873 } 874 875 /** 876 * {@inheritDoc} 877 */ 878 @Override 879 public void onCreate(Bundle savedInstanceState) { 880 super.onCreate(savedInstanceState); 881 if (DEBUG) Log.v(TAG, "onCreate"); 882 // Set correct transition from saved arguments. 883 onProvideFragmentTransitions(); 884 Bundle state = (savedInstanceState != null) ? savedInstanceState : getArguments(); 885 if (state != null) { 886 if (mSelectedIndex == -1) { 887 mSelectedIndex = state.getInt(EXTRA_ACTION_SELECTED_INDEX, -1); 888 } 889 } 890 ArrayList<GuidedAction> actions = new ArrayList<GuidedAction>(); 891 onCreateActions(actions, savedInstanceState); 892 setActions(actions); 893 ArrayList<GuidedAction> buttonActions = new ArrayList<GuidedAction>(); 894 onCreateButtonActions(buttonActions, savedInstanceState); 895 setButtonActions(buttonActions); 896 } 897 898 /** 899 * {@inheritDoc} 900 */ 901 @Override 902 public void onDestroyView() { 903 mGuidanceStylist.onDestroyView(); 904 mActionsStylist.onDestroyView(); 905 mButtonActionsStylist.onDestroyView(); 906 super.onDestroyView(); 907 } 908 909 /** 910 * {@inheritDoc} 911 */ 912 @Override 913 public View onCreateView(LayoutInflater inflater, ViewGroup container, 914 Bundle savedInstanceState) { 915 if (DEBUG) Log.v(TAG, "onCreateView"); 916 917 resolveTheme(); 918 inflater = getThemeInflater(inflater); 919 920 View v = inflater.inflate(R.layout.lb_guidedstep_fragment, container, false); 921 ViewGroup guidanceContainer = (ViewGroup) v.findViewById(R.id.content_fragment); 922 ViewGroup actionContainer = (ViewGroup) v.findViewById(R.id.action_fragment); 923 924 Guidance guidance = onCreateGuidance(savedInstanceState); 925 View guidanceView = mGuidanceStylist.onCreateView(inflater, guidanceContainer, guidance); 926 guidanceContainer.addView(guidanceView); 927 928 View actionsView = mActionsStylist.onCreateView(inflater, actionContainer); 929 actionContainer.addView(actionsView); 930 931 View buttonActionsView = mButtonActionsStylist.onCreateView(inflater, actionContainer); 932 actionContainer.addView(buttonActionsView); 933 View bg = buttonActionsView.findViewById(R.id.guided_button_actions_background); 934 if (bg != null) { 935 bg.setVisibility(View.VISIBLE); 936 } 937 938 GuidedActionAdapter.EditListener editListener = new GuidedActionAdapter.EditListener() { 939 940 @Override 941 public void onImeOpen() { 942 runImeAnimations(true); 943 } 944 945 @Override 946 public void onImeClose() { 947 runImeAnimations(false); 948 } 949 950 @Override 951 public long onGuidedActionEdited(GuidedAction action) { 952 return GuidedStepSupportFragment.this.onGuidedActionEditedAndProceed(action); 953 } 954 }; 955 956 mAdapter = new GuidedActionAdapter(mActions, this, this, editListener, 957 mActionsStylist); 958 mButtonAdapter = new GuidedActionAdapter(mButtonActions, this, this, editListener, 959 mButtonActionsStylist); 960 961 mActionsStylist.getActionsGridView().setAdapter(mAdapter); 962 mButtonActionsStylist.getActionsGridView().setAdapter(mButtonAdapter); 963 if (mButtonActions.size() == 0) { 964 buttonActionsView.setVisibility(View.GONE); 965 } else { 966 Context ctx = mThemeWrapper != null ? mThemeWrapper : getActivity(); 967 TypedValue typedValue = new TypedValue(); 968 if (ctx.getTheme().resolveAttribute(R.attr.guidedActionContentWidthWeightTwoPanels, 969 typedValue, true)) { 970 View actionsRoot = v.findViewById(R.id.action_fragment_root); 971 float weight = typedValue.getFloat(); 972 LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) actionsRoot 973 .getLayoutParams(); 974 lp.weight = weight; 975 actionsRoot.setLayoutParams(lp); 976 } 977 } 978 979 int pos = (mSelectedIndex >= 0 && mSelectedIndex < mActions.size()) ? 980 mSelectedIndex : getFirstCheckedAction(); 981 setSelectedActionPosition(pos); 982 983 setSelectedButtonActionPosition(0); 984 985 return v; 986 } 987 988 @Override 989 public void onResume() { 990 super.onResume(); 991 mActionsStylist.getActionsGridView().requestFocus(); 992 } 993 994 /** 995 * {@inheritDoc} 996 */ 997 @Override 998 public void onSaveInstanceState(Bundle outState) { 999 super.onSaveInstanceState(outState); 1000 outState.putInt(EXTRA_ACTION_SELECTED_INDEX, 1001 (mActionsStylist.getActionsGridView() != null) ? 1002 getSelectedActionPosition() : mSelectedIndex); 1003 } 1004 1005 private static boolean isGuidedStepTheme(Context context) { 1006 int resId = R.attr.guidedStepThemeFlag; 1007 TypedValue typedValue = new TypedValue(); 1008 boolean found = context.getTheme().resolveAttribute(resId, typedValue, true); 1009 if (DEBUG) Log.v(TAG, "Found guided step theme flag? " + found); 1010 return found && typedValue.type == TypedValue.TYPE_INT_BOOLEAN && typedValue.data != 0; 1011 } 1012 1013 /** 1014 * Convenient method to close GuidedStepSupportFragments on top of other content or finish Activity if 1015 * GuidedStepSupportFragments were started in a separate activity. Pops all stack entries including 1016 * {@link #UI_STYLE_ENTRANCE}; if {@link #UI_STYLE_ENTRANCE} is not found, finish the activity. 1017 */ 1018 public void finishGuidedStepSupportFragments() { 1019 final FragmentManager fragmentManager = getFragmentManager(); 1020 final int entryCount = fragmentManager.getBackStackEntryCount(); 1021 if (entryCount > 0) { 1022 for (int i = entryCount - 1; i >= 0; i--) { 1023 BackStackEntry entry = fragmentManager.getBackStackEntryAt(i); 1024 if (isUiStyleEntrance(entry.getName())) { 1025 GuidedStepSupportFragment top = getCurrentGuidedStepSupportFragment(fragmentManager); 1026 if (top != null) { 1027 top.setUiStyle(UI_STYLE_ENTRANCE); 1028 } 1029 fragmentManager.popBackStack(entry.getId(), 1030 FragmentManager.POP_BACK_STACK_INCLUSIVE); 1031 return; 1032 } 1033 } 1034 } 1035 ActivityCompat.finishAfterTransition(getActivity()); 1036 } 1037 1038 /** 1039 * Convenient method to pop to fragment with Given class. 1040 * @param guidedStepFragmentClass Name of the Class of GuidedStepSupportFragment to pop to. 1041 * @param flags Either 0 or {@link FragmentManager#POP_BACK_STACK_INCLUSIVE}. 1042 */ 1043 public void popBackStackToGuidedStepSupportFragment(Class guidedStepFragmentClass, int flags) { 1044 if (!GuidedStepSupportFragment.class.isAssignableFrom(guidedStepFragmentClass)) { 1045 return; 1046 } 1047 final FragmentManager fragmentManager = getFragmentManager(); 1048 final int entryCount = fragmentManager.getBackStackEntryCount(); 1049 String className = guidedStepFragmentClass.getName(); 1050 if (entryCount > 0) { 1051 for (int i = entryCount - 1; i >= 0; i--) { 1052 BackStackEntry entry = fragmentManager.getBackStackEntryAt(i); 1053 String entryClassName = getGuidedStepSupportFragmentClassName(entry.getName()); 1054 if (className.equals(entryClassName)) { 1055 fragmentManager.popBackStack(entry.getId(), flags); 1056 return; 1057 } 1058 } 1059 } 1060 } 1061 1062 private void resolveTheme() { 1063 // Look up the guidedStepTheme in the currently specified theme. If it exists, 1064 // replace the theme with its value. 1065 FragmentActivity activity = getActivity(); 1066 if (mTheme == -1 && !isGuidedStepTheme(activity)) { 1067 // Look up the guidedStepTheme in the activity's currently specified theme. If it 1068 // exists, replace the theme with its value. 1069 int resId = R.attr.guidedStepTheme; 1070 TypedValue typedValue = new TypedValue(); 1071 boolean found = activity.getTheme().resolveAttribute(resId, typedValue, true); 1072 if (DEBUG) Log.v(TAG, "Found guided step theme reference? " + found); 1073 if (found) { 1074 ContextThemeWrapper themeWrapper = 1075 new ContextThemeWrapper(activity, typedValue.resourceId); 1076 if (isGuidedStepTheme(themeWrapper)) { 1077 mTheme = typedValue.resourceId; 1078 mThemeWrapper = themeWrapper; 1079 } else { 1080 found = false; 1081 mThemeWrapper = null; 1082 } 1083 } 1084 if (!found) { 1085 Log.e(TAG, "GuidedStepSupportFragment does not have an appropriate theme set."); 1086 } 1087 } else if (mTheme != -1) { 1088 mThemeWrapper = new ContextThemeWrapper(activity, mTheme); 1089 } 1090 } 1091 1092 private LayoutInflater getThemeInflater(LayoutInflater inflater) { 1093 if (mTheme == -1) { 1094 return inflater; 1095 } else { 1096 return inflater.cloneInContext(mThemeWrapper); 1097 } 1098 } 1099 1100 private int getFirstCheckedAction() { 1101 for (int i = 0, size = mActions.size(); i < size; i++) { 1102 if (mActions.get(i).isChecked()) { 1103 return i; 1104 } 1105 } 1106 return 0; 1107 } 1108 1109 private void runImeAnimations(boolean entering) { 1110 ArrayList<Animator> animators = new ArrayList<Animator>(); 1111 if (entering) { 1112 mGuidanceStylist.onImeAppearing(animators); 1113 mActionsStylist.onImeAppearing(animators); 1114 mButtonActionsStylist.onImeAppearing(animators); 1115 } else { 1116 mGuidanceStylist.onImeDisappearing(animators); 1117 mActionsStylist.onImeDisappearing(animators); 1118 mButtonActionsStylist.onImeDisappearing(animators); 1119 } 1120 AnimatorSet set = new AnimatorSet(); 1121 set.playTogether(animators); 1122 set.start(); 1123 } 1124 1125} 1126