1 // CHECKSTYLE:OFF Generated code 2 /* This file is auto-generated from GuidedStepSupportFragment.java. DO NOT MODIFY. */ 3 4 /* 5 * Copyright (C) 2015 The Android Open Source Project 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 8 * in compliance with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software distributed under the License 13 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 14 * or implied. See the License for the specific language governing permissions and limitations under 15 * the License. 16 */ 17 package androidx.leanback.app; 18 19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21 import android.animation.Animator; 22 import android.animation.AnimatorSet; 23 import android.app.Activity; 24 import android.app.Fragment; 25 import android.app.FragmentManager; 26 import android.app.FragmentManager.BackStackEntry; 27 import android.app.FragmentTransaction; 28 import android.content.Context; 29 import android.os.Build; 30 import android.os.Bundle; 31 import android.util.Log; 32 import android.util.TypedValue; 33 import android.view.ContextThemeWrapper; 34 import android.view.Gravity; 35 import android.view.LayoutInflater; 36 import android.view.View; 37 import android.view.ViewGroup; 38 import android.widget.FrameLayout; 39 import android.widget.LinearLayout; 40 41 import androidx.annotation.NonNull; 42 import androidx.annotation.RestrictTo; 43 import androidx.core.app.ActivityCompat; 44 import androidx.leanback.R; 45 import androidx.leanback.transition.TransitionHelper; 46 import androidx.leanback.widget.DiffCallback; 47 import androidx.leanback.widget.GuidanceStylist; 48 import androidx.leanback.widget.GuidanceStylist.Guidance; 49 import androidx.leanback.widget.GuidedAction; 50 import androidx.leanback.widget.GuidedActionAdapter; 51 import androidx.leanback.widget.GuidedActionAdapterGroup; 52 import androidx.leanback.widget.GuidedActionsStylist; 53 import androidx.leanback.widget.NonOverlappingLinearLayout; 54 import androidx.recyclerview.widget.RecyclerView; 55 56 import java.util.ArrayList; 57 import java.util.List; 58 59 /** 60 * A GuidedStepFragment is used to guide the user through a decision or series of decisions. 61 * It is composed of a guidance view on the left and a view on the right containing a list of 62 * possible actions. 63 * <p> 64 * <h3>Basic Usage</h3> 65 * <p> 66 * Clients of GuidedStepFragment must create a custom subclass to attach to their Activities. 67 * This custom subclass provides the information necessary to construct the user interface and 68 * respond to user actions. At a minimum, subclasses should override: 69 * <ul> 70 * <li>{@link #onCreateGuidance}, to provide instructions to the user</li> 71 * <li>{@link #onCreateActions}, to provide a set of {@link GuidedAction}s the user can take</li> 72 * <li>{@link #onGuidedActionClicked}, to respond to those actions</li> 73 * </ul> 74 * <p> 75 * Clients use following helper functions to add GuidedStepFragment to Activity or FragmentManager: 76 * <ul> 77 * <li>{@link #addAsRoot(Activity, GuidedStepFragment, int)}, to be called during Activity onCreate, 78 * adds GuidedStepFragment as the first Fragment in activity.</li> 79 * <li>{@link #add(FragmentManager, GuidedStepFragment)} or {@link #add(FragmentManager, 80 * GuidedStepFragment, int)}, to add GuidedStepFragment on top of existing Fragments or 81 * replacing existing GuidedStepFragment when moving forward to next step.</li> 82 * <li>{@link #finishGuidedStepFragments()} can either finish the activity or pop all 83 * GuidedStepFragment from stack. 84 * <li>If app chooses not to use the helper function, it is the app's responsibility to call 85 * {@link #setUiStyle(int)} to select fragment transition and remember the stack entry where it 86 * need pops to. 87 * </ul> 88 * <h3>Theming and Stylists</h3> 89 * <p> 90 * GuidedStepFragment delegates its visual styling to classes called stylists. The {@link 91 * GuidanceStylist} is responsible for the left guidance view, while the {@link 92 * GuidedActionsStylist} is responsible for the right actions view. The stylists use theme 93 * attributes to derive values associated with the presentation, such as colors, animations, etc. 94 * Most simple visual aspects of GuidanceStylist and GuidedActionsStylist can be customized 95 * via theming; see their documentation for more information. 96 * <p> 97 * GuidedStepFragments must have access to an appropriate theme in order for the stylists to 98 * function properly. Specifically, the fragment must receive {@link 99 * androidx.leanback.R.style#Theme_Leanback_GuidedStep}, or a theme whose parent is 100 * is set to that theme. Themes can be provided in one of three ways: 101 * <ul> 102 * <li>The simplest way is to set the theme for the host Activity to the GuidedStep theme or a 103 * theme that derives from it.</li> 104 * <li>If the Activity already has a theme and setting its parent theme is inconvenient, the 105 * existing Activity theme can have an entry added for the attribute {@link 106 * androidx.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepTheme}. If present, 107 * this theme will be used by GuidedStepFragment as an overlay to the Activity's theme.</li> 108 * <li>Finally, custom subclasses of GuidedStepFragment may provide a theme through the {@link 109 * #onProvideTheme} method. This can be useful if a subclass is used across multiple 110 * Activities.</li> 111 * </ul> 112 * <p> 113 * If the theme is provided in multiple ways, the onProvideTheme override has priority, followed by 114 * the Activity's theme. (Themes whose parent theme is already set to the guided step theme do not 115 * need to set the guidedStepTheme attribute; if set, it will be ignored.) 116 * <p> 117 * If themes do not provide enough customizability, the stylists themselves may be subclassed and 118 * provided to the GuidedStepFragment through the {@link #onCreateGuidanceStylist} and {@link 119 * #onCreateActionsStylist} methods. The stylists have simple hooks so that subclasses 120 * may override layout files; subclasses may also have more complex logic to determine styling. 121 * <p> 122 * <h3>Guided sequences</h3> 123 * <p> 124 * GuidedStepFragments can be grouped together to provide a guided sequence. GuidedStepFragments 125 * grouped as a sequence use custom animations provided by {@link GuidanceStylist} and 126 * {@link GuidedActionsStylist} (or subclasses) during transitions between steps. Clients 127 * should use {@link #add} to place subsequent GuidedFragments onto the fragment stack so that 128 * custom animations are properly configured. (Custom animations are triggered automatically when 129 * the fragment stack is subsequently popped by any normal mechanism.) 130 * <p> 131 * <i>Note: Currently GuidedStepFragments grouped in this way must all be defined programmatically, 132 * rather than in XML. This restriction may be removed in the future.</i> 133 * 134 * @attr ref androidx.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepTheme 135 * @attr ref androidx.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepBackground 136 * @attr ref androidx.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidthWeight 137 * @attr ref androidx.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionContentWidthWeightTwoPanels 138 * @attr ref androidx.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsBackground 139 * @attr ref androidx.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsBackgroundDark 140 * @attr ref androidx.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsElevation 141 * @see GuidanceStylist 142 * @see GuidanceStylist.Guidance 143 * @see GuidedAction 144 * @see GuidedActionsStylist 145 * @deprecated use {@link GuidedStepSupportFragment} 146 */ 147 @Deprecated 148 public class GuidedStepFragment extends Fragment implements GuidedActionAdapter.FocusListener { 149 150 private static final String TAG_LEAN_BACK_ACTIONS_FRAGMENT = "leanBackGuidedStepFragment"; 151 private static final String EXTRA_ACTION_PREFIX = "action_"; 152 private static final String EXTRA_BUTTON_ACTION_PREFIX = "buttonaction_"; 153 154 private static final String ENTRY_NAME_REPLACE = "GuidedStepDefault"; 155 156 private static final String ENTRY_NAME_ENTRANCE = "GuidedStepEntrance"; 157 158 private static final boolean IS_FRAMEWORK_FRAGMENT = true; 159 160 /** 161 * Fragment argument name for UI style. The argument value is persisted in fragment state and 162 * used to select fragment transition. The value is initially {@link #UI_STYLE_ENTRANCE} and 163 * might be changed in one of the three helper functions: 164 * <ul> 165 * <li>{@link #addAsRoot(Activity, GuidedStepFragment, int)} sets to 166 * {@link #UI_STYLE_ACTIVITY_ROOT}</li> 167 * <li>{@link #add(FragmentManager, GuidedStepFragment)} or {@link #add(FragmentManager, 168 * GuidedStepFragment, int)} sets it to {@link #UI_STYLE_REPLACE} if there is already a 169 * GuidedStepFragment on stack.</li> 170 * <li>{@link #finishGuidedStepFragments()} changes current GuidedStepFragment to 171 * {@link #UI_STYLE_ENTRANCE} for the non activity case. This is a special case that changes 172 * the transition settings after fragment has been created, in order to force current 173 * GuidedStepFragment run a return transition of {@link #UI_STYLE_ENTRANCE}</li> 174 * </ul> 175 * <p> 176 * Argument value can be either: 177 * <ul> 178 * <li>{@link #UI_STYLE_REPLACE}</li> 179 * <li>{@link #UI_STYLE_ENTRANCE}</li> 180 * <li>{@link #UI_STYLE_ACTIVITY_ROOT}</li> 181 * </ul> 182 */ 183 public static final String EXTRA_UI_STYLE = "uiStyle"; 184 185 /** 186 * This is the case that we use GuidedStepFragment to replace another existing 187 * GuidedStepFragment when moving forward to next step. Default behavior of this style is: 188 * <ul> 189 * <li>Enter transition slides in from END(right), exit transition same as 190 * {@link #UI_STYLE_ENTRANCE}. 191 * </li> 192 * </ul> 193 */ 194 public static final int UI_STYLE_REPLACE = 0; 195 196 /** 197 * @deprecated Same value as {@link #UI_STYLE_REPLACE}. 198 */ 199 @Deprecated 200 public static final int UI_STYLE_DEFAULT = 0; 201 202 /** 203 * Default value for argument {@link #EXTRA_UI_STYLE}. The default value is assigned in 204 * GuidedStepFragment constructor. This is the case that we show GuidedStepFragment on top of 205 * other content. The default behavior of this style: 206 * <ul> 207 * <li>Enter transition slides in from two sides, exit transition slide out to START(left). 208 * Background will be faded in. Note: Changing exit transition by UI style is not working 209 * because fragment transition asks for exit transition before UI style is restored in Fragment 210 * .onCreate().</li> 211 * </ul> 212 * When popping multiple GuidedStepFragment, {@link #finishGuidedStepFragments()} also changes 213 * the top GuidedStepFragment to UI_STYLE_ENTRANCE in order to run the return transition 214 * (reverse of enter transition) of UI_STYLE_ENTRANCE. 215 */ 216 public static final int UI_STYLE_ENTRANCE = 1; 217 218 /** 219 * One possible value of argument {@link #EXTRA_UI_STYLE}. This is the case that we show first 220 * GuidedStepFragment in a separate activity. The default behavior of this style: 221 * <ul> 222 * <li>Enter transition is assigned null (will rely on activity transition), exit transition is 223 * same as {@link #UI_STYLE_ENTRANCE}. Note: Changing exit transition by UI style is not working 224 * because fragment transition asks for exit transition before UI style is restored in 225 * Fragment.onCreate().</li> 226 * </ul> 227 */ 228 public static final int UI_STYLE_ACTIVITY_ROOT = 2; 229 230 /** 231 * Animation to slide the contents from the side (left/right). 232 * @hide 233 */ 234 @RestrictTo(LIBRARY_GROUP) 235 public static final int SLIDE_FROM_SIDE = 0; 236 237 /** 238 * Animation to slide the contents from the bottom. 239 * @hide 240 */ 241 @RestrictTo(LIBRARY_GROUP) 242 public static final int SLIDE_FROM_BOTTOM = 1; 243 244 private static final String TAG = "GuidedStepF"; 245 private static final boolean DEBUG = false; 246 247 /** 248 * @hide 249 */ 250 @RestrictTo(LIBRARY_GROUP) 251 public static class DummyFragment extends Fragment { 252 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)253 public View onCreateView(LayoutInflater inflater, ViewGroup container, 254 Bundle savedInstanceState) { 255 final View v = new View(inflater.getContext()); 256 v.setVisibility(View.GONE); 257 return v; 258 } 259 } 260 261 private ContextThemeWrapper mThemeWrapper; 262 private GuidanceStylist mGuidanceStylist; 263 GuidedActionsStylist mActionsStylist; 264 private GuidedActionsStylist mButtonActionsStylist; 265 private GuidedActionAdapter mAdapter; 266 private GuidedActionAdapter mSubAdapter; 267 private GuidedActionAdapter mButtonAdapter; 268 private GuidedActionAdapterGroup mAdapterGroup; 269 private List<GuidedAction> mActions = new ArrayList<GuidedAction>(); 270 private List<GuidedAction> mButtonActions = new ArrayList<GuidedAction>(); 271 private int entranceTransitionType = SLIDE_FROM_SIDE; 272 GuidedStepFragment()273 public GuidedStepFragment() { 274 mGuidanceStylist = onCreateGuidanceStylist(); 275 mActionsStylist = onCreateActionsStylist(); 276 mButtonActionsStylist = onCreateButtonActionsStylist(); 277 onProvideFragmentTransitions(); 278 } 279 280 /** 281 * Creates the presenter used to style the guidance panel. The default implementation returns 282 * a basic GuidanceStylist. 283 * @return The GuidanceStylist used in this fragment. 284 */ onCreateGuidanceStylist()285 public GuidanceStylist onCreateGuidanceStylist() { 286 return new GuidanceStylist(); 287 } 288 289 /** 290 * Creates the presenter used to style the guided actions panel. The default implementation 291 * returns a basic GuidedActionsStylist. 292 * @return The GuidedActionsStylist used in this fragment. 293 */ onCreateActionsStylist()294 public GuidedActionsStylist onCreateActionsStylist() { 295 return new GuidedActionsStylist(); 296 } 297 298 /** 299 * Creates the presenter used to style a sided actions panel for button only. 300 * The default implementation returns a basic GuidedActionsStylist. 301 * @return The GuidedActionsStylist used in this fragment. 302 */ onCreateButtonActionsStylist()303 public GuidedActionsStylist onCreateButtonActionsStylist() { 304 GuidedActionsStylist stylist = new GuidedActionsStylist(); 305 stylist.setAsButtonActions(); 306 return stylist; 307 } 308 309 /** 310 * Returns the theme used for styling the fragment. The default returns -1, indicating that the 311 * host Activity's theme should be used. 312 * @return The theme resource ID of the theme to use in this fragment, or -1 to use the 313 * host Activity's theme. 314 */ onProvideTheme()315 public int onProvideTheme() { 316 return -1; 317 } 318 319 /** 320 * Returns the information required to provide guidance to the user. This hook is called during 321 * {@link #onCreateView}. May be overridden to return a custom subclass of {@link 322 * GuidanceStylist.Guidance} for use in a subclass of {@link GuidanceStylist}. The default 323 * returns a Guidance object with empty fields; subclasses should override. 324 * @param savedInstanceState The saved instance state from onCreateView. 325 * @return The Guidance object representing the information used to guide the user. 326 */ onCreateGuidance(Bundle savedInstanceState)327 public @NonNull Guidance onCreateGuidance(Bundle savedInstanceState) { 328 return new Guidance("", "", "", null); 329 } 330 331 /** 332 * Fills out the set of actions available to the user. This hook is called during {@link 333 * #onCreate}. The default leaves the list of actions empty; subclasses should override. 334 * @param actions A non-null, empty list ready to be populated. 335 * @param savedInstanceState The saved instance state from onCreate. 336 */ onCreateActions(@onNull List<GuidedAction> actions, Bundle savedInstanceState)337 public void onCreateActions(@NonNull List<GuidedAction> actions, Bundle savedInstanceState) { 338 } 339 340 /** 341 * Fills out the set of actions shown at right available to the user. This hook is called during 342 * {@link #onCreate}. The default leaves the list of actions empty; subclasses may override. 343 * @param actions A non-null, empty list ready to be populated. 344 * @param savedInstanceState The saved instance state from onCreate. 345 */ onCreateButtonActions(@onNull List<GuidedAction> actions, Bundle savedInstanceState)346 public void onCreateButtonActions(@NonNull List<GuidedAction> actions, 347 Bundle savedInstanceState) { 348 } 349 350 /** 351 * Callback invoked when an action is taken by the user. Subclasses should override in 352 * order to act on the user's decisions. 353 * @param action The chosen action. 354 */ onGuidedActionClicked(GuidedAction action)355 public void onGuidedActionClicked(GuidedAction action) { 356 } 357 358 /** 359 * Callback invoked when an action in sub actions is taken by the user. Subclasses should 360 * override in order to act on the user's decisions. Default return value is true to close 361 * the sub actions list. 362 * @param action The chosen action. 363 * @return true to collapse the sub actions list, false to keep it expanded. 364 */ onSubGuidedActionClicked(GuidedAction action)365 public boolean onSubGuidedActionClicked(GuidedAction action) { 366 return true; 367 } 368 369 /** 370 * @return True if is current expanded including subactions list or 371 * action with {@link GuidedAction#hasEditableActivatorView()} is true. 372 */ isExpanded()373 public boolean isExpanded() { 374 return mActionsStylist.isExpanded(); 375 } 376 377 /** 378 * @return True if the sub actions list is expanded, false otherwise. 379 */ isSubActionsExpanded()380 public boolean isSubActionsExpanded() { 381 return mActionsStylist.isSubActionsExpanded(); 382 } 383 384 /** 385 * Expand a given action's sub actions list. 386 * @param action GuidedAction to expand. 387 * @see #expandAction(GuidedAction, boolean) 388 */ expandSubActions(GuidedAction action)389 public void expandSubActions(GuidedAction action) { 390 if (!action.hasSubActions()) { 391 return; 392 } 393 expandAction(action, true); 394 } 395 396 /** 397 * Expand a given action with sub actions list or 398 * {@link GuidedAction#hasEditableActivatorView()} is true. The method must be called after 399 * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} creates fragment view. 400 * 401 * @param action GuidedAction to expand. 402 * @param withTransition True to run transition animation, false otherwise. 403 */ expandAction(GuidedAction action, boolean withTransition)404 public void expandAction(GuidedAction action, boolean withTransition) { 405 mActionsStylist.expandAction(action, withTransition); 406 } 407 408 /** 409 * Collapse sub actions list. 410 * @see GuidedAction#getSubActions() 411 */ collapseSubActions()412 public void collapseSubActions() { 413 collapseAction(true); 414 } 415 416 /** 417 * Collapse action which either has a sub actions list or action with 418 * {@link GuidedAction#hasEditableActivatorView()} is true. 419 * 420 * @param withTransition True to run transition animation, false otherwise. 421 */ collapseAction(boolean withTransition)422 public void collapseAction(boolean withTransition) { 423 if (mActionsStylist != null && mActionsStylist.getActionsGridView() != null) { 424 mActionsStylist.collapseAction(withTransition); 425 } 426 } 427 428 /** 429 * Callback invoked when an action is focused (made to be the current selection) by the user. 430 */ 431 @Override onGuidedActionFocused(GuidedAction action)432 public void onGuidedActionFocused(GuidedAction action) { 433 } 434 435 /** 436 * Callback invoked when an action's title or description has been edited, this happens either 437 * when user clicks confirm button in IME or user closes IME window by BACK key. 438 * @deprecated Override {@link #onGuidedActionEditedAndProceed(GuidedAction)} and/or 439 * {@link #onGuidedActionEditCanceled(GuidedAction)}. 440 */ 441 @Deprecated onGuidedActionEdited(GuidedAction action)442 public void onGuidedActionEdited(GuidedAction action) { 443 } 444 445 /** 446 * Callback invoked when an action has been canceled editing, for example when user closes 447 * IME window by BACK key. Default implementation calls deprecated method 448 * {@link #onGuidedActionEdited(GuidedAction)}. 449 * @param action The action which has been canceled editing. 450 */ onGuidedActionEditCanceled(GuidedAction action)451 public void onGuidedActionEditCanceled(GuidedAction action) { 452 onGuidedActionEdited(action); 453 } 454 455 /** 456 * Callback invoked when an action has been edited, for example when user clicks confirm button 457 * in IME window. Default implementation calls deprecated method 458 * {@link #onGuidedActionEdited(GuidedAction)} and returns {@link GuidedAction#ACTION_ID_NEXT}. 459 * 460 * @param action The action that has been edited. 461 * @return ID of the action will be focused or {@link GuidedAction#ACTION_ID_NEXT}, 462 * {@link GuidedAction#ACTION_ID_CURRENT}. 463 */ onGuidedActionEditedAndProceed(GuidedAction action)464 public long onGuidedActionEditedAndProceed(GuidedAction action) { 465 onGuidedActionEdited(action); 466 return GuidedAction.ACTION_ID_NEXT; 467 } 468 469 /** 470 * Adds the specified GuidedStepFragment to the fragment stack, replacing any existing 471 * GuidedStepFragments in the stack, and configuring the fragment-to-fragment custom 472 * transitions. A backstack entry is added, so the fragment will be dismissed when BACK key 473 * is pressed. 474 * <li>If current fragment on stack is GuidedStepFragment: assign {@link #UI_STYLE_REPLACE} 475 * <li>If current fragment on stack is not GuidedStepFragment: assign {@link #UI_STYLE_ENTRANCE} 476 * <p> 477 * Note: currently fragments added using this method must be created programmatically rather 478 * than via XML. 479 * @param fragmentManager The FragmentManager to be used in the transaction. 480 * @param fragment The GuidedStepFragment to be inserted into the fragment stack. 481 * @return The ID returned by the call FragmentTransaction.commit. 482 */ add(FragmentManager fragmentManager, GuidedStepFragment fragment)483 public static int add(FragmentManager fragmentManager, GuidedStepFragment fragment) { 484 return add(fragmentManager, fragment, android.R.id.content); 485 } 486 487 /** 488 * Adds the specified GuidedStepFragment to the fragment stack, replacing any existing 489 * GuidedStepFragments in the stack, and configuring the fragment-to-fragment custom 490 * transitions. A backstack entry is added, so the fragment will be dismissed when BACK key 491 * is pressed. 492 * <li>If current fragment on stack is GuidedStepFragment: assign {@link #UI_STYLE_REPLACE} and 493 * {@link #onAddSharedElementTransition(FragmentTransaction, GuidedStepFragment)} will be called 494 * to perform shared element transition between GuidedStepFragments. 495 * <li>If current fragment on stack is not GuidedStepFragment: assign {@link #UI_STYLE_ENTRANCE} 496 * <p> 497 * Note: currently fragments added using this method must be created programmatically rather 498 * than via XML. 499 * @param fragmentManager The FragmentManager to be used in the transaction. 500 * @param fragment The GuidedStepFragment to be inserted into the fragment stack. 501 * @param id The id of container to add GuidedStepFragment, can be android.R.id.content. 502 * @return The ID returned by the call FragmentTransaction.commit. 503 */ add(FragmentManager fragmentManager, GuidedStepFragment fragment, int id)504 public static int add(FragmentManager fragmentManager, GuidedStepFragment fragment, int id) { 505 GuidedStepFragment current = getCurrentGuidedStepFragment(fragmentManager); 506 boolean inGuidedStep = current != null; 507 if (IS_FRAMEWORK_FRAGMENT && Build.VERSION.SDK_INT >= 21 && Build.VERSION.SDK_INT < 23 508 && !inGuidedStep) { 509 // workaround b/22631964 for framework fragment 510 fragmentManager.beginTransaction() 511 .replace(id, new DummyFragment(), TAG_LEAN_BACK_ACTIONS_FRAGMENT) 512 .commit(); 513 } 514 FragmentTransaction ft = fragmentManager.beginTransaction(); 515 516 fragment.setUiStyle(inGuidedStep ? UI_STYLE_REPLACE : UI_STYLE_ENTRANCE); 517 ft.addToBackStack(fragment.generateStackEntryName()); 518 if (current != null) { 519 fragment.onAddSharedElementTransition(ft, current); 520 } 521 return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit(); 522 } 523 524 /** 525 * Called when this fragment is added to FragmentTransaction with {@link #UI_STYLE_REPLACE} (aka 526 * when the GuidedStepFragment replacing an existing GuidedStepFragment). Default implementation 527 * establishes connections between action background views to morph action background bounds 528 * change from disappearing GuidedStepFragment into this GuidedStepFragment. The default 529 * implementation heavily relies on {@link GuidedActionsStylist}'s layout, app may override this 530 * method when modifying the default layout of {@link GuidedActionsStylist}. 531 * 532 * @see GuidedActionsStylist 533 * @see #onProvideFragmentTransitions() 534 * @param ft The FragmentTransaction to add shared element. 535 * @param disappearing The disappearing fragment. 536 */ onAddSharedElementTransition(FragmentTransaction ft, GuidedStepFragment disappearing)537 protected void onAddSharedElementTransition(FragmentTransaction ft, GuidedStepFragment 538 disappearing) { 539 View fragmentView = disappearing.getView(); 540 addNonNullSharedElementTransition(ft, fragmentView.findViewById( 541 R.id.action_fragment_root), "action_fragment_root"); 542 addNonNullSharedElementTransition(ft, fragmentView.findViewById( 543 R.id.action_fragment_background), "action_fragment_background"); 544 addNonNullSharedElementTransition(ft, fragmentView.findViewById( 545 R.id.action_fragment), "action_fragment"); 546 addNonNullSharedElementTransition(ft, fragmentView.findViewById( 547 R.id.guidedactions_root), "guidedactions_root"); 548 addNonNullSharedElementTransition(ft, fragmentView.findViewById( 549 R.id.guidedactions_content), "guidedactions_content"); 550 addNonNullSharedElementTransition(ft, fragmentView.findViewById( 551 R.id.guidedactions_list_background), "guidedactions_list_background"); 552 addNonNullSharedElementTransition(ft, fragmentView.findViewById( 553 R.id.guidedactions_root2), "guidedactions_root2"); 554 addNonNullSharedElementTransition(ft, fragmentView.findViewById( 555 R.id.guidedactions_content2), "guidedactions_content2"); 556 addNonNullSharedElementTransition(ft, fragmentView.findViewById( 557 R.id.guidedactions_list_background2), "guidedactions_list_background2"); 558 } 559 addNonNullSharedElementTransition(FragmentTransaction ft, View subView, String transitionName)560 private static void addNonNullSharedElementTransition (FragmentTransaction ft, View subView, 561 String transitionName) 562 { 563 if (subView != null) 564 TransitionHelper.addSharedElement(ft, subView, transitionName); 565 } 566 567 /** 568 * Returns BackStackEntry name for the GuidedStepFragment or empty String if no entry is 569 * associated. Note {@link #UI_STYLE_ACTIVITY_ROOT} will return empty String. The method 570 * returns undefined value if the fragment is not in FragmentManager. 571 * @return BackStackEntry name for the GuidedStepFragment or empty String if no entry is 572 * associated. 573 */ generateStackEntryName()574 final String generateStackEntryName() { 575 return generateStackEntryName(getUiStyle(), getClass()); 576 } 577 578 /** 579 * Generates BackStackEntry name for GuidedStepFragment class or empty String if no entry is 580 * associated. Note {@link #UI_STYLE_ACTIVITY_ROOT} is not allowed and returns empty String. 581 * @param uiStyle {@link #UI_STYLE_REPLACE} or {@link #UI_STYLE_ENTRANCE} 582 * @return BackStackEntry name for the GuidedStepFragment or empty String if no entry is 583 * associated. 584 */ generateStackEntryName(int uiStyle, Class guidedStepFragmentClass)585 static String generateStackEntryName(int uiStyle, Class guidedStepFragmentClass) { 586 switch (uiStyle) { 587 case UI_STYLE_REPLACE: 588 return ENTRY_NAME_REPLACE + guidedStepFragmentClass.getName(); 589 case UI_STYLE_ENTRANCE: 590 return ENTRY_NAME_ENTRANCE + guidedStepFragmentClass.getName(); 591 case UI_STYLE_ACTIVITY_ROOT: 592 default: 593 return ""; 594 } 595 } 596 597 /** 598 * Returns true if the backstack entry represents GuidedStepFragment with 599 * {@link #UI_STYLE_ENTRANCE}, i.e. this is the first GuidedStepFragment pushed to stack; false 600 * otherwise. 601 * @see #generateStackEntryName(int, Class) 602 * @param backStackEntryName Name of BackStackEntry. 603 * @return True if the backstack represents GuidedStepFragment with {@link #UI_STYLE_ENTRANCE}; 604 * false otherwise. 605 */ isStackEntryUiStyleEntrance(String backStackEntryName)606 static boolean isStackEntryUiStyleEntrance(String backStackEntryName) { 607 return backStackEntryName != null && backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE); 608 } 609 610 /** 611 * Extract Class name from BackStackEntry name. 612 * @param backStackEntryName Name of BackStackEntry. 613 * @return Class name of GuidedStepFragment. 614 */ getGuidedStepFragmentClassName(String backStackEntryName)615 static String getGuidedStepFragmentClassName(String backStackEntryName) { 616 if (backStackEntryName.startsWith(ENTRY_NAME_REPLACE)) { 617 return backStackEntryName.substring(ENTRY_NAME_REPLACE.length()); 618 } else if (backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE)) { 619 return backStackEntryName.substring(ENTRY_NAME_ENTRANCE.length()); 620 } else { 621 return ""; 622 } 623 } 624 625 /** 626 * Adds the specified GuidedStepFragment as content of Activity; no backstack entry is added so 627 * the activity will be dismissed when BACK key is pressed. The method is typically called in 628 * Activity.onCreate() when savedInstanceState is null. When savedInstanceState is not null, 629 * the Activity is being restored, do not call addAsRoot() to duplicate the Fragment restored 630 * by FragmentManager. 631 * {@link #UI_STYLE_ACTIVITY_ROOT} is assigned. 632 * 633 * Note: currently fragments added using this method must be created programmatically rather 634 * than via XML. 635 * @param activity The Activity to be used to insert GuidedstepFragment. 636 * @param fragment The GuidedStepFragment to be inserted into the fragment stack. 637 * @param id The id of container to add GuidedStepFragment, can be android.R.id.content. 638 * @return The ID returned by the call FragmentTransaction.commit, or -1 there is already 639 * GuidedStepFragment. 640 */ addAsRoot(Activity activity, GuidedStepFragment fragment, int id)641 public static int addAsRoot(Activity activity, GuidedStepFragment fragment, int id) { 642 // Workaround b/23764120: call getDecorView() to force requestFeature of ActivityTransition. 643 activity.getWindow().getDecorView(); 644 FragmentManager fragmentManager = activity.getFragmentManager(); 645 if (fragmentManager.findFragmentByTag(TAG_LEAN_BACK_ACTIONS_FRAGMENT) != null) { 646 Log.w(TAG, "Fragment is already exists, likely calling " 647 + "addAsRoot() when savedInstanceState is not null in Activity.onCreate()."); 648 return -1; 649 } 650 FragmentTransaction ft = fragmentManager.beginTransaction(); 651 fragment.setUiStyle(UI_STYLE_ACTIVITY_ROOT); 652 return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit(); 653 } 654 655 /** 656 * Returns the current GuidedStepFragment on the fragment transaction stack. 657 * @return The current GuidedStepFragment, if any, on the fragment transaction stack. 658 */ getCurrentGuidedStepFragment(FragmentManager fm)659 public static GuidedStepFragment getCurrentGuidedStepFragment(FragmentManager fm) { 660 Fragment f = fm.findFragmentByTag(TAG_LEAN_BACK_ACTIONS_FRAGMENT); 661 if (f instanceof GuidedStepFragment) { 662 return (GuidedStepFragment) f; 663 } 664 return null; 665 } 666 667 /** 668 * Returns the GuidanceStylist that displays guidance information for the user. 669 * @return The GuidanceStylist for this fragment. 670 */ getGuidanceStylist()671 public GuidanceStylist getGuidanceStylist() { 672 return mGuidanceStylist; 673 } 674 675 /** 676 * Returns the GuidedActionsStylist that displays the actions the user may take. 677 * @return The GuidedActionsStylist for this fragment. 678 */ getGuidedActionsStylist()679 public GuidedActionsStylist getGuidedActionsStylist() { 680 return mActionsStylist; 681 } 682 683 /** 684 * Returns the list of button GuidedActions that the user may take in this fragment. 685 * @return The list of button GuidedActions for this fragment. 686 */ getButtonActions()687 public List<GuidedAction> getButtonActions() { 688 return mButtonActions; 689 } 690 691 /** 692 * Find button GuidedAction by Id. 693 * @param id Id of the button action to search. 694 * @return GuidedAction object or null if not found. 695 */ findButtonActionById(long id)696 public GuidedAction findButtonActionById(long id) { 697 int index = findButtonActionPositionById(id); 698 return index >= 0 ? mButtonActions.get(index) : null; 699 } 700 701 /** 702 * Find button GuidedAction position in array by Id. 703 * @param id Id of the button action to search. 704 * @return position of GuidedAction object in array or -1 if not found. 705 */ findButtonActionPositionById(long id)706 public int findButtonActionPositionById(long id) { 707 if (mButtonActions != null) { 708 for (int i = 0; i < mButtonActions.size(); i++) { 709 GuidedAction action = mButtonActions.get(i); 710 if (mButtonActions.get(i).getId() == id) { 711 return i; 712 } 713 } 714 } 715 return -1; 716 } 717 718 /** 719 * Returns the GuidedActionsStylist that displays the button actions the user may take. 720 * @return The GuidedActionsStylist for this fragment. 721 */ getGuidedButtonActionsStylist()722 public GuidedActionsStylist getGuidedButtonActionsStylist() { 723 return mButtonActionsStylist; 724 } 725 726 /** 727 * Sets the list of button GuidedActions that the user may take in this fragment. 728 * @param actions The list of button GuidedActions for this fragment. 729 */ setButtonActions(List<GuidedAction> actions)730 public void setButtonActions(List<GuidedAction> actions) { 731 mButtonActions = actions; 732 if (mButtonAdapter != null) { 733 mButtonAdapter.setActions(mButtonActions); 734 } 735 } 736 737 /** 738 * Notify an button action has changed and update its UI. 739 * @param position Position of the button GuidedAction in array. 740 */ notifyButtonActionChanged(int position)741 public void notifyButtonActionChanged(int position) { 742 if (mButtonAdapter != null) { 743 mButtonAdapter.notifyItemChanged(position); 744 } 745 } 746 747 /** 748 * Returns the view corresponding to the button action at the indicated position in the list of 749 * actions for this fragment. 750 * @param position The integer position of the button action of interest. 751 * @return The View corresponding to the button action at the indicated position, or null if 752 * that action is not currently onscreen. 753 */ getButtonActionItemView(int position)754 public View getButtonActionItemView(int position) { 755 final RecyclerView.ViewHolder holder = mButtonActionsStylist.getActionsGridView() 756 .findViewHolderForPosition(position); 757 return holder == null ? null : holder.itemView; 758 } 759 760 /** 761 * Scrolls the action list to the position indicated, selecting that button action's view. 762 * @param position The integer position of the button action of interest. 763 */ setSelectedButtonActionPosition(int position)764 public void setSelectedButtonActionPosition(int position) { 765 mButtonActionsStylist.getActionsGridView().setSelectedPosition(position); 766 } 767 768 /** 769 * Returns the position if the currently selected button GuidedAction. 770 * @return position The integer position of the currently selected button action. 771 */ getSelectedButtonActionPosition()772 public int getSelectedButtonActionPosition() { 773 return mButtonActionsStylist.getActionsGridView().getSelectedPosition(); 774 } 775 776 /** 777 * Returns the list of GuidedActions that the user may take in this fragment. 778 * @return The list of GuidedActions for this fragment. 779 */ getActions()780 public List<GuidedAction> getActions() { 781 return mActions; 782 } 783 784 /** 785 * Find GuidedAction by Id. 786 * @param id Id of the action to search. 787 * @return GuidedAction object or null if not found. 788 */ findActionById(long id)789 public GuidedAction findActionById(long id) { 790 int index = findActionPositionById(id); 791 return index >= 0 ? mActions.get(index) : null; 792 } 793 794 /** 795 * Find GuidedAction position in array by Id. 796 * @param id Id of the action to search. 797 * @return position of GuidedAction object in array or -1 if not found. 798 */ findActionPositionById(long id)799 public int findActionPositionById(long id) { 800 if (mActions != null) { 801 for (int i = 0; i < mActions.size(); i++) { 802 GuidedAction action = mActions.get(i); 803 if (mActions.get(i).getId() == id) { 804 return i; 805 } 806 } 807 } 808 return -1; 809 } 810 811 /** 812 * Sets the list of GuidedActions that the user may take in this fragment. 813 * Uses DiffCallback set by {@link #setActionsDiffCallback(DiffCallback)}. 814 * 815 * @param actions The list of GuidedActions for this fragment. 816 */ setActions(List<GuidedAction> actions)817 public void setActions(List<GuidedAction> actions) { 818 mActions = actions; 819 if (mAdapter != null) { 820 mAdapter.setActions(mActions); 821 } 822 } 823 824 /** 825 * Sets the RecyclerView DiffCallback used when {@link #setActions(List)} is called. By default 826 * GuidedStepFragment uses 827 * {@link androidx.leanback.widget.GuidedActionDiffCallback}. 828 * Sets it to null if app wants to refresh the whole list. 829 * 830 * @param diffCallback DiffCallback used in {@link #setActions(List)}. 831 */ setActionsDiffCallback(DiffCallback<GuidedAction> diffCallback)832 public void setActionsDiffCallback(DiffCallback<GuidedAction> diffCallback) { 833 mAdapter.setDiffCallback(diffCallback); 834 } 835 836 /** 837 * Notify an action has changed and update its UI. 838 * @param position Position of the GuidedAction in array. 839 */ notifyActionChanged(int position)840 public void notifyActionChanged(int position) { 841 if (mAdapter != null) { 842 mAdapter.notifyItemChanged(position); 843 } 844 } 845 846 /** 847 * Returns the view corresponding to the action at the indicated position in the list of 848 * actions for this fragment. 849 * @param position The integer position of the action of interest. 850 * @return The View corresponding to the action at the indicated position, or null if that 851 * action is not currently onscreen. 852 */ getActionItemView(int position)853 public View getActionItemView(int position) { 854 final RecyclerView.ViewHolder holder = mActionsStylist.getActionsGridView() 855 .findViewHolderForPosition(position); 856 return holder == null ? null : holder.itemView; 857 } 858 859 /** 860 * Scrolls the action list to the position indicated, selecting that action's view. 861 * @param position The integer position of the action of interest. 862 */ setSelectedActionPosition(int position)863 public void setSelectedActionPosition(int position) { 864 mActionsStylist.getActionsGridView().setSelectedPosition(position); 865 } 866 867 /** 868 * Returns the position if the currently selected GuidedAction. 869 * @return position The integer position of the currently selected action. 870 */ getSelectedActionPosition()871 public int getSelectedActionPosition() { 872 return mActionsStylist.getActionsGridView().getSelectedPosition(); 873 } 874 875 /** 876 * Called by Constructor to provide fragment transitions. The default implementation assigns 877 * transitions based on {@link #getUiStyle()}: 878 * <ul> 879 * <li> {@link #UI_STYLE_REPLACE} Slide from/to end(right) for enter transition, slide from/to 880 * start(left) for exit transition, shared element enter transition is set to ChangeBounds. 881 * <li> {@link #UI_STYLE_ENTRANCE} Enter transition is set to slide from both sides, exit 882 * transition is same as {@link #UI_STYLE_REPLACE}, no shared element enter transition. 883 * <li> {@link #UI_STYLE_ACTIVITY_ROOT} Enter transition is set to null and app should rely on 884 * activity transition, exit transition is same as {@link #UI_STYLE_REPLACE}, no shared element 885 * enter transition. 886 * </ul> 887 * <p> 888 * The default implementation heavily relies on {@link GuidedActionsStylist} and 889 * {@link GuidanceStylist} layout, app may override this method when modifying the default 890 * layout of {@link GuidedActionsStylist} or {@link GuidanceStylist}. 891 * <p> 892 * TIP: because the fragment view is removed during fragment transition, in general app cannot 893 * use two Visibility transition together. Workaround is to create your own Visibility 894 * transition that controls multiple animators (e.g. slide and fade animation in one Transition 895 * class). 896 */ onProvideFragmentTransitions()897 protected void onProvideFragmentTransitions() { 898 if (Build.VERSION.SDK_INT >= 21) { 899 final int uiStyle = getUiStyle(); 900 if (uiStyle == UI_STYLE_REPLACE) { 901 Object enterTransition = TransitionHelper.createFadeAndShortSlide(Gravity.END); 902 TransitionHelper.exclude(enterTransition, R.id.guidedstep_background, true); 903 TransitionHelper.exclude(enterTransition, R.id.guidedactions_sub_list_background, 904 true); 905 TransitionHelper.setEnterTransition(this, enterTransition); 906 907 Object fade = TransitionHelper.createFadeTransition( 908 TransitionHelper.FADE_IN | TransitionHelper.FADE_OUT); 909 TransitionHelper.include(fade, R.id.guidedactions_sub_list_background); 910 Object changeBounds = TransitionHelper.createChangeBounds(false); 911 Object sharedElementTransition = TransitionHelper.createTransitionSet(false); 912 TransitionHelper.addTransition(sharedElementTransition, fade); 913 TransitionHelper.addTransition(sharedElementTransition, changeBounds); 914 TransitionHelper.setSharedElementEnterTransition(this, sharedElementTransition); 915 } else if (uiStyle == UI_STYLE_ENTRANCE) { 916 if (entranceTransitionType == SLIDE_FROM_SIDE) { 917 Object fade = TransitionHelper.createFadeTransition( 918 TransitionHelper.FADE_IN | TransitionHelper.FADE_OUT); 919 TransitionHelper.include(fade, R.id.guidedstep_background); 920 Object slideFromSide = TransitionHelper.createFadeAndShortSlide( 921 Gravity.END | Gravity.START); 922 TransitionHelper.include(slideFromSide, R.id.content_fragment); 923 TransitionHelper.include(slideFromSide, R.id.action_fragment_root); 924 Object enterTransition = TransitionHelper.createTransitionSet(false); 925 TransitionHelper.addTransition(enterTransition, fade); 926 TransitionHelper.addTransition(enterTransition, slideFromSide); 927 TransitionHelper.setEnterTransition(this, enterTransition); 928 } else { 929 Object slideFromBottom = TransitionHelper.createFadeAndShortSlide( 930 Gravity.BOTTOM); 931 TransitionHelper.include(slideFromBottom, R.id.guidedstep_background_view_root); 932 Object enterTransition = TransitionHelper.createTransitionSet(false); 933 TransitionHelper.addTransition(enterTransition, slideFromBottom); 934 TransitionHelper.setEnterTransition(this, enterTransition); 935 } 936 // No shared element transition 937 TransitionHelper.setSharedElementEnterTransition(this, null); 938 } else if (uiStyle == UI_STYLE_ACTIVITY_ROOT) { 939 // for Activity root, we don't need enter transition, use activity transition 940 TransitionHelper.setEnterTransition(this, null); 941 // No shared element transition 942 TransitionHelper.setSharedElementEnterTransition(this, null); 943 } 944 // exitTransition is same for all style 945 Object exitTransition = TransitionHelper.createFadeAndShortSlide(Gravity.START); 946 TransitionHelper.exclude(exitTransition, R.id.guidedstep_background, true); 947 TransitionHelper.exclude(exitTransition, R.id.guidedactions_sub_list_background, 948 true); 949 TransitionHelper.setExitTransition(this, exitTransition); 950 } 951 } 952 953 /** 954 * Called by onCreateView to inflate background view. Default implementation loads view 955 * from {@link R.layout#lb_guidedstep_background} which holds a reference to 956 * guidedStepBackground. 957 * @param inflater LayoutInflater to load background view. 958 * @param container Parent view of background view. 959 * @param savedInstanceState 960 * @return Created background view or null if no background. 961 */ onCreateBackgroundView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)962 public View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container, 963 Bundle savedInstanceState) { 964 return inflater.inflate(R.layout.lb_guidedstep_background, container, false); 965 } 966 967 /** 968 * Set UI style to fragment arguments. Default value is {@link #UI_STYLE_ENTRANCE} when fragment 969 * is first initialized. UI style is used to choose different fragment transition animations and 970 * determine if this is the first GuidedStepFragment on backstack. In most cases app does not 971 * directly call this method, app calls helper function 972 * {@link #add(FragmentManager, GuidedStepFragment, int)}. However if the app creates Fragment 973 * transaction and controls backstack by itself, it would need call setUiStyle() to select the 974 * fragment transition to use. 975 * 976 * @param style {@link #UI_STYLE_ACTIVITY_ROOT} {@link #UI_STYLE_REPLACE} or 977 * {@link #UI_STYLE_ENTRANCE}. 978 */ setUiStyle(int style)979 public void setUiStyle(int style) { 980 int oldStyle = getUiStyle(); 981 Bundle arguments = getArguments(); 982 boolean isNew = false; 983 if (arguments == null) { 984 arguments = new Bundle(); 985 isNew = true; 986 } 987 arguments.putInt(EXTRA_UI_STYLE, style); 988 // call setArgument() will validate if the fragment is already added. 989 if (isNew) { 990 setArguments(arguments); 991 } 992 if (style != oldStyle) { 993 onProvideFragmentTransitions(); 994 } 995 } 996 997 /** 998 * Read UI style from fragment arguments. Default value is {@link #UI_STYLE_ENTRANCE} when 999 * fragment is first initialized. UI style is used to choose different fragment transition 1000 * animations and determine if this is the first GuidedStepFragment on backstack. 1001 * 1002 * @return {@link #UI_STYLE_ACTIVITY_ROOT} {@link #UI_STYLE_REPLACE} or 1003 * {@link #UI_STYLE_ENTRANCE}. 1004 * @see #onProvideFragmentTransitions() 1005 */ getUiStyle()1006 public int getUiStyle() { 1007 Bundle b = getArguments(); 1008 if (b == null) return UI_STYLE_ENTRANCE; 1009 return b.getInt(EXTRA_UI_STYLE, UI_STYLE_ENTRANCE); 1010 } 1011 1012 /** 1013 * {@inheritDoc} 1014 */ 1015 @Override onCreate(Bundle savedInstanceState)1016 public void onCreate(Bundle savedInstanceState) { 1017 super.onCreate(savedInstanceState); 1018 if (DEBUG) Log.v(TAG, "onCreate"); 1019 // Set correct transition from saved arguments. 1020 onProvideFragmentTransitions(); 1021 1022 ArrayList<GuidedAction> actions = new ArrayList<GuidedAction>(); 1023 onCreateActions(actions, savedInstanceState); 1024 if (savedInstanceState != null) { 1025 onRestoreActions(actions, savedInstanceState); 1026 } 1027 setActions(actions); 1028 ArrayList<GuidedAction> buttonActions = new ArrayList<GuidedAction>(); 1029 onCreateButtonActions(buttonActions, savedInstanceState); 1030 if (savedInstanceState != null) { 1031 onRestoreButtonActions(buttonActions, savedInstanceState); 1032 } 1033 setButtonActions(buttonActions); 1034 } 1035 1036 /** 1037 * {@inheritDoc} 1038 */ 1039 @Override onDestroyView()1040 public void onDestroyView() { 1041 mGuidanceStylist.onDestroyView(); 1042 mActionsStylist.onDestroyView(); 1043 mButtonActionsStylist.onDestroyView(); 1044 mAdapter = null; 1045 mSubAdapter = null; 1046 mButtonAdapter = null; 1047 mAdapterGroup = null; 1048 super.onDestroyView(); 1049 } 1050 1051 /** 1052 * {@inheritDoc} 1053 */ 1054 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)1055 public View onCreateView(LayoutInflater inflater, ViewGroup container, 1056 Bundle savedInstanceState) { 1057 if (DEBUG) Log.v(TAG, "onCreateView"); 1058 1059 resolveTheme(); 1060 inflater = getThemeInflater(inflater); 1061 1062 GuidedStepRootLayout root = (GuidedStepRootLayout) inflater.inflate( 1063 R.layout.lb_guidedstep_fragment, container, false); 1064 1065 root.setFocusOutStart(isFocusOutStartAllowed()); 1066 root.setFocusOutEnd(isFocusOutEndAllowed()); 1067 1068 ViewGroup guidanceContainer = (ViewGroup) root.findViewById(R.id.content_fragment); 1069 ViewGroup actionContainer = (ViewGroup) root.findViewById(R.id.action_fragment); 1070 ((NonOverlappingLinearLayout) actionContainer).setFocusableViewAvailableFixEnabled(true); 1071 1072 Guidance guidance = onCreateGuidance(savedInstanceState); 1073 View guidanceView = mGuidanceStylist.onCreateView(inflater, guidanceContainer, guidance); 1074 guidanceContainer.addView(guidanceView); 1075 1076 View actionsView = mActionsStylist.onCreateView(inflater, actionContainer); 1077 actionContainer.addView(actionsView); 1078 1079 View buttonActionsView = mButtonActionsStylist.onCreateView(inflater, actionContainer); 1080 actionContainer.addView(buttonActionsView); 1081 1082 GuidedActionAdapter.EditListener editListener = new GuidedActionAdapter.EditListener() { 1083 1084 @Override 1085 public void onImeOpen() { 1086 runImeAnimations(true); 1087 } 1088 1089 @Override 1090 public void onImeClose() { 1091 runImeAnimations(false); 1092 } 1093 1094 @Override 1095 public long onGuidedActionEditedAndProceed(GuidedAction action) { 1096 return GuidedStepFragment.this.onGuidedActionEditedAndProceed(action); 1097 } 1098 1099 @Override 1100 public void onGuidedActionEditCanceled(GuidedAction action) { 1101 GuidedStepFragment.this.onGuidedActionEditCanceled(action); 1102 } 1103 }; 1104 1105 mAdapter = new GuidedActionAdapter(mActions, new GuidedActionAdapter.ClickListener() { 1106 @Override 1107 public void onGuidedActionClicked(GuidedAction action) { 1108 GuidedStepFragment.this.onGuidedActionClicked(action); 1109 if (isExpanded()) { 1110 collapseAction(true); 1111 } else if (action.hasSubActions() || action.hasEditableActivatorView()) { 1112 expandAction(action, true); 1113 } 1114 } 1115 }, this, mActionsStylist, false); 1116 mButtonAdapter = 1117 new GuidedActionAdapter(mButtonActions, new GuidedActionAdapter.ClickListener() { 1118 @Override 1119 public void onGuidedActionClicked(GuidedAction action) { 1120 GuidedStepFragment.this.onGuidedActionClicked(action); 1121 } 1122 }, this, mButtonActionsStylist, false); 1123 mSubAdapter = new GuidedActionAdapter(null, new GuidedActionAdapter.ClickListener() { 1124 @Override 1125 public void onGuidedActionClicked(GuidedAction action) { 1126 if (mActionsStylist.isInExpandTransition()) { 1127 return; 1128 } 1129 if (GuidedStepFragment.this.onSubGuidedActionClicked(action)) { 1130 collapseSubActions(); 1131 } 1132 } 1133 }, this, mActionsStylist, true); 1134 mAdapterGroup = new GuidedActionAdapterGroup(); 1135 mAdapterGroup.addAdpter(mAdapter, mButtonAdapter); 1136 mAdapterGroup.addAdpter(mSubAdapter, null); 1137 mAdapterGroup.setEditListener(editListener); 1138 mActionsStylist.setEditListener(editListener); 1139 1140 mActionsStylist.getActionsGridView().setAdapter(mAdapter); 1141 if (mActionsStylist.getSubActionsGridView() != null) { 1142 mActionsStylist.getSubActionsGridView().setAdapter(mSubAdapter); 1143 } 1144 mButtonActionsStylist.getActionsGridView().setAdapter(mButtonAdapter); 1145 if (mButtonActions.size() == 0) { 1146 // when there is no button actions, we don't need show the second panel, but keep 1147 // the width zero to run ChangeBounds transition. 1148 LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) 1149 buttonActionsView.getLayoutParams(); 1150 lp.weight = 0; 1151 buttonActionsView.setLayoutParams(lp); 1152 } else { 1153 // when there are two actions panel, we need adjust the weight of action to 1154 // guidedActionContentWidthWeightTwoPanels. 1155 Context ctx = mThemeWrapper != null ? mThemeWrapper : FragmentUtil.getContext(GuidedStepFragment.this); 1156 TypedValue typedValue = new TypedValue(); 1157 if (ctx.getTheme().resolveAttribute(R.attr.guidedActionContentWidthWeightTwoPanels, 1158 typedValue, true)) { 1159 View actionsRoot = root.findViewById(R.id.action_fragment_root); 1160 float weight = typedValue.getFloat(); 1161 LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) actionsRoot 1162 .getLayoutParams(); 1163 lp.weight = weight; 1164 actionsRoot.setLayoutParams(lp); 1165 } 1166 } 1167 1168 // Add the background view. 1169 View backgroundView = onCreateBackgroundView(inflater, root, savedInstanceState); 1170 if (backgroundView != null) { 1171 FrameLayout backgroundViewRoot = (FrameLayout)root.findViewById( 1172 R.id.guidedstep_background_view_root); 1173 backgroundViewRoot.addView(backgroundView, 0); 1174 } 1175 1176 return root; 1177 } 1178 1179 @Override onResume()1180 public void onResume() { 1181 super.onResume(); 1182 getView().findViewById(R.id.action_fragment).requestFocus(); 1183 } 1184 1185 /** 1186 * Get the key will be used to save GuidedAction with Fragment. 1187 * @param action GuidedAction to get key. 1188 * @return Key to save the GuidedAction. 1189 */ getAutoRestoreKey(GuidedAction action)1190 final String getAutoRestoreKey(GuidedAction action) { 1191 return EXTRA_ACTION_PREFIX + action.getId(); 1192 } 1193 1194 /** 1195 * Get the key will be used to save GuidedAction with Fragment. 1196 * @param action GuidedAction to get key. 1197 * @return Key to save the GuidedAction. 1198 */ getButtonAutoRestoreKey(GuidedAction action)1199 final String getButtonAutoRestoreKey(GuidedAction action) { 1200 return EXTRA_BUTTON_ACTION_PREFIX + action.getId(); 1201 } 1202 isSaveEnabled(GuidedAction action)1203 static boolean isSaveEnabled(GuidedAction action) { 1204 return action.isAutoSaveRestoreEnabled() && action.getId() != GuidedAction.NO_ID; 1205 } 1206 onRestoreActions(List<GuidedAction> actions, Bundle savedInstanceState)1207 final void onRestoreActions(List<GuidedAction> actions, Bundle savedInstanceState) { 1208 for (int i = 0, size = actions.size(); i < size; i++) { 1209 GuidedAction action = actions.get(i); 1210 if (isSaveEnabled(action)) { 1211 action.onRestoreInstanceState(savedInstanceState, getAutoRestoreKey(action)); 1212 } 1213 } 1214 } 1215 onRestoreButtonActions(List<GuidedAction> actions, Bundle savedInstanceState)1216 final void onRestoreButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) { 1217 for (int i = 0, size = actions.size(); i < size; i++) { 1218 GuidedAction action = actions.get(i); 1219 if (isSaveEnabled(action)) { 1220 action.onRestoreInstanceState(savedInstanceState, getButtonAutoRestoreKey(action)); 1221 } 1222 } 1223 } 1224 onSaveActions(List<GuidedAction> actions, Bundle outState)1225 final void onSaveActions(List<GuidedAction> actions, Bundle outState) { 1226 for (int i = 0, size = actions.size(); i < size; i++) { 1227 GuidedAction action = actions.get(i); 1228 if (isSaveEnabled(action)) { 1229 action.onSaveInstanceState(outState, getAutoRestoreKey(action)); 1230 } 1231 } 1232 } 1233 onSaveButtonActions(List<GuidedAction> actions, Bundle outState)1234 final void onSaveButtonActions(List<GuidedAction> actions, Bundle outState) { 1235 for (int i = 0, size = actions.size(); i < size; i++) { 1236 GuidedAction action = actions.get(i); 1237 if (isSaveEnabled(action)) { 1238 action.onSaveInstanceState(outState, getButtonAutoRestoreKey(action)); 1239 } 1240 } 1241 } 1242 1243 /** 1244 * {@inheritDoc} 1245 */ 1246 @Override onSaveInstanceState(Bundle outState)1247 public void onSaveInstanceState(Bundle outState) { 1248 super.onSaveInstanceState(outState); 1249 onSaveActions(mActions, outState); 1250 onSaveButtonActions(mButtonActions, outState); 1251 } 1252 isGuidedStepTheme(Context context)1253 private static boolean isGuidedStepTheme(Context context) { 1254 int resId = R.attr.guidedStepThemeFlag; 1255 TypedValue typedValue = new TypedValue(); 1256 boolean found = context.getTheme().resolveAttribute(resId, typedValue, true); 1257 if (DEBUG) Log.v(TAG, "Found guided step theme flag? " + found); 1258 return found && typedValue.type == TypedValue.TYPE_INT_BOOLEAN && typedValue.data != 0; 1259 } 1260 1261 /** 1262 * Convenient method to close GuidedStepFragments on top of other content or finish Activity if 1263 * GuidedStepFragments were started in a separate activity. Pops all stack entries including 1264 * {@link #UI_STYLE_ENTRANCE}; if {@link #UI_STYLE_ENTRANCE} is not found, finish the activity. 1265 * Note that this method must be paired with {@link #add(FragmentManager, GuidedStepFragment, 1266 * int)} which sets up the stack entry name for finding which fragment we need to pop back to. 1267 */ finishGuidedStepFragments()1268 public void finishGuidedStepFragments() { 1269 final FragmentManager fragmentManager = getFragmentManager(); 1270 final int entryCount = fragmentManager.getBackStackEntryCount(); 1271 if (entryCount > 0) { 1272 for (int i = entryCount - 1; i >= 0; i--) { 1273 BackStackEntry entry = fragmentManager.getBackStackEntryAt(i); 1274 if (isStackEntryUiStyleEntrance(entry.getName())) { 1275 GuidedStepFragment top = getCurrentGuidedStepFragment(fragmentManager); 1276 if (top != null) { 1277 top.setUiStyle(UI_STYLE_ENTRANCE); 1278 } 1279 fragmentManager.popBackStackImmediate(entry.getId(), 1280 FragmentManager.POP_BACK_STACK_INCLUSIVE); 1281 return; 1282 } 1283 } 1284 } 1285 ActivityCompat.finishAfterTransition(getActivity()); 1286 } 1287 1288 /** 1289 * Convenient method to pop to fragment with Given class. 1290 * @param guidedStepFragmentClass Name of the Class of GuidedStepFragment to pop to. 1291 * @param flags Either 0 or {@link FragmentManager#POP_BACK_STACK_INCLUSIVE}. 1292 */ popBackStackToGuidedStepFragment(Class guidedStepFragmentClass, int flags)1293 public void popBackStackToGuidedStepFragment(Class guidedStepFragmentClass, int flags) { 1294 if (!GuidedStepFragment.class.isAssignableFrom(guidedStepFragmentClass)) { 1295 return; 1296 } 1297 final FragmentManager fragmentManager = getFragmentManager(); 1298 final int entryCount = fragmentManager.getBackStackEntryCount(); 1299 String className = guidedStepFragmentClass.getName(); 1300 if (entryCount > 0) { 1301 for (int i = entryCount - 1; i >= 0; i--) { 1302 BackStackEntry entry = fragmentManager.getBackStackEntryAt(i); 1303 String entryClassName = getGuidedStepFragmentClassName(entry.getName()); 1304 if (className.equals(entryClassName)) { 1305 fragmentManager.popBackStackImmediate(entry.getId(), flags); 1306 return; 1307 } 1308 } 1309 } 1310 } 1311 1312 /** 1313 * Returns true if allows focus out of start edge of GuidedStepFragment, false otherwise. 1314 * Default value is false, the reason is to disable FocusFinder to find focusable views 1315 * beneath content of GuidedStepFragment. Subclass may override. 1316 * @return True if allows focus out of start edge of GuidedStepFragment. 1317 */ isFocusOutStartAllowed()1318 public boolean isFocusOutStartAllowed() { 1319 return false; 1320 } 1321 1322 /** 1323 * Returns true if allows focus out of end edge of GuidedStepFragment, false otherwise. 1324 * Default value is false, the reason is to disable FocusFinder to find focusable views 1325 * beneath content of GuidedStepFragment. Subclass may override. 1326 * @return True if allows focus out of end edge of GuidedStepFragment. 1327 */ isFocusOutEndAllowed()1328 public boolean isFocusOutEndAllowed() { 1329 return false; 1330 } 1331 1332 /** 1333 * Sets the transition type to be used for {@link #UI_STYLE_ENTRANCE} animation. 1334 * Currently we provide 2 different variations for animation - slide in from 1335 * side (default) or bottom. 1336 * 1337 * Ideally we can retrieve the screen mode settings from the theme attribute 1338 * {@code Theme.Leanback.GuidedStep#guidedStepHeightWeight} and use that to 1339 * determine the transition. But the fragment context to retrieve the theme 1340 * isn't available on platform v23 or earlier. 1341 * 1342 * For now clients(subclasses) can call this method inside the constructor. 1343 * @hide 1344 */ 1345 @RestrictTo(LIBRARY_GROUP) setEntranceTransitionType(int transitionType)1346 public void setEntranceTransitionType(int transitionType) { 1347 this.entranceTransitionType = transitionType; 1348 } 1349 1350 /** 1351 * Opens the provided action in edit mode and raises ime. This can be 1352 * used to programmatically skip the extra click required to go into edit mode. This method 1353 * can be invoked in {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}. 1354 */ openInEditMode(GuidedAction action)1355 public void openInEditMode(GuidedAction action) { 1356 mActionsStylist.openInEditMode(action); 1357 } 1358 resolveTheme()1359 private void resolveTheme() { 1360 // Look up the guidedStepTheme in the currently specified theme. If it exists, 1361 // replace the theme with its value. 1362 Context context = FragmentUtil.getContext(GuidedStepFragment.this); 1363 int theme = onProvideTheme(); 1364 if (theme == -1 && !isGuidedStepTheme(context)) { 1365 // Look up the guidedStepTheme in the activity's currently specified theme. If it 1366 // exists, replace the theme with its value. 1367 int resId = R.attr.guidedStepTheme; 1368 TypedValue typedValue = new TypedValue(); 1369 boolean found = context.getTheme().resolveAttribute(resId, typedValue, true); 1370 if (DEBUG) Log.v(TAG, "Found guided step theme reference? " + found); 1371 if (found) { 1372 ContextThemeWrapper themeWrapper = 1373 new ContextThemeWrapper(context, typedValue.resourceId); 1374 if (isGuidedStepTheme(themeWrapper)) { 1375 mThemeWrapper = themeWrapper; 1376 } else { 1377 found = false; 1378 mThemeWrapper = null; 1379 } 1380 } 1381 if (!found) { 1382 Log.e(TAG, "GuidedStepFragment does not have an appropriate theme set."); 1383 } 1384 } else if (theme != -1) { 1385 mThemeWrapper = new ContextThemeWrapper(context, theme); 1386 } 1387 } 1388 getThemeInflater(LayoutInflater inflater)1389 private LayoutInflater getThemeInflater(LayoutInflater inflater) { 1390 if (mThemeWrapper == null) { 1391 return inflater; 1392 } else { 1393 return inflater.cloneInContext(mThemeWrapper); 1394 } 1395 } 1396 getFirstCheckedAction()1397 private int getFirstCheckedAction() { 1398 for (int i = 0, size = mActions.size(); i < size; i++) { 1399 if (mActions.get(i).isChecked()) { 1400 return i; 1401 } 1402 } 1403 return 0; 1404 } 1405 runImeAnimations(boolean entering)1406 void runImeAnimations(boolean entering) { 1407 ArrayList<Animator> animators = new ArrayList<Animator>(); 1408 if (entering) { 1409 mGuidanceStylist.onImeAppearing(animators); 1410 mActionsStylist.onImeAppearing(animators); 1411 mButtonActionsStylist.onImeAppearing(animators); 1412 } else { 1413 mGuidanceStylist.onImeDisappearing(animators); 1414 mActionsStylist.onImeDisappearing(animators); 1415 mButtonActionsStylist.onImeDisappearing(animators); 1416 } 1417 AnimatorSet set = new AnimatorSet(); 1418 set.playTogether(animators); 1419 set.start(); 1420 } 1421 } 1422