public abstract class

FragmentManager

extends java.lang.Object

implements FragmentResultOwner

 java.lang.Object

↳androidx.fragment.app.FragmentManager

Gradle dependencies

compile group: 'androidx.fragment', name: 'fragment', version: '1.8.3'

  • groupId: androidx.fragment
  • artifactId: fragment
  • version: 1.8.3

Artifact androidx.fragment:fragment:1.8.3 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.fragment:fragment com.android.support:support-fragment

Androidx class mapping:

androidx.fragment.app.FragmentManager android.support.v4.app.FragmentManager

Overview

Static library support version of the framework's android.app.FragmentManager. Used to write apps that run on platforms prior to Android 3.0. When running on Android 3.0 or above, this implementation is still used; it does not try to switch to the framework's implementation. See the framework FragmentManager documentation for a class overview.

Your activity must derive from FragmentActivity to use this. From such an activity, you can acquire the FragmentManager by calling FragmentActivity.getSupportFragmentManager().

Summary

Fields
public static final intPOP_BACK_STACK_INCLUSIVE

Flag for FragmentManager.popBackStack(String, int) and FragmentManager.popBackStack(int, int): If set, and the name or ID of a back stack entry has been supplied, then all matching entries will be consumed until one that doesn't match is found or the bottom of the stack is reached.

public static final java.lang.StringTAG

Constructors
publicFragmentManager()

Methods
public voidaddFragmentOnAttachListener(FragmentOnAttachListener listener)

Add a FragmentOnAttachListener that should receive a call to FragmentOnAttachListener.onAttachFragment(FragmentManager, Fragment) when a new Fragment is attached to this FragmentManager.

public voidaddOnBackStackChangedListener(FragmentManager.OnBackStackChangedListener listener)

Add a new listener for changes to the fragment back stack.

public FragmentTransactionbeginTransaction()

Start a series of edit operations on the Fragments associated with this FragmentManager.

public voidclearBackStack(java.lang.String name)

Clears the back stack previously saved via FragmentManager.saveBackStack(String).

public final voidclearFragmentResult(java.lang.String requestKey)

public final voidclearFragmentResultListener(java.lang.String requestKey)

public voiddump(java.lang.String prefix, java.io.FileDescriptor fd, java.io.PrintWriter writer, java.lang.String args[])

Print the FragmentManager's state into the given stream.

public static voidenableDebugLogging(boolean enabled)

Control whether the framework's internal fragment manager debugging logs are turned on.

public static voidenablePredictiveBack(boolean enabled)

Control whether FragmentManager uses the new state predictive back feature that allows seeing the previous Fragment when using gesture back.

public booleanexecutePendingTransactions()

After a FragmentTransaction is committed with FragmentTransaction.commit(), it is scheduled to be executed asynchronously on the process's main thread.

public static FragmentfindFragment(View view)

Find a Fragment associated with the given View.

public FragmentfindFragmentById(int id)

Finds a fragment that was identified by the given id either when inflated from XML or as the container ID when added in a transaction.

public FragmentfindFragmentByTag(java.lang.String tag)

Finds a fragment that was identified by the given tag either when inflated from XML or as supplied when added in a transaction.

public static FragmentManagerfindFragmentManager(View view)

Recurse up the view hierarchy, looking for a FragmentManager

public FragmentManager.BackStackEntrygetBackStackEntryAt(int index)

Return the BackStackEntry at index index in the back stack; entries start index 0 being the bottom of the stack.

public intgetBackStackEntryCount()

Return the number of entries currently in the back stack.

public FragmentgetFragment(Bundle bundle, java.lang.String key)

Retrieve the current Fragment instance for a reference previously placed with FragmentManager.putFragment(Bundle, String, Fragment).

public FragmentFactorygetFragmentFactory()

Gets the current FragmentFactory used to instantiate new Fragment instances.

public java.util.List<Fragment>getFragments()

Get a list of all fragments that are currently added to the FragmentManager.

public FragmentHostCallback<java.lang.Object>getHost()

public FragmentgetPrimaryNavigationFragment()

Return the currently active primary navigation fragment for this FragmentManager.

public FragmentStrictMode.PolicygetStrictModePolicy()

Returns the current policy for this FragmentManager.

public booleanisDestroyed()

Returns true if the final call has been made on the FragmentManager's Activity, so this instance is now dead.

public static booleanisLoggingEnabled(int level)

public booleanisStateSaved()

Returns true if the FragmentManager's state has already been saved by its host.

public final voidonContainerAvailable(FragmentContainerView container)

Callback for when the FragmentContainerView becomes available in the view hierarchy and the fragment manager can add the fragment view to its hierarchy.

public FragmentTransactionopenTransaction()

public voidpopBackStack()

Pop the top state off the back stack.

public voidpopBackStack(int id, int flags)

Pop all back stack states up to the one with the given identifier.

public voidpopBackStack(java.lang.String name, int flags)

Pop the last fragment transition from the manager's fragment back stack.

public booleanpopBackStackImmediate()

Like FragmentManager.popBackStack(), but performs the operation immediately inside of the call.

public booleanpopBackStackImmediate(int id, int flags)

Like FragmentManager.popBackStack(int, int), but performs the operation immediately inside of the call.

public booleanpopBackStackImmediate(java.lang.String name, int flags)

Like FragmentManager.popBackStack(String, int), but performs the operation immediately inside of the call.

public voidputFragment(Bundle bundle, java.lang.String key, Fragment fragment)

Put a reference to a fragment in a Bundle.

public voidregisterFragmentLifecycleCallbacks(FragmentManager.FragmentLifecycleCallbacks cb, boolean recursive)

Registers a FragmentManager.FragmentLifecycleCallbacks to listen to fragment lifecycle events happening in this FragmentManager.

public voidremoveFragmentOnAttachListener(FragmentOnAttachListener listener)

Remove a FragmentOnAttachListener that was previously added via FragmentManager.addFragmentOnAttachListener(FragmentOnAttachListener).

public voidremoveOnBackStackChangedListener(FragmentManager.OnBackStackChangedListener listener)

Remove a listener that was previously added with FragmentManager.addOnBackStackChangedListener(FragmentManager.OnBackStackChangedListener).

public voidrestoreBackStack(java.lang.String name)

Restores the back stack previously saved via FragmentManager.saveBackStack(String).

public voidsaveBackStack(java.lang.String name)

Save the back stack.

public Fragment.SavedStatesaveFragmentInstanceState(Fragment fragment)

Save the current instance state of the given Fragment.

public voidsetFragmentFactory(FragmentFactory fragmentFactory)

Set a FragmentFactory for this FragmentManager that will be used to create new Fragment instances from this point onward.

public final voidsetFragmentResult(java.lang.String requestKey, Bundle result)

public final voidsetFragmentResultListener(java.lang.String requestKey, LifecycleOwner lifecycleOwner, FragmentResultListener listener)

public voidsetStrictModePolicy(FragmentStrictMode.Policy policy)

Sets the policy for what actions should be detected, as well as the penalty if such actions occur.

public java.lang.StringtoString()

public voidunregisterFragmentLifecycleCallbacks(FragmentManager.FragmentLifecycleCallbacks cb)

Unregisters a previously registered FragmentManager.FragmentLifecycleCallbacks.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait

Fields

public static final java.lang.String TAG

public static final int POP_BACK_STACK_INCLUSIVE

Flag for FragmentManager.popBackStack(String, int) and FragmentManager.popBackStack(int, int): If set, and the name or ID of a back stack entry has been supplied, then all matching entries will be consumed until one that doesn't match is found or the bottom of the stack is reached. Otherwise, all entries up to but not including that entry will be removed.

Constructors

public FragmentManager()

Methods

public static void enablePredictiveBack(boolean enabled)

Control whether FragmentManager uses the new state predictive back feature that allows seeing the previous Fragment when using gesture back.

This should only be changed before any fragment transactions are done (i.e., in your Application class or prior to super.onCreate() in every activity).

Parameters:

enabled: Whether predictive back should be enabled.

public static void enableDebugLogging(boolean enabled)

Deprecated: FragmentManager now respects for debug logging, allowing you to use adb shell setprop log.tag.FragmentManager VERBOSE.

Control whether the framework's internal fragment manager debugging logs are turned on. If enabled, you will see output in logcat as the framework performs fragment operations.

See also:

public static boolean isLoggingEnabled(int level)

public FragmentTransaction openTransaction()

Deprecated: Use FragmentManager.beginTransaction().

public FragmentTransaction beginTransaction()

Start a series of edit operations on the Fragments associated with this FragmentManager.

Note: A fragment transaction can only be created/committed prior to an activity saving its state. If you try to commit a transaction after FragmentActivity.onSaveInstanceState() (and prior to a following FragmentActivity.onStart or FragmentActivity.onResume(), you will get an error. This is because the framework takes care of saving your current fragments in the state, and if changes are made after the state is saved then they will be lost.

public boolean executePendingTransactions()

After a FragmentTransaction is committed with FragmentTransaction.commit(), it is scheduled to be executed asynchronously on the process's main thread. If you want to immediately executing any such pending operations, you can call this function (only from the main thread) to do so. Note that all callbacks and other related behavior will be done from within this call, so be careful about where this is called from.

If you are committing a single transaction that does not modify the fragment back stack, strongly consider using FragmentTransaction.commitNow() instead. This can help avoid unwanted side effects when other code in your app has pending committed transactions that expect different timing.

This also forces the start of any postponed Transactions where Fragment.postponeEnterTransition() has been called.

Returns:

Returns true if there were any pending transactions to be executed.

public void restoreBackStack(java.lang.String name)

Restores the back stack previously saved via FragmentManager.saveBackStack(String). This will result in all of the transactions that made up that back stack to be re-executed, thus re-adding any fragments that were added through those transactions. All state of those fragments will be restored as part of this process. If no state was previously saved with the given name, this operation does nothing.

This function is asynchronous -- it enqueues the request to restore, but the action will not be performed until the application returns to its event loop.

Parameters:

name: The name of the back stack previously saved by FragmentManager.saveBackStack(String).

public void saveBackStack(java.lang.String name)

Save the back stack. While this functions similarly to FragmentManager.popBackStack(String, int), it does not throw away the state of any fragments that were added through those transactions. Instead, the back stack that is saved by this method can later be restored with its state in tact.

This function is asynchronous -- it enqueues the request to pop, but the action will not be performed until the application returns to its event loop.

Parameters:

name: The name set by FragmentTransaction.addToBackStack(String).

public void clearBackStack(java.lang.String name)

Clears the back stack previously saved via FragmentManager.saveBackStack(String). This will result in all of the transactions that made up that back stack to be thrown away, thus destroying any fragments that were added through those transactions. All state of those fragments will be cleared as part of this process. If no state was previously saved with the given name, this operation does nothing.

This function is asynchronous -- it enqueues the request to clear, but the action will not be performed until the application returns to its event loop.

Parameters:

name: The name of the back stack previously saved by FragmentManager.saveBackStack(String).

public void popBackStack()

Pop the top state off the back stack. This function is asynchronous -- it enqueues the request to pop, but the action will not be performed until the application returns to its event loop.

public boolean popBackStackImmediate()

Like FragmentManager.popBackStack(), but performs the operation immediately inside of the call. This is like calling FragmentManager.executePendingTransactions() afterwards without forcing the start of postponed Transactions.

Returns:

Returns true if there was something popped, else false.

public void popBackStack(java.lang.String name, int flags)

Pop the last fragment transition from the manager's fragment back stack. This function is asynchronous -- it enqueues the request to pop, but the action will not be performed until the application returns to its event loop.

Parameters:

name: If non-null, this is the name of a previous back state to look for; if found, all states up to that state will be popped. The FragmentManager.POP_BACK_STACK_INCLUSIVE flag can be used to control whether the named state itself is popped. If null, only the top state is popped.
flags: Either 0 or FragmentManager.POP_BACK_STACK_INCLUSIVE.

public boolean popBackStackImmediate(java.lang.String name, int flags)

Like FragmentManager.popBackStack(String, int), but performs the operation immediately inside of the call. This is like calling FragmentManager.executePendingTransactions() afterwards without forcing the start of postponed Transactions.

Returns:

Returns true if there was something popped, else false.

public void popBackStack(int id, int flags)

Pop all back stack states up to the one with the given identifier. This function is asynchronous -- it enqueues the request to pop, but the action will not be performed until the application returns to its event loop.

Parameters:

id: Identifier of the stated to be popped. If no identifier exists, false is returned. The identifier is the number returned by FragmentTransaction.commit(). The FragmentManager.POP_BACK_STACK_INCLUSIVE flag can be used to control whether the named state itself is popped.
flags: Either 0 or FragmentManager.POP_BACK_STACK_INCLUSIVE.

public boolean popBackStackImmediate(int id, int flags)

Like FragmentManager.popBackStack(int, int), but performs the operation immediately inside of the call. This is like calling FragmentManager.executePendingTransactions() afterwards without forcing the start of postponed Transactions.

Returns:

Returns true if there was something popped, else false.

public int getBackStackEntryCount()

Return the number of entries currently in the back stack.

public FragmentManager.BackStackEntry getBackStackEntryAt(int index)

Return the BackStackEntry at index index in the back stack; entries start index 0 being the bottom of the stack.

public void addOnBackStackChangedListener(FragmentManager.OnBackStackChangedListener listener)

Add a new listener for changes to the fragment back stack.

public void removeOnBackStackChangedListener(FragmentManager.OnBackStackChangedListener listener)

Remove a listener that was previously added with FragmentManager.addOnBackStackChangedListener(FragmentManager.OnBackStackChangedListener).

public final void setFragmentResult(java.lang.String requestKey, Bundle result)

public final void clearFragmentResult(java.lang.String requestKey)

public final void setFragmentResultListener(java.lang.String requestKey, LifecycleOwner lifecycleOwner, FragmentResultListener listener)

public final void clearFragmentResultListener(java.lang.String requestKey)

public void putFragment(Bundle bundle, java.lang.String key, Fragment fragment)

Put a reference to a fragment in a Bundle. This Bundle can be persisted as saved state, and when later restoring FragmentManager.getFragment(Bundle, String) will return the current instance of the same fragment.

Parameters:

bundle: The bundle in which to put the fragment reference.
key: The name of the entry in the bundle.
fragment: The Fragment whose reference is to be stored.

public Fragment getFragment(Bundle bundle, java.lang.String key)

Retrieve the current Fragment instance for a reference previously placed with FragmentManager.putFragment(Bundle, String, Fragment).

Parameters:

bundle: The bundle from which to retrieve the fragment reference.
key: The name of the entry in the bundle.

Returns:

Returns the current Fragment instance that is associated with the given reference.

public static Fragment findFragment(View view)

Find a Fragment associated with the given View. This method will locate the Fragment associated with this view. This is automatically populated for the View returned by Fragment.onCreateView(LayoutInflater, ViewGroup, Bundle) and its children.

Parameters:

view: the view to search from

Returns:

the locally scoped Fragment to the given view

public final void onContainerAvailable(FragmentContainerView container)

Callback for when the FragmentContainerView becomes available in the view hierarchy and the fragment manager can add the fragment view to its hierarchy.

Parameters:

container: the container that the active fragment should add their views to

public static FragmentManager findFragmentManager(View view)

Recurse up the view hierarchy, looking for a FragmentManager

Parameters:

view: the view to search from

Returns:

The containing FragmentManager of the given view.

public java.util.List<Fragment> getFragments()

Get a list of all fragments that are currently added to the FragmentManager. This may include those that are hidden as well as those that are shown. This will not include any fragments only in the back stack, or fragments that are detached or removed.

The order of the fragments in the list is the order in which they were added or attached.

Returns:

A list of all fragments that are added to the FragmentManager.

public Fragment.SavedState saveFragmentInstanceState(Fragment fragment)

Save the current instance state of the given Fragment. This can be used later when creating a new instance of the Fragment and adding it to the fragment manager, to have it create itself to match the current state returned here. Note that there are limits on how this can be used:

  • The Fragment must currently be attached to the FragmentManager.
  • A new Fragment created using this saved state must be the same class type as the Fragment it was created from.
  • The saved state can not contain dependencies on other fragments -- that is it can't use FragmentManager.putFragment(Bundle, String, Fragment) to store a fragment reference because that reference may not be valid when this saved state is later used. Likewise the Fragment's target and result code are not included in this state.

Parameters:

fragment: The Fragment whose state is to be saved.

Returns:

The generated state. This will be null if there was no interesting state created by the fragment.

public boolean isDestroyed()

Returns true if the final call has been made on the FragmentManager's Activity, so this instance is now dead.

public java.lang.String toString()

public void dump(java.lang.String prefix, java.io.FileDescriptor fd, java.io.PrintWriter writer, java.lang.String args[])

Print the FragmentManager's state into the given stream.

Parameters:

prefix: Text to print at the front of each line.
fd: The raw file descriptor that the dump is being sent to.
writer: A PrintWriter to which the dump is to be set.
args: Additional arguments to the dump request.

public Fragment findFragmentById(int id)

Finds a fragment that was identified by the given id either when inflated from XML or as the container ID when added in a transaction. This first searches through fragments that are currently added to the manager's activity; if no such fragment is found, then all fragments currently on the back stack associated with this ID are searched.

Returns:

The fragment if found or null otherwise.

public Fragment findFragmentByTag(java.lang.String tag)

Finds a fragment that was identified by the given tag either when inflated from XML or as supplied when added in a transaction. This first searches through fragments that are currently added to the manager's activity; if no such fragment is found, then all fragments currently on the back stack are searched.

If provided a null tag, this method returns null.

Parameters:

tag: the tag used to search for the fragment

Returns:

The fragment if found or null otherwise.

public boolean isStateSaved()

Returns true if the FragmentManager's state has already been saved by its host. Any operations that would change saved state should not be performed if this method returns true. For example, any popBackStack() method, such as FragmentManager.popBackStackImmediate() or any FragmentTransaction using FragmentTransaction.commit() instead of FragmentTransaction.commitAllowingStateLoss() will change the state and will result in an error.

Returns:

true if this FragmentManager's state has already been saved by its host

public FragmentHostCallback<java.lang.Object> getHost()

public Fragment getPrimaryNavigationFragment()

Return the currently active primary navigation fragment for this FragmentManager. The primary navigation fragment is set by fragment transactions using FragmentTransaction.setPrimaryNavigationFragment(Fragment).

The primary navigation fragment's child FragmentManager will be called first to process delegated navigation actions such as FragmentManager.popBackStack() if no ID or transaction name is provided to pop to.

Returns:

the fragment designated as the primary navigation fragment

public void setFragmentFactory(FragmentFactory fragmentFactory)

Set a FragmentFactory for this FragmentManager that will be used to create new Fragment instances from this point onward.

The child FragmentManager of all Fragments in this FragmentManager will also use this factory if one is not explicitly set.

Parameters:

fragmentFactory: the factory to use to create new Fragment instances

See also: FragmentManager.getFragmentFactory()

public FragmentFactory getFragmentFactory()

Gets the current FragmentFactory used to instantiate new Fragment instances.

If no factory has been explicitly set on this FragmentManager via FragmentManager.setFragmentFactory(FragmentFactory), the FragmentFactory of the parent FragmentManager will be returned.

Returns:

the current FragmentFactory

public void registerFragmentLifecycleCallbacks(FragmentManager.FragmentLifecycleCallbacks cb, boolean recursive)

Registers a FragmentManager.FragmentLifecycleCallbacks to listen to fragment lifecycle events happening in this FragmentManager. All registered callbacks will be automatically unregistered when this FragmentManager is destroyed.

Parameters:

cb: Callbacks to register
recursive: true to automatically register this callback for all child FragmentManagers

public void unregisterFragmentLifecycleCallbacks(FragmentManager.FragmentLifecycleCallbacks cb)

Unregisters a previously registered FragmentManager.FragmentLifecycleCallbacks. If the callback was not previously registered this call has no effect. All registered callbacks will be automatically unregistered when this FragmentManager is destroyed.

Parameters:

cb: Callbacks to unregister

public void addFragmentOnAttachListener(FragmentOnAttachListener listener)

Add a FragmentOnAttachListener that should receive a call to FragmentOnAttachListener.onAttachFragment(FragmentManager, Fragment) when a new Fragment is attached to this FragmentManager.

Parameters:

listener: Listener to add

public void removeFragmentOnAttachListener(FragmentOnAttachListener listener)

Remove a FragmentOnAttachListener that was previously added via FragmentManager.addFragmentOnAttachListener(FragmentOnAttachListener). It will no longer get called when a new Fragment is attached.

Parameters:

listener: Listener to remove

public FragmentStrictMode.Policy getStrictModePolicy()

Returns the current policy for this FragmentManager. If no policy is set, returns null.

public void setStrictModePolicy(FragmentStrictMode.Policy policy)

Sets the policy for what actions should be detected, as well as the penalty if such actions occur. The child FragmentManager of all Fragments in this FragmentManager will also use this policy if one is not explicitly set. Pass null to clear the policy.

Parameters:

policy: the policy to put into place

Source

/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.fragment.app;

import static androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult.EXTRA_ACTIVITY_OPTIONS_BUNDLE;
import static androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult.ACTION_INTENT_SENDER_REQUEST;
import static androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult.EXTRA_INTENT_SENDER_REQUEST;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;

import androidx.activity.BackEventCompat;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
import androidx.activity.OnBackPressedDispatcherOwner;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.ActivityResultRegistry;
import androidx.activity.result.ActivityResultRegistryOwner;
import androidx.activity.result.IntentSenderRequest;
import androidx.activity.result.contract.ActivityResultContract;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.IdRes;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.StringRes;
import androidx.core.app.MultiWindowModeChangedInfo;
import androidx.core.app.OnMultiWindowModeChangedProvider;
import androidx.core.app.OnPictureInPictureModeChangedProvider;
import androidx.core.app.PictureInPictureModeChangedInfo;
import androidx.core.content.OnConfigurationChangedProvider;
import androidx.core.content.OnTrimMemoryProvider;
import androidx.core.util.Consumer;
import androidx.core.view.MenuHost;
import androidx.core.view.MenuProvider;
import androidx.fragment.R;
import androidx.fragment.app.strictmode.FragmentStrictMode;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelStore;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.savedstate.SavedStateRegistry;
import androidx.savedstate.SavedStateRegistryOwner;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Static library support version of the framework's {@link android.app.FragmentManager}.
 * Used to write apps that run on platforms prior to Android 3.0.  When running
 * on Android 3.0 or above, this implementation is still used; it does not try
 * to switch to the framework's implementation.  See the framework {@link FragmentManager}
 * documentation for a class overview.
 *
 * <p>Your activity must derive from {@link FragmentActivity} to use this. From such an activity,
 * you can acquire the {@link FragmentManager} by calling
 * {@link FragmentActivity#getSupportFragmentManager}.
 */
public abstract class FragmentManager implements FragmentResultOwner {
    private static final String SAVED_STATE_KEY = "android:support:fragments";
    private static final String FRAGMENT_MANAGER_STATE_KEY = "state";
    private static final String RESULT_KEY_PREFIX = "result_";
    private static final String FRAGMENT_KEY_PREFIX = "fragment_";

    private static boolean DEBUG = false;

    @RestrictTo(RestrictTo.Scope.LIBRARY)
    public static final String TAG = "FragmentManager";

    static boolean USE_PREDICTIVE_BACK = true;

    /**
     * Control whether FragmentManager uses the new state predictive back feature that allows
     * seeing the previous Fragment when using gesture back.
     * <p>
     * This should only be changed <strong>before</strong> any fragment transactions are done
     * (i.e., in your <code>Application</code> class or prior to <code>super.onCreate()</code>
     * in every activity).
     *
     * @param enabled Whether predictive back should be enabled.
     */
    @PredictiveBackControl
    public static void enablePredictiveBack(boolean enabled) {
        FragmentManager.USE_PREDICTIVE_BACK = enabled;
    }

    /**
     * Control whether the framework's internal fragment manager debugging
     * logs are turned on.  If enabled, you will see output in logcat as
     * the framework performs fragment operations.
     * @deprecated FragmentManager now respects {@link Log#isLoggable(String, int)} for debug
     * logging, allowing you to use <code>adb shell setprop log.tag.FragmentManager VERBOSE</code>.
     * @see Log#isLoggable(String, int)
     */
    @Deprecated
    public static void enableDebugLogging(boolean enabled) {
        FragmentManager.DEBUG = enabled;
    }

    @RestrictTo(RestrictTo.Scope.LIBRARY)
    public static boolean isLoggingEnabled(int level) {
        return DEBUG || Log.isLoggable(TAG, level);
    }

    /**
     * Flag for {@link #popBackStack(String, int)}
     * and {@link #popBackStack(int, int)}: If set, and the name or ID of
     * a back stack entry has been supplied, then all matching entries will
     * be consumed until one that doesn't match is found or the bottom of
     * the stack is reached.  Otherwise, all entries up to but not including that entry
     * will be removed.
     */
    public static final int POP_BACK_STACK_INCLUSIVE = 1;

    /**
     * Representation of an entry on the fragment back stack, as created
     * with {@link FragmentTransaction#addToBackStack(String)
     * FragmentTransaction.addToBackStack()}.  Entries can later be
     * retrieved with {@link FragmentManager#getBackStackEntryAt(int)
     * FragmentManager.getBackStackEntryAt()}.
     *
     * <p>Note that you should never hold on to a BackStackEntry object;
     * the identifier as returned by {@link #getId} is the only thing that
     * will be persisted across activity instances.
     */
    public interface BackStackEntry {
        /**
         * Return the unique identifier for the entry.  This is the only
         * representation of the entry that will persist across activity
         * instances.
         */
        int getId();

        /**
         * Get the name that was supplied to
         * {@link FragmentTransaction#addToBackStack(String)
         * FragmentTransaction.addToBackStack(String)} when creating this entry.
         */
        @Nullable
        String getName();

        /**
         * Return the full bread crumb title resource identifier for the entry,
         * or 0 if it does not have one.
         * @deprecated Store breadcrumb titles separately from back stack entries. For example,
         * by using an <code>android:label</code> on a fragment in a navigation graph.
         */
        @Deprecated
        @StringRes
        int getBreadCrumbTitleRes();

        /**
         * Return the short bread crumb title resource identifier for the entry,
         * or 0 if it does not have one.
         * @deprecated Store breadcrumb short titles separately from back stack entries. For
         * example, by using an <code>android:label</code> on a fragment in a navigation graph.
         */
        @Deprecated
        @StringRes
        int getBreadCrumbShortTitleRes();

        /**
         * Return the full bread crumb title for the entry, or null if it
         * does not have one.
         * @deprecated Store breadcrumb titles separately from back stack entries. For example,
         *          * by using an <code>android:label</code> on a fragment in a navigation graph.
         */
        @Deprecated
        @Nullable
        CharSequence getBreadCrumbTitle();

        /**
         * Return the short bread crumb title for the entry, or null if it
         * does not have one.
         * @deprecated Store breadcrumb short titles separately from back stack entries. For
         * example, by using an <code>android:label</code> on a fragment in a navigation graph.
         */
        @Deprecated
        @Nullable
        CharSequence getBreadCrumbShortTitle();
    }

    /**
     * Interface to watch for changes to the back stack.
     */
    public interface OnBackStackChangedListener {
        /**
         * Called whenever the contents of the back stack change, after the
         * fragment manager has moved all of the fragments to their final state.
         *
         * <p>
         * This is the final callback that will be delivered to the listener in all cases except
         * for when the predictive back gesture is cancelled. It will be called after
         * {@link #onBackStackChangeCommitted(Fragment, boolean)}.
         */
        @MainThread
        void onBackStackChanged();

        /**
         * Called whenever the contents of the back stack are starting to be changed, before
         * any fragment actually changes its lifecycle state. If this is caused by a forward
         * transaction and the given fragment is incoming, it will return <code>false</code> from
         * {@link Fragment#isRemoving()}. If this is caused by a pop operation and the given
         * fragment is being popped, it will return <code>true</code> from
         * {@link Fragment#isRemoving()}.
         *
         * <p>
         * This is the first callback that will be delivered to the listener. If the transaction
         * is caused by a predictive back gesture, it will be followed by an
         * {@link #onBackStackChangeProgressed(BackEventCompat)} callback otherwise, the next
         * callback will be {@link #onBackStackChangeCommitted(Fragment, boolean)}.
         *
         * @param fragment one of the fragments that is affected by the starting back stack change
         * @param pop true, if this callback was triggered by a pop operation, false otherwise
         */
        @MainThread
        default void onBackStackChangeStarted(@NonNull Fragment fragment, boolean pop) { }

        /**
         * Called whenever a predictive back gesture is changing the back stack. This will continue
         * to be called with an updated {@link BackEventCompat} object until the gesture is either
         * cancelled or completed.
         *
         * <p>
         * This is called immediately after {@link #onBackStackChangeStarted(Fragment, boolean)}
         * during a predictive back gesture. If the gesture is completed, this will be followed by
         * {@link #onBackStackChangeCommitted(Fragment, boolean)} and if it is cancelled, it will be
         * followed by {@link #onBackStackChangeCancelled()}.
         *
         * @param backEventCompat provides the current progress of the active predictive back
         *                        gesture
         */
        @MainThread
        default void onBackStackChangeProgressed(@NonNull BackEventCompat backEventCompat) { }

        /**
         * Called whenever the contents of a back stack change is committed. If this is caused by
         * a forward transaction and the given fragment is incoming, it will return
         * <code>false</code> from {@link Fragment#isRemoving()}. If this is caused by a pop
         * operation and the given fragment is being popped, it will return <code>true</code> from
         * {@link Fragment#isRemoving()}.
         *
         * <p>
         * This is called immediately after {@link #onBackStackChangeStarted(Fragment, boolean)}
         * for a forward transaction or for a non-predictive back pop. If this is caused by a
         * predictive back gesture, it will be called after
         * {@link #onBackStackChangeProgressed(BackEventCompat)} before the fragment moves to the
         * final state.
         *
         * @param fragment one of the fragments that is affected by the committed back stack change
         * @param pop true, if this callback was triggered by a pop operation, false otherwise
         */
        @MainThread
        default void onBackStackChangeCommitted(@NonNull Fragment fragment, boolean pop) { }

        /**
         * Called whenever a predictive back gesture is cancelled.
         *
         * <p>
         * This is called immediately after {@link #onBackStackChangeProgressed(BackEventCompat)}
         * once a predictive back gesture has been cancelled and will place the FragmentManager back
         * into the state before the predictive back gesture was started.
         */
        @MainThread
        default void onBackStackChangeCancelled() { }
    }

    /**
     * A {@link FragmentResultListener} that is lifecycle aware so that
     * the listener can be fired when the lifecycle is {@link Lifecycle.State#STARTED}.
     */
    private static class LifecycleAwareResultListener implements FragmentResultListener {
        private final Lifecycle mLifecycle;
        private final FragmentResultListener mListener;
        private final LifecycleEventObserver mObserver;

        LifecycleAwareResultListener(@NonNull Lifecycle lifecycle,
                @NonNull FragmentResultListener listener,
                @NonNull LifecycleEventObserver observer) {
            mLifecycle = lifecycle;
            mListener = listener;
            mObserver = observer;
        }

        public boolean isAtLeast(Lifecycle.State state) {
            return mLifecycle.getCurrentState().isAtLeast(state);
        }

        @Override
        public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle result) {
            mListener.onFragmentResult(requestKey, result);
        }

        public void removeObserver() {
            mLifecycle.removeObserver(mObserver);
        }
    }

    /**
     * Callback interface for listening to fragment state changes that happen
     * within a given FragmentManager.
     */
    @SuppressWarnings("unused")
    public abstract static class FragmentLifecycleCallbacks {
        /**
         * Called right before the fragment's {@link Fragment#onAttach(Context)} method is called.
         * This is a good time to inject any required dependencies or perform other configuration
         * for the fragment before any of the fragment's lifecycle methods are invoked.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         * @param context Context that the Fragment is being attached to
         */
        public void onFragmentPreAttached(@NonNull FragmentManager fm, @NonNull Fragment f,
                @NonNull Context context) {}

        /**
         * Called after the fragment has been attached to its host. Its host will have had
         * <code>onAttachFragment</code> called before this call happens.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         * @param context Context that the Fragment was attached to
         */
        public void onFragmentAttached(@NonNull FragmentManager fm, @NonNull Fragment f,
                @NonNull Context context) {}

        /**
         * Called right before the fragment's {@link Fragment#onCreate(Bundle)} method is called.
         * This is a good time to inject any required dependencies or perform other configuration
         * for the fragment.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         * @param savedInstanceState Saved instance bundle from a previous instance
         */
        public void onFragmentPreCreated(@NonNull FragmentManager fm, @NonNull Fragment f,
                @Nullable Bundle savedInstanceState) {}

        /**
         * Called after the fragment has returned from the FragmentManager's call to
         * {@link Fragment#onCreate(Bundle)}. This will only happen once for any given
         * fragment instance, though the fragment may be attached and detached multiple times.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         * @param savedInstanceState Saved instance bundle from a previous instance
         */
        public void onFragmentCreated(@NonNull FragmentManager fm, @NonNull Fragment f,
                @Nullable Bundle savedInstanceState) {}

        /**
         * Called after the fragment has returned from the FragmentManager's call to
         * {@link Fragment#onActivityCreated(Bundle)}. This will only happen once for any given
         * fragment instance, though the fragment may be attached and detached multiple times.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         * @param savedInstanceState Saved instance bundle from a previous instance
         *
         * @deprecated To get a callback specifically when a Fragment activity's
         * {@link android.app.Activity#onCreate(Bundle)} is called, register a
         * {@link androidx.lifecycle.LifecycleObserver} on the Activity's {@link Lifecycle} in
         * {@link #onFragmentAttached(FragmentManager, Fragment, Context)}, removing it when it
         * receives the {@link Lifecycle.State#CREATED} callback.
         */
        @Deprecated
        public void onFragmentActivityCreated(@NonNull FragmentManager fm, @NonNull Fragment f,
                @Nullable Bundle savedInstanceState) {}

        /**
         * Called after the fragment has returned a non-null view from the FragmentManager's
         * request to {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}.
         *
         * @param fm Host FragmentManager
         * @param f Fragment that created and owns the view
         * @param v View returned by the fragment
         * @param savedInstanceState Saved instance bundle from a previous instance
         */
        public void onFragmentViewCreated(@NonNull FragmentManager fm, @NonNull Fragment f,
                @NonNull View v, @Nullable Bundle savedInstanceState) {}

        /**
         * Called after the fragment has returned from the FragmentManager's call to
         * {@link Fragment#onStart()}.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         */
        public void onFragmentStarted(@NonNull FragmentManager fm, @NonNull Fragment f) {}

        /**
         * Called after the fragment has returned from the FragmentManager's call to
         * {@link Fragment#onResume()}.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         */
        public void onFragmentResumed(@NonNull FragmentManager fm, @NonNull Fragment f) {}

        /**
         * Called after the fragment has returned from the FragmentManager's call to
         * {@link Fragment#onPause()}.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         */
        public void onFragmentPaused(@NonNull FragmentManager fm, @NonNull Fragment f) {}

        /**
         * Called after the fragment has returned from the FragmentManager's call to
         * {@link Fragment#onStop()}.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         */
        public void onFragmentStopped(@NonNull FragmentManager fm, @NonNull Fragment f) {}

        /**
         * Called after the fragment has returned from the FragmentManager's call to
         * {@link Fragment#onSaveInstanceState(Bundle)}.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         * @param outState Saved state bundle for the fragment
         */
        public void onFragmentSaveInstanceState(@NonNull FragmentManager fm, @NonNull Fragment f,
                @NonNull Bundle outState) {}

        /**
         * Called after the fragment has returned from the FragmentManager's call to
         * {@link Fragment#onDestroyView()}.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         */
        public void onFragmentViewDestroyed(@NonNull FragmentManager fm, @NonNull Fragment f) {}

        /**
         * Called after the fragment has returned from the FragmentManager's call to
         * {@link Fragment#onDestroy()}.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         */
        public void onFragmentDestroyed(@NonNull FragmentManager fm, @NonNull Fragment f) {}

        /**
         * Called after the fragment has returned from the FragmentManager's call to
         * {@link Fragment#onDetach()}.
         *
         * @param fm Host FragmentManager
         * @param f Fragment changing state
         */
        public void onFragmentDetached(@NonNull FragmentManager fm, @NonNull Fragment f) {}
    }

    private final ArrayList<OpGenerator> mPendingActions = new ArrayList<>();
    private boolean mExecutingActions;

    private final FragmentStore mFragmentStore = new FragmentStore();
    ArrayList<BackStackRecord> mBackStack = new ArrayList<>();
    private ArrayList<Fragment> mCreatedMenus;
    private final FragmentLayoutInflaterFactory mLayoutInflaterFactory =
            new FragmentLayoutInflaterFactory(this);
    private OnBackPressedDispatcher mOnBackPressedDispatcher;

    BackStackRecord mTransitioningOp = null;

    boolean mBackStarted = false;
    private final OnBackPressedCallback mOnBackPressedCallback =
            new OnBackPressedCallback(false) {

                @Override
                public void handleOnBackStarted(@NonNull BackEventCompat backEvent) {
                    if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
                        Log.d(FragmentManager.TAG,
                                "handleOnBackStarted. PREDICTIVE_BACK = " + USE_PREDICTIVE_BACK
                                        + " fragment manager " + FragmentManager.this
                        );
                    }
                    if (USE_PREDICTIVE_BACK) {
                        endAnimatingAwayFragments();
                        prepareBackStackTransition();
                    }
                }

                @Override
                public void handleOnBackProgressed(@NonNull BackEventCompat backEvent) {
                    if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
                        Log.v(FragmentManager.TAG,
                                "handleOnBackProgressed. PREDICTIVE_BACK = " + USE_PREDICTIVE_BACK
                                        + " fragment manager " + FragmentManager.this
                        );
                    }
                    if (mTransitioningOp != null) {
                        // Collect the correct SpecialEffectsControllers and pass in the progress
                        Set<SpecialEffectsController> changedControllers  =
                                collectChangedControllers(
                                        new ArrayList<>(
                                                Collections.singletonList(mTransitioningOp)
                                        ), 0, 1
                                );
                        for (SpecialEffectsController controller: changedControllers) {
                            controller.processProgress(backEvent);
                        }
                        for (OnBackStackChangedListener listener : mBackStackChangeListeners) {
                            listener.onBackStackChangeProgressed(backEvent);
                        }
                    }
                }

                @Override
                public void handleOnBackPressed() {
                    if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
                        Log.d(FragmentManager.TAG,
                                "handleOnBackPressed. PREDICTIVE_BACK = " + USE_PREDICTIVE_BACK
                                        + " fragment manager " + FragmentManager.this
                        );
                    }
                    FragmentManager.this.handleOnBackPressed();
                }

                @Override
                public void handleOnBackCancelled() {
                    if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
                        Log.d(FragmentManager.TAG,
                                "handleOnBackCancelled. PREDICTIVE_BACK = " + USE_PREDICTIVE_BACK
                                        + " fragment manager " + FragmentManager.this
                        );
                    }
                    if (USE_PREDICTIVE_BACK) {
                        cancelBackStackTransition();
                        mTransitioningOp = null;
                    }
                }
            };

    private final AtomicInteger mBackStackIndex = new AtomicInteger();

    private final Map<String, BackStackState> mBackStackStates =
            Collections.synchronizedMap(new HashMap<String, BackStackState>());

    private final Map<String, Bundle> mResults =
            Collections.synchronizedMap(new HashMap<String, Bundle>());
    private final Map<String, LifecycleAwareResultListener> mResultListeners =
            Collections.synchronizedMap(new HashMap<String, LifecycleAwareResultListener>());

    ArrayList<OnBackStackChangedListener> mBackStackChangeListeners = new ArrayList<>();
    private final FragmentLifecycleCallbacksDispatcher mLifecycleCallbacksDispatcher =
            new FragmentLifecycleCallbacksDispatcher(this);
    private final CopyOnWriteArrayList<FragmentOnAttachListener> mOnAttachListeners =
            new CopyOnWriteArrayList<>();

    private final Consumer<Configuration> mOnConfigurationChangedListener = newConfig -> {
        if (isParentAdded()) {
            dispatchConfigurationChanged(newConfig, false);
        }
    };
    private final Consumer<Integer> mOnTrimMemoryListener = level -> {
        if (isParentAdded() && level == ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
            dispatchLowMemory(false);
        }
    };
    private final Consumer<MultiWindowModeChangedInfo> mOnMultiWindowModeChangedListener =
            info -> {
                if (isParentAdded()) {
                    dispatchMultiWindowModeChanged(info.isInMultiWindowMode(), false);
                }
            };
    private final Consumer<PictureInPictureModeChangedInfo>
            mOnPictureInPictureModeChangedListener = info -> {
                if (isParentAdded()) {
                    dispatchPictureInPictureModeChanged(info.isInPictureInPictureMode(), false);
                }
            };

    private final MenuProvider mMenuProvider = new MenuProvider() {
        @Override
        public void onPrepareMenu(@NonNull Menu menu) {
            dispatchPrepareOptionsMenu(menu);
        }

        @Override
        public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
            dispatchCreateOptionsMenu(menu, menuInflater);
        }

        @Override
        public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {
            return dispatchOptionsItemSelected(menuItem);
        }

        @Override
        public void onMenuClosed(@NonNull Menu menu) {
            dispatchOptionsMenuClosed(menu);
        }
    };

    int mCurState = Fragment.INITIALIZING;
    private FragmentHostCallback<?> mHost;
    private FragmentContainer mContainer;
    private Fragment mParent;
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    @Nullable
    Fragment mPrimaryNav;
    private FragmentFactory mFragmentFactory = null;
    private FragmentFactory mHostFragmentFactory = new FragmentFactory() {
        @SuppressWarnings("deprecation")
        @NonNull
        @Override
        public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
            return getHost().instantiate(getHost().getContext(), className, null);
        }
    };
    private SpecialEffectsControllerFactory mSpecialEffectsControllerFactory = null;
    private SpecialEffectsControllerFactory mDefaultSpecialEffectsControllerFactory =
            new SpecialEffectsControllerFactory() {
                @NonNull
                @Override
                public SpecialEffectsController createController(@NonNull ViewGroup container) {
                    return new DefaultSpecialEffectsController(container);
                }
            };

    private ActivityResultLauncher<Intent> mStartActivityForResult;
    private ActivityResultLauncher<IntentSenderRequest> mStartIntentSenderForResult;
    private ActivityResultLauncher<String[]> mRequestPermissions;

    ArrayDeque<LaunchedFragmentInfo> mLaunchedFragments = new ArrayDeque<>();

    private static final String EXTRA_CREATED_FILLIN_INTENT = "androidx.fragment"
            + ".extra.ACTIVITY_OPTIONS_BUNDLE";

    private boolean mNeedMenuInvalidate;
    private boolean mStateSaved;
    private boolean mStopped;
    private boolean mDestroyed;
    private boolean mHavePendingDeferredStart;

    // Temporary vars for removing redundant operations in BackStackRecords:
    private ArrayList<BackStackRecord> mTmpRecords;
    private ArrayList<Boolean> mTmpIsPop;
    private ArrayList<Fragment> mTmpAddedFragments;

    private FragmentManagerViewModel mNonConfig;

    private FragmentStrictMode.Policy mStrictModePolicy;

    private Runnable mExecCommit = new Runnable() {
        @Override
        public void run() {
            execPendingActions(true);
        }
    };

    private void throwException(RuntimeException ex) {
        Log.e(TAG, ex.getMessage());
        Log.e(TAG, "Activity state:");
        LogWriter logw = new LogWriter(TAG);
        PrintWriter pw = new PrintWriter(logw);
        if (mHost != null) {
            try {
                mHost.onDump("  ", null, pw, new String[] { });
            } catch (Exception e) {
                Log.e(TAG, "Failed dumping state", e);
            }
        } else {
            try {
                dump("  ", null, pw, new String[] { });
            } catch (Exception e) {
                Log.e(TAG, "Failed dumping state", e);
            }
        }
        throw ex;
    }

    /**
     * @deprecated Use {@link #beginTransaction()}.
     */
    @RestrictTo(LIBRARY_GROUP_PREFIX)
    @Deprecated
    @NonNull
    public FragmentTransaction openTransaction() {
        return beginTransaction();
    }

    /**
     * Start a series of edit operations on the Fragments associated with
     * this FragmentManager.
     *
     * <p>Note: A fragment transaction can only be created/committed prior
     * to an activity saving its state.  If you try to commit a transaction
     * after {@link FragmentActivity#onSaveInstanceState FragmentActivity.onSaveInstanceState()}
     * (and prior to a following {@link FragmentActivity#onStart FragmentActivity.onStart}
     * or {@link FragmentActivity#onResume FragmentActivity.onResume()}, you will get an error.
     * This is because the framework takes care of saving your current fragments
     * in the state, and if changes are made after the state is saved then they
     * will be lost.</p>
     */
    @NonNull
    public FragmentTransaction beginTransaction() {
        return new BackStackRecord(this);
    }

    /**
     * After a {@link FragmentTransaction} is committed with
     * {@link FragmentTransaction#commit FragmentTransaction.commit()}, it
     * is scheduled to be executed asynchronously on the process's main thread.
     * If you want to immediately executing any such pending operations, you
     * can call this function (only from the main thread) to do so.  Note that
     * all callbacks and other related behavior will be done from within this
     * call, so be careful about where this is called from.
     *
     * <p>If you are committing a single transaction that does not modify the
     * fragment back stack, strongly consider using
     * {@link FragmentTransaction#commitNow()} instead. This can help avoid
     * unwanted side effects when other code in your app has pending committed
     * transactions that expect different timing.</p>
     * <p>
     * This also forces the start of any postponed Transactions where
     * {@link Fragment#postponeEnterTransition()} has been called.
     *
     * @return Returns true if there were any pending transactions to be
     * executed.
     */
    @MainThread
    public boolean executePendingTransactions() {
        boolean updates = execPendingActions(true);
        forcePostponedTransactions();
        return updates;
    }

    private void updateOnBackPressedCallbackEnabled() {
        // Always enable the callback if we have pending actions
        // as we don't know if they'll change the back stack entry count.
        // See handleOnBackPressed() for more explanation
        synchronized (mPendingActions) {
            if (!mPendingActions.isEmpty()) {
                mOnBackPressedCallback.setEnabled(true);
                if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
                    Log.d(TAG, "FragmentManager " + FragmentManager.this + " enabling "
                            + "OnBackPressedCallback, caused by non-empty pending actions");
                }
                return;
            }
        }
        // This FragmentManager needs to have a back stack for this to be enabled
        // And the parent fragment, if it exists, needs to be the primary navigation
        // fragment.
        boolean isEnabled = getBackStackEntryCount() > 0
                && isPrimaryNavigation(mParent);
        if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
            Log.d(FragmentManager.TAG,
                    "OnBackPressedCallback for FragmentManager " + this + " enabled state is "
                            + isEnabled
            );
        }
        mOnBackPressedCallback.setEnabled(isEnabled);
    }

    /**
     * Recursively check up the FragmentManager hierarchy of primary
     * navigation Fragments to ensure that all of the parent Fragments are the
     * primary navigation Fragment for their associated FragmentManager
     */
    boolean isPrimaryNavigation(@Nullable Fragment parent) {
        // If the parent is null, then we're at the root host
        // and we're always the primary navigation
        if (parent == null) {
            return true;
        }
        FragmentManager parentFragmentManager = parent.mFragmentManager;
        Fragment primaryNavigationFragment = parentFragmentManager
                .getPrimaryNavigationFragment();
        // The parent Fragment needs to be the primary navigation Fragment
        // and, if it has a parent itself, that parent also needs to be
        // the primary navigation fragment, recursively up the stack
        return parent.equals(primaryNavigationFragment)
                && isPrimaryNavigation(parentFragmentManager.mParent);
    }

    /**
     * Recursively check up the FragmentManager hierarchy of Fragments to see
     * if the menus are all visible.
     */
    boolean isParentMenuVisible(@Nullable Fragment parent) {
        if (parent == null) {
            return true;
        }

        return parent.isMenuVisible();
    }

    /**
     * Recursively check up the FragmentManager hierarchy of Fragments to see
     * if the fragment is hidden.
     */
    boolean isParentHidden(@Nullable Fragment parent) {
        if (parent == null) {
            return false;
        }

        return parent.isHidden();
    }

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void handleOnBackPressed() {
        // First, execute any pending actions to make sure we're in an
        // up to date view of the world just in case anyone is queuing
        // up transactions that change the back stack then immediately
        // calling onBackPressed()
        execPendingActions(true);
        if (USE_PREDICTIVE_BACK && mTransitioningOp != null) {
            if (!mBackStackChangeListeners.isEmpty()) {
                // Build a list of fragments based on the records
                Set<Fragment> fragments = new LinkedHashSet<>(
                        fragmentsFromRecord(mTransitioningOp));
                // Dispatch to all of the fragments in the list
                for (OnBackStackChangedListener listener : mBackStackChangeListeners) {
                    // We give all fragment the back stack changed started signal first
                    for (Fragment fragment : fragments) {
                        listener.onBackStackChangeCommitted(fragment, true);
                    }
                }
            }
            for (FragmentTransaction.Op op : mTransitioningOp.mOps) {
                Fragment fragment = op.mFragment;
                if (fragment != null) {
                    fragment.mTransitioning = false;
                }
            }
            Set<SpecialEffectsController> changedControllers = collectChangedControllers(
                    new ArrayList<>(Collections.singletonList(mTransitioningOp)), 0, 1
            );
            for (SpecialEffectsController controller : changedControllers) {
                controller.completeBack();
            }
            for (FragmentTransaction.Op op : mTransitioningOp.mOps) {
                Fragment fragment = op.mFragment;
                if (fragment != null) {
                    if (fragment.mContainer == null) {
                        FragmentStateManager stateManager =
                                createOrGetFragmentStateManager(fragment);
                        stateManager.moveToExpectedState();
                    }
                }
            }
            mTransitioningOp = null;
            updateOnBackPressedCallbackEnabled();
            if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
                Log.d(TAG, "Op is being set to null");
                Log.d(TAG, "OnBackPressedCallback enabled=" + mOnBackPressedCallback.isEnabled()
                        + " for  FragmentManager " + this);
            }
        } else {
            if (mOnBackPressedCallback.isEnabled()) {
                if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
                    Log.d(TAG, "Calling popBackStackImmediate via onBackPressed callback");
                }
                // We still have a back stack, so we can pop
                popBackStackImmediate();
            } else {
                if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
                    Log.d(TAG, "Calling onBackPressed via onBackPressed callback");
                }
                // Sigh. Due to FragmentManager's asynchronicity, we can
                // get into cases where we *think* we can handle the back
                // button but because of frame perfect dispatch, we fell
                // on our face. Since our callback is disabled, we can
                // re-trigger the onBackPressed() to dispatch to the next
                // enabled callback
                mOnBackPressedDispatcher.onBackPressed();
            }
        }
    }

    /**
     * Restores the back stack previously saved via {@link #saveBackStack(String)}. This
     * will result in all of the transactions that made up that back stack to be re-executed,
     * thus re-adding any fragments that were added through those transactions. All state of
     * those fragments will be restored as part of this process. If no state was previously
     * saved with the given name, this operation does nothing.
     * <p>
     * This function is asynchronous -- it enqueues the
     * request to restore, but the action will not be performed until the application
     * returns to its event loop.
     *
     * @param name The name of the back stack previously saved by {@link #saveBackStack(String)}.
     */
    public void restoreBackStack(@NonNull String name) {
        enqueueAction(new RestoreBackStackState(name), false);
    }

    /**
     * Save the back stack. While this functions similarly to
     * {@link #popBackStack(String, int)}, it <strong>does not</strong> throw away the
     * state of any fragments that were added through those transactions. Instead, the
     * back stack that is saved by this method can later be restored with its state
     * in tact.
     * <p>
     * This function is asynchronous -- it enqueues the
     * request to pop, but the action will not be performed until the application
     * returns to its event loop.
     *
     * @param name The name set by {@link FragmentTransaction#addToBackStack(String)}.
     */
    public void saveBackStack(@NonNull String name) {
        enqueueAction(new SaveBackStackState(name), false);
    }

    /**
     * Clears the back stack previously saved via {@link #saveBackStack(String)}. This
     * will result in all of the transactions that made up that back stack to be thrown away,
     * thus destroying any fragments that were added through those transactions. All state of
     * those fragments will be cleared as part of this process. If no state was previously
     * saved with the given name, this operation does nothing.
     * <p>
     * This function is asynchronous -- it enqueues the
     * request to clear, but the action will not be performed until the application
     * returns to its event loop.
     *
     * @param name The name of the back stack previously saved by {@link #saveBackStack(String)}.
     */
    public void clearBackStack(@NonNull String name) {
        enqueueAction(new ClearBackStackState(name), false);
    }

    /**
     * Pop the top state off the back stack. This function is asynchronous -- it enqueues the
     * request to pop, but the action will not be performed until the application
     * returns to its event loop.
     */
    public void popBackStack() {
        enqueueAction(new PopBackStackState(null, -1, 0), false);
    }

    /**
     * Like {@link #popBackStack()}, but performs the operation immediately
     * inside of the call.  This is like calling {@link #executePendingTransactions()}
     * afterwards without forcing the start of postponed Transactions.
     * @return Returns true if there was something popped, else false.
     */
    @MainThread
    public boolean popBackStackImmediate() {
        return popBackStackImmediate(null, -1, 0);
    }

    /**
     * Pop the last fragment transition from the manager's fragment
     * back stack.
     * This function is asynchronous -- it enqueues the
     * request to pop, but the action will not be performed until the application
     * returns to its event loop.
     *
     * @param name If non-null, this is the name of a previous back state
     * to look for; if found, all states up to that state will be popped.  The
     * {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether
     * the named state itself is popped. If null, only the top state is popped.
     * @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}.
     */
    public void popBackStack(@Nullable final String name, final int flags) {
        enqueueAction(new PopBackStackState(name, -1, flags), false);
    }

    /**
     * Like {@link #popBackStack(String, int)}, but performs the operation immediately
     * inside of the call.  This is like calling {@link #executePendingTransactions()}
     * afterwards without forcing the start of postponed Transactions.
     * @return Returns true if there was something popped, else false.
     */
    @MainThread
    public boolean popBackStackImmediate(@Nullable String name, int flags) {
        return popBackStackImmediate(name, -1, flags);
    }

    /**
     * Pop all back stack states up to the one with the given identifier.
     * This function is asynchronous -- it enqueues the
     * request to pop, but the action will not be performed until the application
     * returns to its event loop.
     *
     * @param id Identifier of the stated to be popped. If no identifier exists,
     * false is returned.
     * The identifier is the number returned by
     * {@link FragmentTransaction#commit() FragmentTransaction.commit()}.  The
     * {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether
     * the named state itself is popped.
     * @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}.
     */
    public void popBackStack(final int id, final int flags) {
        popBackStack(id, flags, false);
    }

    void popBackStack(final int id, final int flags, boolean allowStateLoss) {
        if (id < 0) {
            throw new IllegalArgumentException("Bad id: " + id);
        }
        enqueueAction(new PopBackStackState(null, id, flags), allowStateLoss);
    }

    void prepareBackStackTransition() {
        enqueueAction(new PrepareBackStackTransitionState(), false);
    }

    void cancelBackStackTransition() {
        if (mTransitioningOp != null) {
            mTransitioningOp.mCommitted = false;
            mTransitioningOp.runOnCommitInternal(true, () -> {
                for (OnBackStackChangedListener listener : mBackStackChangeListeners) {
                    listener.onBackStackChangeCancelled();
                }
            });
            mTransitioningOp.commit();
            executePendingTransactions();
        }
    }

    /**
     * Like {@link #popBackStack(int, int)}, but performs the operation immediately
     * inside of the call.  This is like calling {@link #executePendingTransactions()}
     * afterwards without forcing the start of postponed Transactions.
     * @return Returns true if there was something popped, else false.
     */
    public boolean popBackStackImmediate(int id, int flags) {
        if (id < 0) {
            throw new IllegalArgumentException("Bad id: " + id);
        }
        return popBackStackImmediate(null, id, flags);
    }

    /**
     * Used by all public popBackStackImmediate methods, this executes pending transactions and
     * returns true if the pop action did anything, regardless of what other pending
     * transactions did.
     *
     * @return true if the pop operation did anything or false otherwise.
     */
    private boolean popBackStackImmediate(@Nullable String name, int id, int flags) {
        execPendingActions(false);
        ensureExecReady(true);

        if (mPrimaryNav != null // We have a primary nav fragment
                && id < 0 // No valid id (since they're local)
                && name == null) { // no name to pop to (since they're local)
            final FragmentManager childManager = mPrimaryNav.getChildFragmentManager();
            if (childManager.popBackStackImmediate()) {
                // We did something, just not to this specific FragmentManager. Return true.
                return true;
            }
        }

        boolean executePop = popBackStackState(mTmpRecords, mTmpIsPop, name, id, flags);
        if (executePop) {
            mExecutingActions = true;
            try {
                removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop);
            } finally {
                cleanupExec();
            }
        }

        updateOnBackPressedCallbackEnabled();
        doPendingDeferredStart();
        mFragmentStore.burpActive();
        return executePop;
    }

    /**
     * Return the number of entries currently in the back stack.
     */
    public int getBackStackEntryCount() {
        return mBackStack.size() + (mTransitioningOp != null ? 1 : 0);
    }

    /**
     * Return the BackStackEntry at index <var>index</var> in the back stack;
     * entries start index 0 being the bottom of the stack.
     */
    @NonNull
    public BackStackEntry getBackStackEntryAt(int index) {
        if (index == mBackStack.size()) {
            if (mTransitioningOp == null) {
                throw new IndexOutOfBoundsException();
            }
            return mTransitioningOp;
        }
        return mBackStack.get(index);
    }

    /**
     * Add a new listener for changes to the fragment back stack.
     */
    public void addOnBackStackChangedListener(@NonNull OnBackStackChangedListener listener) {
        mBackStackChangeListeners.add(listener);
    }

    /**
     * Remove a listener that was previously added with
     * {@link #addOnBackStackChangedListener(OnBackStackChangedListener)}.
     */
    public void removeOnBackStackChangedListener(@NonNull OnBackStackChangedListener listener) {
        mBackStackChangeListeners.remove(listener);
    }

    @Override
    public final void setFragmentResult(@NonNull String requestKey, @NonNull Bundle result) {
        // Check if there is a listener waiting for a result with this key
        LifecycleAwareResultListener resultListener = mResultListeners.get(requestKey);
        // if there is and it is started, fire the callback
        if (resultListener != null && resultListener.isAtLeast(Lifecycle.State.STARTED)) {
            resultListener.onFragmentResult(requestKey, result);
        } else {
            // else, save the result for later
            mResults.put(requestKey, result);
        }
        if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
            Log.v(FragmentManager.TAG, "Setting fragment result with key " + requestKey + " and "
                    + "result " + result);
        }
    }

    @Override
    public final void clearFragmentResult(@NonNull String requestKey) {
        mResults.remove(requestKey);
        if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
            Log.v(FragmentManager.TAG, "Clearing fragment result with key " + requestKey);
        }
    }

    @Override
    public final void setFragmentResultListener(@NonNull final String requestKey,
            @NonNull final LifecycleOwner lifecycleOwner,
            @NonNull final FragmentResultListener listener) {
        final Lifecycle lifecycle = lifecycleOwner.getLifecycle();
        if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {
            return;
        }

        LifecycleEventObserver observer = new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_START) {
                    // once we are started, check for any stored results
                    Bundle storedResult = mResults.get(requestKey);
                    if (storedResult != null) {
                        // if there is a result, fire the callback
                        listener.onFragmentResult(requestKey, storedResult);
                        // and clear the result
                        clearFragmentResult(requestKey);
                    }
                }

                if (event == Lifecycle.Event.ON_DESTROY) {
                    lifecycle.removeObserver(this);
                    mResultListeners.remove(requestKey);
                }
            }
        };
        LifecycleAwareResultListener storedListener = mResultListeners.put(requestKey,
                new LifecycleAwareResultListener(lifecycle, listener, observer));
        if (storedListener != null) {
            storedListener.removeObserver();
        }
        if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
            Log.v(FragmentManager.TAG, "Setting FragmentResultListener with key " + requestKey
                    + " lifecycleOwner " + lifecycle + " and listener " + listener);
        }
        // Only add the observer after we've added the listener to the map
        // to ensure that re-entrant removals actually have a registered listener to remove
        lifecycle.addObserver(observer);
    }

    @Override
    public final void clearFragmentResultListener(@NonNull String requestKey) {
        LifecycleAwareResultListener listener = mResultListeners.remove(requestKey);
        if (listener != null) {
            listener.removeObserver();
        }
        if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
            Log.v(FragmentManager.TAG, "Clearing FragmentResultListener for key " + requestKey);
        }
    }

    /**
     * Put a reference to a fragment in a Bundle.  This Bundle can be
     * persisted as saved state, and when later restoring
     * {@link #getFragment(Bundle, String)} will return the current
     * instance of the same fragment.
     *
     * @param bundle The bundle in which to put the fragment reference.
     * @param key The name of the entry in the bundle.
     * @param fragment The Fragment whose reference is to be stored.
     */
    public void putFragment(@NonNull Bundle bundle, @NonNull String key,
            @NonNull Fragment fragment) {
        if (fragment.mFragmentManager != this) {
            throwException(new IllegalStateException("Fragment " + fragment
                    + " is not currently in the FragmentManager"));
        }
        bundle.putString(key, fragment.mWho);
    }

    /**
     * Retrieve the current Fragment instance for a reference previously
     * placed with {@link #putFragment(Bundle, String, Fragment)}.
     *
     * @param bundle The bundle from which to retrieve the fragment reference.
     * @param key The name of the entry in the bundle.
     * @return Returns the current Fragment instance that is associated with
     * the given reference.
     */
    @Nullable
    public Fragment getFragment(@NonNull Bundle bundle, @NonNull String key) {
        String who = bundle.getString(key);
        if (who == null) {
            return null;
        }
        Fragment f = findActiveFragment(who);
        if (f == null) {
            throwException(new IllegalStateException("Fragment no longer exists for key "
                    + key + ": unique id " + who));
        }
        return f;
    }

    /**
     * Find a {@link Fragment} associated with the given {@link View}.
     *
     * This method will locate the {@link Fragment} associated with this view. This is automatically
     * populated for the View returned by {@link Fragment#onCreateView} and its children.
     *
     * @param view the view to search from
     * @return the locally scoped {@link Fragment} to the given view
     * @throws IllegalStateException if the given view does not correspond with a
     * {@link Fragment}.
     */
    @NonNull
    @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"}) // We should throw a ClassCast
    // exception if the type is wrong
    public static <F extends Fragment> F findFragment(@NonNull View view) {
        Fragment fragment = findViewFragment(view);
        if (fragment == null) {
            throw new IllegalStateException("View " + view + " does not have a Fragment set");
        }
        return (F) fragment;
    }

    /**
     * Recurse up the view hierarchy, looking for the Fragment
     * @param view the view to search from
     * @return the locally scoped {@link Fragment} to the given view, if found
     */
    @Nullable
    static Fragment findViewFragment(@NonNull View view) {
        while (view != null) {
            Fragment fragment = getViewFragment(view);
            if (fragment != null) {
                return fragment;
            }
            ViewParent parent = view.getParent();
            view = parent instanceof View ? (View) parent : null;
        }
        return null;
    }

    /**
     * Check if this view has an associated Fragment
     * @param view the view to search from
     * @return the locally scoped {@link Fragment} to the given view, if found
     */
    @Nullable
    static Fragment getViewFragment(@NonNull View view) {
        Object tag = view.getTag(R.id.fragment_container_view_tag);
        if (tag instanceof Fragment) {
            return (Fragment) tag;
        }
        return null;
    }

    /**
     * Callback for when the {@link FragmentContainerView} becomes available in the view hierarchy
     * and the fragment manager can add the fragment view to its hierarchy.
     *
     * @param container the container that the active fragment should add their views to
     */
    public final void onContainerAvailable(@NonNull FragmentContainerView container) {
        for (FragmentStateManager fragmentStateManager:
                mFragmentStore.getActiveFragmentStateManagers()) {
            Fragment fragment = fragmentStateManager.getFragment();
            if (fragment.mContainerId == container.getId() && fragment.mView != null
                    && fragment.mView.getParent() == null
            ) {
                fragment.mContainer = container;
                fragmentStateManager.addViewToContainer();
                fragmentStateManager.moveToExpectedState();
            }
        }
    }

    /**
     * Recurse up the view hierarchy, looking for a FragmentManager
     *
     * @param view the view to search from
     * @return The containing {@link FragmentManager} of the given view.
     * @throws IllegalStateException if there no Fragment associated with the view and the
     * view's context is not a {@link FragmentActivity}.
     */
    @NonNull
    public static FragmentManager findFragmentManager(@NonNull View view) {
        // Search the view ancestors for a Fragment
        Fragment fragment = findViewFragment(view);
        FragmentManager fm;
        // If there is a Fragment in the hierarchy, get its childFragmentManager, otherwise
        // use the fragmentManager of the Activity.
        if (fragment != null) {
            if (!fragment.isAdded()) {
                throw new IllegalStateException("The Fragment " + fragment + " that owns View "
                        + view + " has already been destroyed. Nested fragments should always "
                        + "use the child FragmentManager.");
            }
            fm = fragment.getChildFragmentManager();
        } else {
            Context context = view.getContext();
            FragmentActivity fragmentActivity = null;
            while (context instanceof ContextWrapper) {
                if (context instanceof FragmentActivity) {
                    fragmentActivity = (FragmentActivity) context;
                    break;
                }
                context = ((ContextWrapper) context).getBaseContext();
            }
            if (fragmentActivity != null) {
                fm = fragmentActivity.getSupportFragmentManager();
            } else {
                throw new IllegalStateException("View " + view + " is not within a subclass of "
                        + "FragmentActivity.");
            }

        }
        return fm;
    }

    /**
     * Get a list of all fragments that are currently added to the FragmentManager.
     * This may include those that are hidden as well as those that are shown.
     * This will not include any fragments only in the back stack, or fragments that
     * are detached or removed.
     * <p>
     * The order of the fragments in the list is the order in which they were
     * added or attached.
     *
     * @return A list of all fragments that are added to the FragmentManager.
     */
    @NonNull
    @SuppressWarnings("unchecked")
    public List<Fragment> getFragments() {
        return mFragmentStore.getFragments();
    }

    @NonNull
    ViewModelStore getViewModelStore(@NonNull Fragment f) {
        return mNonConfig.getViewModelStore(f);
    }

    @NonNull
    private FragmentManagerViewModel getChildNonConfig(@NonNull Fragment f) {
        return mNonConfig.getChildNonConfig(f);
    }

    void addRetainedFragment(@NonNull Fragment f) {
        mNonConfig.addRetainedFragment(f);
    }

    void removeRetainedFragment(@NonNull Fragment f) {
        mNonConfig.removeRetainedFragment(f);
    }

    /**
     * This is used by FragmentController to get the Active fragments.
     *
     * @return A list of active fragments in the fragment manager, including those that are in the
     * back stack.
     */
    @NonNull
    List<Fragment> getActiveFragments() {
        return mFragmentStore.getActiveFragments();
    }

    /**
     * Used by FragmentController to get the number of Active Fragments.
     *
     * @return The number of active fragments.
     */
    int getActiveFragmentCount() {
        return mFragmentStore.getActiveFragmentCount();
    }

    /**
     * Save the current instance state of the given Fragment.  This can be
     * used later when creating a new instance of the Fragment and adding
     * it to the fragment manager, to have it create itself to match the
     * current state returned here.  Note that there are limits on how
     * this can be used:
     *
     * <ul>
     * <li>The Fragment must currently be attached to the FragmentManager.
     * <li>A new Fragment created using this saved state must be the same class
     * type as the Fragment it was created from.
     * <li>The saved state can not contain dependencies on other fragments --
     * that is it can't use {@link #putFragment(Bundle, String, Fragment)} to
     * store a fragment reference because that reference may not be valid when
     * this saved state is later used.  Likewise the Fragment's target and
     * result code are not included in this state.
     * </ul>
     *
     * @param fragment The Fragment whose state is to be saved.
     * @return The generated state.  This will be null if there was no
     * interesting state created by the fragment.
     */
    @Nullable
    public Fragment.SavedState saveFragmentInstanceState(@NonNull Fragment fragment) {
        FragmentStateManager fragmentStateManager = mFragmentStore.getFragmentStateManager(
                fragment.mWho);
        if (fragmentStateManager == null || !fragmentStateManager.getFragment().equals(fragment)) {
            throwException(new IllegalStateException("Fragment " + fragment
                    + " is not currently in the FragmentManager"));
        }
        return fragmentStateManager.saveInstanceState();
    }

    private void clearBackStackStateViewModels() {
        boolean shouldClear;
        if (mHost instanceof ViewModelStoreOwner) {
            shouldClear = mFragmentStore.getNonConfig().isCleared();
        } else if (mHost.getContext() instanceof Activity) {
            Activity activity = (Activity) mHost.getContext();
            shouldClear = !activity.isChangingConfigurations();
        } else {
            shouldClear = true;
        }
        if (shouldClear) {
            for (BackStackState backStackState : mBackStackStates.values()) {
                for (String who : backStackState.mFragments) {
                    mFragmentStore.getNonConfig().clearNonConfigState(who, false);
                }
            }
        }
    }

    /**
     * Returns true if the final {@link android.app.Activity#onDestroy() Activity.onDestroy()}
     * call has been made on the FragmentManager's Activity, so this instance is now dead.
     */
    public boolean isDestroyed() {
        return mDestroyed;
    }

    @NonNull
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(128);
        sb.append("FragmentManager{");
        sb.append(Integer.toHexString(System.identityHashCode(this)));
        sb.append(" in ");
        if (mParent != null) {
            Class<?> cls = mParent.getClass();
            sb.append(cls.getSimpleName());
            sb.append("{");
            sb.append(Integer.toHexString(System.identityHashCode(mParent)));
            sb.append("}");
        } else if (mHost != null) {
            Class<?> cls = mHost.getClass();
            sb.append(cls.getSimpleName());
            sb.append("{");
            sb.append(Integer.toHexString(System.identityHashCode(mHost)));
            sb.append("}");
        } else {
            sb.append("null");
        }
        sb.append("}}");
        return sb.toString();
    }

    /**
     * Print the FragmentManager's state into the given stream.
     *
     * @param prefix Text to print at the front of each line.
     * @param fd The raw file descriptor that the dump is being sent to.
     * @param writer A PrintWriter to which the dump is to be set.
     * @param args Additional arguments to the dump request.
     */
    public void dump(@NonNull String prefix, @Nullable FileDescriptor fd,
            @NonNull PrintWriter writer, @Nullable String[] args) {
        String innerPrefix = prefix + "    ";

        mFragmentStore.dump(prefix, fd, writer, args);

        int count;
        if (mCreatedMenus != null) {
            count = mCreatedMenus.size();
            if (count > 0) {
                writer.print(prefix); writer.println("Fragments Created Menus:");
                for (int i = 0; i < count; i++) {
                    Fragment f = mCreatedMenus.get(i);
                    writer.print(prefix);
                    writer.print("  #");
                    writer.print(i);
                    writer.print(": ");
                    writer.println(f.toString());
                }
            }
        }

        count = mBackStack.size();
        if (count > 0) {
            writer.print(prefix); writer.println("Back Stack:");
            for (int i = 0; i < count; i++) {
                BackStackRecord bs = mBackStack.get(i);
                writer.print(prefix);
                writer.print("  #");
                writer.print(i);
                writer.print(": ");
                writer.println(bs.toString());
                bs.dump(innerPrefix, writer);
            }
        }

        writer.print(prefix);
        writer.println("Back Stack Index: " + mBackStackIndex.get());

        synchronized (mPendingActions) {
            count = mPendingActions.size();
            if (count > 0) {
                writer.print(prefix); writer.println("Pending Actions:");
                for (int i = 0; i < count; i++) {
                    OpGenerator r = mPendingActions.get(i);
                    writer.print(prefix);
                    writer.print("  #");
                    writer.print(i);
                    writer.print(": ");
                    writer.println(r);
                }
            }
        }

        writer.print(prefix);
        writer.println("FragmentManager misc state:");
        writer.print(prefix);
        writer.print("  mHost=");
        writer.println(mHost);
        writer.print(prefix);
        writer.print("  mContainer=");
        writer.println(mContainer);
        if (mParent != null) {
            writer.print(prefix);
            writer.print("  mParent=");
            writer.println(mParent);
        }
        writer.print(prefix);
        writer.print("  mCurState=");
        writer.print(mCurState);
        writer.print(" mStateSaved=");
        writer.print(mStateSaved);
        writer.print(" mStopped=");
        writer.print(mStopped);
        writer.print(" mDestroyed=");
        writer.println(mDestroyed);
        if (mNeedMenuInvalidate) {
            writer.print(prefix);
            writer.print("  mNeedMenuInvalidate=");
            writer.println(mNeedMenuInvalidate);
        }
    }

    void performPendingDeferredStart(@NonNull FragmentStateManager fragmentStateManager) {
        Fragment f = fragmentStateManager.getFragment();
        if (f.mDeferStart) {
            if (mExecutingActions) {
                // Wait until we're done executing our pending transactions
                mHavePendingDeferredStart = true;
                return;
            }
            f.mDeferStart = false;
            fragmentStateManager.moveToExpectedState();
        }
    }

    boolean isStateAtLeast(int state) {
        return mCurState >= state;
    }

    /**
     * Allows for changing the draw order on a container, if the container is a
     * FragmentContainerView.
     */
    void setExitAnimationOrder(@NonNull Fragment f, boolean isPop) {
        ViewGroup container = getFragmentContainer(f);
        if (container != null) {
            if (container instanceof FragmentContainerView) {
                ((FragmentContainerView) container).setDrawDisappearingViewsLast(!isPop);
            }
        }
    }

    /**
     * Changes the state of the fragment manager to {@code newState}. If the fragment manager
     * changes state or {@code always} is {@code true}, any fragments within it have their
     * states updated as well.
     *
     * @param newState The new state for the fragment manager
     * @param always If {@code true}, all fragments update their state, even
     *               if {@code newState} matches the current fragment manager's state.
     */
    void moveToState(int newState, boolean always) {
        if (mHost == null && newState != Fragment.INITIALIZING) {
            throw new IllegalStateException("No activity");
        }

        if (!always && newState == mCurState) {
            return;
        }

        mCurState = newState;
        mFragmentStore.moveToExpectedState();
        startPendingDeferredFragments();

        if (mNeedMenuInvalidate && mHost != null && mCurState == Fragment.RESUMED) {
            mHost.onSupportInvalidateOptionsMenu();
            mNeedMenuInvalidate = false;
        }
    }

    private void startPendingDeferredFragments() {
        for (FragmentStateManager fragmentStateManager :
                mFragmentStore.getActiveFragmentStateManagers()) {
            performPendingDeferredStart(fragmentStateManager);
        }
    }

    /**
     * For a given Fragment, get any existing FragmentStateManager found in the
     * {@link FragmentStore} or create a brand new FragmentStateManager if one does
     * not exist.
     *
     * @param f The Fragment to create a FragmentStateManager for
     * @return A valid FragmentStateManager
     */
    @NonNull
    FragmentStateManager createOrGetFragmentStateManager(@NonNull Fragment f) {
        FragmentStateManager existing = mFragmentStore.getFragmentStateManager(f.mWho);
        if (existing != null) {
            return existing;
        }
        FragmentStateManager fragmentStateManager = new FragmentStateManager(
                mLifecycleCallbacksDispatcher, mFragmentStore, f);
        // Restore state any state set via setInitialSavedState()
        fragmentStateManager.restoreState(mHost.getContext().getClassLoader());
        // Catch the FragmentStateManager up to our current state
        fragmentStateManager.setFragmentManagerState(mCurState);
        return fragmentStateManager;
    }

    FragmentStateManager addFragment(@NonNull Fragment fragment) {
        if (fragment.mPreviousWho != null) {
            FragmentStrictMode.onFragmentReuse(fragment, fragment.mPreviousWho);
        }
        if (isLoggingEnabled(Log.VERBOSE)) Log.v(TAG, "add: " + fragment);
        FragmentStateManager fragmentStateManager = createOrGetFragmentStateManager(fragment);
        fragment.mFragmentManager = this;
        mFragmentStore.makeActive(fragmentStateManager);
        if (!fragment.mDetached) {
            mFragmentStore.addFragment(fragment);
            fragment.mRemoving = false;
            if (fragment.mView == null) {
                fragment.mHiddenChanged = false;
            }
            if (isMenuAvailable(fragment)) {
                mNeedMenuInvalidate = true;
            }
        }
        return fragmentStateManager;
    }

    void removeFragment(@NonNull Fragment fragment) {
        if (isLoggingEnabled(Log.VERBOSE)) {
            Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting);
        }
        final boolean inactive = !fragment.isInBackStack();
        if (!fragment.mDetached || inactive) {
            mFragmentStore.removeFragment(fragment);
            if (isMenuAvailable(fragment)) {
                mNeedMenuInvalidate = true;
            }
            fragment.mRemoving = true;
            setVisibleRemovingFragment(fragment);
        }
    }

    /**
     * Marks a fragment as hidden to be later animated.
     *
     * @param fragment The fragment to be shown.
     */
    void hideFragment(@NonNull Fragment fragment) {
        if (isLoggingEnabled(Log.VERBOSE)) Log.v(TAG, "hide: " + fragment);
        if (!fragment.mHidden) {
            fragment.mHidden = true;
            // Toggle hidden changed so that if a fragment goes through show/hide/show
            // it doesn't go through the animation.
            fragment.mHiddenChanged = !fragment.mHiddenChanged;
            setVisibleRemovingFragment(fragment);
        }
    }

    /**
     * Marks a fragment as shown to be later animated.
     *
     * @param fragment The fragment to be shown.
     */
    void showFragment(@NonNull Fragment fragment) {
        if (isLoggingEnabled(Log.VERBOSE)) Log.v(TAG, "show: " + fragment);
        if (fragment.mHidden) {
            fragment.mHidden = false;
            // Toggle hidden changed so that if a fragment goes through show/hide/show
            // it doesn't go through the animation.
            fragment.mHiddenChanged = !fragment.mHiddenChanged;
        }
    }

    void detachFragment(@NonNull Fragment fragment) {
        if (isLoggingEnabled(Log.VERBOSE)) Log.v(TAG, "detach: " + fragment);
        if (!fragment.mDetached) {
            fragment.mDetached = true;
            if (fragment.mAdded) {
                // We are not already in back stack, so need to remove the fragment.
                if (isLoggingEnabled(Log.VERBOSE)) Log.v(TAG, "remove from detach: " + fragment);
                mFragmentStore.removeFragment(fragment);
                if (isMenuAvailable(fragment)) {
                    mNeedMenuInvalidate = true;
                }
                setVisibleRemovingFragment(fragment);
            }
        }
    }

    void attachFragment(@NonNull Fragment fragment) {
        if (isLoggingEnabled(Log.VERBOSE)) Log.v(TAG, "attach: " + fragment);
        if (fragment.mDetached) {
            fragment.mDetached = false;
            if (!fragment.mAdded) {
                mFragmentStore.addFragment(fragment);
                if (isLoggingEnabled(Log.VERBOSE)) Log.v(TAG, "add from attach: " + fragment);
                if (isMenuAvailable(fragment)) {
                    mNeedMenuInvalidate = true;
                }
            }
        }
    }

    /**
     * Finds a fragment that was identified by the given id either when inflated
     * from XML or as the container ID when added in a transaction.  This first
     * searches through fragments that are currently added to the manager's
     * activity; if no such fragment is found, then all fragments currently
     * on the back stack associated with this ID are searched.
     * @return The fragment if found or null otherwise.
     */
    @Nullable
    public Fragment findFragmentById(@IdRes int id) {
        return mFragmentStore.findFragmentById(id);
    }

    /**
     * Finds a fragment that was identified by the given tag either when inflated
     * from XML or as supplied when added in a transaction.  This first
     * searches through fragments that are currently added to the manager's
     * activity; if no such fragment is found, then all fragments currently
     * on the back stack are searched.
     * <p>
     * If provided a {@code null} tag, this method returns null.
     *
     * @param tag the tag used to search for the fragment
     * @return The fragment if found or null otherwise.
     */
    @Nullable
    public Fragment findFragmentByTag(@Nullable String tag) {
        return mFragmentStore.findFragmentByTag(tag);
    }

    Fragment findFragmentByWho(@NonNull String who) {
        return mFragmentStore.findFragmentByWho(who);
    }

    @Nullable
    Fragment findActiveFragment(@NonNull String who) {
        return mFragmentStore.findActiveFragment(who);
    }

    private void checkStateLoss() {
        if (isStateSaved()) {
            throw new IllegalStateException(
                    "Can not perform this action after onSaveInstanceState");
        }
    }

    /**
     * Returns {@code true} if the FragmentManager's state has already been saved
     * by its host. Any operations that would change saved state should not be performed
     * if this method returns true. For example, any popBackStack() method, such as
     * {@link #popBackStackImmediate()} or any FragmentTransaction using
     * {@link FragmentTransaction#commit()} instead of
     * {@link FragmentTransaction#commitAllowingStateLoss()} will change
     * the state and will result in an error.
     *
     * @return true if this FragmentManager's state has already been saved by its host
     */
    public boolean isStateSaved() {
        // See saveAllState() for the explanation of this.  We do this for
        // all platform versions, to keep our behavior more consistent between
        // them.
        return mStateSaved || mStopped;
    }

    /**
     * Adds an action to the queue of pending actions.
     *
     * @param action the action to add
     * @param allowStateLoss whether to allow loss of state information
     * @throws IllegalStateException if the activity has been destroyed
     */
    void enqueueAction(@NonNull OpGenerator action, boolean allowStateLoss) {
        if (!allowStateLoss) {
            if (mHost == null) {
                if (mDestroyed) {
                    throw new IllegalStateException("FragmentManager has been destroyed");
                } else {
                    throw new IllegalStateException("FragmentManager has not been attached to a "
                            + "host.");
                }
            }
            checkStateLoss();
        }
        synchronized (mPendingActions) {
            if (mHost == null) {
                if (allowStateLoss) {
                    // This FragmentManager isn't attached, so drop the entire transaction.
                    return;
                }
                throw new IllegalStateException("Activity has been destroyed");
            }
            mPendingActions.add(action);
            scheduleCommit();
        }
    }

    /**
     * Schedules the execution when one hasn't been scheduled already. This should happen
     * the first time {@link #enqueueAction(OpGenerator, boolean)} is called or when
     * a postponed transaction has been started with
     * {@link Fragment#startPostponedEnterTransition()}
     */
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void scheduleCommit() {
        synchronized (mPendingActions) {
            boolean pendingReady = mPendingActions.size() == 1;
            if (pendingReady) {
                mHost.getHandler().removeCallbacks(mExecCommit);
                mHost.getHandler().post(mExecCommit);
                updateOnBackPressedCallbackEnabled();
            }
        }
    }

    int allocBackStackIndex() {
        return mBackStackIndex.getAndIncrement();
    }

    /**
     * Broken out from exec*, this prepares for gathering and executing operations.
     *
     * @param allowStateLoss true if state loss should be ignored or false if it should be
     *                       checked.
     */
    private void ensureExecReady(boolean allowStateLoss) {
        if (mExecutingActions) {
            throw new IllegalStateException("FragmentManager is already executing transactions");
        }

        if (mHost == null) {
            if (mDestroyed) {
                throw new IllegalStateException("FragmentManager has been destroyed");
            } else {
                throw new IllegalStateException("FragmentManager has not been attached to a host.");
            }
        }

        if (Looper.myLooper() != mHost.getHandler().getLooper()) {
            throw new IllegalStateException("Must be called from main thread of fragment host");
        }

        if (!allowStateLoss) {
            checkStateLoss();
        }

        if (mTmpRecords == null) {
            mTmpRecords = new ArrayList<>();
            mTmpIsPop = new ArrayList<>();
        }
    }

    void execSingleAction(@NonNull OpGenerator action, boolean allowStateLoss) {
        if (allowStateLoss && (mHost == null || mDestroyed)) {
            // This FragmentManager isn't attached, so drop the entire transaction.
            return;
        }
        ensureExecReady(allowStateLoss);
        if (action.generateOps(mTmpRecords, mTmpIsPop)) {
            mExecutingActions = true;
            try {
                removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop);
            } finally {
                cleanupExec();
            }
        }

        updateOnBackPressedCallbackEnabled();
        doPendingDeferredStart();
        mFragmentStore.burpActive();
    }

    /**
     * Broken out of exec*, this cleans up the mExecutingActions and the temporary structures
     * used in executing operations.
     */
    private void cleanupExec() {
        mExecutingActions = false;
        mTmpIsPop.clear();
        mTmpRecords.clear();
    }

    /**
     * Only call from main thread!
     */
    boolean execPendingActions(boolean allowStateLoss) {
        ensureExecReady(allowStateLoss);

        boolean didSomething = false;
        while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) {
            mExecutingActions = true;
            try {
                removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop);
            } finally {
                cleanupExec();
            }
            didSomething = true;
        }

        updateOnBackPressedCallbackEnabled();
        doPendingDeferredStart();
        mFragmentStore.burpActive();

        return didSomething;
    }

    /**
     * Remove redundant BackStackRecord operations and executes them. This method merges operations
     * of proximate records that allow reordering. See
     * {@link FragmentTransaction#setReorderingAllowed(boolean)}.
     * <p>
     * For example, a transaction that adds to the back stack and then another that pops that
     * back stack record will be optimized to remove the unnecessary operation.
     * <p>
     * Likewise, two transactions committed that are executed at the same time will be optimized
     * to remove the redundant operations as well as two pop operations executed together.
     *
     * @param records The records pending execution
     * @param isRecordPop The direction that these records are being run.
     */
    private void removeRedundantOperationsAndExecute(@NonNull ArrayList<BackStackRecord> records,
            @NonNull ArrayList<Boolean> isRecordPop) {
        if (records.isEmpty()) {
            return;
        }

        if (records.size() != isRecordPop.size()) {
            throw new IllegalStateException("Internal error with the back stack records");
        }

        final int numRecords = records.size();
        int startIndex = 0;
        for (int recordNum = 0; recordNum < numRecords; recordNum++) {
            final boolean canReorder = records.get(recordNum).mReorderingAllowed;
            if (!canReorder) {
                // execute all previous transactions
                if (startIndex != recordNum) {
                    executeOpsTogether(records, isRecordPop, startIndex, recordNum);
                }
                // execute all pop operations that don't allow reordering together or
                // one add operation
                int reorderingEnd = recordNum + 1;
                if (isRecordPop.get(recordNum)) {
                    while (reorderingEnd < numRecords
                            && isRecordPop.get(reorderingEnd)
                            && !records.get(reorderingEnd).mReorderingAllowed) {
                        reorderingEnd++;
                    }
                }
                executeOpsTogether(records, isRecordPop, recordNum, reorderingEnd);
                startIndex = reorderingEnd;
                recordNum = reorderingEnd - 1;
            }
        }
        if (startIndex != numRecords) {
            executeOpsTogether(records, isRecordPop, startIndex, numRecords);
        }
    }

    /**
     * Executes a subset of a list of BackStackRecords, all of which either allow reordering or
     * do not allow ordering.
     * @param records A list of BackStackRecords that are to be executed
     * @param isRecordPop The direction that these records are being run.
     * @param startIndex The index of the first record in <code>records</code> to be executed
     * @param endIndex One more than the final record index in <code>records</code> to executed.
     */
    private void executeOpsTogether(@NonNull ArrayList<BackStackRecord> records,
            @NonNull ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {
        final boolean allowReordering = records.get(startIndex).mReorderingAllowed;
        boolean addToBackStack = false;
        if (mTmpAddedFragments == null) {
            mTmpAddedFragments = new ArrayList<>();
        } else {
            mTmpAddedFragments.clear();
        }
        mTmpAddedFragments.addAll(mFragmentStore.getFragments());
        Fragment oldPrimaryNav = getPrimaryNavigationFragment();
        for (int recordNum = startIndex; recordNum < endIndex; recordNum++) {
            final BackStackRecord record = records.get(recordNum);
            final boolean isPop = isRecordPop.get(recordNum);
            if (!isPop) {
                oldPrimaryNav = record.expandOps(mTmpAddedFragments, oldPrimaryNav);
            } else {
                oldPrimaryNav = record.trackAddedFragmentsInPop(mTmpAddedFragments, oldPrimaryNav);
            }
            addToBackStack = addToBackStack || record.mAddToBackStack;
        }
        mTmpAddedFragments.clear();

        if (!allowReordering && mCurState >= Fragment.CREATED) {
            // When reordering isn't allowed, we may be operating on Fragments that haven't
            // been made active
            for (int index = startIndex; index < endIndex; index++) {
                BackStackRecord record = records.get(index);
                for (FragmentTransaction.Op op : record.mOps) {
                    Fragment fragment = op.mFragment;
                    if (fragment != null && fragment.mFragmentManager != null) {
                        FragmentStateManager fragmentStateManager =
                                createOrGetFragmentStateManager(fragment);
                        mFragmentStore.makeActive(fragmentStateManager);
                    }
                }
            }
        }
        executeOps(records, isRecordPop, startIndex, endIndex);

        // The last operation determines the overall direction, this ensures that operations
        // such as push, push, pop, push are correctly considered a push
        boolean isPop = isRecordPop.get(endIndex - 1);

        if (addToBackStack && !mBackStackChangeListeners.isEmpty()) {
            Set<Fragment> fragments = new LinkedHashSet<>();
            // Build a list of fragments based on the records
            for (BackStackRecord record : records) {
                fragments.addAll(fragmentsFromRecord(record));
            }
            if (mTransitioningOp == null) {
                // Dispatch to all of the fragments in the list
                for (OnBackStackChangedListener listener : mBackStackChangeListeners) {
                    // We give all fragment the back stack changed started signal first
                    for (Fragment fragment : fragments) {
                        listener.onBackStackChangeStarted(fragment, isPop);
                    }
                }
                for (OnBackStackChangedListener listener : mBackStackChangeListeners) {
                    // Then we give them all the committed signal
                    for (Fragment fragment : fragments) {
                        listener.onBackStackChangeCommitted(fragment, isPop);
                    }
                }
            }
        }
        // Ensure that Fragments directly affected by operations
        // are moved to their expected state in operation order
        for (int index = startIndex; index < endIndex; index++) {
            BackStackRecord record = records.get(index);
            if (isPop) {
                // Pop operations get applied in reverse order
                for (int opIndex = record.mOps.size() - 1; opIndex >= 0; opIndex--) {
                    FragmentTransaction.Op op = record.mOps.get(opIndex);
                    Fragment fragment = op.mFragment;
                    if (fragment != null) {
                        FragmentStateManager fragmentStateManager =
                                createOrGetFragmentStateManager(fragment);
                        fragmentStateManager.moveToExpectedState();
                    }
                }
            } else {
                for (FragmentTransaction.Op op : record.mOps) {
                    Fragment fragment = op.mFragment;
                    if (fragment != null) {
                        FragmentStateManager fragmentStateManager =
                                createOrGetFragmentStateManager(fragment);
                        fragmentStateManager.moveToExpectedState();
                    }
                }
            }

        }
        // And only then do we move all other fragments to the current state
        moveToState(mCurState, true);
        Set<SpecialEffectsController> changedControllers = collectChangedControllers(
                records, startIndex, endIndex);
        for (SpecialEffectsController controller : changedControllers) {
            controller.updateOperationDirection(isPop);
            controller.markPostponedState();
            controller.executePendingOperations();
        }

        for (int recordNum = startIndex; recordNum < endIndex; recordNum++) {
            final BackStackRecord record = records.get(recordNum);
            isPop = isRecordPop.get(recordNum);
            if (isPop && record.mIndex >= 0) {
                record.mIndex = -1;
            }
            record.runOnCommitRunnables();
        }
        if (addToBackStack) {
            reportBackStackChanged();
        }
    }

    Set<SpecialEffectsController> collectChangedControllers(
            @NonNull ArrayList<BackStackRecord> records, int startIndex, int endIndex) {
        Set<SpecialEffectsController> controllers = new HashSet<>();
        for (int index = startIndex; index < endIndex; index++) {
            BackStackRecord record = records.get(index);
            for (FragmentTransaction.Op op : record.mOps) {
                Fragment fragment = op.mFragment;
                if (fragment != null) {
                    ViewGroup container = fragment.mContainer;
                    if (container != null) {
                        controllers.add(SpecialEffectsController.getOrCreateController(
                                container, this));
                    }
                }
            }
        }
        return controllers;
    }

    /**
     * Run the operations in the BackStackRecords, either to push or pop.
     *
     * @param records The list of records whose operations should be run.
     * @param isRecordPop The direction that these records are being run.
     * @param startIndex The index of the first entry in records to run.
     * @param endIndex One past the index of the final entry in records to run.
     */
    private static void executeOps(@NonNull ArrayList<BackStackRecord> records,
            @NonNull ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {
        for (int i = startIndex; i < endIndex; i++) {
            final BackStackRecord record = records.get(i);
            final boolean isPop = isRecordPop.get(i);
            if (isPop) {
                record.bumpBackStackNesting(-1);
                record.executePopOps();
            } else {
                record.bumpBackStackNesting(1);
                record.executeOps();
            }
        }
    }

    /**
     * Set a Fragment that is visibly being removed from the screen to a tag on its container.
     * If a Fragment with the same container is already set, the previously added
     * Fragment has its exit animation updated to the correct exit animation (either exit or
     * pop_exit).
     */
    private void setVisibleRemovingFragment(@NonNull Fragment f) {
        ViewGroup container = getFragmentContainer(f);
        if (container != null
                && f.getEnterAnim() + f.getExitAnim() + f.getPopEnterAnim() + f.getPopExitAnim() > 0
        ) {
            if (container.getTag(R.id.visible_removing_fragment_view_tag) == null) {
                container.setTag(R.id.visible_removing_fragment_view_tag, f);
            }
            ((Fragment) container.getTag(R.id.visible_removing_fragment_view_tag))
                    .setPopDirection(f.getPopDirection());
        }
    }

    private ViewGroup getFragmentContainer(@NonNull Fragment f) {
        // If there's already a container, just return it
        if (f.mContainer != null) {
            return f.mContainer;
        }
        // If the fragment has no containerId we should return null immediately.
        if (f.mContainerId <= 0) {
            return null;
        }
        // This will be false if a child fragment is added to its parent's childFragmentManager
        // before a view is created for Parent. In all other cases (adding a fragment to an
        // FragmentActivity's fragmentManager, adding a child fragment to a parent that has a view),
        // it should be true.
        if (mContainer.onHasView()) {
            View view = mContainer.onFindViewById(f.mContainerId);
            // We should handle the case where the container may not be a ViewGroup
            if (view instanceof ViewGroup) {
                return (ViewGroup) view;
            }
        }
        return null;
    }

    /**
     * Starts all postponed transactions regardless of whether they are ready or not.
     */
    private void forcePostponedTransactions() {
        Set<SpecialEffectsController> controllers = collectAllSpecialEffectsController();
        for (SpecialEffectsController controller : controllers) {
            controller.forcePostponedExecutePendingOperations();
        }
    }

    /**
     * Ends the animations of fragments so that they immediately reach the end state.
     * This is used prior to saving the state so that the correct state is saved.
     */
    private void endAnimatingAwayFragments() {
        Set<SpecialEffectsController> controllers = collectAllSpecialEffectsController();
        for (SpecialEffectsController controller : controllers) {
            controller.forceCompleteAllOperations();
        }
    }

    private Set<SpecialEffectsController> collectAllSpecialEffectsController() {
        Set<SpecialEffectsController> controllers = new HashSet<>();
        for (FragmentStateManager fragmentStateManager :
                mFragmentStore.getActiveFragmentStateManagers()) {
            ViewGroup container = fragmentStateManager.getFragment().mContainer;
            if (container != null) {
                controllers.add(SpecialEffectsController.getOrCreateController(container,
                        getSpecialEffectsControllerFactory()));
            }
        }
        return controllers;
    }

    /**
     * Adds all records in the pending actions to records and whether they are add or pop
     * operations to isPop. After executing, the pending actions will be empty.
     *
     * @param records All pending actions will generate BackStackRecords added to this.
     *                This contains the transactions, in order, to execute.
     * @param isPop All pending actions will generate booleans to add to this. This contains
     *              an entry for each entry in records to indicate whether or not it is a
     *              pop action.
     */
    private boolean generateOpsForPendingActions(@NonNull ArrayList<BackStackRecord> records,
            @NonNull ArrayList<Boolean> isPop) {
        boolean didSomething = false;
        synchronized (mPendingActions) {
            if (mPendingActions.isEmpty()) {
                return false;
            }

            try {
                final int numActions = mPendingActions.size();
                for (int i = 0; i < numActions; i++) {
                    didSomething |= mPendingActions.get(i).generateOps(records, isPop);
                }
            } finally {
                // Whether generateOps succeeds or not, we clear the pending actions
                // to avoid re-processing the same set of actions a second time
                mPendingActions.clear();
                mHost.getHandler().removeCallbacks(mExecCommit);
            }
        }
        return didSomething;
    }

    private void doPendingDeferredStart() {
        if (mHavePendingDeferredStart) {
            mHavePendingDeferredStart = false;
            startPendingDeferredFragments();
        }
    }

    private void reportBackStackChanged() {
        for (int i = 0; i < mBackStackChangeListeners.size(); i++) {
            mBackStackChangeListeners.get(i).onBackStackChanged();
        }
    }

    Set<Fragment> fragmentsFromRecord(@NonNull BackStackRecord record) {
        Set<Fragment> fragments = new HashSet<>();
        for (int i = 0; i < record.mOps.size(); i++) {
            Fragment f = record.mOps.get(i).mFragment;
            if (f != null && record.mAddToBackStack) {
                fragments.add(f);
            }
        }
        return fragments;
    }

    void addBackStackState(BackStackRecord state) {
        mBackStack.add(state);
    }

    boolean restoreBackStackState(@NonNull ArrayList<BackStackRecord> records,
            @NonNull ArrayList<Boolean> isRecordPop, @NonNull String name) {
        BackStackState backStackState = mBackStackStates.remove(name);
        if (backStackState == null) {
            return false;
        }

        HashMap<String, Fragment> pendingSavedFragments = new HashMap<>();
        for (BackStackRecord record : records) {
            if (record.mBeingSaved) {
                for (FragmentTransaction.Op op : record.mOps) {
                    if (op.mFragment != null) {
                        pendingSavedFragments.put(op.mFragment.mWho, op.mFragment);
                    }
                }
            }
        }
        List<BackStackRecord> backStackRecords = backStackState.instantiate(this,
                pendingSavedFragments);
        boolean added = false;
        for (BackStackRecord record : backStackRecords) {
            added = record.generateOps(records, isRecordPop) || added;
        }
        return added;
    }

    boolean saveBackStackState(@NonNull ArrayList<BackStackRecord> records,
            @NonNull ArrayList<Boolean> isRecordPop, @NonNull String name) {
        final int index = findBackStackIndex(name, -1, true);
        if (index < 0) {
            return false;
        }

        // Assert that all of the transactions use setReorderingAllowed(true)
        // to ensure that when they are restored, they are restored as a single
        // atomic operation and intermediate fragments aren't moved all the way
        // up to the RESUMED state
        for (int i = index; i < mBackStack.size(); i++) {
            BackStackRecord record = mBackStack.get(i);
            if (!record.mReorderingAllowed) {
                throwException(new IllegalArgumentException("saveBackStack(\"" + name + "\") "
                        + "included FragmentTransactions must use setReorderingAllowed(true) "
                        + "to ensure that the back stack can be restored as an atomic operation. "
                        + "Found " + record + " that did not use setReorderingAllowed(true)."));
            }
        }

        // Assert that the set of affected fragments are entirely self contained within
        // the set of transactions being saved by ensuring that the first transaction including
        // that fragment includes an OP_ADD
        HashSet<Fragment> allFragments = new HashSet<>();
        for (int i = index; i < mBackStack.size(); i++) {
            BackStackRecord record = mBackStack.get(i);
            HashSet<Fragment> affectedFragments = new HashSet<>();
            HashSet<Fragment> addedFragments = new HashSet<>();
            for (FragmentTransaction.Op op : record.mOps) {
                Fragment f = op.mFragment;
                if (f == null) {
                    continue;
                }
                if (!op.mFromExpandedOp || op.mCmd == FragmentTransaction.OP_ADD
                        || op.mCmd == FragmentTransaction.OP_REPLACE
                        || op.mCmd == FragmentTransaction.OP_SET_PRIMARY_NAV) {
                    allFragments.add(f);
                    affectedFragments.add(f);
                }
                if (op.mCmd == FragmentTransaction.OP_ADD
                        || op.mCmd == FragmentTransaction.OP_REPLACE) {
                    addedFragments.add(f);
                }
            }
            affectedFragments.removeAll(addedFragments);
            if (!affectedFragments.isEmpty()) {
                throwException(new IllegalArgumentException("saveBackStack(\"" + name + "\") "
                        + "must be self contained and not reference fragments from "
                        + "non-saved FragmentTransactions. Found reference to fragment"
                        + (affectedFragments.size() == 1
                        ? " " + affectedFragments.iterator().next()
                        : "s " + affectedFragments)
                        + " in " + record + " that were previously "
                        + "added to the FragmentManager through a separate FragmentTransaction."));
            }
        }

        // Ensure that there are no retained fragments in the affected fragments or
        // their transitive set of child fragments
        ArrayDeque<Fragment> fragmentsToSearch = new ArrayDeque<>(allFragments);
        while (!fragmentsToSearch.isEmpty()) {
            Fragment currentFragment = fragmentsToSearch.removeFirst();
            if (currentFragment.mRetainInstance) {
                throwException(new IllegalArgumentException("saveBackStack(\"" + name + "\") "
                        + "must not contain retained fragments. Found "
                        + (allFragments.contains(currentFragment)
                        ? "direct reference to retained "
                        : "retained child ")
                        + "fragment " + currentFragment));
            }
            // Then recursively check the child fragments for retained fragments
            for (Fragment f : currentFragment.mChildFragmentManager.getActiveFragments()) {
                if (f != null) {
                    fragmentsToSearch.addLast(f);
                }
            }
        }

        // Now actually record each save
        final ArrayList<String> fragments = new ArrayList<>();
        for (Fragment f : allFragments) {
            fragments.add(f.mWho);
        }
        final ArrayList<BackStackRecordState> backStackRecordStates =
                new ArrayList<>(mBackStack.size() - index);
        // Add placeholders for each BackStackRecordState
        for (int i = index; i < mBackStack.size(); i++) {
            backStackRecordStates.add(null);
        }
        final BackStackState backStackState = new BackStackState(
                fragments, backStackRecordStates);
        for (int i = mBackStack.size() - 1; i >= index; i--) {
            BackStackRecord record = mBackStack.remove(i);

            // Create a copy of the record to save
            BackStackRecord copy = new BackStackRecord(record);
            copy.collapseOps();
            BackStackRecordState state = new BackStackRecordState(copy);
            backStackRecordStates.set(i - index, state);

            // And now mark the record as being saved to ensure that each
            // fragment saves its state properly
            record.mBeingSaved = true;
            records.add(record);
            isRecordPop.add(true);
        }
        mBackStackStates.put(name, backStackState);
        return true;
    }

    /**
     * We have to handle a number of cases here:
     * 1. We have no back stack state at all
     * 2. We have previously saved the back stack state and we now only have the state
     * 3. We are in the process of handling a saveBackStack() operation (it is in
     * the set of records to be processed prior to this)
     * 3a. We are in the process of handling a saveBackStack() and there are other
     * FragmentTransactions queued up between that save and this clear (maybe even
     * including a restoreBackStack operation).
     *
     * This comes together to mean that we can't actually 'clear' anything at the time
     * when this particular method is called - instead, we need to enqueue exactly what
     * records, etc. we need to do to get the back stack and state into the right state
     * after they're all executed. This means 'clear' really means 'restore'+'pop' - as
     * we 'pop' instead of 'save', any saved state (and ViewModels, etc.) will be cleared
     * no matter what pending operations are enqueued up before or after this.
     */
    boolean clearBackStackState(@NonNull ArrayList<BackStackRecord> records,
            @NonNull ArrayList<Boolean> isRecordPop, @NonNull String name) {
        boolean restoredBackStackState = restoreBackStackState(records, isRecordPop, name);
        if (!restoredBackStackState) {
            return false;
        }
        return popBackStackState(records, isRecordPop, name, -1, POP_BACK_STACK_INCLUSIVE);
    }

    @SuppressWarnings({"unused", "WeakerAccess"}) /* synthetic access */
    boolean popBackStackState(@NonNull ArrayList<BackStackRecord> records,
            @NonNull ArrayList<Boolean> isRecordPop, @Nullable String name, int id, int flags) {
        int index = findBackStackIndex(name, id, (flags & POP_BACK_STACK_INCLUSIVE) != 0);
        if (index < 0) {
            return false;
        }
        for (int i = mBackStack.size() - 1; i >= index; i--) {
            records.add(mBackStack.remove(i));
            isRecordPop.add(true);
        }
        return true;
    }

    boolean prepareBackStackState(@NonNull ArrayList<BackStackRecord> records,
            @NonNull ArrayList<Boolean> isRecordPop) {
        if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
            Log.v(
                    TAG, "FragmentManager has the following pending actions inside of "
                            + "prepareBackStackState: " + mPendingActions
            );
        }
        if (mBackStack.isEmpty()) {
            Log.i(TAG, "Ignoring call to start back stack pop because the back stack is empty.");
            return false;
        }
        // The transitioning record is the last one on the back stack.
        mTransitioningOp = mBackStack.get(mBackStack.size() - 1);
        // Mark all fragments in the record as transitioning
        for (FragmentTransaction.Op op: mTransitioningOp.mOps) {
            if (op.mFragment != null) {
                op.mFragment.mTransitioning = true;
            }
        }
        return popBackStackState(records, isRecordPop, null, -1, 0);
    }

    /**
     * Find the index in the back stack associated with the given name / id.
     * <p>
     * When <code>inclusive</code> is <code>true</code>, the index of the matching record
     * will be returned. When it is <code>false</code>, the index of the record directly
     * after it will be returned. In cases where you are doing an inclusive search and
     * multiple records have the same name / id, the index returned includes all
     * consecutive matches following the first match.
     *
     * @param name The name set via {@link FragmentTransaction#addToBackStack(String)}. Use
     *             <code>null</code> if you do not want to search by name.
     * @param id The id returned by {@link FragmentTransaction#commit()}. Use
     *           <code>-1</code> if you do not want to search by id.
     * @param inclusive Whether to include the record specified by name or id.
     * @return
     */
    private int findBackStackIndex(@Nullable String name, int id, boolean inclusive) {
        if (mBackStack.isEmpty()) {
            return -1;
        }
        if (name == null && id < 0) {
            if (inclusive) {
                return 0;
            } else {
                return mBackStack.size() - 1;
            }
        } else {
            // If a name or ID is specified, look for that place in
            // the stack.
            int index = mBackStack.size() - 1;
            while (index >= 0) {
                BackStackRecord bss = mBackStack.get(index);
                if (name != null && name.equals(bss.getName())) {
                    break;
                }
                if (id >= 0 && id == bss.mIndex) {
                    break;
                }
                index--;
            }
            if (index < 0) {
                return index;
            }
            if (inclusive) {
                // Consume all following entries that match.
                while (index > 0) {
                    BackStackRecord bss = mBackStack.get(index - 1);
                    if ((name != null && name.equals(bss.getName()))
                            || (id >= 0 && id == bss.mIndex)) {
                        index--;
                        continue;
                    }
                    break;
                }
            } else if (index == mBackStack.size() - 1) {
                // For a non-inclusive search, finding the last record
                // is the same as finding nothing at all since the
                // matching record itself is not included
                return -1;
            } else {
                // Non-inclusive, so skip the actual matching record
                index++;
            }
            return index;
        }
    }

    /**
     * @deprecated Ideally, all {@link androidx.fragment.app.FragmentHostCallback} instances
     * implement ViewModelStoreOwner and we can remove this method entirely.
     */
    @Deprecated
    FragmentManagerNonConfig retainNonConfig() {
        if (mHost instanceof ViewModelStoreOwner) {
            throwException(new IllegalStateException("You cannot use retainNonConfig when your "
                    + "FragmentHostCallback implements ViewModelStoreOwner."));
        }
        return mNonConfig.getSnapshot();
    }

    Parcelable saveAllState() {
        if (mHost instanceof SavedStateRegistryOwner) {
            throwException(new IllegalStateException("You cannot use saveAllState when your "
                    + "FragmentHostCallback implements SavedStateRegistryOwner."));
        }
        Bundle savedState = saveAllStateInternal();
        return savedState.isEmpty() ? null : savedState;
    }

    @NonNull
    Bundle saveAllStateInternal() {
        Bundle bundle = new Bundle();
        // Make sure all pending operations have now been executed to get
        // our state update-to-date.
        forcePostponedTransactions();
        endAnimatingAwayFragments();
        execPendingActions(true);

        mStateSaved = true;
        mNonConfig.setIsStateSaved(true);

        // First save all active fragments.
        ArrayList<String> active = mFragmentStore.saveActiveFragments();

        // And grab all fragments' saved state bundles
        HashMap<String, Bundle> savedState = mFragmentStore.getAllSavedState();
        if (savedState.isEmpty()) {
            if (isLoggingEnabled(Log.VERBOSE)) {
                Log.v(TAG, "saveAllState: no fragments!");
            }
        } else {
            // Build list of currently added fragments.
            ArrayList<String> added = mFragmentStore.saveAddedFragments();

            // Now save back stack.
            BackStackRecordState[] backStack = null;
            int size = mBackStack.size();
            if (size > 0) {
                backStack = new BackStackRecordState[size];
                for (int i = 0; i < size; i++) {
                    backStack[i] = new BackStackRecordState(mBackStack.get(i));
                    if (isLoggingEnabled(Log.VERBOSE)) {
                        Log.v(TAG, "saveAllState: adding back stack #" + i
                                + ": " + mBackStack.get(i));
                    }
                }
            }

            FragmentManagerState fms = new FragmentManagerState();
            fms.mActive = active;
            fms.mAdded = added;
            fms.mBackStack = backStack;
            fms.mBackStackIndex = mBackStackIndex.get();
            if (mPrimaryNav != null) {
                fms.mPrimaryNavActiveWho = mPrimaryNav.mWho;
            }
            fms.mBackStackStateKeys.addAll(mBackStackStates.keySet());
            fms.mBackStackStates.addAll(mBackStackStates.values());
            fms.mLaunchedFragments = new ArrayList<>(mLaunchedFragments);
            bundle.putParcelable(FRAGMENT_MANAGER_STATE_KEY, fms);

            for (String resultName : mResults.keySet()) {
                bundle.putBundle(RESULT_KEY_PREFIX + resultName, mResults.get(resultName));
            }

            for (String fWho : savedState.keySet()) {
                bundle.putBundle(FRAGMENT_KEY_PREFIX + fWho, savedState.get(fWho));
            }
        }

        return bundle;
    }

    @SuppressWarnings("deprecation")
    void restoreAllState(@Nullable Parcelable state, @Nullable FragmentManagerNonConfig nonConfig) {
        if (mHost instanceof ViewModelStoreOwner) {
            throwException(new IllegalStateException("You must use restoreSaveState when your "
                    + "FragmentHostCallback implements ViewModelStoreOwner"));
        }
        mNonConfig.restoreFromSnapshot(nonConfig);
        restoreSaveStateInternal(state);
    }

    void restoreSaveState(@Nullable Parcelable state) {
        if (mHost instanceof SavedStateRegistryOwner) {
            throwException(new IllegalStateException("You cannot use restoreSaveState when your "
                    + "FragmentHostCallback implements SavedStateRegistryOwner."));
        }
        restoreSaveStateInternal(state);
    }

    @SuppressWarnings("deprecation")
    void restoreSaveStateInternal(@Nullable Parcelable state) {
        // If there is no saved state at all, then there's nothing else to do
        if (state == null) return;
        Bundle bundle = (Bundle) state;

        // Restore the fragment results
        for (String bundleKey : bundle.keySet()) {
            if (bundleKey.startsWith(RESULT_KEY_PREFIX)) {
                Bundle savedResult = bundle.getBundle(bundleKey);
                if (savedResult != null) {
                    savedResult.setClassLoader(mHost.getContext().getClassLoader());
                    String resultKey = bundleKey.substring(RESULT_KEY_PREFIX.length());
                    mResults.put(resultKey, savedResult);
                }
            }
        }

        // Restore the saved bundle for all fragments
        HashMap<String, Bundle> allStateBundles = new HashMap<>();
        for (String bundleKey : bundle.keySet()) {
            if (bundleKey.startsWith(FRAGMENT_KEY_PREFIX)) {
                Bundle savedFragmentBundle = bundle.getBundle(bundleKey);
                if (savedFragmentBundle != null) {
                    savedFragmentBundle.setClassLoader(mHost.getContext().getClassLoader());
                    String fragmentKey = bundleKey.substring(FRAGMENT_KEY_PREFIX.length());
                    allStateBundles.put(fragmentKey, savedFragmentBundle);
                }
            }
        }
        mFragmentStore.restoreSaveState(allStateBundles);

        FragmentManagerState fms = bundle.getParcelable(FRAGMENT_MANAGER_STATE_KEY);
        if (fms == null) return;

        // Build the full list of active fragments, instantiating them from
        // their saved state.
        mFragmentStore.resetActiveFragments();
        for (String who : fms.mActive) {
            // Retrieve any saved state, clearing it out for future calls
            Bundle stateBundle = mFragmentStore.setSavedState(who, null);
            if (stateBundle != null) {
                FragmentStateManager fragmentStateManager;
                FragmentState fs = stateBundle.getParcelable(
                        FragmentStateManager.FRAGMENT_STATE_KEY);
                Fragment retainedFragment = mNonConfig.findRetainedFragmentByWho(fs.mWho);
                if (retainedFragment != null) {
                    if (isLoggingEnabled(Log.VERBOSE)) {
                        Log.v(TAG, "restoreSaveState: re-attaching retained "
                                + retainedFragment);
                    }
                    fragmentStateManager = new FragmentStateManager(mLifecycleCallbacksDispatcher,
                            mFragmentStore, retainedFragment, stateBundle);
                } else {
                    fragmentStateManager = new FragmentStateManager(mLifecycleCallbacksDispatcher,
                            mFragmentStore, mHost.getContext().getClassLoader(),
                            getFragmentFactory(), stateBundle);
                }
                Fragment f = fragmentStateManager.getFragment();
                f.mSavedFragmentState = stateBundle;
                f.mFragmentManager = this;
                if (isLoggingEnabled(Log.VERBOSE)) {
                    Log.v(TAG, "restoreSaveState: active (" + f.mWho + "): " + f);
                }
                fragmentStateManager.restoreState(mHost.getContext().getClassLoader());
                mFragmentStore.makeActive(fragmentStateManager);
                // Catch the FragmentStateManager up to our current state
                // In almost all cases, this is Fragment.INITIALIZING, but just in
                // case a FragmentController does something...unique, let's do this anyways.
                fragmentStateManager.setFragmentManagerState(mCurState);
            }
        }

        // Check to make sure there aren't any retained fragments that aren't in mActive
        // This can happen if a retained fragment is added after the state is saved
        for (Fragment f : mNonConfig.getRetainedFragments()) {
            if (!mFragmentStore.containsActiveFragment(f.mWho)) {
                if (isLoggingEnabled(Log.VERBOSE)) {
                    Log.v(TAG, "Discarding retained Fragment " + f
                            + " that was not found in the set of active Fragments " + fms.mActive);
                }
                mNonConfig.removeRetainedFragment(f);
                // We need to ensure that onDestroy and any other clean up is done
                // so move the Fragment up to CREATED, then mark it as being removed, then
                // destroy it without actually adding the Fragment to the FragmentStore
                f.mFragmentManager = this;
                FragmentStateManager fragmentStateManager = new FragmentStateManager(
                        mLifecycleCallbacksDispatcher, mFragmentStore, f);
                fragmentStateManager.setFragmentManagerState(Fragment.CREATED);
                fragmentStateManager.moveToExpectedState();
                f.mRemoving = true;
                fragmentStateManager.moveToExpectedState();
            }
        }

        // Build the list of currently added fragments.
        mFragmentStore.restoreAddedFragments(fms.mAdded);

        // Build the back stack.
        if (fms.mBackStack != null) {
            mBackStack = new ArrayList<>(fms.mBackStack.length);
            for (int i = 0; i < fms.mBackStack.length; i++) {
                BackStackRecord bse = fms.mBackStack[i].instantiate(this);
                if (isLoggingEnabled(Log.VERBOSE)) {
                    Log.v(TAG, "restoreAllState: back stack #" + i
                            + " (index " + bse.mIndex + "): " + bse);
                    LogWriter logw = new LogWriter(TAG);
                    PrintWriter pw = new PrintWriter(logw);
                    bse.dump("  ", pw, false);
                    pw.close();
                }
                mBackStack.add(bse);
            }
        } else {
            mBackStack = new ArrayList<>();
        }
        mBackStackIndex.set(fms.mBackStackIndex);

        if (fms.mPrimaryNavActiveWho != null) {
            mPrimaryNav = findActiveFragment(fms.mPrimaryNavActiveWho);
            dispatchParentPrimaryNavigationFragmentChanged(mPrimaryNav);
        }

        ArrayList<String> savedBackStackStateKeys = fms.mBackStackStateKeys;
        if (savedBackStackStateKeys != null) {
            for (int i = 0; i < savedBackStackStateKeys.size(); i++) {
                mBackStackStates.put(savedBackStackStateKeys.get(i), fms.mBackStackStates.get(i));
            }
        }

        mLaunchedFragments = new ArrayDeque<>(fms.mLaunchedFragments);
    }

    @RestrictTo(RestrictTo.Scope.LIBRARY)
    @NonNull
    public FragmentHostCallback<?> getHost() {
        return mHost;
    }

    @Nullable
    Fragment getParent() {
        return mParent;
    }

    @NonNull
    FragmentContainer getContainer() {
        return mContainer;
    }

    @NonNull
    FragmentStore getFragmentStore() {
        return mFragmentStore;
    }

    @SuppressWarnings("deprecation")
    void attachController(@NonNull FragmentHostCallback<?> host,
            @NonNull FragmentContainer container, @Nullable final Fragment parent) {
        if (mHost != null) throw new IllegalStateException("Already attached");
        mHost = host;
        mContainer = container;
        mParent = parent;

        // Add a FragmentOnAttachListener to the parent fragment / host to support
        // backward compatibility with the deprecated onAttachFragment() APIs
        if (mParent != null) {
            addFragmentOnAttachListener(new FragmentOnAttachListener() {
                @SuppressWarnings("deprecation")
                @Override
                public void onAttachFragment(@NonNull FragmentManager fragmentManager,
                        @NonNull Fragment fragment) {
                    parent.onAttachFragment(fragment);
                }
            });
        } else if (host instanceof FragmentOnAttachListener) {
            addFragmentOnAttachListener((FragmentOnAttachListener) host);
        }

        if (mParent != null) {
            // Since the callback depends on us being the primary navigation fragment,
            // update our callback now that we have a parent so that we have the correct
            // state by default
            updateOnBackPressedCallbackEnabled();
        }
        // Set up the OnBackPressedCallback
        if (host instanceof OnBackPressedDispatcherOwner) {
            OnBackPressedDispatcherOwner dispatcherOwner = ((OnBackPressedDispatcherOwner) host);
            mOnBackPressedDispatcher = dispatcherOwner.getOnBackPressedDispatcher();
            LifecycleOwner owner = parent != null ? parent : dispatcherOwner;
            mOnBackPressedDispatcher.addCallback(owner, mOnBackPressedCallback);
        }

        // Get the FragmentManagerViewModel
        if (parent != null) {
            mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
        } else if (host instanceof ViewModelStoreOwner) {
            ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
            mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
        } else {
            mNonConfig = new FragmentManagerViewModel(false);
        }
        // Ensure that the state is in sync with FragmentManager
        mNonConfig.setIsStateSaved(isStateSaved());
        mFragmentStore.setNonConfig(mNonConfig);

        if (mHost instanceof SavedStateRegistryOwner && parent == null) {
            SavedStateRegistry registry =
                    ((SavedStateRegistryOwner) mHost).getSavedStateRegistry();
            registry.registerSavedStateProvider(SAVED_STATE_KEY, () -> {
                        return saveAllStateInternal();
                    }
            );

            Bundle savedInstanceState = registry
                    .consumeRestoredStateForKey(SAVED_STATE_KEY);
            if (savedInstanceState != null) {
                restoreSaveStateInternal(savedInstanceState);
            }
        }

        if (mHost instanceof ActivityResultRegistryOwner) {
            ActivityResultRegistry registry =
                    ((ActivityResultRegistryOwner) mHost).getActivityResultRegistry();

            String parentId = parent != null ? parent.mWho + ":" : "";
            String keyPrefix = "FragmentManager:" + parentId;

            mStartActivityForResult = registry.register(keyPrefix + "StartActivityForResult",
                    new ActivityResultContracts.StartActivityForResult(),
                    new ActivityResultCallback<ActivityResult>() {
                        @Override
                        public void onActivityResult(ActivityResult result) {
                            LaunchedFragmentInfo requestInfo = mLaunchedFragments.pollLast();
                            if (requestInfo == null) {
                                Log.w(TAG, "No Activities were started for result for " + this);
                                return;
                            }
                            String fragmentWho = requestInfo.mWho;
                            int requestCode = requestInfo.mRequestCode;
                            Fragment fragment =  mFragmentStore.findFragmentByWho(fragmentWho);
                            // Although unlikely, it is possible this fragment could be null if a
                            // fragment transactions was committed immediately after the for
                            // result call
                            if (fragment == null) {
                                Log.w(TAG,
                                        "Activity result delivered for unknown Fragment "
                                                + fragmentWho);
                                return;
                            }
                            fragment.onActivityResult(requestCode, result.getResultCode(),
                                    result.getData());
                        }
                    });

            mStartIntentSenderForResult = registry.register(keyPrefix
                            + "StartIntentSenderForResult",
                    new FragmentManager.FragmentIntentSenderContract(),
                    new ActivityResultCallback<ActivityResult>() {
                        @Override
                        public void onActivityResult(ActivityResult result) {
                            LaunchedFragmentInfo requestInfo = mLaunchedFragments.pollFirst();
                            if (requestInfo == null) {
                                Log.w(TAG, "No IntentSenders were started for " + this);
                                return;
                            }
                            String fragmentWho = requestInfo.mWho;
                            int requestCode = requestInfo.mRequestCode;
                            Fragment fragment =  mFragmentStore.findFragmentByWho(fragmentWho);
                            // Although unlikely, it is possible this fragment could be null if a
                            // fragment transactions was committed immediately after the for
                            // result call
                            if (fragment == null) {
                                Log.w(TAG, "Intent Sender result delivered for unknown Fragment "
                                                + fragmentWho);
                                return;
                            }
                            fragment.onActivityResult(requestCode, result.getResultCode(),
                                    result.getData());
                        }
                    });

            mRequestPermissions = registry.register(keyPrefix + "RequestPermissions",
                    new ActivityResultContracts.RequestMultiplePermissions(),
                    new ActivityResultCallback<Map<String, Boolean>>() {
                        @Override
                        public void onActivityResult(Map<String, Boolean> result) {
                            String[] permissions = result.keySet().toArray(new String[0]);
                            ArrayList<Boolean> resultValues = new ArrayList<>(result.values());
                            int[] grantResults = new int[resultValues.size()];
                            for (int i = 0; i < resultValues.size(); i++) {
                                grantResults[i] = resultValues.get(i)
                                        ? PackageManager.PERMISSION_GRANTED
                                        : PackageManager.PERMISSION_DENIED;
                            }
                            LaunchedFragmentInfo requestInfo = mLaunchedFragments.pollFirst();
                            if (requestInfo == null) {
                                Log.w(TAG, "No permissions were requested for " + this);
                                return;
                            }
                            String fragmentWho = requestInfo.mWho;
                            int requestCode = requestInfo.mRequestCode;
                            Fragment fragment =  mFragmentStore.findFragmentByWho(fragmentWho);
                            // Although unlikely, it is possible this fragment could be null if a
                            // fragment transactions was committed immediately after the request
                            // permissions call
                            if (fragment == null) {
                                Log.w(TAG, "Permission request result delivered for unknown "
                                        + "Fragment " + fragmentWho);
                                return;
                            }
                            fragment.onRequestPermissionsResult(requestCode, permissions,
                                    grantResults);
                        }
                    });
        }

        if (mHost instanceof OnConfigurationChangedProvider) {
            OnConfigurationChangedProvider onConfigurationChangedProvider =
                    (OnConfigurationChangedProvider) mHost;
            onConfigurationChangedProvider.addOnConfigurationChangedListener(
                    mOnConfigurationChangedListener);
        }

        if (mHost instanceof OnTrimMemoryProvider) {
            OnTrimMemoryProvider onTrimMemoryProvider = (OnTrimMemoryProvider) mHost;
            onTrimMemoryProvider.addOnTrimMemoryListener(mOnTrimMemoryListener);
        }

        if (mHost instanceof OnMultiWindowModeChangedProvider) {
            OnMultiWindowModeChangedProvider onMultiWindowModeChangedProvider =
                    (OnMultiWindowModeChangedProvider) mHost;
            onMultiWindowModeChangedProvider.addOnMultiWindowModeChangedListener(
                    mOnMultiWindowModeChangedListener);
        }

        if (mHost instanceof OnPictureInPictureModeChangedProvider) {
            OnPictureInPictureModeChangedProvider onPictureInPictureModeChangedProvider =
                    (OnPictureInPictureModeChangedProvider) mHost;
            onPictureInPictureModeChangedProvider.addOnPictureInPictureModeChangedListener(
                    mOnPictureInPictureModeChangedListener);
        }

        if (mHost instanceof MenuHost && parent == null) {
            ((MenuHost) mHost).addMenuProvider(mMenuProvider);
        }
    }

    void noteStateNotSaved() {
        // A fragment added via the <fragment> tag can have noteStateNotSaved() called
        // by its parent fragment before attachController() has been called. In this case,
        // we should early return as the state not being saved is the default.
        if (mHost == null) {
            return;
        }
        mStateSaved = false;
        mStopped = false;
        mNonConfig.setIsStateSaved(false);
        for (Fragment fragment : mFragmentStore.getFragments()) {
            if (fragment != null) {
                fragment.noteStateNotSaved();
            }
        }
    }

    void launchStartActivityForResult(@NonNull Fragment f,
            @NonNull Intent intent,
            int requestCode, @Nullable Bundle options) {
        if (mStartActivityForResult != null) {
            LaunchedFragmentInfo info = new LaunchedFragmentInfo(f.mWho, requestCode);
            mLaunchedFragments.addLast(info);
            if (options != null) {
                intent.putExtra(EXTRA_ACTIVITY_OPTIONS_BUNDLE, options);
            }
            mStartActivityForResult.launch(intent);
        } else {
            mHost.onStartActivityFromFragment(f, intent, requestCode, options);
        }
    }

    @SuppressWarnings("deprecation")
    void launchStartIntentSenderForResult(@NonNull Fragment f,
            @NonNull IntentSender intent,
            int requestCode, @Nullable Intent fillInIntent, int flagsMask, int flagsValues,
            int extraFlags, @Nullable Bundle options) throws IntentSender.SendIntentException {
        if (mStartIntentSenderForResult != null) {
            if (options != null) {
                if (fillInIntent == null) {
                    fillInIntent = new Intent();
                    fillInIntent.putExtra(EXTRA_CREATED_FILLIN_INTENT, true);
                }
                if (isLoggingEnabled(Log.VERBOSE)) {
                    Log.v(TAG, "ActivityOptions " + options + " were added to fillInIntent "
                            + fillInIntent + " for fragment " + f);
                }
                fillInIntent.putExtra(EXTRA_ACTIVITY_OPTIONS_BUNDLE, options);
            }
            IntentSenderRequest request =
                    new IntentSenderRequest.Builder(intent).setFillInIntent(fillInIntent)
                            .setFlags(flagsValues, flagsMask).build();
            LaunchedFragmentInfo info = new LaunchedFragmentInfo(f.mWho, requestCode);
            mLaunchedFragments.addLast(info);
            if (isLoggingEnabled(Log.VERBOSE)) {
                Log.v(TAG, "Fragment " + f + "is launching an IntentSender for result ");
            }
            mStartIntentSenderForResult.launch(request);
        } else {
            mHost.onStartIntentSenderFromFragment(f, intent, requestCode, fillInIntent,
                    flagsMask, flagsValues, extraFlags, options);
        }
    }

    @SuppressWarnings("deprecation")
    void launchRequestPermissions(@NonNull Fragment f, @NonNull String[] permissions,
            int requestCode) {
        if (mRequestPermissions != null) {
            LaunchedFragmentInfo info = new LaunchedFragmentInfo(f.mWho, requestCode);
            mLaunchedFragments.addLast(info);
            mRequestPermissions.launch(permissions);
        } else {
            mHost.onRequestPermissionsFromFragment(f, permissions, requestCode);
        }
    }

    void dispatchAttach() {
        mStateSaved = false;
        mStopped = false;
        mNonConfig.setIsStateSaved(false);
        dispatchStateChange(Fragment.ATTACHED);
    }

    void dispatchCreate() {
        mStateSaved = false;
        mStopped = false;
        mNonConfig.setIsStateSaved(false);
        dispatchStateChange(Fragment.CREATED);
    }

    void dispatchViewCreated() {
        dispatchStateChange(Fragment.VIEW_CREATED);
    }

    void dispatchActivityCreated() {
        mStateSaved = false;
        mStopped = false;
        mNonConfig.setIsStateSaved(false);
        dispatchStateChange(Fragment.ACTIVITY_CREATED);
    }

    void dispatchStart() {
        mStateSaved = false;
        mStopped = false;
        mNonConfig.setIsStateSaved(false);
        dispatchStateChange(Fragment.STARTED);
    }

    void dispatchResume() {
        mStateSaved = false;
        mStopped = false;
        mNonConfig.setIsStateSaved(false);
        dispatchStateChange(Fragment.RESUMED);
    }

    void dispatchPause() {
        dispatchStateChange(Fragment.STARTED);
    }

    void dispatchStop() {
        mStopped = true;
        mNonConfig.setIsStateSaved(true);
        dispatchStateChange(Fragment.ACTIVITY_CREATED);
    }

    void dispatchDestroyView() {
        dispatchStateChange(Fragment.CREATED);
    }

    void dispatchDestroy() {
        mDestroyed = true;
        execPendingActions(true);
        endAnimatingAwayFragments();
        clearBackStackStateViewModels();
        dispatchStateChange(Fragment.INITIALIZING);
        if (mHost instanceof OnTrimMemoryProvider) {
            OnTrimMemoryProvider onTrimMemoryProvider = (OnTrimMemoryProvider) mHost;
            onTrimMemoryProvider.removeOnTrimMemoryListener(mOnTrimMemoryListener);
        }
        if (mHost instanceof OnConfigurationChangedProvider) {
            OnConfigurationChangedProvider onConfigurationChangedProvider =
                    (OnConfigurationChangedProvider) mHost;
            onConfigurationChangedProvider.removeOnConfigurationChangedListener(
                    mOnConfigurationChangedListener);
        }
        if (mHost instanceof OnMultiWindowModeChangedProvider) {
            OnMultiWindowModeChangedProvider onMultiWindowModeChangedProvider =
                    (OnMultiWindowModeChangedProvider) mHost;
            onMultiWindowModeChangedProvider.removeOnMultiWindowModeChangedListener(
                    mOnMultiWindowModeChangedListener);
        }
        if (mHost instanceof OnPictureInPictureModeChangedProvider) {
            OnPictureInPictureModeChangedProvider onPictureInPictureModeChangedProvider =
                    (OnPictureInPictureModeChangedProvider) mHost;
            onPictureInPictureModeChangedProvider.removeOnPictureInPictureModeChangedListener(
                    mOnPictureInPictureModeChangedListener);
        }
        if (mHost instanceof MenuHost && mParent == null) {
            ((MenuHost) mHost).removeMenuProvider(mMenuProvider);
        }
        mHost = null;
        mContainer = null;
        mParent = null;
        if (mOnBackPressedDispatcher != null) {
            // mOnBackPressedDispatcher can hold a reference to the host
            // so we need to null it out to prevent memory leaks
            mOnBackPressedCallback.remove();
            mOnBackPressedDispatcher = null;
        }
        if (mStartActivityForResult != null) {
            mStartActivityForResult.unregister();
            mStartIntentSenderForResult.unregister();
            mRequestPermissions.unregister();
        }
    }

    private void dispatchStateChange(int nextState) {
        try {
            mExecutingActions = true;
            mFragmentStore.dispatchStateChange(nextState);
            moveToState(nextState, false);
            Set<SpecialEffectsController> controllers = collectAllSpecialEffectsController();
            for (SpecialEffectsController controller : controllers) {
                controller.forceCompleteAllOperations();
            }
        } finally {
            mExecutingActions = false;
        }
        execPendingActions(true);
    }

    void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode, boolean recursive) {
        if (recursive && mHost instanceof OnMultiWindowModeChangedProvider) {
            throwException(new IllegalStateException("Do not call dispatchMultiWindowModeChanged() "
                    + "on host. Host implements OnMultiWindowModeChangedProvider and automatically "
                    + "dispatches multi-window mode changes to fragments."));
        }
        for (Fragment f : mFragmentStore.getFragments()) {
            if (f != null) {
                f.performMultiWindowModeChanged(isInMultiWindowMode);
                if (recursive) {
                    f.mChildFragmentManager.dispatchMultiWindowModeChanged(
                            isInMultiWindowMode, true
                    );
                }
            }
        }
    }

    void dispatchPictureInPictureModeChanged(boolean isInPictureInPictureMode, boolean recursive) {
        if (recursive && mHost instanceof OnPictureInPictureModeChangedProvider) {
            throwException(new IllegalStateException("Do not call "
                    + "dispatchPictureInPictureModeChanged() on host. Host implements "
                    + "OnPictureInPictureModeChangedProvider and automatically dispatches "
                    + "picture-in-picture mode changes to fragments."));
        }
        for (Fragment f : mFragmentStore.getFragments()) {
            if (f != null) {
                f.performPictureInPictureModeChanged(isInPictureInPictureMode);
                if (recursive) {
                    f.mChildFragmentManager.dispatchPictureInPictureModeChanged(
                            isInPictureInPictureMode, true
                    );
                }
            }
        }
    }

    void dispatchConfigurationChanged(@NonNull Configuration newConfig, boolean recursive) {
        if (recursive && mHost instanceof OnConfigurationChangedProvider) {
            throwException(new IllegalStateException("Do not call dispatchConfigurationChanged() "
                    + "on host. Host implements OnConfigurationChangedProvider and automatically "
                    + "dispatches configuration changes to fragments."));
        }
        for (Fragment f : mFragmentStore.getFragments()) {
            if (f != null) {
                f.performConfigurationChanged(newConfig);
                if (recursive) {
                    f.mChildFragmentManager.dispatchConfigurationChanged(newConfig, true);
                }
            }
        }
    }

    void dispatchLowMemory(boolean recursive) {
        if (recursive && mHost instanceof OnTrimMemoryProvider) {
            throwException(new IllegalStateException("Do not call dispatchLowMemory() on host. "
                    + "Host implements OnTrimMemoryProvider and automatically dispatches "
                    + "low memory callbacks to fragments."));
        }
        for (Fragment f : mFragmentStore.getFragments()) {
            if (f != null) {
                f.performLowMemory();
                if (recursive) {
                    f.mChildFragmentManager.dispatchLowMemory(true);
                }
            }
        }
    }

    @SuppressWarnings({"deprecation", "DeprecatedIsStillUsed"})
    boolean dispatchCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
        if (mCurState < Fragment.CREATED) {
            return false;
        }
        boolean show = false;
        ArrayList<Fragment> newMenus = null;
        for (Fragment f : mFragmentStore.getFragments()) {
            if (f != null) {
                if (isParentMenuVisible(f) && f.performCreateOptionsMenu(menu, inflater)) {
                    show = true;
                    if (newMenus == null) {
                        newMenus = new ArrayList<>();
                    }
                    newMenus.add(f);
                }
            }
        }

        if (mCreatedMenus != null) {
            for (int i = 0; i < mCreatedMenus.size(); i++) {
                Fragment f = mCreatedMenus.get(i);
                if (newMenus == null || !newMenus.contains(f)) {
                    f.onDestroyOptionsMenu();
                }
            }
        }

        mCreatedMenus = newMenus;

        return show;
    }

    boolean dispatchPrepareOptionsMenu(@NonNull Menu menu) {
        if (mCurState < Fragment.CREATED) {
            return false;
        }
        boolean show = false;
        for (Fragment f : mFragmentStore.getFragments()) {
            if (f != null) {
                if (isParentMenuVisible(f) && f.performPrepareOptionsMenu(menu)) {
                    show = true;
                }
            }
        }
        return show;
    }

    boolean dispatchOptionsItemSelected(@NonNull MenuItem item) {
        if (mCurState < Fragment.CREATED) {
            return false;
        }
        for (Fragment f : mFragmentStore.getFragments()) {
            if (f != null) {
                if (f.performOptionsItemSelected(item)) {
                    return true;
                }
            }
        }
        return false;
    }

    boolean dispatchContextItemSelected(@NonNull MenuItem item) {
        if (mCurState < Fragment.CREATED) {
            return false;
        }
        for (Fragment f : mFragmentStore.getFragments()) {
            if (f != null) {
                if (f.performContextItemSelected(item)) {
                    return true;
                }
            }
        }
        return false;
    }

    void dispatchOptionsMenuClosed(@NonNull Menu menu) {
        if (mCurState < Fragment.CREATED) {
            return;
        }
        for (Fragment f : mFragmentStore.getFragments()) {
            if (f != null) {
                f.performOptionsMenuClosed(menu);
            }
        }
    }

    void setPrimaryNavigationFragment(@Nullable Fragment f) {
        if (f != null && (!f.equals(findActiveFragment(f.mWho))
                || (f.mHost != null && f.mFragmentManager != this))) {
            throw new IllegalArgumentException("Fragment " + f
                    + " is not an active fragment of FragmentManager " + this);
        }
        Fragment previousPrimaryNav = mPrimaryNav;
        mPrimaryNav = f;
        dispatchParentPrimaryNavigationFragmentChanged(previousPrimaryNav);
        dispatchParentPrimaryNavigationFragmentChanged(mPrimaryNav);
    }

    private void dispatchParentPrimaryNavigationFragmentChanged(@Nullable Fragment f) {
        if (f != null && f.equals(findActiveFragment(f.mWho))) {
            f.performPrimaryNavigationFragmentChanged();
        }
    }

    void dispatchPrimaryNavigationFragmentChanged() {
        updateOnBackPressedCallbackEnabled();
        // Dispatch the change event to this FragmentManager's primary navigation fragment
        dispatchParentPrimaryNavigationFragmentChanged(mPrimaryNav);
    }

    /**
     * Return the currently active primary navigation fragment for this FragmentManager.
     * The primary navigation fragment is set by fragment transactions using
     * {@link FragmentTransaction#setPrimaryNavigationFragment(Fragment)}.
     *
     * <p>The primary navigation fragment's
     * {@link Fragment#getChildFragmentManager() child FragmentManager} will be called first
     * to process delegated navigation actions such as {@link #popBackStack()} if no ID
     * or transaction name is provided to pop to.</p>
     *
     * @return the fragment designated as the primary navigation fragment
     */
    @Nullable
    public Fragment getPrimaryNavigationFragment() {
        return mPrimaryNav;
    }

    void setMaxLifecycle(@NonNull Fragment f, @NonNull Lifecycle.State state) {
        if (!f.equals(findActiveFragment(f.mWho))
                || (f.mHost != null && f.mFragmentManager != this)) {
            throw new IllegalArgumentException("Fragment " + f
                    + " is not an active fragment of FragmentManager " + this);
        }
        f.mMaxState = state;
    }

    /**
     * Set a {@link FragmentFactory} for this FragmentManager that will be used
     * to create new Fragment instances from this point onward.
     * <p>
     * The {@link Fragment#getChildFragmentManager() child FragmentManager} of all Fragments
     * in this FragmentManager will also use this factory if one is not explicitly set.
     *
     * @param fragmentFactory the factory to use to create new Fragment instances
     * @see #getFragmentFactory()
     */
    public void setFragmentFactory(@NonNull FragmentFactory fragmentFactory) {
        mFragmentFactory = fragmentFactory;
    }

    /**
     * Gets the current {@link FragmentFactory} used to instantiate new Fragment instances.
     * <p>
     * If no factory has been explicitly set on this FragmentManager via
     * {@link #setFragmentFactory(FragmentFactory)}, the FragmentFactory of the
     * {@link Fragment#getParentFragmentManager() parent FragmentManager} will be returned.
     *
     * @return the current FragmentFactory
     */
    @NonNull
    public FragmentFactory getFragmentFactory() {
        if (mFragmentFactory != null) {
            return mFragmentFactory;
        }
        if (mParent != null) {
            // This can't call setFragmentFactory since we need to
            // compute this each time getFragmentFactory() is called
            // so that if the parent's FragmentFactory changes, we
            // pick the change up here.
            return mParent.mFragmentManager.getFragmentFactory();
        }
        return mHostFragmentFactory;
    }

    /**
     * Set a {@link SpecialEffectsControllerFactory} for this FragmentManager that will be used
     * to create new SpecialEffectsController instances from this point onward.
     *
     * @param specialEffectsControllerFactory the factory to use to create new
     *                                        SpecialEffectsController instances.
     */
    void setSpecialEffectsControllerFactory(
            @NonNull SpecialEffectsControllerFactory specialEffectsControllerFactory) {
        mSpecialEffectsControllerFactory = specialEffectsControllerFactory;
    }

    /**
     * Gets the current {@link SpecialEffectsControllerFactory} used to instantiate new
     * SpecialEffectsController instances.
     *
     * @return the current SpecialEffectsControllerFactory
     */
    @NonNull
    SpecialEffectsControllerFactory getSpecialEffectsControllerFactory() {
        if (mSpecialEffectsControllerFactory != null) {
            return mSpecialEffectsControllerFactory;
        }
        if (mParent != null) {
            // This can't call setSpecialEffectsControllerFactory since we need to
            // compute this each time getSpecialEffectsControllerFactory() is called
            // so that if the parent's SpecialEffectsControllerFactory changes, we
            // pick the change up here.
            return mParent.mFragmentManager.getSpecialEffectsControllerFactory();
        }
        return mDefaultSpecialEffectsControllerFactory;
    }

    @NonNull
    FragmentLifecycleCallbacksDispatcher getLifecycleCallbacksDispatcher() {
        return mLifecycleCallbacksDispatcher;
    }

    /**
     * Registers a {@link FragmentLifecycleCallbacks} to listen to fragment lifecycle events
     * happening in this FragmentManager. All registered callbacks will be automatically
     * unregistered when this FragmentManager is destroyed.
     *
     * @param cb Callbacks to register
     * @param recursive true to automatically register this callback for all child FragmentManagers
     */
    public void registerFragmentLifecycleCallbacks(@NonNull FragmentLifecycleCallbacks cb,
            boolean recursive) {
        mLifecycleCallbacksDispatcher.registerFragmentLifecycleCallbacks(cb, recursive);
    }

    /**
     * Unregisters a previously registered {@link FragmentLifecycleCallbacks}. If the callback
     * was not previously registered this call has no effect. All registered callbacks will be
     * automatically unregistered when this FragmentManager is destroyed.
     *
     * @param cb Callbacks to unregister
     */
    public void unregisterFragmentLifecycleCallbacks(@NonNull FragmentLifecycleCallbacks cb) {
        mLifecycleCallbacksDispatcher.unregisterFragmentLifecycleCallbacks(cb);
    }

    /**
     * Add a {@link FragmentOnAttachListener} that should receive a call to
     * {@link FragmentOnAttachListener#onAttachFragment(FragmentManager, Fragment)} when a
     * new Fragment is attached to this FragmentManager.
     *
     * @param listener Listener to add
     */
    public void addFragmentOnAttachListener(@NonNull FragmentOnAttachListener listener) {
        mOnAttachListeners.add(listener);
    }

    /**
     * Dispatch {@link FragmentOnAttachListener#onAttachFragment(FragmentManager, Fragment)} to
     * each listener registered via {@link #addFragmentOnAttachListener(FragmentOnAttachListener)}.
     *
     * @param fragment The Fragment that was attached
     */
    void dispatchOnAttachFragment(@NonNull Fragment fragment) {
        for (FragmentOnAttachListener listener : mOnAttachListeners) {
            listener.onAttachFragment(this, fragment);
        }
    }

    /**
     * Remove a {@link FragmentOnAttachListener} that was previously added via
     * {@link #addFragmentOnAttachListener(FragmentOnAttachListener)}. It will no longer
     * get called when a new Fragment is attached.
     *
     * @param listener Listener to remove
     */
    public void removeFragmentOnAttachListener(@NonNull FragmentOnAttachListener listener) {
        mOnAttachListeners.remove(listener);
    }

    void dispatchOnHiddenChanged() {
        for (Fragment fragment : mFragmentStore.getActiveFragments()) {
            if (fragment != null) {
                fragment.onHiddenChanged(fragment.isHidden());
                fragment.mChildFragmentManager.dispatchOnHiddenChanged();
            }
        }
    }

    // Checks if fragments that belong to this fragment manager (or their children) have menus,
    // and if they are visible.
    boolean checkForMenus() {
        boolean hasMenu = false;
        for (Fragment fragment : mFragmentStore.getActiveFragments()) {
            if (fragment != null) {
                hasMenu = isMenuAvailable(fragment);
            }
            if (hasMenu) {
                return true;
            }
        }
        return false;
    }

    private boolean isMenuAvailable(@NonNull Fragment f) {
        return (f.mHasMenu && f.mMenuVisible) || f.mChildFragmentManager.checkForMenus();
    }

    void invalidateMenuForFragment(@NonNull Fragment f) {
        if (f.mAdded && isMenuAvailable(f)) {
            mNeedMenuInvalidate = true;
        }
    }

    private boolean isParentAdded() {
        // The root fragment manager is always considered added
        if (mParent == null) {
            return true;
        }
        return mParent.isAdded() && mParent.getParentFragmentManager().isParentAdded();
    }

    static int reverseTransit(int transit) {
        int rev = 0;
        switch (transit) {
            case FragmentTransaction.TRANSIT_FRAGMENT_OPEN:
                rev = FragmentTransaction.TRANSIT_FRAGMENT_CLOSE;
                break;
            case FragmentTransaction.TRANSIT_FRAGMENT_CLOSE:
                rev = FragmentTransaction.TRANSIT_FRAGMENT_OPEN;
                break;
            case FragmentTransaction.TRANSIT_FRAGMENT_FADE:
                rev = FragmentTransaction.TRANSIT_FRAGMENT_FADE;
                break;
            case FragmentTransaction.TRANSIT_FRAGMENT_MATCH_ACTIVITY_OPEN:
                rev = FragmentTransaction.TRANSIT_FRAGMENT_MATCH_ACTIVITY_CLOSE;
                break;
            case FragmentTransaction.TRANSIT_FRAGMENT_MATCH_ACTIVITY_CLOSE:
                rev = FragmentTransaction.TRANSIT_FRAGMENT_MATCH_ACTIVITY_OPEN;
                break;
        }
        return rev;

    }

    @NonNull
    LayoutInflater.Factory2 getLayoutInflaterFactory() {
        return mLayoutInflaterFactory;
    }

    /** Returns the current policy for this FragmentManager. If no policy is set, returns null. */
    @Nullable
    public FragmentStrictMode.Policy getStrictModePolicy() {
        return mStrictModePolicy;
    }

    /**
     * Sets the policy for what actions should be detected, as well as the penalty if such actions
     * occur. The {@link Fragment#getChildFragmentManager() child FragmentManager} of all Fragments
     * in this FragmentManager will also use this policy if one is not explicitly set. Pass null to
     * clear the policy.
     *
     * @param policy the policy to put into place
     */
    public void setStrictModePolicy(@Nullable FragmentStrictMode.Policy policy) {
        mStrictModePolicy = policy;
    }

    /**
     * An add or pop transaction to be scheduled for the UI thread.
     */
    interface OpGenerator {
        /**
         * Generate transactions to add to {@code records} and whether or not the transaction is
         * an add or pop to {@code isRecordPop}.
         *
         * records and isRecordPop must be added equally so that each transaction in records
         * matches the boolean for whether or not it is a pop in isRecordPop.
         *
         * @param records A list to add transactions to.
         * @param isRecordPop A list to add whether or not the transactions added to records is
         *                    a pop transaction.
         * @return true if something was added or false otherwise.
         */
        boolean generateOps(@NonNull ArrayList<BackStackRecord> records,
                @NonNull ArrayList<Boolean> isRecordPop);
    }

    /**
     * A pop operation OpGenerator. This will be run on the UI thread and will generate the
     * transactions that will be popped if anything can be popped.
     */
    private class PopBackStackState implements OpGenerator {
        final String mName;
        final int mId;
        final int mFlags;

        PopBackStackState(@Nullable String name, int id, int flags) {
            mName = name;
            mId = id;
            mFlags = flags;
        }

        @Override
        public boolean generateOps(@NonNull ArrayList<BackStackRecord> records,
                @NonNull ArrayList<Boolean> isRecordPop) {
            if (mPrimaryNav != null // We have a primary nav fragment
                    && mId < 0 // No valid id (since they're local)
                    && mName == null) { // no name to pop to (since they're local)
                final FragmentManager childManager = mPrimaryNav.getChildFragmentManager();
                if (childManager.popBackStackImmediate()) {
                    // We didn't add any operations for this FragmentManager even though
                    // a child did do work.
                    return false;
                }
            }
            return popBackStackState(records, isRecordPop, mName, mId, mFlags);
        }
    }

    private class RestoreBackStackState implements OpGenerator {

        private final String mName;

        RestoreBackStackState(@NonNull String name) {
            mName = name;
        }

        @Override
        public boolean generateOps(@NonNull ArrayList<BackStackRecord> records,
                @NonNull ArrayList<Boolean> isRecordPop) {
            return restoreBackStackState(records, isRecordPop, mName);
        }
    }

    private class SaveBackStackState implements OpGenerator {

        private final String mName;

        SaveBackStackState(@NonNull String name) {
            mName = name;
        }

        @Override
        public boolean generateOps(@NonNull ArrayList<BackStackRecord> records,
                @NonNull ArrayList<Boolean> isRecordPop) {
            return saveBackStackState(records, isRecordPop, mName);
        }
    }

    private class ClearBackStackState implements OpGenerator {

        private final String mName;

        ClearBackStackState(@NonNull String name) {
            mName = name;
        }

        @Override
        public boolean generateOps(@NonNull ArrayList<BackStackRecord> records,
                @NonNull ArrayList<Boolean> isRecordPop) {
            return clearBackStackState(records, isRecordPop, mName);
        }
    }

    class PrepareBackStackTransitionState implements OpGenerator {

        @Override
        public boolean generateOps(@NonNull ArrayList<BackStackRecord> records,
                @NonNull ArrayList<Boolean> isRecordPop) {
            boolean result = prepareBackStackState(records, isRecordPop);
            mBackStarted = true;
            // Dispatch started signal to onBackStackChangedListeners.
            if (!mBackStackChangeListeners.isEmpty()) {
                if (records.size() > 0) {
                    boolean isPop = isRecordPop.get(records.size() - 1);
                    Set<Fragment> fragments = new LinkedHashSet<>();
                    // Build a list of fragments based on the records
                    for (BackStackRecord record : records) {
                        fragments.addAll(fragmentsFromRecord(record));
                    }
                    // Dispatch to all of the fragments in the list
                    for (OnBackStackChangedListener listener : mBackStackChangeListeners) {
                        // We give all fragment the back stack changed started signal first
                        for (Fragment fragment : fragments) {
                            listener.onBackStackChangeStarted(fragment, isPop);
                        }
                    }
                }
            }
            return result;
        }
    }

    @SuppressLint("BanParcelableUsage")
    static class LaunchedFragmentInfo implements Parcelable {
        String mWho;
        int mRequestCode;

        LaunchedFragmentInfo(@NonNull String who, int requestCode) {
            mWho = who;
            mRequestCode = requestCode;
        }

        LaunchedFragmentInfo(@NonNull Parcel in) {
            mWho = in.readString();
            mRequestCode = in.readInt();
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(mWho);
            dest.writeInt(mRequestCode);
        }

        public static final Parcelable.Creator<LaunchedFragmentInfo> CREATOR =
                new Creator<LaunchedFragmentInfo>() {
                    @Override
                    public LaunchedFragmentInfo createFromParcel(Parcel in) {
                        return new LaunchedFragmentInfo(in);
                    }

                    @Override
                    public LaunchedFragmentInfo[] newArray(int size) {
                        return new LaunchedFragmentInfo[size];
                    }
                };
    }

    static class FragmentIntentSenderContract extends ActivityResultContract<IntentSenderRequest,
            ActivityResult> {

        @NonNull
        @Override
        public Intent createIntent(@NonNull Context context, IntentSenderRequest input) {
            Intent result = new Intent(ACTION_INTENT_SENDER_REQUEST);
            Intent fillInIntent = input.getFillInIntent();
            if (fillInIntent != null) {
                Bundle activityOptions = fillInIntent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS_BUNDLE);
                if (activityOptions != null) {
                    result.putExtra(EXTRA_ACTIVITY_OPTIONS_BUNDLE, activityOptions);
                    fillInIntent.removeExtra(EXTRA_ACTIVITY_OPTIONS_BUNDLE);
                    if (fillInIntent.getBooleanExtra(EXTRA_CREATED_FILLIN_INTENT, false)) {
                        input = new IntentSenderRequest.Builder(input.getIntentSender())
                                .setFillInIntent(null)
                                .setFlags(input.getFlagsValues(), input.getFlagsMask())
                                .build();
                    }
                }
            }
            result.putExtra(EXTRA_INTENT_SENDER_REQUEST, input);
            if (isLoggingEnabled(Log.VERBOSE)) {
                Log.v(TAG, "CreateIntent created the following intent: " + result);
            }
            return result;
        }

        @NonNull
        @Override
        public ActivityResult parseResult(int resultCode, @Nullable Intent intent) {
            return new ActivityResult(resultCode, intent);
        }
    }
}