DialogFragment.java

/*
 * 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.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import static androidx.fragment.app.FragmentManager.TAG;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;

import androidx.activity.ComponentDialog;
import androidx.annotation.CallSuper;
import androidx.annotation.IntDef;
import androidx.annotation.LayoutRes;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.StyleRes;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewTreeLifecycleOwner;
import androidx.lifecycle.ViewTreeViewModelStoreOwner;
import androidx.savedstate.ViewTreeSavedStateRegistryOwner;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * A fragment that displays a dialog window, floating in the foreground of its
 * activity's window.  This fragment contains a Dialog object, which it
 * displays as appropriate based on the fragment's state.  Control of
 * the dialog (deciding when to show, hide, dismiss it) should be done through
 * the APIs here, not with direct calls on the dialog.
 *
 * <p>Implementations should override this class and implement
 * {@link #onViewCreated(View, Bundle)} to supply the
 * content of the dialog.  Alternatively, they can override
 * {@link #onCreateDialog(Bundle)} to create an entirely custom dialog, such
 * as an AlertDialog, with its own content.
 *
 * <p>Topics covered here:
 * <ol>
 * <li><a href="#Lifecycle">Lifecycle</a>
 * <li><a href="#BasicDialog">Basic Dialog</a>
 * <li><a href="#AlertDialog">Alert Dialog</a>
 * <li><a href="#DialogOrEmbed">Selecting Between Dialog or Embedding</a>
 * </ol>
 *
 * <a name="Lifecycle"></a>
 * <h3>Lifecycle</h3>
 *
 * <p>DialogFragment does various things to keep the fragment's lifecycle
 * driving it, instead of the Dialog.  Note that dialogs are generally
 * autonomous entities -- they are their own window, receiving their own
 * input events, and often deciding on their own when to disappear (by
 * receiving a back key event or the user clicking on a button).
 *
 * <p>DialogFragment needs to ensure that what is happening with the Fragment
 * and Dialog states remains consistent.  To do this, it watches for dismiss
 * events from the dialog and takes care of removing its own state when they
 * happen.  This means you should use {@link #show(FragmentManager, String)},
 * {@link #show(FragmentTransaction, String)}, or {@link #showNow(FragmentManager, String)}
 * to add an instance of DialogFragment to your UI, as these keep track of
 * how DialogFragment should remove itself when the dialog is dismissed.
 *
 * <a name="BasicDialog"></a>
 * <h3>Basic Dialog</h3>
 *
 * <p>The simplest use of DialogFragment is as a floating container for the
 * fragment's view hierarchy.  A simple implementation may look like this:
 *
 * <pre>{@code
 * public class MyDialogFragment extends DialogFragment {
 *     int mNum;
 *
 *     // Create a new instance of MyDialogFragment, providing "num" as an argument.
 *     static MyDialogFragment newInstance(int num) {
 *         MyDialogFragment f = new MyDialogFragment();
 *
 *         // Supply num input as an argument.
 *         Bundle args = new Bundle();
 *         args.putInt("num", num);
 *         f.setArguments(args);
 *
 *         return f;
 *     }
 *
 *     {@literal @}Override
 *     public void onCreate(Bundle savedInstanceState) {
 *         super.onCreate(savedInstanceState);
 *         mNum = getArguments().getInt("num");
 *
 *         // Pick a style based on the num.
 *         int style = DialogFragment.STYLE_NORMAL, theme = 0;
 *         switch ((mNum-1)%6) {
 *             case 1: style = DialogFragment.STYLE_NO_TITLE; break;
 *             case 2: style = DialogFragment.STYLE_NO_FRAME; break;
 *             case 3: style = DialogFragment.STYLE_NO_INPUT; break;
 *             case 4: style = DialogFragment.STYLE_NORMAL; break;
 *             case 5: style = DialogFragment.STYLE_NORMAL; break;
 *             case 6: style = DialogFragment.STYLE_NO_TITLE; break;
 *             case 7: style = DialogFragment.STYLE_NO_FRAME; break;
 *             case 8: style = DialogFragment.STYLE_NORMAL; break;
 *         }
 *         switch ((mNum-1)%6) {
 *             case 4: theme = android.R.style.Theme_Holo; break;
 *             case 5: theme = android.R.style.Theme_Holo_Light_Dialog; break;
 *             case 6: theme = android.R.style.Theme_Holo_Light; break;
 *             case 7: theme = android.R.style.Theme_Holo_Light_Panel; break;
 *             case 8: theme = android.R.style.Theme_Holo_Light; break;
 *         }
 *         setStyle(style, theme);
 *     }
 *
 *     {@literal @}Override
 *     public View onCreateView(LayoutInflater inflater, ViewGroup container,
 *                              Bundle savedInstanceState) {
 *         return inflater.inflate(R.layout.fragment_dialog, container, false);
 *     }
 *
 *     {@literal @}Override
 *     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
 *         super.onViewCreated(view, savedInstanceState);
 *
 *         // set DialogFragment title
 *         getDialog().setTitle("Dialog #" + mNum);
 *     }
 * }
 * }</pre>
 *
 * <p>An example showDialog() method on the Activity could be:
 *
 * <pre>{@code
 * public void showDialog() {
 *     mStackLevel++;
 *
 *     // DialogFragment.show() will take care of adding the fragment
 *     // in a transaction.  We also want to remove any currently showing
 *     // dialog, so make our own transaction and take care of that here.
 *     FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
 *     Fragment prev = getSupportFragmentManager().findFragmentByTag("dialog");
 *     if (prev != null) {
 *         ft.remove(prev);
 *     }
 *     ft.addToBackStack(null);
 *
 *     // Create and show the dialog.
 *     DialogFragment newFragment = MyDialogFragment.newInstance(mStackLevel);
 *     newFragment.show(ft, "dialog");
 * }
 * }</pre>
 *
 * <p>This removes any currently shown dialog, creates a new DialogFragment
 * with an argument, and shows it as a new state on the back stack.  When the
 * transaction is popped, the current DialogFragment and its Dialog will be
 * destroyed, and the previous one (if any) re-shown.  Note that in this case
 * DialogFragment will take care of popping the transaction of the Dialog that
 * is dismissed separately from it.
 *
 * <a name="AlertDialog"></a>
 * <h3>Alert Dialog</h3>
 *
 * <p>Instead of (or in addition to) implementing {@link #onViewCreated(View, Bundle)} to
 * generate the view hierarchy inside of a dialog, you may implement
 * {@link #onCreateDialog(Bundle)} to create your own custom Dialog object.
 *
 * <p>This is most useful for creating an AlertDialog, allowing you
 * to display standard alerts to the user that are managed by a fragment.
 * A simple example implementation of this is:
 *
 * <pre>{@code
 * public static class MyAlertDialogFragment extends DialogFragment {
 *
 *     public static MyAlertDialogFragment newInstance(int title) {
 *         MyAlertDialogFragment frag = new MyAlertDialogFragment();
 *         Bundle args = new Bundle();
 *         args.putInt("title", title);
 *         frag.setArguments(args);
 *         return frag;
 *     }
 *
 *     {@literal @}Override
 *     public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
 *
 *         return new AlertDialog.Builder(getActivity())
 *                 .setIcon(R.drawable.alert_dialog_icon)
 *                 .setTitle(title)
 *                 .setPositiveButton(R.string.alert_dialog_ok,
 *                         (dialogInterface, i) -> ((MainActivity)getActivity()).doPositiveClick())
 *                 .setNegativeButton(R.string.alert_dialog_cancel,
 *                         (dialogInterface, i) -> ((MainActivity)getActivity()).doNegativeClick())
 *                 .create();
 *         return super.onCreateDialog(savedInstanceState);
 *     }
 * }
 * }</pre>
 *
 * <p>The activity creating this fragment may have the following methods to
 * show the dialog and receive results from it:
 *
 * <pre>{@code
 * void showDialog() {
 *     DialogFragment newFragment = MyAlertDialogFragment.newInstance(
 *             R.string.alert_dialog_two_buttons_title);
 *     newFragment.show(getSupportFragmentManager(), "dialog");
 * }
 *
 * public void doPositiveClick() {
 *     // Do stuff here.
 *     Log.i("MainActivity", "Positive click!");
 * }
 *
 * public void doNegativeClick() {
 *     // Do stuff here.
 *     Log.i("MainActivity", "Negative click!");
 * }
 * }</pre>
 *
 * <p>Note that in this case the fragment is not placed on the back stack, it
 * is just added as an indefinitely running fragment.  Because dialogs normally
 * are modal, this will still operate as a back stack, since the dialog will
 * capture user input until it is dismissed.  When it is dismissed, DialogFragment
 * will take care of removing itself from its fragment manager.
 *
 * <a name="DialogOrEmbed"></a>
 * <h3>Selecting Between Dialog or Embedding</h3>
 *
 * <p>A DialogFragment can still optionally be used as a normal fragment, if
 * desired.  This is useful if you have a fragment that in some cases should
 * be shown as a dialog and others embedded in a larger UI.  This behavior
 * will normally be automatically selected for you based on how you are using
 * the fragment, but can be customized with {@link #setShowsDialog(boolean)}.
 *
 * <p>For example, here is a simple dialog fragment:
 *
 * <pre>{@code
 * public static class MyDialogFragment extends DialogFragment {
 *     static MyDialogFragment newInstance() {
 *         return new MyDialogFragment();
 *     }
 *
 *     {@literal @}Override
 *     public void onCreate(Bundle savedInstanceState) {
 *         super.onCreate(savedInstanceState);
 *
 *         // this fragment will be displayed in a dialog
 *         setShowsDialog(true);
 *     }
 *
 *     {@literal @}Override
 *     public View onCreateView(LayoutInflater inflater, ViewGroup container,
 *             Bundle savedInstanceState) {
 *         View v = inflater.inflate(R.layout.hello_world, container, false);
 *         View tv = v.findViewById(R.id.text);
 *         ((TextView)tv).setText("This is an instance of MyDialogFragment");
 *         return v;
 *     }
 * }
 * }</pre>
 *
 * <p>An instance of this fragment can be created and shown as a dialog:
 *
 * <pre>{@code
 * void showDialog() {
 *     // Create the fragment and show it as a dialog.
 *     DialogFragment newFragment = MyDialogFragment.newInstance();
 *     newFragment.show(getSupportFragmentManager(), "dialog");
 * }
 * }</pre>
 *
 * <p>It can also be added as content in a view hierarchy:
 *
 * <pre>{@code
 * FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
 * DialogFragment newFragment = MyDialogFragment.newInstance();
 * ft.add(R.id.embedded, newFragment);
 * ft.commit();
 * }</pre>
 */
public class DialogFragment extends Fragment
        implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener {

    /** @hide */
    @RestrictTo(LIBRARY_GROUP_PREFIX)
    @IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})
    @Retention(RetentionPolicy.SOURCE)
    private @interface DialogStyle {}

    /**
     * Style for {@link #setStyle(int, int)}: a basic,
     * normal dialog.
     */
    public static final int STYLE_NORMAL = 0;

    /**
     * Style for {@link #setStyle(int, int)}: don't include
     * a title area.
     */
    public static final int STYLE_NO_TITLE = 1;

    /**
     * Style for {@link #setStyle(int, int)}: don't draw
     * any frame at all; the view hierarchy returned by {@link #onCreateView}
     * is entirely responsible for drawing the dialog.
     */
    public static final int STYLE_NO_FRAME = 2;

    /**
     * Style for {@link #setStyle(int, int)}: like
     * {@link #STYLE_NO_FRAME}, but also disables all input to the dialog.
     * The user can not touch it, and its window will not receive input focus.
     */
    public static final int STYLE_NO_INPUT = 3;

    private static final String SAVED_DIALOG_STATE_TAG = "android:savedDialogState";
    private static final String SAVED_STYLE = "android:style";
    private static final String SAVED_THEME = "android:theme";
    private static final String SAVED_CANCELABLE = "android:cancelable";
    private static final String SAVED_SHOWS_DIALOG = "android:showsDialog";
    private static final String SAVED_BACK_STACK_ID = "android:backStackId";
    /**
     * Copied from {@link Dialog}.
     */
    private static final String SAVED_INTERNAL_DIALOG_SHOWING = "android:dialogShowing";

    private Handler mHandler;
    private Runnable mDismissRunnable = new Runnable() {
        @SuppressLint("SyntheticAccessor")
        @Override
        public void run() {
            mOnDismissListener.onDismiss(mDialog);
        }
    };

    private DialogInterface.OnCancelListener mOnCancelListener =
            new DialogInterface.OnCancelListener() {
        @SuppressLint("SyntheticAccessor")
        @Override
        public void onCancel(@Nullable DialogInterface dialog) {
            if (mDialog != null) {
                DialogFragment.this.onCancel(mDialog);
            }
        }
    };

    private DialogInterface.OnDismissListener mOnDismissListener =
            new DialogInterface.OnDismissListener() {
        @SuppressLint("SyntheticAccessor")
        @Override
        public void onDismiss(@Nullable DialogInterface dialog) {
            if (mDialog != null) {
                DialogFragment.this.onDismiss(mDialog);
            }
        }
    };

    private int mStyle = STYLE_NORMAL;
    private int mTheme = 0;
    private boolean mCancelable = true;
    private boolean mShowsDialog = true;
    private int mBackStackId = -1;
    private boolean mCreatingDialog;
    private Observer<LifecycleOwner> mObserver = new Observer<LifecycleOwner>() {
        @SuppressLint("SyntheticAccessor")
        @Override
        public void onChanged(LifecycleOwner lifecycleOwner) {
            if (lifecycleOwner != null && mShowsDialog) {
                View view = requireView();
                if (view.getParent() != null) {
                    throw new IllegalStateException(
                            "DialogFragment can not be attached to a container view");
                }
                if (mDialog != null) {
                    if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
                        Log.d(TAG, "DialogFragment " + this + " setting the content view on "
                                + mDialog);
                    }
                    mDialog.setContentView(view);
                }
            }
        }
    };

    @Nullable
    private Dialog mDialog;
    private boolean mViewDestroyed;
    private boolean mDismissed;
    private boolean mShownByMe;
    private boolean mDialogCreated = false;


    /**
     * Constructor used by the default {@link FragmentFactory}. You must
     * {@link FragmentManager#setFragmentFactory(FragmentFactory) set a custom FragmentFactory}
     * if you want to use a non-default constructor to ensure that your constructor
     * is called when the fragment is re-instantiated.
     *
     * <p>It is strongly recommended to supply arguments with {@link #setArguments}
     * and later retrieved by the Fragment with {@link #getArguments}. These arguments
     * are automatically saved and restored alongside the Fragment.
     *
     * <p>Applications should generally not implement a constructor. Prefer
     * {@link #onAttach(Context)} instead. It is the first place application code can run where
     * the fragment is ready to be used - the point where the fragment is actually associated with
     * its context.
     */
    public DialogFragment() {
        super();
    }

    /**
     * Alternate constructor that can be called from your default, no argument constructor to
     * provide a default layout that will be inflated by
     * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
     *
     * <pre class="prettyprint">
     * class MyDialogFragment extends DialogFragment {
     *   public MyDialogFragment() {
     *     super(R.layout.dialog_fragment_main);
     *   }
     * }
     * </pre>
     *
     * You must
     * {@link FragmentManager#setFragmentFactory(FragmentFactory) set a custom FragmentFactory}
     * if you want to use a non-default constructor to ensure that your constructor is called
     * when the fragment is re-instantiated.
     *
     * @see #DialogFragment()
     * @see #onCreateView(LayoutInflater, ViewGroup, Bundle)
     */
    public DialogFragment(@LayoutRes int contentLayoutId) {
        super(contentLayoutId);
    }

    /**
     * Call to customize the basic appearance and behavior of the
     * fragment's dialog.  This can be used for some common dialog behaviors,
     * taking care of selecting flags, theme, and other options for you.  The
     * same effect can be achieve by manually setting Dialog and Window
     * attributes yourself.  Calling this after the fragment's Dialog is
     * created will have no effect.
     *
     * @param style Selects a standard style: may be {@link #STYLE_NORMAL},
     * {@link #STYLE_NO_TITLE}, {@link #STYLE_NO_FRAME}, or
     * {@link #STYLE_NO_INPUT}.
     * @param theme Optional custom theme.  If 0, an appropriate theme (based
     * on the style) will be selected for you.
     */
    public void setStyle(@DialogStyle int style, @StyleRes int theme) {
        if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
            Log.d(TAG, "Setting style and theme for DialogFragment " + this + " to " + style
                    + ", " + theme);
        }
        mStyle = style;
        if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) {
            mTheme = android.R.style.Theme_Panel;
        }
        if (theme != 0) {
            mTheme = theme;
        }
    }

    /**
     * Display the dialog, adding the fragment to the given FragmentManager.  This
     * is a convenience for explicitly creating a transaction, adding the
     * fragment to it with the given tag, and {@link FragmentTransaction#commit() committing} it.
     * This does <em>not</em> add the transaction to the fragment back stack.  When the fragment
     * is dismissed, a new transaction will be executed to remove it from
     * the activity.
     * @param manager The FragmentManager this fragment will be added to.
     * @param tag The tag for this fragment, as per
     * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
     */
    public void show(@NonNull FragmentManager manager, @Nullable String tag) {
        mDismissed = false;
        mShownByMe = true;
        FragmentTransaction ft = manager.beginTransaction();
        ft.setReorderingAllowed(true);
        ft.add(this, tag);
        ft.commit();
    }

    /**
     * Display the dialog, adding the fragment using an existing transaction
     * and then {@link FragmentTransaction#commit() committing} the transaction.
     * @param transaction An existing transaction in which to add the fragment.
     * @param tag The tag for this fragment, as per
     * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
     * @return Returns the identifier of the committed transaction, as per
     * {@link FragmentTransaction#commit() FragmentTransaction.commit()}.
     */
    public int show(@NonNull FragmentTransaction transaction, @Nullable String tag) {
        mDismissed = false;
        mShownByMe = true;
        transaction.add(this, tag);
        mViewDestroyed = false;
        mBackStackId = transaction.commit();
        return mBackStackId;
    }

    /**
     * Display the dialog, immediately adding the fragment to the given FragmentManager.  This
     * is a convenience for explicitly creating a transaction, adding the
     * fragment to it with the given tag, and calling {@link FragmentTransaction#commitNow()}.
     * This does <em>not</em> add the transaction to the fragment back stack.  When the fragment
     * is dismissed, a new transaction will be executed to remove it from
     * the activity.
     * @param manager The FragmentManager this fragment will be added to.
     * @param tag The tag for this fragment, as per
     * {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
     */
    public void showNow(@NonNull FragmentManager manager, @Nullable String tag) {
        mDismissed = false;
        mShownByMe = true;
        FragmentTransaction ft = manager.beginTransaction();
        ft.setReorderingAllowed(true);
        ft.add(this, tag);
        ft.commitNow();
    }

    /**
     * Dismiss the fragment and its dialog.  If the fragment was added to the
     * back stack, all back stack state up to and including this entry will
     * be popped.  Otherwise, a new transaction will be committed to remove
     * the fragment.
     */
    public void dismiss() {
        dismissInternal(false, false, false);
    }

    /**
     * Version of {@link #dismiss()} that uses {@link FragmentTransaction#commitNow()}.
     * See linked documentation for further details.
     */
    @MainThread
    public void dismissNow() {
        dismissInternal(false, false, true);
    }

    /**
     * Version of {@link #dismiss()} that uses
     * {@link FragmentTransaction#commitAllowingStateLoss()
     * FragmentTransaction.commitAllowingStateLoss()}. See linked
     * documentation for further details.
     */
    public void dismissAllowingStateLoss() {
        dismissInternal(true, false, false);
    }

    private void dismissInternal(boolean allowStateLoss, boolean fromOnDismiss, boolean immediate) {
        if (mDismissed) {
            return;
        }
        mDismissed = true;
        mShownByMe = false;
        if (mDialog != null) {
            // Instead of waiting for a posted onDismiss(), null out
            // the listener and call onDismiss() manually to ensure
            // that the callback happens before onDestroy()
            mDialog.setOnDismissListener(null);
            mDialog.dismiss();
            if (!fromOnDismiss) {
                // onDismiss() is always called on the main thread, so
                // we mimic that behavior here. The difference here is that
                // we don't post the message to ensure that the onDismiss()
                // callback still happens before onDestroy()
                if (Looper.myLooper() == mHandler.getLooper()) {
                    onDismiss(mDialog);
                } else {
                    mHandler.post(mDismissRunnable);
                }
            }
        }
        mViewDestroyed = true;
        if (mBackStackId >= 0) {
            if (immediate) {
                getParentFragmentManager().popBackStackImmediate(mBackStackId,
                        FragmentManager.POP_BACK_STACK_INCLUSIVE);
            } else {
                getParentFragmentManager().popBackStack(mBackStackId,
                        FragmentManager.POP_BACK_STACK_INCLUSIVE, allowStateLoss);
            }
            mBackStackId = -1;
        } else {
            FragmentTransaction ft = getParentFragmentManager().beginTransaction();
            ft.setReorderingAllowed(true);
            ft.remove(this);
            // allowStateLoss and immediate should not both be true
            if (immediate) {
                ft.commitNow();
            } else if (allowStateLoss) {
                ft.commitAllowingStateLoss();
            } else {
                ft.commit();
            }
        }
    }

    /**
     * Return the {@link Dialog} this fragment is currently controlling.
     *
     * @see #requireDialog()
     */
    @Nullable
    public Dialog getDialog() {
        return mDialog;
    }

    /**
     * Return the {@link Dialog} this fragment is currently controlling.
     *
     * @throws IllegalStateException if the Dialog has not yet been created (before
     * {@link #onCreateDialog(Bundle)}) or has been destroyed (after {@link #onDestroyView()}.
     * @see #getDialog()
     */
    @NonNull
    public final Dialog requireDialog() {
        Dialog dialog = getDialog();
        if (dialog == null) {
            throw new IllegalStateException("DialogFragment " + this + " does not have a Dialog.");
        }
        return dialog;
    }

    @StyleRes
    public int getTheme() {
        return mTheme;
    }

    /**
     * Control whether the shown Dialog is cancelable.  Use this instead of
     * directly calling {@link Dialog#setCancelable(boolean)
     * Dialog.setCancelable(boolean)}, because DialogFragment needs to change
     * its behavior based on this.
     *
     * @param cancelable If true, the dialog is cancelable.  The default
     * is true.
     */
    public void setCancelable(boolean cancelable) {
        mCancelable = cancelable;
        if (mDialog != null) mDialog.setCancelable(cancelable);
    }

    /**
     * Return the current value of {@link #setCancelable(boolean)}.
     */
    public boolean isCancelable() {
        return mCancelable;
    }

    /**
     * Controls whether this fragment should be shown in a dialog.  If not
     * set, no Dialog will be created and the fragment's view hierarchy will
     * thus not be added to it.  This allows you to instead use it as a
     * normal fragment (embedded inside of its activity).
     *
     * <p>This is normally set for you based on whether the fragment is
     * associated with a container view ID passed to
     * {@link FragmentTransaction#add(int, Fragment) FragmentTransaction.add(int, Fragment)}.
     * If the fragment was added with a container, setShowsDialog will be
     * initialized to false; otherwise, it will be true.
     *
     * <p>If calling this manually, it should be called in {@link #onCreate(Bundle)}
     * as calling it any later will have no effect.
     *
     * @param showsDialog If true, the fragment will be displayed in a Dialog.
     * If false, no Dialog will be created and the fragment's view hierarchy
     * left undisturbed.
     */
    public void setShowsDialog(boolean showsDialog) {
        mShowsDialog = showsDialog;
    }

    /**
     * Return the current value of {@link #setShowsDialog(boolean)}.
     */
    public boolean getShowsDialog() {
        return mShowsDialog;
    }

    @MainThread
    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        getViewLifecycleOwnerLiveData().observeForever(mObserver);
        if (!mShownByMe) {
            // If not explicitly shown through our API, take this as an
            // indication that the dialog is no longer dismissed.
            mDismissed = false;
        }
    }

    @MainThread
    @Override
    public void onDetach() {
        super.onDetach();
        if (!mShownByMe && !mDismissed) {
            // The fragment was not shown by a direct call here, it is not
            // dismissed, and now it is being detached...  well, okay, thou
            // art now dismissed.  Have fun.
            mDismissed = true;
        }
        getViewLifecycleOwnerLiveData().removeObserver(mObserver);
    }

    @MainThread
    @Override
    @SuppressWarnings("deprecation")
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // This assumes that onCreate() is being called on the main thread
        mHandler = new Handler();

        mShowsDialog = mContainerId == 0;

        if (savedInstanceState != null) {
            mStyle = savedInstanceState.getInt(SAVED_STYLE, STYLE_NORMAL);
            mTheme = savedInstanceState.getInt(SAVED_THEME, 0);
            mCancelable = savedInstanceState.getBoolean(SAVED_CANCELABLE, true);
            mShowsDialog = savedInstanceState.getBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
            mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, -1);
        }
    }

    @Override
    void performCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState) {
        super.performCreateView(inflater, container, savedInstanceState);
        // If no view was set, we need to call onRestoreInstance on the dialog to ensure
        // the state is restored.
        if (mView == null) {
            if (mDialog != null && savedInstanceState != null) {
                Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG);
                if (dialogState != null) {
                    mDialog.onRestoreInstanceState(dialogState);
                }
            }
        }
    }

    @NonNull
    @Override
    FragmentContainer createFragmentContainer() {
        final FragmentContainer fragmentContainer = super.createFragmentContainer();
        return new FragmentContainer() {
            @Nullable
            @Override
            public View onFindViewById(int id) {
                if (fragmentContainer.onHasView()) {
                    return fragmentContainer.onFindViewById(id);
                }
                return DialogFragment.this.onFindViewById(id);
            }

            @Override
            public boolean onHasView() {
                return  fragmentContainer.onHasView() || DialogFragment.this.onHasView();
            }
        };
    }

    @Nullable
    View onFindViewById(int id) {
        if (mDialog != null) {
            return mDialog.findViewById(id);
        }
        return null;
    }

    boolean onHasView() {
        return mDialogCreated;
    }

    /**
     * {@inheritDoc}
     *
     * <p>
     * If this is called from within {@link #onCreateDialog(Bundle)}, the layout inflater from
     * {@link Fragment#onGetLayoutInflater(Bundle)}, without the dialog theme, will be returned.
     */
    @Override
    @NonNull
    public LayoutInflater onGetLayoutInflater(@Nullable Bundle savedInstanceState) {
        LayoutInflater layoutInflater = super.onGetLayoutInflater(savedInstanceState);
        if (!mShowsDialog || mCreatingDialog) {
            if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
                String message = "getting layout inflater for DialogFragment " + this;
                if (!mShowsDialog) {
                    Log.d(TAG, "mShowsDialog = false: " + message);
                } else {
                    Log.d(TAG, "mCreatingDialog = true: " + message);
                }
            }
            return layoutInflater;
        }

        prepareDialog(savedInstanceState);

        if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
            Log.d(TAG, "get layout inflater for DialogFragment " + this + " from dialog context");
        }

        if (mDialog != null) {
            layoutInflater = layoutInflater.cloneInContext(mDialog.getContext());
        }
        return layoutInflater;
    }

    /** @hide */
    @RestrictTo(LIBRARY_GROUP_PREFIX)
    public void setupDialog(@NonNull Dialog dialog, int style) {
        switch (style) {
            case STYLE_NO_INPUT:
                Window window = dialog.getWindow();
                if (window != null) {
                    window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                            | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
                }
                // fall through...
            case STYLE_NO_FRAME:
            case STYLE_NO_TITLE:
                dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        }
    }

    /**
     * Override to build your own custom Dialog container.  This is typically
     * used to show an AlertDialog instead of a generic Dialog; when doing so,
     * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} does not need
     * to be implemented since the AlertDialog takes care of its own content.
     *
     * <p>This method will be called after {@link #onCreate(Bundle)} and
     * immediately before {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.  The
     * default implementation simply instantiates and returns a {@link Dialog}
     * class.
     *
     * <p><em>Note: DialogFragment own the {@link Dialog#setOnCancelListener
     * Dialog.setOnCancelListener} and {@link Dialog#setOnDismissListener
     * Dialog.setOnDismissListener} callbacks.  You must not set them yourself.</em>
     * To find out about these events, override {@link #onCancel(DialogInterface)}
     * and {@link #onDismiss(DialogInterface)}.</p>
     *
     * @param savedInstanceState The last saved instance state of the Fragment,
     * or null if this is a freshly created Fragment.
     *
     * @return Return a new Dialog instance to be displayed by the Fragment.
     */
    @MainThread
    @NonNull
    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
        if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
            Log.d(TAG, "onCreateDialog called for DialogFragment " + this);
        }
        return new ComponentDialog(requireContext(), getTheme());
    }

    @Override
    public void onCancel(@NonNull DialogInterface dialog) {
    }

    @CallSuper
    @Override
    public void onDismiss(@NonNull DialogInterface dialog) {
        if (!mViewDestroyed) {
            // Note: we need to use allowStateLoss, because the dialog
            // dispatches this asynchronously so we can receive the call
            // after the activity is paused.  Worst case, when the user comes
            // back to the activity they see the dialog again.
            if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
                Log.d(TAG, "onDismiss called for DialogFragment " + this);
            }
            dismissInternal(true, true, false);
        }
    }

    private void prepareDialog(@Nullable Bundle savedInstanceState) {
        if (!mShowsDialog) {
            return;
        }

        if (!mDialogCreated) {
            try {
                mCreatingDialog = true;
                mDialog = onCreateDialog(savedInstanceState);
                // mShowsDialog might have changed in onCreateDialog, so we should only proceed
                // with setting up the dialog if mShowsDialog is still true
                if (mShowsDialog) {
                    setupDialog(mDialog, mStyle);
                    final Context context = getContext();
                    if (context instanceof Activity) {
                        mDialog.setOwnerActivity((Activity) context);
                    }
                    mDialog.setCancelable(mCancelable);
                    mDialog.setOnCancelListener(mOnCancelListener);
                    mDialog.setOnDismissListener(mOnDismissListener);
                    mDialogCreated = true;
                } else {
                    // Ensure that when mShowsDialog is set to false in onCreateDialog
                    // that getDialog() returns null
                    mDialog = null;
                }
            } finally {
                mCreatingDialog = false;
            }
        }
    }

    @MainThread
    @Override
    public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
        super.onViewStateRestored(savedInstanceState);
        if (mDialog != null && savedInstanceState != null) {
            Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG);
            if (dialogState != null) {
                mDialog.onRestoreInstanceState(dialogState);
            }
        }
    }

    /**
     * {@inheritDoc}
     *
     * @deprecated use {@link #onCreateDialog} for code touching the dialog created by
     * {@link #onCreateDialog}, {@link #onViewCreated(View, Bundle)} for code touching the
     * view created by {@link #onCreateView} and {@link #onCreate(Bundle)} for other initialization.
     * To get a callback specifically when a Fragment activity's
     * {@link Activity#onCreate(Bundle)} is called, register a
     * {@link androidx.lifecycle.LifecycleObserver} on the Activity's
     * {@link Lifecycle} in {@link #onAttach(Context)}, removing it when it receives the
     * {@link Lifecycle.State#CREATED} callback.
     */
    @SuppressWarnings("deprecation")
    @MainThread
    @Override
    @Deprecated
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
    }

    @MainThread
    @Override
    public void onStart() {
        super.onStart();

        if (mDialog != null) {
            mViewDestroyed = false;
            mDialog.show();
            // Only after we show does the dialog window actually return a decor view.
            View decorView = mDialog.getWindow().getDecorView();
            ViewTreeLifecycleOwner.set(decorView, this);
            ViewTreeViewModelStoreOwner.set(decorView, this);
            ViewTreeSavedStateRegistryOwner.set(decorView, this);
        }
    }

    @MainThread
    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        if (mDialog != null) {
            Bundle dialogState = mDialog.onSaveInstanceState();
            dialogState.putBoolean(SAVED_INTERNAL_DIALOG_SHOWING, false);
            outState.putBundle(SAVED_DIALOG_STATE_TAG, dialogState);
        }
        if (mStyle != STYLE_NORMAL) {
            outState.putInt(SAVED_STYLE, mStyle);
        }
        if (mTheme != 0) {
            outState.putInt(SAVED_THEME, mTheme);
        }
        if (!mCancelable) {
            outState.putBoolean(SAVED_CANCELABLE, mCancelable);
        }
        if (!mShowsDialog) {
            outState.putBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
        }
        if (mBackStackId != -1) {
            outState.putInt(SAVED_BACK_STACK_ID, mBackStackId);
        }
    }

    @MainThread
    @Override
    public void onStop() {
        super.onStop();
        if (mDialog != null) {
            mDialog.hide();
        }
    }

    /**
     * Remove dialog.
     */
    @MainThread
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        if (mDialog != null) {
            // Set removed here because this dismissal is just to hide
            // the dialog -- we don't want this to cause the fragment to
            // actually be removed.
            mViewDestroyed = true;
            // Instead of waiting for a posted onDismiss(), null out
            // the listener and call onDismiss() manually to ensure
            // that the callback happens before onDestroy()
            mDialog.setOnDismissListener(null);
            mDialog.dismiss();
            if (!mDismissed) {
                // Don't send a second onDismiss() callback if we've already
                // dismissed the dialog manually in dismissInternal()
                onDismiss(mDialog);
            }
            mDialog = null;
            mDialogCreated = false;
        }
    }
}