diff --git a/build.gradle b/build.gradle
index 379d90c85..273800211 100644
--- a/build.gradle
+++ b/build.gradle
@@ -54,6 +54,8 @@ allprojects {
// Replace with modded library (leanback-1.0.0)
exclude group: 'androidx.leanback', module: 'leanback'
+ // Replace with modded library (fragment-1.1.0)
+ exclude group: 'androidx.fragment', module: 'fragment'
// Don't work! Replace with modded library (leanback-1.0.0)
// resolutionStrategy.dependencySubstitution {
diff --git a/common/build.gradle b/common/build.gradle
index eafeb060d..d2c9696ae 100644
--- a/common/build.gradle
+++ b/common/build.gradle
@@ -54,6 +54,7 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:' + testXSupportLibraryVersion
implementation project(':sharedutils')
+ implementation project(':fragment-1.1.0')
implementation project(':mediaserviceinterfaces')
implementation project(':youtubeapi')
diff --git a/fragment-1.1.0/.gitignore b/fragment-1.1.0/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/fragment-1.1.0/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/fragment-1.1.0/build.gradle b/fragment-1.1.0/build.gradle
new file mode 100644
index 000000000..ea2d539f7
--- /dev/null
+++ b/fragment-1.1.0/build.gradle
@@ -0,0 +1,41 @@
+apply from: gradle.ext.sharedModulesConstants
+apply plugin: 'com.android.library'
+
+android {
+ // FIX: Default interface methods are only supported starting with Android N (--min-api 24)
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ compileSdkVersion project.properties.compileSdkVersion
+ buildToolsVersion project.properties.buildToolsVersion
+ testOptions.unitTests.includeAndroidResources = true
+
+ defaultConfig {
+ minSdkVersion project.properties.minSdkVersion
+ targetSdkVersion project.properties.targetSdkVersion
+ versionCode 10
+ versionName "1.0.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles 'consumer-rules.pro'
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ api 'androidx.activity:activity:' + leanbackXLibraryVersion // used inside other app modules
+ implementation 'androidx.annotation:annotation:' + annotationXLibraryVersion
+ implementation 'androidx.core:core:' + kotlinCoreVersion
+ implementation 'androidx.viewpager:viewpager:' + leanbackXLibraryVersion
+ implementation 'androidx.loader:loader:' + leanbackXLibraryVersion
+}
diff --git a/fragment-1.1.0/src/main/AndroidManifest.xml b/fragment-1.1.0/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..48a146811
--- /dev/null
+++ b/fragment-1.1.0/src/main/AndroidManifest.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/fragment-1.1.0/src/main/java/androidx/fragment/app/BackStackRecord.java b/fragment-1.1.0/src/main/java/androidx/fragment/app/BackStackRecord.java
new file mode 100644
index 000000000..73fceec02
--- /dev/null
+++ b/fragment-1.1.0/src/main/java/androidx/fragment/app/BackStackRecord.java
@@ -0,0 +1,667 @@
+/*
+ * 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 android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.util.LogWriter;
+import androidx.lifecycle.Lifecycle;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Entry of an operation on the fragment back stack.
+ */
+final class BackStackRecord extends FragmentTransaction implements
+ FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {
+ static final String TAG = FragmentManagerImpl.TAG;
+
+ final FragmentManagerImpl mManager;
+
+ boolean mCommitted;
+ int mIndex = -1;
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("BackStackEntry{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ if (mIndex >= 0) {
+ sb.append(" #");
+ sb.append(mIndex);
+ }
+ if (mName != null) {
+ sb.append(" ");
+ sb.append(mName);
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ public void dump(String prefix, PrintWriter writer) {
+ dump(prefix, writer, true);
+ }
+
+ public void dump(String prefix, PrintWriter writer, boolean full) {
+ if (full) {
+ writer.print(prefix); writer.print("mName="); writer.print(mName);
+ writer.print(" mIndex="); writer.print(mIndex);
+ writer.print(" mCommitted="); writer.println(mCommitted);
+ if (mTransition != FragmentTransaction.TRANSIT_NONE) {
+ writer.print(prefix); writer.print("mTransition=#");
+ writer.print(Integer.toHexString(mTransition));
+ writer.print(" mTransitionStyle=#");
+ writer.println(Integer.toHexString(mTransitionStyle));
+ }
+ if (mEnterAnim != 0 || mExitAnim !=0) {
+ writer.print(prefix); writer.print("mEnterAnim=#");
+ writer.print(Integer.toHexString(mEnterAnim));
+ writer.print(" mExitAnim=#");
+ writer.println(Integer.toHexString(mExitAnim));
+ }
+ if (mPopEnterAnim != 0 || mPopExitAnim !=0) {
+ writer.print(prefix); writer.print("mPopEnterAnim=#");
+ writer.print(Integer.toHexString(mPopEnterAnim));
+ writer.print(" mPopExitAnim=#");
+ writer.println(Integer.toHexString(mPopExitAnim));
+ }
+ if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) {
+ writer.print(prefix); writer.print("mBreadCrumbTitleRes=#");
+ writer.print(Integer.toHexString(mBreadCrumbTitleRes));
+ writer.print(" mBreadCrumbTitleText=");
+ writer.println(mBreadCrumbTitleText);
+ }
+ if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) {
+ writer.print(prefix); writer.print("mBreadCrumbShortTitleRes=#");
+ writer.print(Integer.toHexString(mBreadCrumbShortTitleRes));
+ writer.print(" mBreadCrumbShortTitleText=");
+ writer.println(mBreadCrumbShortTitleText);
+ }
+ }
+
+ if (!mOps.isEmpty()) {
+ writer.print(prefix); writer.println("Operations:");
+ final int numOps = mOps.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ final Op op = mOps.get(opNum);
+ String cmdStr;
+ switch (op.mCmd) {
+ case OP_NULL: cmdStr="NULL"; break;
+ case OP_ADD: cmdStr="ADD"; break;
+ case OP_REPLACE: cmdStr="REPLACE"; break;
+ case OP_REMOVE: cmdStr="REMOVE"; break;
+ case OP_HIDE: cmdStr="HIDE"; break;
+ case OP_SHOW: cmdStr="SHOW"; break;
+ case OP_DETACH: cmdStr="DETACH"; break;
+ case OP_ATTACH: cmdStr="ATTACH"; break;
+ case OP_SET_PRIMARY_NAV: cmdStr="SET_PRIMARY_NAV"; break;
+ case OP_UNSET_PRIMARY_NAV: cmdStr="UNSET_PRIMARY_NAV";break;
+ case OP_SET_MAX_LIFECYCLE: cmdStr = "OP_SET_MAX_LIFECYCLE"; break;
+ default: cmdStr = "cmd=" + op.mCmd; break;
+ }
+ writer.print(prefix); writer.print(" Op #"); writer.print(opNum);
+ writer.print(": "); writer.print(cmdStr);
+ writer.print(" "); writer.println(op.mFragment);
+ if (full) {
+ if (op.mEnterAnim != 0 || op.mExitAnim != 0) {
+ writer.print(prefix); writer.print("enterAnim=#");
+ writer.print(Integer.toHexString(op.mEnterAnim));
+ writer.print(" exitAnim=#");
+ writer.println(Integer.toHexString(op.mExitAnim));
+ }
+ if (op.mPopEnterAnim != 0 || op.mPopExitAnim != 0) {
+ writer.print(prefix); writer.print("popEnterAnim=#");
+ writer.print(Integer.toHexString(op.mPopEnterAnim));
+ writer.print(" popExitAnim=#");
+ writer.println(Integer.toHexString(op.mPopExitAnim));
+ }
+ }
+ }
+ }
+ }
+
+ public BackStackRecord(FragmentManagerImpl manager) {
+ mManager = manager;
+ }
+
+ @Override
+ public int getId() {
+ return mIndex;
+ }
+
+ @Override
+ public int getBreadCrumbTitleRes() {
+ return mBreadCrumbTitleRes;
+ }
+
+ @Override
+ public int getBreadCrumbShortTitleRes() {
+ return mBreadCrumbShortTitleRes;
+ }
+
+ @Override
+ @Nullable
+ public CharSequence getBreadCrumbTitle() {
+ if (mBreadCrumbTitleRes != 0) {
+ return mManager.mHost.getContext().getText(mBreadCrumbTitleRes);
+ }
+ return mBreadCrumbTitleText;
+ }
+
+ @Override
+ @Nullable
+ public CharSequence getBreadCrumbShortTitle() {
+ if (mBreadCrumbShortTitleRes != 0) {
+ return mManager.mHost.getContext().getText(mBreadCrumbShortTitleRes);
+ }
+ return mBreadCrumbShortTitleText;
+ }
+
+ @Override
+ void doAddOp(int containerViewId, Fragment fragment, @Nullable String tag, int opcmd) {
+ super.doAddOp(containerViewId, fragment, tag, opcmd);
+ fragment.mFragmentManager = mManager;
+ }
+
+ @NonNull
+ @Override
+ public FragmentTransaction remove(@NonNull Fragment fragment) {
+ if (fragment.mFragmentManager != null && fragment.mFragmentManager != mManager) {
+ throw new IllegalStateException("Cannot remove Fragment attached to "
+ + "a different FragmentManager. Fragment " + fragment.toString() + " is already"
+ + " attached to a FragmentManager.");
+ }
+ return super.remove(fragment);
+ }
+
+ @NonNull
+ @Override
+ public FragmentTransaction hide(@NonNull Fragment fragment) {
+ if (fragment.mFragmentManager != null && fragment.mFragmentManager != mManager) {
+ throw new IllegalStateException("Cannot hide Fragment attached to "
+ + "a different FragmentManager. Fragment " + fragment.toString() + " is already"
+ + " attached to a FragmentManager.");
+ }
+ return super.hide(fragment);
+ }
+
+ @NonNull
+ @Override
+ public FragmentTransaction show(@NonNull Fragment fragment) {
+ if (fragment.mFragmentManager != null && fragment.mFragmentManager != mManager) {
+ throw new IllegalStateException("Cannot show Fragment attached to "
+ + "a different FragmentManager. Fragment " + fragment.toString() + " is already"
+ + " attached to a FragmentManager.");
+ }
+ return super.show(fragment);
+ }
+
+ @NonNull
+ @Override
+ public FragmentTransaction detach(@NonNull Fragment fragment) {
+ if (fragment.mFragmentManager != null && fragment.mFragmentManager != mManager) {
+ throw new IllegalStateException("Cannot detach Fragment attached to "
+ + "a different FragmentManager. Fragment " + fragment.toString() + " is already"
+ + " attached to a FragmentManager.");
+ }
+ return super.detach(fragment);
+ }
+
+ @NonNull
+ @Override
+ public FragmentTransaction setPrimaryNavigationFragment(@Nullable Fragment fragment) {
+ if (fragment != null
+ && fragment.mFragmentManager != null && fragment.mFragmentManager != mManager) {
+ throw new IllegalStateException("Cannot setPrimaryNavigation for Fragment attached to "
+ + "a different FragmentManager. Fragment " + fragment.toString() + " is already"
+ + " attached to a FragmentManager.");
+ }
+ return super.setPrimaryNavigationFragment(fragment);
+ }
+
+ @NonNull
+ @Override
+ public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
+ @NonNull Lifecycle.State state) {
+ if (fragment.mFragmentManager != mManager) {
+ throw new IllegalArgumentException("Cannot setMaxLifecycle for Fragment not attached to"
+ + " FragmentManager " + mManager);
+ }
+ if (!state.isAtLeast(Lifecycle.State.CREATED)) {
+ throw new IllegalArgumentException("Cannot set maximum Lifecycle below "
+ + Lifecycle.State.CREATED);
+ }
+ return super.setMaxLifecycle(fragment, state);
+ }
+
+ void bumpBackStackNesting(int amt) {
+ if (!mAddToBackStack) {
+ return;
+ }
+ if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting in " + this
+ + " by " + amt);
+ final int numOps = mOps.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ final Op op = mOps.get(opNum);
+ if (op.mFragment != null) {
+ op.mFragment.mBackStackNesting += amt;
+ if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
+ + op.mFragment + " to " + op.mFragment.mBackStackNesting);
+ }
+ }
+ }
+
+ public void runOnCommitRunnables() {
+ if (mCommitRunnables != null) {
+ for (int i = 0; i < mCommitRunnables.size(); i++) {
+ mCommitRunnables.get(i).run();
+ }
+ mCommitRunnables = null;
+ }
+ }
+
+ @Override
+ public int commit() {
+ return commitInternal(false);
+ }
+
+ @Override
+ public int commitAllowingStateLoss() {
+ return commitInternal(true);
+ }
+
+ @Override
+ public void commitNow() {
+ disallowAddToBackStack();
+ mManager.execSingleAction(this, false);
+ }
+
+ @Override
+ public void commitNowAllowingStateLoss() {
+ disallowAddToBackStack();
+ mManager.execSingleAction(this, true);
+ }
+
+ int commitInternal(boolean allowStateLoss) {
+ if (mCommitted) throw new IllegalStateException("commit already called");
+ if (FragmentManagerImpl.DEBUG) {
+ Log.v(TAG, "Commit: " + this);
+ LogWriter logw = new LogWriter(TAG);
+ PrintWriter pw = new PrintWriter(logw);
+ dump(" ", pw);
+ pw.close();
+ }
+ mCommitted = true;
+ if (mAddToBackStack) {
+ mIndex = mManager.allocBackStackIndex(this);
+ } else {
+ mIndex = -1;
+ }
+ mManager.enqueueAction(this, allowStateLoss);
+ return mIndex;
+ }
+
+ /**
+ * Implementation of {@link FragmentManagerImpl.OpGenerator}.
+ * This operation is added to the list of pending actions during {@link #commit()}, and
+ * will be executed on the UI thread to run this FragmentTransaction.
+ *
+ * @param records Modified to add this BackStackRecord
+ * @param isRecordPop Modified to add a false (this isn't a pop)
+ * @return true always because the records and isRecordPop will always be changed
+ */
+ @Override
+ public boolean generateOps(ArrayList records, ArrayList isRecordPop) {
+ if (FragmentManagerImpl.DEBUG) {
+ Log.v(TAG, "Run: " + this);
+ }
+
+ records.add(this);
+ isRecordPop.add(false);
+ if (mAddToBackStack) {
+ mManager.addBackStackState(this);
+ }
+ return true;
+ }
+
+ boolean interactsWith(int containerId) {
+ final int numOps = mOps.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ final Op op = mOps.get(opNum);
+ final int fragContainer = op.mFragment != null ? op.mFragment.mContainerId : 0;
+ if (fragContainer != 0 && fragContainer == containerId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean interactsWith(ArrayList records, int startIndex, int endIndex) {
+ if (endIndex == startIndex) {
+ return false;
+ }
+ final int numOps = mOps.size();
+ int lastContainer = -1;
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ final Op op = mOps.get(opNum);
+ final int container = op.mFragment != null ? op.mFragment.mContainerId : 0;
+ if (container != 0 && container != lastContainer) {
+ lastContainer = container;
+ for (int i = startIndex; i < endIndex; i++) {
+ BackStackRecord record = records.get(i);
+ final int numThoseOps = record.mOps.size();
+ for (int thoseOpIndex = 0; thoseOpIndex < numThoseOps; thoseOpIndex++) {
+ final Op thatOp = record.mOps.get(thoseOpIndex);
+ final int thatContainer = thatOp.mFragment != null
+ ? thatOp.mFragment.mContainerId : 0;
+ if (thatContainer == container) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Executes the operations contained within this transaction. The Fragment states will only
+ * be modified if optimizations are not allowed.
+ */
+ void executeOps() {
+ final int numOps = mOps.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ final Op op = mOps.get(opNum);
+ final Fragment f = op.mFragment;
+ if (f != null) {
+ f.setNextTransition(mTransition, mTransitionStyle);
+ }
+ switch (op.mCmd) {
+ case OP_ADD:
+ f.setNextAnim(op.mEnterAnim);
+ mManager.addFragment(f, false);
+ break;
+ case OP_REMOVE:
+ f.setNextAnim(op.mExitAnim);
+ mManager.removeFragment(f);
+ break;
+ case OP_HIDE:
+ f.setNextAnim(op.mExitAnim);
+ mManager.hideFragment(f);
+ break;
+ case OP_SHOW:
+ f.setNextAnim(op.mEnterAnim);
+ mManager.showFragment(f);
+ break;
+ case OP_DETACH:
+ f.setNextAnim(op.mExitAnim);
+ mManager.detachFragment(f);
+ break;
+ case OP_ATTACH:
+ f.setNextAnim(op.mEnterAnim);
+ mManager.attachFragment(f);
+ break;
+ case OP_SET_PRIMARY_NAV:
+ mManager.setPrimaryNavigationFragment(f);
+ break;
+ case OP_UNSET_PRIMARY_NAV:
+ mManager.setPrimaryNavigationFragment(null);
+ break;
+ case OP_SET_MAX_LIFECYCLE:
+ mManager.setMaxLifecycle(f, op.mCurrentMaxState);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown cmd: " + op.mCmd);
+ }
+ if (!mReorderingAllowed && op.mCmd != OP_ADD && f != null) {
+ mManager.moveFragmentToExpectedState(f);
+ }
+ }
+ if (!mReorderingAllowed) {
+ // Added fragments are added at the end to comply with prior behavior.
+ mManager.moveToState(mManager.mCurState, true);
+ }
+ }
+
+ /**
+ * Reverses the execution of the operations within this transaction. The Fragment states will
+ * only be modified if reordering is not allowed.
+ *
+ * @param moveToState {@code true} if added fragments should be moved to their final state
+ * in ordered transactions
+ */
+ void executePopOps(boolean moveToState) {
+ for (int opNum = mOps.size() - 1; opNum >= 0; opNum--) {
+ final Op op = mOps.get(opNum);
+ Fragment f = op.mFragment;
+ if (f != null) {
+ f.setNextTransition(FragmentManagerImpl.reverseTransit(mTransition),
+ mTransitionStyle);
+ }
+ switch (op.mCmd) {
+ case OP_ADD:
+ f.setNextAnim(op.mPopExitAnim);
+ mManager.removeFragment(f);
+ break;
+ case OP_REMOVE:
+ f.setNextAnim(op.mPopEnterAnim);
+ mManager.addFragment(f, false);
+ break;
+ case OP_HIDE:
+ f.setNextAnim(op.mPopEnterAnim);
+ mManager.showFragment(f);
+ break;
+ case OP_SHOW:
+ f.setNextAnim(op.mPopExitAnim);
+ mManager.hideFragment(f);
+ break;
+ case OP_DETACH:
+ f.setNextAnim(op.mPopEnterAnim);
+ mManager.attachFragment(f);
+ break;
+ case OP_ATTACH:
+ f.setNextAnim(op.mPopExitAnim);
+ mManager.detachFragment(f);
+ break;
+ case OP_SET_PRIMARY_NAV:
+ mManager.setPrimaryNavigationFragment(null);
+ break;
+ case OP_UNSET_PRIMARY_NAV:
+ mManager.setPrimaryNavigationFragment(f);
+ break;
+ case OP_SET_MAX_LIFECYCLE:
+ mManager.setMaxLifecycle(f, op.mOldMaxState);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown cmd: " + op.mCmd);
+ }
+ if (!mReorderingAllowed && op.mCmd != OP_REMOVE && f != null) {
+ mManager.moveFragmentToExpectedState(f);
+ }
+ }
+ if (!mReorderingAllowed && moveToState) {
+ mManager.moveToState(mManager.mCurState, true);
+ }
+ }
+
+ /**
+ * Expands all meta-ops into their more primitive equivalents. This must be called prior to
+ * {@link #executeOps()} or any other call that operations on mOps for forward navigation.
+ * It should not be called for pop/reverse navigation operations.
+ *
+ *
Removes all OP_REPLACE ops and replaces them with the proper add and remove
+ * operations that are equivalent to the replace.
+ *
+ *
Adds OP_UNSET_PRIMARY_NAV ops to match OP_SET_PRIMARY_NAV, OP_REMOVE and OP_DETACH
+ * ops so that we can restore the old primary nav fragment later. Since callers call this
+ * method in a loop before running ops from several transactions at once, the caller should
+ * pass the return value from this method as the oldPrimaryNav parameter for the next call.
+ * The first call in such a loop should pass the value of
+ * {@link FragmentManager#getPrimaryNavigationFragment()}.
+ *
+ * @param added Initialized to the fragments that are in the mManager.mAdded, this
+ * will be modified to contain the fragments that will be in mAdded
+ * after the execution ({@link #executeOps()}.
+ * @param oldPrimaryNav The tracked primary navigation fragment as of the beginning of
+ * this set of ops
+ * @return the new oldPrimaryNav fragment after this record's ops would be run
+ */
+ @SuppressWarnings("ReferenceEquality")
+ Fragment expandOps(ArrayList added, Fragment oldPrimaryNav) {
+ for (int opNum = 0; opNum < mOps.size(); opNum++) {
+ final Op op = mOps.get(opNum);
+ switch (op.mCmd) {
+ case OP_ADD:
+ case OP_ATTACH:
+ added.add(op.mFragment);
+ break;
+ case OP_REMOVE:
+ case OP_DETACH: {
+ added.remove(op.mFragment);
+ if (op.mFragment == oldPrimaryNav) {
+ mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, op.mFragment));
+ opNum++;
+ oldPrimaryNav = null;
+ }
+ }
+ break;
+ case OP_REPLACE: {
+ final Fragment f = op.mFragment;
+ final int containerId = f.mContainerId;
+ boolean alreadyAdded = false;
+ for (int i = added.size() - 1; i >= 0; i--) {
+ final Fragment old = added.get(i);
+ if (old.mContainerId == containerId) {
+ if (old == f) {
+ alreadyAdded = true;
+ } else {
+ // This is duplicated from above since we only make
+ // a single pass for expanding ops. Unset any outgoing primary nav.
+ if (old == oldPrimaryNav) {
+ mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, old));
+ opNum++;
+ oldPrimaryNav = null;
+ }
+ final Op removeOp = new Op(OP_REMOVE, old);
+ removeOp.mEnterAnim = op.mEnterAnim;
+ removeOp.mPopEnterAnim = op.mPopEnterAnim;
+ removeOp.mExitAnim = op.mExitAnim;
+ removeOp.mPopExitAnim = op.mPopExitAnim;
+ mOps.add(opNum, removeOp);
+ added.remove(old);
+ opNum++;
+ }
+ }
+ }
+ if (alreadyAdded) {
+ mOps.remove(opNum);
+ opNum--;
+ } else {
+ op.mCmd = OP_ADD;
+ added.add(f);
+ }
+ }
+ break;
+ case OP_SET_PRIMARY_NAV: {
+ // It's ok if this is null, that means we will restore to no active
+ // primary navigation fragment on a pop.
+ mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, oldPrimaryNav));
+ opNum++;
+ // Will be set by the OP_SET_PRIMARY_NAV we inserted before when run
+ oldPrimaryNav = op.mFragment;
+ }
+ break;
+ }
+ }
+ return oldPrimaryNav;
+ }
+
+ /**
+ * Removes fragments that are added or removed during a pop operation.
+ *
+ * @param added Initialized to the fragments that are in the mManager.mAdded, this
+ * will be modified to contain the fragments that will be in mAdded
+ * after the execution ({@link #executeOps()}.
+ * @param oldPrimaryNav The tracked primary navigation fragment as of the beginning of
+ * this set of ops
+ * @return the new oldPrimaryNav fragment after this record's ops would be popped
+ */
+ Fragment trackAddedFragmentsInPop(ArrayList added, Fragment oldPrimaryNav) {
+ for (int opNum = mOps.size() - 1; opNum >= 0; opNum--) {
+ final Op op = mOps.get(opNum);
+ switch (op.mCmd) {
+ case OP_ADD:
+ case OP_ATTACH:
+ added.remove(op.mFragment);
+ break;
+ case OP_REMOVE:
+ case OP_DETACH:
+ added.add(op.mFragment);
+ break;
+ case OP_UNSET_PRIMARY_NAV:
+ oldPrimaryNav = op.mFragment;
+ break;
+ case OP_SET_PRIMARY_NAV:
+ oldPrimaryNav = null;
+ break;
+ case OP_SET_MAX_LIFECYCLE:
+ op.mCurrentMaxState = op.mOldMaxState;
+ break;
+ }
+ }
+ return oldPrimaryNav;
+ }
+
+ boolean isPostponed() {
+ for (int opNum = 0; opNum < mOps.size(); opNum++) {
+ final Op op = mOps.get(opNum);
+ if (isFragmentPostponed(op)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void setOnStartPostponedListener(Fragment.OnStartEnterTransitionListener listener) {
+ for (int opNum = 0; opNum < mOps.size(); opNum++) {
+ final Op op = mOps.get(opNum);
+ if (isFragmentPostponed(op)) {
+ op.mFragment.setOnStartEnterTransitionListener(listener);
+ }
+ }
+ }
+
+ private static boolean isFragmentPostponed(Op op) {
+ final Fragment fragment = op.mFragment;
+ return fragment != null && fragment.mAdded && fragment.mView != null && !fragment.mDetached
+ && !fragment.mHidden && fragment.isPostponed();
+ }
+
+ @Override
+ @Nullable
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return mOps.isEmpty();
+ }
+}
diff --git a/fragment-1.1.0/src/main/java/androidx/fragment/app/BackStackState.java b/fragment-1.1.0/src/main/java/androidx/fragment/app/BackStackState.java
new file mode 100644
index 000000000..7ae173953
--- /dev/null
+++ b/fragment-1.1.0/src/main/java/androidx/fragment/app/BackStackState.java
@@ -0,0 +1,182 @@
+/*
+ * 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 android.annotation.SuppressLint;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.lifecycle.Lifecycle;
+
+import java.util.ArrayList;
+
+@SuppressLint("BanParcelableUsage")
+final class BackStackState implements Parcelable {
+ final int[] mOps;
+ final ArrayList mFragmentWhos;
+ final int[] mOldMaxLifecycleStates;
+ final int[] mCurrentMaxLifecycleStates;
+ final int mTransition;
+ final int mTransitionStyle;
+ final String mName;
+ final int mIndex;
+ final int mBreadCrumbTitleRes;
+ final CharSequence mBreadCrumbTitleText;
+ final int mBreadCrumbShortTitleRes;
+ final CharSequence mBreadCrumbShortTitleText;
+ final ArrayList mSharedElementSourceNames;
+ final ArrayList mSharedElementTargetNames;
+ final boolean mReorderingAllowed;
+
+ public BackStackState(BackStackRecord bse) {
+ final int numOps = bse.mOps.size();
+ mOps = new int[numOps * 5];
+
+ if (!bse.mAddToBackStack) {
+ throw new IllegalStateException("Not on back stack");
+ }
+
+ mFragmentWhos = new ArrayList<>(numOps);
+ mOldMaxLifecycleStates = new int[numOps];
+ mCurrentMaxLifecycleStates = new int[numOps];
+ int pos = 0;
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ final BackStackRecord.Op op = bse.mOps.get(opNum);
+ mOps[pos++] = op.mCmd;
+ mFragmentWhos.add(op.mFragment != null ? op.mFragment.mWho : null);
+ mOps[pos++] = op.mEnterAnim;
+ mOps[pos++] = op.mExitAnim;
+ mOps[pos++] = op.mPopEnterAnim;
+ mOps[pos++] = op.mPopExitAnim;
+ mOldMaxLifecycleStates[opNum] = op.mOldMaxState.ordinal();
+ mCurrentMaxLifecycleStates[opNum] = op.mCurrentMaxState.ordinal();
+ }
+ mTransition = bse.mTransition;
+ mTransitionStyle = bse.mTransitionStyle;
+ mName = bse.mName;
+ mIndex = bse.mIndex;
+ mBreadCrumbTitleRes = bse.mBreadCrumbTitleRes;
+ mBreadCrumbTitleText = bse.mBreadCrumbTitleText;
+ mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes;
+ mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText;
+ mSharedElementSourceNames = bse.mSharedElementSourceNames;
+ mSharedElementTargetNames = bse.mSharedElementTargetNames;
+ mReorderingAllowed = bse.mReorderingAllowed;
+ }
+
+ public BackStackState(Parcel in) {
+ mOps = in.createIntArray();
+ mFragmentWhos = in.createStringArrayList();
+ mOldMaxLifecycleStates = in.createIntArray();
+ mCurrentMaxLifecycleStates = in.createIntArray();
+ mTransition = in.readInt();
+ mTransitionStyle = in.readInt();
+ mName = in.readString();
+ mIndex = in.readInt();
+ mBreadCrumbTitleRes = in.readInt();
+ mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mBreadCrumbShortTitleRes = in.readInt();
+ mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mSharedElementSourceNames = in.createStringArrayList();
+ mSharedElementTargetNames = in.createStringArrayList();
+ mReorderingAllowed = in.readInt() != 0;
+ }
+
+ public BackStackRecord instantiate(FragmentManagerImpl fm) {
+ BackStackRecord bse = new BackStackRecord(fm);
+ int pos = 0;
+ int num = 0;
+ while (pos < mOps.length) {
+ BackStackRecord.Op op = new BackStackRecord.Op();
+ op.mCmd = mOps[pos++];
+ if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
+ "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]);
+ String fWho = mFragmentWhos.get(num);
+ if (fWho != null) {
+ Fragment f = fm.mActive.get(fWho);
+ op.mFragment = f;
+ } else {
+ op.mFragment = null;
+ }
+ op.mOldMaxState = Lifecycle.State.values()[mOldMaxLifecycleStates[num]];
+ op.mCurrentMaxState = Lifecycle.State.values()[mCurrentMaxLifecycleStates[num]];
+ op.mEnterAnim = mOps[pos++];
+ op.mExitAnim = mOps[pos++];
+ op.mPopEnterAnim = mOps[pos++];
+ op.mPopExitAnim = mOps[pos++];
+ bse.mEnterAnim = op.mEnterAnim;
+ bse.mExitAnim = op.mExitAnim;
+ bse.mPopEnterAnim = op.mPopEnterAnim;
+ bse.mPopExitAnim = op.mPopExitAnim;
+ bse.addOp(op);
+ num++;
+ }
+ bse.mTransition = mTransition;
+ bse.mTransitionStyle = mTransitionStyle;
+ bse.mName = mName;
+ bse.mIndex = mIndex;
+ bse.mAddToBackStack = true;
+ bse.mBreadCrumbTitleRes = mBreadCrumbTitleRes;
+ bse.mBreadCrumbTitleText = mBreadCrumbTitleText;
+ bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes;
+ bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText;
+ bse.mSharedElementSourceNames = mSharedElementSourceNames;
+ bse.mSharedElementTargetNames = mSharedElementTargetNames;
+ bse.mReorderingAllowed = mReorderingAllowed;
+ bse.bumpBackStackNesting(1);
+ return bse;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeIntArray(mOps);
+ dest.writeStringList(mFragmentWhos);
+ dest.writeIntArray(mOldMaxLifecycleStates);
+ dest.writeIntArray(mCurrentMaxLifecycleStates);
+ dest.writeInt(mTransition);
+ dest.writeInt(mTransitionStyle);
+ dest.writeString(mName);
+ dest.writeInt(mIndex);
+ dest.writeInt(mBreadCrumbTitleRes);
+ TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0);
+ dest.writeInt(mBreadCrumbShortTitleRes);
+ TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0);
+ dest.writeStringList(mSharedElementSourceNames);
+ dest.writeStringList(mSharedElementTargetNames);
+ dest.writeInt(mReorderingAllowed ? 1 : 0);
+ }
+
+ public static final Parcelable.Creator CREATOR
+ = new Parcelable.Creator() {
+ @Override
+ public BackStackState createFromParcel(Parcel in) {
+ return new BackStackState(in);
+ }
+
+ @Override
+ public BackStackState[] newArray(int size) {
+ return new BackStackState[size];
+ }
+ };
+}
\ No newline at end of file
diff --git a/fragment-1.1.0/src/main/java/androidx/fragment/app/DialogFragment.java b/fragment-1.1.0/src/main/java/androidx/fragment/app/DialogFragment.java
new file mode 100644
index 000000000..74f516bdc
--- /dev/null
+++ b/fragment-1.1.0/src/main/java/androidx/fragment/app/DialogFragment.java
@@ -0,0 +1,548 @@
+/*
+ * 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 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.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.StyleRes;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Static library support version of the framework's {@link android.app.DialogFragment}.
+ * 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 SDK
+ * documentation for a class overview.
+ */
+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";
+
+ private Handler mHandler;
+ private Runnable mDismissRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mDialog != null) {
+ onDismiss(mDialog);
+ }
+ }
+ };
+ int mStyle = STYLE_NORMAL;
+ int mTheme = 0;
+ boolean mCancelable = true;
+ boolean mShowsDialog = true;
+ int mBackStackId = -1;
+
+ @Nullable Dialog mDialog;
+ boolean mViewDestroyed;
+ boolean mDismissed;
+ boolean mShownByMe;
+
+ public DialogFragment() {
+ }
+
+ /**
+ * 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) {
+ 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 not 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.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 not 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.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);
+ }
+
+ /**
+ * Version of {@link #dismiss()} that uses
+ * {@link FragmentTransaction#commitAllowingStateLoss()
+ * FragmentTransaction.commitAllowingStateLoss()}. See linked
+ * documentation for further details.
+ */
+ public void dismissAllowingStateLoss() {
+ dismissInternal(true, false);
+ }
+
+ void dismissInternal(boolean allowStateLoss, boolean fromOnDismiss) {
+ 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) {
+ requireFragmentManager().popBackStack(mBackStackId,
+ FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ mBackStackId = -1;
+ } else {
+ FragmentTransaction ft = requireFragmentManager().beginTransaction();
+ ft.remove(this);
+ 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 in {@link #onActivityCreated(Bundle)},
+ * 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).
+ *
+ *
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.
+ *
+ * @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;
+ }
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+ if (!mShownByMe) {
+ // If not explicitly shown through our API, take this as an
+ // indication that the dialog is no longer dismissed.
+ mDismissed = false;
+ }
+ }
+
+ @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;
+ }
+ }
+
+ @Override
+ 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
+ @NonNull
+ public LayoutInflater onGetLayoutInflater(@Nullable Bundle savedInstanceState) {
+ if (!mShowsDialog) {
+ return super.onGetLayoutInflater(savedInstanceState);
+ }
+
+ mDialog = onCreateDialog(savedInstanceState);
+
+ if (mDialog != null) {
+ setupDialog(mDialog, mStyle);
+
+ return (LayoutInflater) mDialog.getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ }
+ return (LayoutInflater) mHost.getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+ /** @hide */
+ @RestrictTo(LIBRARY_GROUP_PREFIX)
+ public void setupDialog(@NonNull Dialog dialog, int style) {
+ switch (style) {
+ case STYLE_NO_INPUT:
+ dialog.getWindow().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.
+ *
+ *
This method will be called after {@link #onCreate(Bundle)} and
+ * before {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}. The
+ * default implementation simply instantiates and returns a {@link Dialog}
+ * class.
+ *
+ *
Note: DialogFragment own the {@link Dialog#setOnCancelListener
+ * Dialog.setOnCancelListener} and {@link Dialog#setOnDismissListener
+ * Dialog.setOnDismissListener} callbacks. You must not set them yourself.
+ * To find out about these events, override {@link #onCancel(DialogInterface)}
+ * and {@link #onDismiss(DialogInterface)}.
+ *
+ * @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.
+ */
+ @NonNull
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+ return new Dialog(requireContext(), getTheme());
+ }
+
+ @Override
+ public void onCancel(@NonNull DialogInterface dialog) {
+ }
+
+ @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.
+ dismissInternal(true, true);
+ }
+ }
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ if (!mShowsDialog) {
+ return;
+ }
+
+ View view = getView();
+ if (view != null) {
+ if (view.getParent() != null) {
+ throw new IllegalStateException(
+ "DialogFragment can not be attached to a container view");
+ }
+ mDialog.setContentView(view);
+ }
+ final Activity activity = getActivity();
+ if (activity != null) {
+ mDialog.setOwnerActivity(activity);
+ }
+ mDialog.setCancelable(mCancelable);
+ mDialog.setOnCancelListener(this);
+ mDialog.setOnDismissListener(this);
+ if (savedInstanceState != null) {
+ Bundle dialogState = savedInstanceState.getBundle(SAVED_DIALOG_STATE_TAG);
+ if (dialogState != null) {
+ mDialog.onRestoreInstanceState(dialogState);
+ }
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ if (mDialog != null) {
+ mViewDestroyed = false;
+ mDialog.show();
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (mDialog != null) {
+ Bundle dialogState = mDialog.onSaveInstanceState();
+ if (dialogState != null) {
+ 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);
+ }
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (mDialog != null) {
+ mDialog.hide();
+ }
+ }
+
+ /**
+ * Remove dialog.
+ */
+ @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;
+ }
+ }
+}
diff --git a/fragment-1.1.0/src/main/java/androidx/fragment/app/Fragment.java b/fragment-1.1.0/src/main/java/androidx/fragment/app/Fragment.java
new file mode 100644
index 000000000..ed482bda8
--- /dev/null
+++ b/fragment-1.1.0/src/main/java/androidx/fragment/app/Fragment.java
@@ -0,0 +1,3041 @@
+/*
+ * 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 android.animation.Animator;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.ComponentCallbacks;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnCreateContextMenuListener;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.widget.AdapterView;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.ContentView;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.StringRes;
+import androidx.core.app.SharedElementCallback;
+import androidx.core.util.DebugUtils;
+import androidx.core.view.LayoutInflaterCompat;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleEventObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModelStore;
+import androidx.lifecycle.ViewModelStoreOwner;
+import androidx.loader.app.LoaderManager;
+import androidx.savedstate.SavedStateRegistry;
+import androidx.savedstate.SavedStateRegistryController;
+import androidx.savedstate.SavedStateRegistryOwner;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Static library support version of the framework's {@link android.app.Fragment}.
+ * 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 android.app.Fragment}
+ * documentation for a class overview.
+ *
+ *
The main differences when using this support version instead of the framework version are:
+ *
+ *
Your activity must extend {@link FragmentActivity}
+ *
You must call {@link FragmentActivity#getSupportFragmentManager} to get the
+ * {@link FragmentManager}
+ *
+ *
+ */
+public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener, LifecycleOwner,
+ ViewModelStoreOwner, SavedStateRegistryOwner {
+
+ static final Object USE_DEFAULT_TRANSITION = new Object();
+
+ static final int INITIALIZING = 0; // Not yet created.
+ static final int CREATED = 1; // Created.
+ static final int ACTIVITY_CREATED = 2; // Fully created, not started.
+ static final int STARTED = 3; // Created and started, not resumed.
+ static final int RESUMED = 4; // Created started and resumed.
+
+ int mState = INITIALIZING;
+
+ // When instantiated from saved state, this is the saved state.
+ Bundle mSavedFragmentState;
+ SparseArray mSavedViewState;
+ // If the userVisibleHint is changed before the state is set,
+ // it is stored here
+ @Nullable Boolean mSavedUserVisibleHint;
+
+ // Internal unique name for this fragment;
+ @NonNull
+ String mWho = UUID.randomUUID().toString();
+
+ // Construction arguments;
+ Bundle mArguments;
+
+ // Target fragment.
+ Fragment mTarget;
+
+ // For use when retaining a fragment: this is the who of the last mTarget.
+ String mTargetWho = null;
+
+ // Target request code.
+ int mTargetRequestCode;
+
+ // Boolean indicating whether this Fragment is the primary navigation fragment
+ private Boolean mIsPrimaryNavigationFragment = null;
+
+ // True if the fragment is in the list of added fragments.
+ boolean mAdded;
+
+ // If set this fragment is being removed from its activity.
+ boolean mRemoving;
+
+ // Set to true if this fragment was instantiated from a layout file.
+ boolean mFromLayout;
+
+ // Set to true when the view has actually been inflated in its layout.
+ boolean mInLayout;
+
+ // True if this fragment has been restored from previously saved state.
+ boolean mRestored;
+
+ // True if performCreateView has been called and a matching call to performDestroyView
+ // has not yet happened.
+ boolean mPerformedCreateView;
+
+ // Number of active back stack entries this fragment is in.
+ int mBackStackNesting;
+
+ // The fragment manager we are associated with. Set as soon as the
+ // fragment is used in a transaction; cleared after it has been removed
+ // from all transactions.
+ FragmentManagerImpl mFragmentManager;
+
+ // Host this fragment is attached to.
+ FragmentHostCallback mHost;
+
+ // Private fragment manager for child fragments inside of this one.
+ @NonNull
+ FragmentManagerImpl mChildFragmentManager = new FragmentManagerImpl();
+
+ // If this Fragment is contained in another Fragment, this is that container.
+ Fragment mParentFragment;
+
+ // The optional identifier for this fragment -- either the container ID if it
+ // was dynamically added to the view hierarchy, or the ID supplied in
+ // layout.
+ int mFragmentId;
+
+ // When a fragment is being dynamically added to the view hierarchy, this
+ // is the identifier of the parent container it is being added to.
+ int mContainerId;
+
+ // The optional named tag for this fragment -- usually used to find
+ // fragments that are not part of the layout.
+ String mTag;
+
+ // Set to true when the app has requested that this fragment be hidden
+ // from the user.
+ boolean mHidden;
+
+ // Set to true when the app has requested that this fragment be deactivated.
+ boolean mDetached;
+
+ // If set this fragment would like its instance retained across
+ // configuration changes.
+ boolean mRetainInstance;
+
+ // If set this fragment changed its mRetainInstance while it was detached
+ boolean mRetainInstanceChangedWhileDetached;
+
+ // If set this fragment has menu items to contribute.
+ boolean mHasMenu;
+
+ // Set to true to allow the fragment's menu to be shown.
+ boolean mMenuVisible = true;
+
+ // Used to verify that subclasses call through to super class.
+ private boolean mCalled;
+
+ // The parent container of the fragment after dynamically added to UI.
+ ViewGroup mContainer;
+
+ // The View generated for this fragment.
+ View mView;
+
+ // The real inner view that will save/restore state.
+ View mInnerView;
+
+ // Whether this fragment should defer starting until after other fragments
+ // have been started and their loaders are finished.
+ boolean mDeferStart;
+
+ // Hint provided by the app that this fragment is currently visible to the user.
+ boolean mUserVisibleHint = true;
+
+ // The animation and transition information for the fragment. This will be null
+ // unless the elements are explicitly accessed and should remain null for Fragments
+ // without Views.
+ AnimationInfo mAnimationInfo;
+
+ // Runnable that is used to indicate if the Fragment has a postponed transition that is on a
+ // timeout.
+ Runnable mPostponedDurationRunnable = new Runnable() {
+ @Override
+ public void run() {
+ startPostponedEnterTransition();
+ }
+ };
+
+ // True if the View was added, and its animation has yet to be run. This could
+ // also indicate that the fragment view hasn't been made visible, even if there is no
+ // animation for this fragment.
+ boolean mIsNewlyAdded;
+
+ // True if mHidden has been changed and the animation should be scheduled.
+ boolean mHiddenChanged;
+
+ // The alpha of the view when the view was added and then postponed. If the value is less
+ // than zero, this means that the view's add was canceled and should not participate in
+ // removal animations.
+ float mPostponedAlpha;
+
+ // The cached value from onGetLayoutInflater(Bundle) that will be returned from
+ // getLayoutInflater()
+ LayoutInflater mLayoutInflater;
+
+ // Keep track of whether or not this Fragment has run performCreate(). Retained instance
+ // fragments can have mRetaining set to true without going through creation, so we must
+ // track it separately.
+ boolean mIsCreated;
+
+ // Max Lifecycle state this Fragment can achieve.
+ Lifecycle.State mMaxState = Lifecycle.State.RESUMED;
+
+ LifecycleRegistry mLifecycleRegistry;
+
+ // This is initialized in performCreateView and unavailable outside of the
+ // onCreateView/onDestroyView lifecycle
+ @Nullable FragmentViewLifecycleOwner mViewLifecycleOwner;
+ MutableLiveData mViewLifecycleOwnerLiveData = new MutableLiveData<>();
+
+ SavedStateRegistryController mSavedStateRegistryController;
+
+ @LayoutRes
+ private int mContentLayoutId;
+
+ /**
+ * {@inheritDoc}
+ *
+ * Overriding this method is no longer supported and this method will be made
+ * final in a future version of Fragment.
+ */
+ @Override
+ @NonNull
+ public Lifecycle getLifecycle() {
+ return mLifecycleRegistry;
+ }
+
+ /**
+ * Get a {@link LifecycleOwner} that represents the {@link #getView() Fragment's View}
+ * lifecycle. In most cases, this mirrors the lifecycle of the Fragment itself, but in cases
+ * of {@link FragmentTransaction#detach(Fragment) detached} Fragments, the lifecycle of the
+ * Fragment can be considerably longer than the lifecycle of the View itself.
+ *
+ * Namely, the lifecycle of the Fragment's View is:
+ *
+ *
{@link Lifecycle.Event#ON_CREATE created} after {@link #onViewStateRestored(Bundle)}
+ *
{@link Lifecycle.Event#ON_START started} after {@link #onStart()}
+ *
{@link Lifecycle.Event#ON_RESUME resumed} after {@link #onResume()}
+ *
{@link Lifecycle.Event#ON_PAUSE paused} before {@link #onPause()}
+ *
{@link Lifecycle.Event#ON_STOP stopped} before {@link #onStop()}
+ *
{@link Lifecycle.Event#ON_DESTROY destroyed} before {@link #onDestroyView()}
+ *
+ *
+ * The first method where it is safe to access the view lifecycle is
+ * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} under the condition that you must
+ * return a non-null view (an IllegalStateException will be thrown if you access the view
+ * lifecycle but don't return a non-null view).
+ *
The view lifecycle remains valid through the call to {@link #onDestroyView()}, after which
+ * {@link #getView()} will return null, the view lifecycle will be destroyed, and this method
+ * will throw an IllegalStateException. Consider using
+ * {@link #getViewLifecycleOwnerLiveData()} or {@link FragmentTransaction#runOnCommit(Runnable)}
+ * to receive a callback for when the Fragment's view lifecycle is available.
+ *
+ * This should only be called on the main thread.
+ *
+ * Overriding this method is no longer supported and this method will be made
+ * final in a future version of Fragment.
+ *
+ * @return A {@link LifecycleOwner} that represents the {@link #getView() Fragment's View}
+ * lifecycle.
+ * @throws IllegalStateException if the {@link #getView() Fragment's View is null}.
+ */
+ @MainThread
+ @NonNull
+ public LifecycleOwner getViewLifecycleOwner() {
+ if (mViewLifecycleOwner == null) {
+ throw new IllegalStateException("Can't access the Fragment View's LifecycleOwner when "
+ + "getView() is null i.e., before onCreateView() or after onDestroyView()");
+ }
+ return mViewLifecycleOwner;
+ }
+
+ /**
+ * Retrieve a {@link LiveData} which allows you to observe the
+ * {@link #getViewLifecycleOwner() lifecycle of the Fragment's View}.
+ *
+ * This will be set to the new {@link LifecycleOwner} after {@link #onCreateView} returns a
+ * non-null View and will set to null after {@link #onDestroyView()}.
+ *
+ * Overriding this method is no longer supported and this method will be made
+ * final in a future version of Fragment.
+ *
+ * @return A LiveData that changes in sync with {@link #getViewLifecycleOwner()}.
+ */
+ @NonNull
+ public LiveData getViewLifecycleOwnerLiveData() {
+ return mViewLifecycleOwnerLiveData;
+ }
+
+ /**
+ * Returns the {@link ViewModelStore} associated with this Fragment
+ *
+ * Overriding this method is no longer supported and this method will be made
+ * final in a future version of Fragment.
+ *
+ * @return a {@code ViewModelStore}
+ * @throws IllegalStateException if called before the Fragment is attached i.e., before
+ * onAttach().
+ */
+ @NonNull
+ @Override
+ public ViewModelStore getViewModelStore() {
+ if (mFragmentManager == null) {
+ throw new IllegalStateException("Can't access ViewModels from detached fragment");
+ }
+ return mFragmentManager.getViewModelStore(this);
+ }
+
+ @NonNull
+ @Override
+ public final SavedStateRegistry getSavedStateRegistry() {
+ return mSavedStateRegistryController.getSavedStateRegistry();
+ }
+
+ /**
+ * State information that has been retrieved from a fragment instance
+ * through {@link FragmentManager#saveFragmentInstanceState(Fragment)
+ * FragmentManager.saveFragmentInstanceState}.
+ */
+ @SuppressLint("BanParcelableUsage")
+ public static class SavedState implements Parcelable {
+ final Bundle mState;
+
+ SavedState(Bundle state) {
+ mState = state;
+ }
+
+ SavedState(@NonNull Parcel in, @Nullable ClassLoader loader) {
+ mState = in.readBundle();
+ if (loader != null && mState != null) {
+ mState.setClassLoader(loader);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeBundle(mState);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator CREATOR =
+ new Parcelable.ClassLoaderCreator() {
+ @Override
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in, null);
+ }
+
+ @Override
+ public SavedState createFromParcel(Parcel in, ClassLoader loader) {
+ return new SavedState(in, loader);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ /**
+ * Thrown by {@link FragmentFactory#instantiate(ClassLoader, String)} when
+ * there is an instantiation failure.
+ */
+ @SuppressWarnings("JavaLangClash")
+ public static class InstantiationException extends RuntimeException {
+ public InstantiationException(@NonNull String msg, @Nullable Exception cause) {
+ super(msg, cause);
+ }
+ }
+
+ /**
+ * 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.
+ *
+ *
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.
+ *
+ *
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. Some applications may also want to implement {@link #onInflate} to retrieve
+ * attributes from a layout resource, although note this happens when the fragment is attached.
+ */
+ public Fragment() {
+ initLifecycle();
+ }
+
+ /**
+ * Alternate constructor that can be used to provide a default layout
+ * that will be inflated by {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
+ *
+ * @see #Fragment()
+ * @see #onCreateView(LayoutInflater, ViewGroup, Bundle)
+ */
+ @ContentView
+ public Fragment(@LayoutRes int contentLayoutId) {
+ this();
+ mContentLayoutId = contentLayoutId;
+ }
+
+ private void initLifecycle() {
+ mLifecycleRegistry = new LifecycleRegistry(this);
+ mSavedStateRegistryController = SavedStateRegistryController.create(this);
+ if (Build.VERSION.SDK_INT >= 19) {
+ mLifecycleRegistry.addObserver(new LifecycleEventObserver() {
+ @Override
+ public void onStateChanged(@NonNull LifecycleOwner source,
+ @NonNull Lifecycle.Event event) {
+ if (event == Lifecycle.Event.ON_STOP) {
+ if (mView != null) {
+ mView.cancelPendingInputEvents();
+ }
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Like {@link #instantiate(Context, String, Bundle)} but with a null
+ * argument Bundle.
+ * @deprecated Use {@link FragmentManager#getFragmentFactory()} and
+ * {@link FragmentFactory#instantiate(ClassLoader, String)}
+ */
+ @SuppressWarnings("deprecation")
+ @Deprecated
+ @NonNull
+ public static Fragment instantiate(@NonNull Context context, @NonNull String fname) {
+ return instantiate(context, fname, null);
+ }
+
+ /**
+ * Create a new instance of a Fragment with the given class name. This is
+ * the same as calling its empty constructor, setting the {@link ClassLoader} on the
+ * supplied arguments, then calling {@link #setArguments(Bundle)}.
+ *
+ * @param context The calling context being used to instantiate the fragment.
+ * This is currently just used to get its ClassLoader.
+ * @param fname The class name of the fragment to instantiate.
+ * @param args Bundle of arguments to supply to the fragment, which it
+ * can retrieve with {@link #getArguments()}. May be null.
+ * @return Returns a new fragment instance.
+ * @throws InstantiationException If there is a failure in instantiating
+ * the given fragment class. This is a runtime exception; it is not
+ * normally expected to happen.
+ * @deprecated Use {@link FragmentManager#getFragmentFactory()} and
+ * {@link FragmentFactory#instantiate(ClassLoader, String)}, manually calling
+ * {@link #setArguments(Bundle)} on the returned Fragment.
+ */
+ @Deprecated
+ @NonNull
+ public static Fragment instantiate(@NonNull Context context, @NonNull String fname,
+ @Nullable Bundle args) {
+ try {
+ Class extends Fragment> clazz = FragmentFactory.loadFragmentClass(
+ context.getClassLoader(), fname);
+ Fragment f = clazz.getConstructor().newInstance();
+ if (args != null) {
+ args.setClassLoader(f.getClass().getClassLoader());
+ f.setArguments(args);
+ }
+ return f;
+ } catch (java.lang.InstantiationException e) {
+ throw new InstantiationException("Unable to instantiate fragment " + fname
+ + ": make sure class name exists, is public, and has an"
+ + " empty constructor that is public", e);
+ } catch (IllegalAccessException e) {
+ throw new InstantiationException("Unable to instantiate fragment " + fname
+ + ": make sure class name exists, is public, and has an"
+ + " empty constructor that is public", e);
+ } catch (NoSuchMethodException e) {
+ throw new InstantiationException("Unable to instantiate fragment " + fname
+ + ": could not find Fragment constructor", e);
+ } catch (InvocationTargetException e) {
+ throw new InstantiationException("Unable to instantiate fragment " + fname
+ + ": calling Fragment constructor caused an exception", e);
+ }
+ }
+
+ final void restoreViewState(Bundle savedInstanceState) {
+ if (mSavedViewState != null) {
+ mInnerView.restoreHierarchyState(mSavedViewState);
+ mSavedViewState = null;
+ }
+ mCalled = false;
+ onViewStateRestored(savedInstanceState);
+ if (!mCalled) {
+ throw new SuperNotCalledException("Fragment " + this
+ + " did not call through to super.onViewStateRestored()");
+ }
+ if (mView != null) {
+ mViewLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
+ }
+ }
+
+ final boolean isInBackStack() {
+ return mBackStackNesting > 0;
+ }
+
+ /**
+ * Subclasses can not override equals().
+ */
+ @Override public final boolean equals(@Nullable Object o) {
+ return super.equals(o);
+ }
+
+ /**
+ * Subclasses can not override hashCode().
+ */
+ @Override public final int hashCode() {
+ return super.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ DebugUtils.buildShortClassTag(this, sb);
+ sb.append(" (");
+ sb.append(mWho);
+ sb.append(")");
+ if (mFragmentId != 0) {
+ sb.append(" id=0x");
+ sb.append(Integer.toHexString(mFragmentId));
+ }
+ if (mTag != null) {
+ sb.append(" ");
+ sb.append(mTag);
+ }
+ sb.append('}');
+ return sb.toString();
+ }
+
+ /**
+ * Return the identifier this fragment is known by. This is either
+ * the android:id value supplied in a layout or the container view ID
+ * supplied when adding the fragment.
+ */
+ final public int getId() {
+ return mFragmentId;
+ }
+
+ /**
+ * Get the tag name of the fragment, if specified.
+ */
+ @Nullable
+ final public String getTag() {
+ return mTag;
+ }
+
+ /**
+ * Supply the construction arguments for this fragment.
+ * The arguments supplied here will be retained across fragment destroy and
+ * creation.
+ *
This method cannot be called if the fragment is added to a FragmentManager and
+ * if {@link #isStateSaved()} would return true.
+ */
+ public void setArguments(@Nullable Bundle args) {
+ if (mFragmentManager != null && isStateSaved()) {
+ throw new IllegalStateException("Fragment already added and state has been saved");
+ }
+ mArguments = args;
+ }
+
+ /**
+ * Return the arguments supplied when the fragment was instantiated,
+ * if any.
+ */
+ @Nullable
+ final public Bundle getArguments() {
+ return mArguments;
+ }
+
+ /**
+ * Return the arguments supplied when the fragment was instantiated.
+ *
+ * @throws IllegalStateException if no arguments were supplied to the Fragment.
+ * @see #getArguments()
+ */
+ @NonNull
+ public final Bundle requireArguments() {
+ Bundle arguments = getArguments();
+ if (arguments == null) {
+ throw new IllegalStateException("Fragment " + this + " does not have any arguments.");
+ }
+ return arguments;
+ }
+
+ /**
+ * Returns true if this fragment is added and its state has already been saved
+ * by its host. Any operations that would change saved state should not be performed
+ * if this method returns true, and some operations such as {@link #setArguments(Bundle)}
+ * will fail.
+ *
+ * @return true if this fragment's state has already been saved by its host
+ */
+ public final boolean isStateSaved() {
+ if (mFragmentManager == null) {
+ return false;
+ }
+ return mFragmentManager.isStateSaved();
+ }
+
+ /**
+ * Set the initial saved state that this Fragment should restore itself
+ * from when first being constructed, as returned by
+ * {@link FragmentManager#saveFragmentInstanceState(Fragment)
+ * FragmentManager.saveFragmentInstanceState}.
+ *
+ * @param state The state the fragment should be restored from.
+ */
+ public void setInitialSavedState(@Nullable SavedState state) {
+ if (mFragmentManager != null) {
+ throw new IllegalStateException("Fragment already added");
+ }
+ mSavedFragmentState = state != null && state.mState != null
+ ? state.mState : null;
+ }
+
+ /**
+ * Optional target for this fragment. This may be used, for example,
+ * if this fragment is being started by another, and when done wants to
+ * give a result back to the first. The target set here is retained
+ * across instances via {@link FragmentManager#putFragment
+ * FragmentManager.putFragment()}.
+ *
+ * @param fragment The fragment that is the target of this one.
+ * @param requestCode Optional request code, for convenience if you
+ * are going to call back with {@link #onActivityResult(int, int, Intent)}.
+ */
+ @SuppressWarnings("ReferenceEquality")
+ public void setTargetFragment(@Nullable Fragment fragment, int requestCode) {
+ // Don't allow a caller to set a target fragment in another FragmentManager,
+ // but there's a snag: people do set target fragments before fragments get added.
+ // We'll have the FragmentManager check that for validity when we move
+ // the fragments to a valid state.
+ final FragmentManager mine = getFragmentManager();
+ final FragmentManager theirs = fragment != null ? fragment.getFragmentManager() : null;
+ if (mine != null && theirs != null && mine != theirs) {
+ throw new IllegalArgumentException("Fragment " + fragment
+ + " must share the same FragmentManager to be set as a target fragment");
+ }
+
+ // Don't let someone create a cycle.
+ for (Fragment check = fragment; check != null; check = check.getTargetFragment()) {
+ if (check == this) {
+ throw new IllegalArgumentException("Setting " + fragment + " as the target of "
+ + this + " would create a target cycle");
+ }
+ }
+ if (fragment == null) {
+ mTargetWho = null;
+ mTarget = null;
+ } else if (mFragmentManager != null && fragment.mFragmentManager != null) {
+ // Just save the reference to the Fragment
+ mTargetWho = fragment.mWho;
+ mTarget = null;
+ } else {
+ // Save the Fragment itself, waiting until we're attached
+ mTargetWho = null;
+ mTarget = fragment;
+ }
+ mTargetRequestCode = requestCode;
+ }
+
+ /**
+ * Return the target fragment set by {@link #setTargetFragment}.
+ */
+ @Nullable
+ final public Fragment getTargetFragment() {
+ if (mTarget != null) {
+ // Ensure that any Fragment set with setTargetFragment is immediately
+ // available here
+ return mTarget;
+ } else if (mFragmentManager != null && mTargetWho != null) {
+ // Look up the target Fragment from the FragmentManager
+ return mFragmentManager.mActive.get(mTargetWho);
+ }
+ return null;
+ }
+
+ /**
+ * Return the target request code set by {@link #setTargetFragment}.
+ */
+ final public int getTargetRequestCode() {
+ return mTargetRequestCode;
+ }
+
+ /**
+ * Return the {@link Context} this fragment is currently associated with.
+ *
+ * @see #requireContext()
+ */
+ @Nullable
+ public Context getContext() {
+ return mHost == null ? null : mHost.getContext();
+ }
+
+ /**
+ * Return the {@link Context} this fragment is currently associated with.
+ *
+ * @throws IllegalStateException if not currently associated with a context.
+ * @see #getContext()
+ */
+ @NonNull
+ public final Context requireContext() {
+ Context context = getContext();
+ if (context == null) {
+ throw new IllegalStateException("Fragment " + this + " not attached to a context.");
+ }
+ return context;
+ }
+
+ /**
+ * Return the {@link FragmentActivity} this fragment is currently associated with.
+ * May return {@code null} if the fragment is associated with a {@link Context}
+ * instead.
+ *
+ * @see #requireActivity()
+ */
+ @Nullable
+ final public FragmentActivity getActivity() {
+ return mHost == null ? null : (FragmentActivity) mHost.getActivity();
+ }
+
+ /**
+ * Return the {@link FragmentActivity} this fragment is currently associated with.
+ *
+ * @throws IllegalStateException if not currently associated with an activity or if associated
+ * only with a context.
+ * @see #getActivity()
+ */
+ @NonNull
+ public final FragmentActivity requireActivity() {
+ FragmentActivity activity = getActivity();
+ if (activity == null) {
+ throw new IllegalStateException("Fragment " + this + " not attached to an activity.");
+ }
+ return activity;
+ }
+
+ /**
+ * Return the host object of this fragment. May return {@code null} if the fragment
+ * isn't currently being hosted.
+ *
+ * @see #requireHost()
+ */
+ @Nullable
+ final public Object getHost() {
+ return mHost == null ? null : mHost.onGetHost();
+ }
+
+ /**
+ * Return the host object of this fragment.
+ *
+ * @throws IllegalStateException if not currently associated with a host.
+ * @see #getHost()
+ */
+ @NonNull
+ public final Object requireHost() {
+ Object host = getHost();
+ if (host == null) {
+ throw new IllegalStateException("Fragment " + this + " not attached to a host.");
+ }
+ return host;
+ }
+
+ /**
+ * Return requireActivity().getResources().
+ */
+ @NonNull
+ final public Resources getResources() {
+ return requireContext().getResources();
+ }
+
+ /**
+ * Return a localized, styled CharSequence from the application's package's
+ * default string table.
+ *
+ * @param resId Resource id for the CharSequence text
+ */
+ @NonNull
+ public final CharSequence getText(@StringRes int resId) {
+ return getResources().getText(resId);
+ }
+
+ /**
+ * Return a localized string from the application's package's
+ * default string table.
+ *
+ * @param resId Resource id for the string
+ */
+ @NonNull
+ public final String getString(@StringRes int resId) {
+ return getResources().getString(resId);
+ }
+
+ /**
+ * Return a localized formatted string from the application's package's
+ * default string table, substituting the format arguments as defined in
+ * {@link java.util.Formatter} and {@link java.lang.String#format}.
+ *
+ * @param resId Resource id for the format string
+ * @param formatArgs The format arguments that will be used for substitution.
+ */
+ @NonNull
+ public final String getString(@StringRes int resId, @Nullable Object... formatArgs) {
+ return getResources().getString(resId, formatArgs);
+ }
+
+ /**
+ * Return the FragmentManager for interacting with fragments associated
+ * with this fragment's activity. Note that this will be non-null slightly
+ * before {@link #getActivity()}, during the time from when the fragment is
+ * placed in a {@link FragmentTransaction} until it is committed and
+ * attached to its activity.
+ *
+ *
If this Fragment is a child of another Fragment, the FragmentManager
+ * returned here will be the parent's {@link #getChildFragmentManager()}.
+ *
+ * @see #requireFragmentManager()
+ */
+ @Nullable
+ final public FragmentManager getFragmentManager() {
+ return mFragmentManager;
+ }
+
+ /**
+ * Return the FragmentManager for interacting with fragments associated
+ * with this fragment's activity. Note that this will available slightly
+ * before {@link #getActivity()}, during the time from when the fragment is
+ * placed in a {@link FragmentTransaction} until it is committed and
+ * attached to its activity.
+ *
+ *
If this Fragment is a child of another Fragment, the FragmentManager
+ * returned here will be the parent's {@link #getChildFragmentManager()}.
+ *
+ * @throws IllegalStateException if not associated with a transaction or host.
+ * @see #getFragmentManager()
+ */
+ @NonNull
+ public final FragmentManager requireFragmentManager() {
+ FragmentManager fragmentManager = getFragmentManager();
+ if (fragmentManager == null) {
+ throw new IllegalStateException(
+ "Fragment " + this + " not associated with a fragment manager.");
+ }
+ return fragmentManager;
+ }
+
+ /**
+ * Return a private FragmentManager for placing and managing Fragments
+ * inside of this Fragment.
+ */
+ @NonNull
+ final public FragmentManager getChildFragmentManager() {
+ if (mHost == null) {
+ throw new IllegalStateException("Fragment " + this + " has not been attached yet.");
+ }
+ return mChildFragmentManager;
+ }
+
+ /**
+ * Returns the parent Fragment containing this Fragment. If this Fragment
+ * is attached directly to an Activity, returns null.
+ */
+ @Nullable
+ final public Fragment getParentFragment() {
+ return mParentFragment;
+ }
+
+ /**
+ * Returns the parent Fragment containing this Fragment.
+ *
+ * @throws IllegalStateException if this Fragment is attached directly to an Activity or
+ * other Fragment host.
+ * @see #getParentFragment()
+ */
+ @NonNull
+ public final Fragment requireParentFragment() {
+ Fragment parentFragment = getParentFragment();
+ if (parentFragment == null) {
+ Context context = getContext();
+ if (context == null) {
+ throw new IllegalStateException("Fragment " + this + " is not attached to"
+ + " any Fragment or host");
+ } else {
+ throw new IllegalStateException("Fragment " + this + " is not a child Fragment, it"
+ + " is directly attached to " + getContext());
+ }
+ }
+ return parentFragment;
+ }
+
+ /**
+ * Return true if the fragment is currently added to its activity.
+ */
+ final public boolean isAdded() {
+ return mHost != null && mAdded;
+ }
+
+ /**
+ * Return true if the fragment has been explicitly detached from the UI.
+ * That is, {@link FragmentTransaction#detach(Fragment)
+ * FragmentTransaction.detach(Fragment)} has been used on it.
+ */
+ final public boolean isDetached() {
+ return mDetached;
+ }
+
+ /**
+ * Return true if this fragment is currently being removed from its
+ * activity. This is not whether its activity is finishing, but
+ * rather whether it is in the process of being removed from its activity.
+ */
+ final public boolean isRemoving() {
+ return mRemoving;
+ }
+
+ /**
+ * Return true if the layout is included as part of an activity view
+ * hierarchy via the <fragment> tag. This will always be true when
+ * fragments are created through the <fragment> tag, except
+ * in the case where an old fragment is restored from a previous state and
+ * it does not appear in the layout of the current state.
+ */
+ final public boolean isInLayout() {
+ return mInLayout;
+ }
+
+ /**
+ * Return true if the fragment is in the resumed state. This is true
+ * for the duration of {@link #onResume()} and {@link #onPause()} as well.
+ */
+ final public boolean isResumed() {
+ return mState >= RESUMED;
+ }
+
+ /**
+ * Return true if the fragment is currently visible to the user. This means
+ * it: (1) has been added, (2) has its view attached to the window, and
+ * (3) is not hidden.
+ */
+ final public boolean isVisible() {
+ return isAdded() && !isHidden() && mView != null
+ && mView.getWindowToken() != null && mView.getVisibility() == View.VISIBLE;
+ }
+
+ /**
+ * Return true if the fragment has been hidden. By default fragments
+ * are shown. You can find out about changes to this state with
+ * {@link #onHiddenChanged}. Note that the hidden state is orthogonal
+ * to other states -- that is, to be visible to the user, a fragment
+ * must be both started and not hidden.
+ */
+ final public boolean isHidden() {
+ return mHidden;
+ }
+
+ /** @hide */
+ @RestrictTo(LIBRARY_GROUP_PREFIX)
+ final public boolean hasOptionsMenu() {
+ return mHasMenu;
+ }
+
+ /** @hide */
+ @RestrictTo(LIBRARY_GROUP_PREFIX)
+ final public boolean isMenuVisible() {
+ return mMenuVisible;
+ }
+
+ /**
+ * Called when the hidden state (as returned by {@link #isHidden()} of
+ * the fragment has changed. Fragments start out not hidden; this will
+ * be called whenever the fragment changes state from that.
+ * @param hidden True if the fragment is now hidden, false otherwise.
+ */
+ public void onHiddenChanged(boolean hidden) {
+ }
+
+ /**
+ * Control whether a fragment instance is retained across Activity
+ * re-creation (such as from a configuration change). If set, the fragment
+ * lifecycle will be slightly different when an activity is recreated:
+ *
+ *
{@link #onDestroy()} will not be called (but {@link #onDetach()} still
+ * will be, because the fragment is being detached from its current activity).
+ *
{@link #onCreate(Bundle)} will not be called since the fragment
+ * is not being re-created.
+ *
{@link #onAttach(Activity)} and {@link #onActivityCreated(Bundle)} will
+ * still be called.
+ *
+ */
+ public void setRetainInstance(boolean retain) {
+ mRetainInstance = retain;
+ if (mFragmentManager != null) {
+ if (retain) {
+ mFragmentManager.addRetainedFragment(this);
+ } else {
+ mFragmentManager.removeRetainedFragment(this);
+ }
+ } else {
+ mRetainInstanceChangedWhileDetached = true;
+ }
+ }
+
+ final public boolean getRetainInstance() {
+ return mRetainInstance;
+ }
+
+ /**
+ * Report that this fragment would like to participate in populating
+ * the options menu by receiving a call to {@link #onCreateOptionsMenu}
+ * and related methods.
+ *
+ * @param hasMenu If true, the fragment has menu items to contribute.
+ */
+ public void setHasOptionsMenu(boolean hasMenu) {
+ if (mHasMenu != hasMenu) {
+ mHasMenu = hasMenu;
+ if (isAdded() && !isHidden()) {
+ mHost.onSupportInvalidateOptionsMenu();
+ }
+ }
+ }
+
+ /**
+ * Set a hint for whether this fragment's menu should be visible. This
+ * is useful if you know that a fragment has been placed in your view
+ * hierarchy so that the user can not currently seen it, so any menu items
+ * it has should also not be shown.
+ *
+ * @param menuVisible The default is true, meaning the fragment's menu will
+ * be shown as usual. If false, the user will not see the menu.
+ */
+ public void setMenuVisibility(boolean menuVisible) {
+ if (mMenuVisible != menuVisible) {
+ mMenuVisible = menuVisible;
+ if (mHasMenu && isAdded() && !isHidden()) {
+ mHost.onSupportInvalidateOptionsMenu();
+ }
+ }
+ }
+
+ /**
+ * Set a hint to the system about whether this fragment's UI is currently visible
+ * to the user. This hint defaults to true and is persistent across fragment instance
+ * state save and restore.
+ *
+ *
An app may set this to false to indicate that the fragment's UI is
+ * scrolled out of visibility or is otherwise not directly visible to the user.
+ * This may be used by the system to prioritize operations such as fragment lifecycle updates
+ * or loader ordering behavior.
+ *
+ *
Note: This method may be called outside of the fragment lifecycle.
+ * and thus has no ordering guarantees with regard to fragment lifecycle method calls.
+ *
+ * @param isVisibleToUser true if this fragment's UI is currently visible to the user (default),
+ * false if it is not.
+ *
+ * @deprecated Use {@link FragmentTransaction#setMaxLifecycle(Fragment, Lifecycle.State)}
+ * instead.
+ */
+ @Deprecated
+ public void setUserVisibleHint(boolean isVisibleToUser) {
+ if (!mUserVisibleHint && isVisibleToUser && mState < STARTED
+ && mFragmentManager != null && isAdded() && mIsCreated) {
+ mFragmentManager.performPendingDeferredStart(this);
+ }
+ mUserVisibleHint = isVisibleToUser;
+ mDeferStart = mState < STARTED && !isVisibleToUser;
+ if (mSavedFragmentState != null) {
+ // Ensure that if the user visible hint is set before the Fragment has
+ // restored its state that we don't lose the new value
+ mSavedUserVisibleHint = isVisibleToUser;
+ }
+ }
+
+ /**
+ * @return The current value of the user-visible hint on this fragment.
+ * @see #setUserVisibleHint(boolean)
+ *
+ * @deprecated Use {@link FragmentTransaction#setMaxLifecycle(Fragment, Lifecycle.State)}
+ * instead.
+ */
+ @Deprecated
+ public boolean getUserVisibleHint() {
+ return mUserVisibleHint;
+ }
+
+ /**
+ * Return the LoaderManager for this fragment.
+ *
+ * @deprecated Use
+ * {@link LoaderManager#getInstance(LifecycleOwner) LoaderManager.getInstance(this)}.
+ */
+ @Deprecated
+ @NonNull
+ public LoaderManager getLoaderManager() {
+ return LoaderManager.getInstance(this);
+ }
+
+ /**
+ * Call {@link Activity#startActivity(Intent)} from the fragment's
+ * containing Activity.
+ */
+ public void startActivity(@SuppressLint("UnknownNullness") Intent intent) {
+ startActivity(intent, null);
+ }
+
+ /**
+ * Call {@link Activity#startActivity(Intent, Bundle)} from the fragment's
+ * containing Activity.
+ */
+ public void startActivity(@SuppressLint("UnknownNullness") Intent intent,
+ @Nullable Bundle options) {
+ if (mHost == null) {
+ throw new IllegalStateException("Fragment " + this + " not attached to Activity");
+ }
+ mHost.onStartActivityFromFragment(this /*fragment*/, intent, -1, options);
+ }
+
+ /**
+ * Call {@link Activity#startActivityForResult(Intent, int)} from the fragment's
+ * containing Activity.
+ */
+ public void startActivityForResult(@SuppressLint("UnknownNullness") Intent intent,
+ int requestCode) {
+ startActivityForResult(intent, requestCode, null);
+ }
+
+ /**
+ * Call {@link Activity#startActivityForResult(Intent, int, Bundle)} from the fragment's
+ * containing Activity.
+ */
+ public void startActivityForResult(@SuppressLint("UnknownNullness") Intent intent,
+ int requestCode, @Nullable Bundle options) {
+ if (mHost == null) {
+ throw new IllegalStateException("Fragment " + this + " not attached to Activity");
+ }
+ mHost.onStartActivityFromFragment(this /*fragment*/, intent, requestCode, options);
+ }
+
+ /**
+ * Call {@link Activity#startIntentSenderForResult(IntentSender, int, Intent, int, int, int,
+ * Bundle)} from the fragment's containing Activity.
+ */
+ public void startIntentSenderForResult(@SuppressLint("UnknownNullness") IntentSender intent,
+ int requestCode, @Nullable Intent fillInIntent, int flagsMask, int flagsValues,
+ int extraFlags, @Nullable Bundle options) throws IntentSender.SendIntentException {
+ if (mHost == null) {
+ throw new IllegalStateException("Fragment " + this + " not attached to Activity");
+ }
+ mHost.onStartIntentSenderFromFragment(this, intent, requestCode, fillInIntent, flagsMask,
+ flagsValues, extraFlags, options);
+ }
+
+ /**
+ * Receive the result from a previous call to
+ * {@link #startActivityForResult(Intent, int)}. This follows the
+ * related Activity API as described there in
+ * {@link Activity#onActivityResult(int, int, Intent)}.
+ *
+ * @param requestCode The integer request code originally supplied to
+ * startActivityForResult(), allowing you to identify who this
+ * result came from.
+ * @param resultCode The integer result code returned by the child activity
+ * through its setResult().
+ * @param data An Intent, which can return result data to the caller
+ * (various data can be attached to Intent "extras").
+ */
+ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ }
+
+ /**
+ * Requests permissions to be granted to this application. These permissions
+ * must be requested in your manifest, they should not be granted to your app,
+ * and they should have protection level {@link android.content.pm.PermissionInfo
+ * #PROTECTION_DANGEROUS dangerous}, regardless whether they are declared by
+ * the platform or a third-party app.
+ *
+ * Normal permissions {@link android.content.pm.PermissionInfo#PROTECTION_NORMAL}
+ * are granted at install time if requested in the manifest. Signature permissions
+ * {@link android.content.pm.PermissionInfo#PROTECTION_SIGNATURE} are granted at
+ * install time if requested in the manifest and the signature of your app matches
+ * the signature of the app declaring the permissions.
+ *
+ *
+ * If your app does not have the requested permissions the user will be presented
+ * with UI for accepting them. After the user has accepted or rejected the
+ * requested permissions you will receive a callback on {@link
+ * #onRequestPermissionsResult(int, String[], int[])} reporting whether the
+ * permissions were granted or not.
+ *
+ *
+ * Note that requesting a permission does not guarantee it will be granted and
+ * your app should be able to run without having this permission.
+ *
+ *
+ * This method may start an activity allowing the user to choose which permissions
+ * to grant and which to reject. Hence, you should be prepared that your activity
+ * may be paused and resumed. Further, granting some permissions may require
+ * a restart of you application. In such a case, the system will recreate the
+ * activity stack before delivering the result to {@link
+ * #onRequestPermissionsResult(int, String[], int[])}.
+ *
+ *
+ * When checking whether you have a permission you should use {@link
+ * android.content.Context#checkSelfPermission(String)}.
+ *
+ *
+ * Calling this API for permissions already granted to your app would show UI
+ * to the user to decided whether the app can still hold these permissions. This
+ * can be useful if the way your app uses the data guarded by the permissions
+ * changes significantly.
+ *
+ *
+ * A sample permissions request looks like this:
+ *
+ *
+ * @param permissions The requested permissions.
+ * @param requestCode Application specific request code to match with a result
+ * reported to {@link #onRequestPermissionsResult(int, String[], int[])}.
+ *
+ * @see #onRequestPermissionsResult(int, String[], int[])
+ * @see android.content.Context#checkSelfPermission(String)
+ */
+ public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
+ if (mHost == null) {
+ throw new IllegalStateException("Fragment " + this + " not attached to Activity");
+ }
+ mHost.onRequestPermissionsFromFragment(this, permissions, requestCode);
+ }
+
+ /**
+ * Callback for the result from requesting permissions. This method
+ * is invoked for every call on {@link #requestPermissions(String[], int)}.
+ *
+ * Note: It is possible that the permissions request interaction
+ * with the user is interrupted. In this case you will receive empty permissions
+ * and results arrays which should be treated as a cancellation.
+ *
+ *
+ * @param requestCode The request code passed in {@link #requestPermissions(String[], int)}.
+ * @param permissions The requested permissions. Never null.
+ * @param grantResults The grant results for the corresponding permissions
+ * which is either {@link android.content.pm.PackageManager#PERMISSION_GRANTED}
+ * or {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null.
+ *
+ * @see #requestPermissions(String[], int)
+ */
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+ @NonNull int[] grantResults) {
+ /* callback - do nothing */
+ }
+
+ /**
+ * Gets whether you should show UI with rationale for requesting a permission.
+ * You should do this only if you do not have the permission and the context in
+ * which the permission is requested does not clearly communicate to the user
+ * what would be the benefit from granting this permission.
+ *
+ * For example, if you write a camera app, requesting the camera permission
+ * would be expected by the user and no rationale for why it is requested is
+ * needed. If however, the app needs location for tagging photos then a non-tech
+ * savvy user may wonder how location is related to taking photos. In this case
+ * you may choose to show UI with rationale of requesting this permission.
+ *
+ *
+ * @param permission A permission your app wants to request.
+ * @return Whether you can show permission rationale UI.
+ *
+ * @see Context#checkSelfPermission(String)
+ * @see #requestPermissions(String[], int)
+ * @see #onRequestPermissionsResult(int, String[], int[])
+ */
+ public boolean shouldShowRequestPermissionRationale(@NonNull String permission) {
+ if (mHost != null) {
+ return mHost.onShouldShowRequestPermissionRationale(permission);
+ }
+ return false;
+ }
+
+ /**
+ * Returns the LayoutInflater used to inflate Views of this Fragment. The default
+ * implementation will throw an exception if the Fragment is not attached.
+ *
+ * @param savedInstanceState If the fragment is being re-created from
+ * a previous saved state, this is the state.
+ * @return The LayoutInflater used to inflate Views of this Fragment.
+ */
+ @NonNull
+ public LayoutInflater onGetLayoutInflater(@Nullable Bundle savedInstanceState) {
+ // TODO: move the implementation in getLayoutInflater to here
+ return getLayoutInflater(savedInstanceState);
+ }
+
+ /**
+ * Returns the cached LayoutInflater used to inflate Views of this Fragment. If
+ * {@link #onGetLayoutInflater(Bundle)} has not been called {@link #onGetLayoutInflater(Bundle)}
+ * will be called with a {@code null} argument and that value will be cached.
+ *
+ * The cached LayoutInflater will be replaced immediately prior to
+ * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} and cleared immediately after
+ * {@link #onDetach()}.
+ *
+ * @return The LayoutInflater used to inflate Views of this Fragment.
+ */
+ @NonNull
+ public final LayoutInflater getLayoutInflater() {
+ if (mLayoutInflater == null) {
+ return performGetLayoutInflater(null);
+ }
+ return mLayoutInflater;
+ }
+
+ /**
+ * Calls {@link #onGetLayoutInflater(Bundle)} and caches the result for use by
+ * {@link #getLayoutInflater()}.
+ *
+ * @param savedInstanceState If the fragment is being re-created from
+ * a previous saved state, this is the state.
+ * @return The LayoutInflater used to inflate Views of this Fragment.
+ */
+ @NonNull
+ LayoutInflater performGetLayoutInflater(@Nullable Bundle savedInstanceState) {
+ LayoutInflater layoutInflater = onGetLayoutInflater(savedInstanceState);
+ mLayoutInflater = layoutInflater;
+ return mLayoutInflater;
+ }
+
+ /**
+ * Override {@link #onGetLayoutInflater(Bundle)} when you need to change the
+ * LayoutInflater or call {@link #getLayoutInflater()} when you want to
+ * retrieve the current LayoutInflater.
+ *
+ * @hide
+ * @deprecated Override {@link #onGetLayoutInflater(Bundle)} or call
+ * {@link #getLayoutInflater()} instead of this method.
+ */
+ @Deprecated
+ @NonNull
+ @RestrictTo(LIBRARY_GROUP_PREFIX)
+ public LayoutInflater getLayoutInflater(@Nullable Bundle savedFragmentState) {
+ if (mHost == null) {
+ throw new IllegalStateException("onGetLayoutInflater() cannot be executed until the "
+ + "Fragment is attached to the FragmentManager.");
+ }
+ LayoutInflater result = mHost.onGetLayoutInflater();
+ LayoutInflaterCompat.setFactory2(result, mChildFragmentManager.getLayoutInflaterFactory());
+ return result;
+ }
+
+ /**
+ * Called when a fragment is being created as part of a view layout
+ * inflation, typically from setting the content view of an activity. This
+ * may be called immediately after the fragment is created from a
+ * tag in a layout file. Note this is before the fragment's
+ * {@link #onAttach(Activity)} has been called; all you should do here is
+ * parse the attributes and save them away.
+ *
+ *
This is called every time the fragment is inflated, even if it is
+ * being inflated into a new instance with saved state. It typically makes
+ * sense to re-parse the parameters each time, to allow them to change with
+ * different configurations.
+ *
+ *
Here is a typical implementation of a fragment that can take parameters
+ * both through attributes supplied here as well from {@link #getArguments()}:
This fragment can also be created dynamically from arguments given
+ * at runtime in the arguments Bundle; here is an example of doing so at
+ * creation of the containing activity:
+ *
+ * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentArgumentsSupport.java
+ * create}
+ *
+ * @param context The Activity that is inflating this fragment.
+ * @param attrs The attributes at the tag where the fragment is
+ * being created.
+ * @param savedInstanceState If the fragment is being re-created from
+ * a previous saved state, this is the state.
+ */
+ @CallSuper
+ public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs,
+ @Nullable Bundle savedInstanceState) {
+ mCalled = true;
+ final Activity hostActivity = mHost == null ? null : mHost.getActivity();
+ if (hostActivity != null) {
+ mCalled = false;
+ onInflate(hostActivity, attrs, savedInstanceState);
+ }
+ }
+
+ /**
+ * Called when a fragment is being created as part of a view layout
+ * inflation, typically from setting the content view of an activity.
+ *
+ * @deprecated See {@link #onInflate(Context, AttributeSet, Bundle)}.
+ */
+ @Deprecated
+ @CallSuper
+ public void onInflate(@NonNull Activity activity, @NonNull AttributeSet attrs,
+ @Nullable Bundle savedInstanceState) {
+ mCalled = true;
+ }
+
+ /**
+ * Called when a fragment is attached as a child of this fragment.
+ *
+ *
This is called after the attached fragment's onAttach and before
+ * the attached fragment's onCreate if the fragment has not yet had a previous
+ * call to onCreate.
+ *
+ * @param childFragment child fragment being attached
+ */
+ public void onAttachFragment(@NonNull Fragment childFragment) {
+ }
+
+ /**
+ * Called when a fragment is first attached to its context.
+ * {@link #onCreate(Bundle)} will be called after this.
+ */
+ @CallSuper
+ public void onAttach(@NonNull Context context) {
+ mCalled = true;
+ final Activity hostActivity = mHost == null ? null : mHost.getActivity();
+ if (hostActivity != null) {
+ mCalled = false;
+ onAttach(hostActivity);
+ }
+ }
+
+ /**
+ * Called when a fragment is first attached to its activity.
+ * {@link #onCreate(Bundle)} will be called after this.
+ *
+ * @deprecated See {@link #onAttach(Context)}.
+ */
+ @Deprecated
+ @CallSuper
+ public void onAttach(@NonNull Activity activity) {
+ mCalled = true;
+ }
+
+ /**
+ * Called when a fragment loads an animation. Note that if
+ * {@link FragmentTransaction#setCustomAnimations(int, int)} was called with
+ * {@link Animator} resources instead of {@link Animation} resources, {@code nextAnim}
+ * will be an animator resource.
+ *
+ * @param transit The value set in {@link FragmentTransaction#setTransition(int)} or 0 if not
+ * set.
+ * @param enter {@code true} when the fragment is added/attached/shown or {@code false} when
+ * the fragment is removed/detached/hidden.
+ * @param nextAnim The resource set in
+ * {@link FragmentTransaction#setCustomAnimations(int, int)},
+ * {@link FragmentTransaction#setCustomAnimations(int, int, int, int)}, or
+ * 0 if neither was called. The value will depend on the current operation.
+ */
+ @Nullable
+ public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
+ return null;
+ }
+
+ /**
+ * Called when a fragment loads an animator. This will be called when
+ * {@link #onCreateAnimation(int, boolean, int)} returns null. Note that if
+ * {@link FragmentTransaction#setCustomAnimations(int, int)} was called with
+ * {@link Animation} resources instead of {@link Animator} resources, {@code nextAnim}
+ * will be an animation resource.
+ *
+ * @param transit The value set in {@link FragmentTransaction#setTransition(int)} or 0 if not
+ * set.
+ * @param enter {@code true} when the fragment is added/attached/shown or {@code false} when
+ * the fragment is removed/detached/hidden.
+ * @param nextAnim The resource set in
+ * {@link FragmentTransaction#setCustomAnimations(int, int)},
+ * {@link FragmentTransaction#setCustomAnimations(int, int, int, int)}, or
+ * 0 if neither was called. The value will depend on the current operation.
+ */
+ @Nullable
+ public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
+ return null;
+ }
+
+ /**
+ * Called to do initial creation of a fragment. This is called after
+ * {@link #onAttach(Activity)} and before
+ * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
+ *
+ *
Note that this can be called while the fragment's activity is
+ * still in the process of being created. As such, you can not rely
+ * on things like the activity's content view hierarchy being initialized
+ * at this point. If you want to do work once the activity itself is
+ * created, see {@link #onActivityCreated(Bundle)}.
+ *
+ *
Any restored child fragments will be created before the base
+ * Fragment.onCreate method returns.
+ *
+ * @param savedInstanceState If the fragment is being re-created from
+ * a previous saved state, this is the state.
+ */
+ @CallSuper
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ mCalled = true;
+ restoreChildFragmentState(savedInstanceState);
+ if (!mChildFragmentManager.isStateAtLeast(Fragment.CREATED)) {
+ mChildFragmentManager.dispatchCreate();
+ }
+ }
+
+ /**
+ * Restore the state of the child FragmentManager. Called by either
+ * {@link #onCreate(Bundle)} for non-retained instance fragments or by
+ * {@link FragmentManagerImpl#moveToState(Fragment, int, int, int, boolean)}
+ * for retained instance fragments.
+ *
+ *
Postcondition: if there were child fragments to restore,
+ * the child FragmentManager will be instantiated and brought to the {@link #CREATED} state.
+ *
+ *
+ * @param savedInstanceState the savedInstanceState potentially containing fragment info
+ */
+ void restoreChildFragmentState(@Nullable Bundle savedInstanceState) {
+ if (savedInstanceState != null) {
+ Parcelable p = savedInstanceState.getParcelable(
+ FragmentActivity.FRAGMENTS_TAG);
+ if (p != null) {
+ mChildFragmentManager.restoreSaveState(p);
+ mChildFragmentManager.dispatchCreate();
+ }
+ }
+ }
+
+ /**
+ * Called to have the fragment instantiate its user interface view.
+ * This is optional, and non-graphical fragments can return null. This will be called between
+ * {@link #onCreate(Bundle)} and {@link #onActivityCreated(Bundle)}.
+ *
A default View can be returned by calling {@link #Fragment(int)} in your
+ * constructor. Otherwise, this method returns null.
+ *
+ *
It is recommended to only inflate the layout in this method and move
+ * logic that operates on the returned View to {@link #onViewCreated(View, Bundle)}.
+ *
+ *
If you return a View from here, you will later be called in
+ * {@link #onDestroyView} when the view is being released.
+ *
+ * @param inflater The LayoutInflater object that can be used to inflate
+ * any views in the fragment,
+ * @param container If non-null, this is the parent view that the fragment's
+ * UI should be attached to. The fragment should not add the view itself,
+ * but this can be used to generate the LayoutParams of the view.
+ * @param savedInstanceState If non-null, this fragment is being re-constructed
+ * from a previous saved state as given here.
+ *
+ * @return Return the View for the fragment's UI, or null.
+ */
+ @Nullable
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ if (mContentLayoutId != 0) {
+ return inflater.inflate(mContentLayoutId, container, false);
+ }
+ return null;
+ }
+
+ /**
+ * Called immediately after {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}
+ * has returned, but before any saved state has been restored in to the view.
+ * This gives subclasses a chance to initialize themselves once
+ * they know their view hierarchy has been completely created. The fragment's
+ * view hierarchy is not however attached to its parent at this point.
+ * @param view The View returned by {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
+ * @param savedInstanceState If non-null, this fragment is being re-constructed
+ * from a previous saved state as given here.
+ */
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ }
+
+ /**
+ * Get the root view for the fragment's layout (the one returned by {@link #onCreateView}),
+ * if provided.
+ *
+ * @return The fragment's root view, or null if it has no layout.
+ */
+ @Nullable
+ public View getView() {
+ return mView;
+ }
+
+ /**
+ * Get the root view for the fragment's layout (the one returned by {@link #onCreateView}).
+ *
+ * @throws IllegalStateException if no view was returned by {@link #onCreateView}.
+ * @see #getView()
+ */
+ @NonNull
+ public final View requireView() {
+ View view = getView();
+ if (view == null) {
+ throw new IllegalStateException("Fragment " + this + " did not return a View from"
+ + " onCreateView() or this was called before onCreateView().");
+ }
+ return view;
+ }
+
+ /**
+ * Called when the fragment's activity has been created and this
+ * fragment's view hierarchy instantiated. It can be used to do final
+ * initialization once these pieces are in place, such as retrieving
+ * views or restoring state. It is also useful for fragments that use
+ * {@link #setRetainInstance(boolean)} to retain their instance,
+ * as this callback tells the fragment when it is fully associated with
+ * the new activity instance. This is called after {@link #onCreateView}
+ * and before {@link #onViewStateRestored(Bundle)}.
+ *
+ * @param savedInstanceState If the fragment is being re-created from
+ * a previous saved state, this is the state.
+ */
+ @CallSuper
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ mCalled = true;
+ }
+
+ /**
+ * Called when all saved state has been restored into the view hierarchy
+ * of the fragment. This can be used to do initialization based on saved
+ * state that you are letting the view hierarchy track itself, such as
+ * whether check box widgets are currently checked. This is called
+ * after {@link #onActivityCreated(Bundle)} and before
+ * {@link #onStart()}.
+ *
+ * @param savedInstanceState If the fragment is being re-created from
+ * a previous saved state, this is the state.
+ */
+ @CallSuper
+ public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
+ mCalled = true;
+ }
+
+ /**
+ * Called when the Fragment is visible to the user. This is generally
+ * tied to {@link Activity#onStart() Activity.onStart} of the containing
+ * Activity's lifecycle.
+ */
+ @CallSuper
+ public void onStart() {
+ mCalled = true;
+ }
+
+ /**
+ * Called when the fragment is visible to the user and actively running.
+ * This is generally
+ * tied to {@link Activity#onResume() Activity.onResume} of the containing
+ * Activity's lifecycle.
+ */
+ @CallSuper
+ public void onResume() {
+ mCalled = true;
+ }
+
+ /**
+ * Called to ask the fragment to save its current dynamic state, so it
+ * can later be reconstructed in a new instance of its process is
+ * restarted. If a new instance of the fragment later needs to be
+ * created, the data you place in the Bundle here will be available
+ * in the Bundle given to {@link #onCreate(Bundle)},
+ * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}, and
+ * {@link #onActivityCreated(Bundle)}.
+ *
+ *
This corresponds to {@link Activity#onSaveInstanceState(Bundle)
+ * Activity.onSaveInstanceState(Bundle)} and most of the discussion there
+ * applies here as well. Note however: this method may be called
+ * at any time before {@link #onDestroy()}. There are many situations
+ * where a fragment may be mostly torn down (such as when placed on the
+ * back stack with no UI showing), but its state will not be saved until
+ * its owning activity actually needs to save its state.
+ *
+ * @param outState Bundle in which to place your saved state.
+ */
+ public void onSaveInstanceState(@NonNull Bundle outState) {
+ }
+
+ /**
+ * Called when the Fragment's activity changes from fullscreen mode to multi-window mode and
+ * visa-versa. This is generally tied to {@link Activity#onMultiWindowModeChanged} of the
+ * containing Activity.
+ *
+ * @param isInMultiWindowMode True if the activity is in multi-window mode.
+ */
+ public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
+ }
+
+ /**
+ * Called by the system when the activity changes to and from picture-in-picture mode. This is
+ * generally tied to {@link Activity#onPictureInPictureModeChanged} of the containing Activity.
+ *
+ * @param isInPictureInPictureMode True if the activity is in picture-in-picture mode.
+ */
+ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ }
+
+ @Override
+ @CallSuper
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ mCalled = true;
+ }
+
+ /**
+ * Callback for when the primary navigation state of this Fragment has changed. This can be
+ * the result of the {@link #getFragmentManager() containing FragmentManager} having its
+ * primary navigation fragment changed via
+ * {@link androidx.fragment.app.FragmentTransaction#setPrimaryNavigationFragment} or due to
+ * the primary navigation fragment changing in a parent FragmentManager.
+ *
+ * @param isPrimaryNavigationFragment True if and only if this Fragment and any
+ * {@link #getParentFragment() parent fragment} is set as the primary navigation fragment
+ * via {@link androidx.fragment.app.FragmentTransaction#setPrimaryNavigationFragment}.
+ */
+ public void onPrimaryNavigationFragmentChanged(boolean isPrimaryNavigationFragment) {
+ }
+
+ /**
+ * Called when the Fragment is no longer resumed. This is generally
+ * tied to {@link Activity#onPause() Activity.onPause} of the containing
+ * Activity's lifecycle.
+ */
+ @CallSuper
+ public void onPause() {
+ mCalled = true;
+ }
+
+ /**
+ * Called when the Fragment is no longer started. This is generally
+ * tied to {@link Activity#onStop() Activity.onStop} of the containing
+ * Activity's lifecycle.
+ */
+ @CallSuper
+ public void onStop() {
+ mCalled = true;
+ }
+
+ @Override
+ @CallSuper
+ public void onLowMemory() {
+ mCalled = true;
+ }
+
+ /**
+ * Called when the view previously created by {@link #onCreateView} has
+ * been detached from the fragment. The next time the fragment needs
+ * to be displayed, a new view will be created. This is called
+ * after {@link #onStop()} and before {@link #onDestroy()}. It is called
+ * regardless of whether {@link #onCreateView} returned a
+ * non-null view. Internally it is called after the view's state has
+ * been saved but before it has been removed from its parent.
+ */
+ @CallSuper
+ public void onDestroyView() {
+ mCalled = true;
+ }
+
+ /**
+ * Called when the fragment is no longer in use. This is called
+ * after {@link #onStop()} and before {@link #onDetach()}.
+ */
+ @CallSuper
+ public void onDestroy() {
+ mCalled = true;
+ }
+
+ /**
+ * Called by the fragment manager once this fragment has been removed,
+ * so that we don't have any left-over state if the application decides
+ * to re-use the instance. This only clears state that the framework
+ * internally manages, not things the application sets.
+ */
+ void initState() {
+ initLifecycle();
+ mWho = UUID.randomUUID().toString();
+ mAdded = false;
+ mRemoving = false;
+ mFromLayout = false;
+ mInLayout = false;
+ mRestored = false;
+ mBackStackNesting = 0;
+ mFragmentManager = null;
+ mChildFragmentManager = new FragmentManagerImpl();
+ mHost = null;
+ mFragmentId = 0;
+ mContainerId = 0;
+ mTag = null;
+ mHidden = false;
+ mDetached = false;
+ }
+
+ /**
+ * Called when the fragment is no longer attached to its activity. This
+ * is called after {@link #onDestroy()}.
+ */
+ @CallSuper
+ public void onDetach() {
+ mCalled = true;
+ }
+
+ /**
+ * Initialize the contents of the Fragment host's standard options menu. You
+ * should place your menu items in to menu. For this method
+ * to be called, you must have first called {@link #setHasOptionsMenu}. See
+ * {@link Activity#onCreateOptionsMenu(Menu) Activity.onCreateOptionsMenu}
+ * for more information.
+ *
+ * @param menu The options menu in which you place your items.
+ *
+ * @see #setHasOptionsMenu
+ * @see #onPrepareOptionsMenu
+ * @see #onOptionsItemSelected
+ */
+ public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
+ }
+
+ /**
+ * Prepare the Fragment host's standard options menu to be displayed. This is
+ * called right before the menu is shown, every time it is shown. You can
+ * use this method to efficiently enable/disable items or otherwise
+ * dynamically modify the contents. See
+ * {@link Activity#onPrepareOptionsMenu(Menu) Activity.onPrepareOptionsMenu}
+ * for more information.
+ *
+ * @param menu The options menu as last shown or first initialized by
+ * onCreateOptionsMenu().
+ *
+ * @see #setHasOptionsMenu
+ * @see #onCreateOptionsMenu
+ */
+ public void onPrepareOptionsMenu(@NonNull Menu menu) {
+ }
+
+ /**
+ * Called when this fragment's option menu items are no longer being
+ * included in the overall options menu. Receiving this call means that
+ * the menu needed to be rebuilt, but this fragment's items were not
+ * included in the newly built menu (its {@link #onCreateOptionsMenu(Menu, MenuInflater)}
+ * was not called).
+ */
+ public void onDestroyOptionsMenu() {
+ }
+
+ /**
+ * This hook is called whenever an item in your options menu is selected.
+ * The default implementation simply returns false to have the normal
+ * processing happen (calling the item's Runnable or sending a message to
+ * its Handler as appropriate). You can use this method for any items
+ * for which you would like to do processing without those other
+ * facilities.
+ *
+ *
Derived classes should call through to the base class for it to
+ * perform the default menu handling.
+ *
+ * @param item The menu item that was selected.
+ *
+ * @return boolean Return false to allow normal menu processing to
+ * proceed, true to consume it here.
+ *
+ * @see #onCreateOptionsMenu
+ */
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ return false;
+ }
+
+ /**
+ * This hook is called whenever the options menu is being closed (either by the user canceling
+ * the menu with the back/menu button, or when an item is selected).
+ *
+ * @param menu The options menu as last shown or first initialized by
+ * onCreateOptionsMenu().
+ */
+ public void onOptionsMenuClosed(@NonNull Menu menu) {
+ }
+
+ /**
+ * Called when a context menu for the {@code view} is about to be shown.
+ * Unlike {@link #onCreateOptionsMenu}, this will be called every
+ * time the context menu is about to be shown and should be populated for
+ * the view (or item inside the view for {@link AdapterView} subclasses,
+ * this can be found in the {@code menuInfo})).
+ *
+ * Use {@link #onContextItemSelected(android.view.MenuItem)} to know when an
+ * item has been selected.
+ *
+ * The default implementation calls up to
+ * {@link Activity#onCreateContextMenu Activity.onCreateContextMenu}, though
+ * you can not call this implementation if you don't want that behavior.
+ *
+ * It is not safe to hold onto the context menu after this method returns.
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreateContextMenu(@NonNull ContextMenu menu, @NonNull View v,
+ @Nullable ContextMenuInfo menuInfo) {
+ requireActivity().onCreateContextMenu(menu, v, menuInfo);
+ }
+
+ /**
+ * Registers a context menu to be shown for the given view (multiple views
+ * can show the context menu). This method will set the
+ * {@link OnCreateContextMenuListener} on the view to this fragment, so
+ * {@link #onCreateContextMenu(ContextMenu, View, ContextMenuInfo)} will be
+ * called when it is time to show the context menu.
+ *
+ * @see #unregisterForContextMenu(View)
+ * @param view The view that should show a context menu.
+ */
+ public void registerForContextMenu(@NonNull View view) {
+ view.setOnCreateContextMenuListener(this);
+ }
+
+ /**
+ * Prevents a context menu to be shown for the given view. This method will
+ * remove the {@link OnCreateContextMenuListener} on the view.
+ *
+ * @see #registerForContextMenu(View)
+ * @param view The view that should stop showing a context menu.
+ */
+ public void unregisterForContextMenu(@NonNull View view) {
+ view.setOnCreateContextMenuListener(null);
+ }
+
+ /**
+ * This hook is called whenever an item in a context menu is selected. The
+ * default implementation simply returns false to have the normal processing
+ * happen (calling the item's Runnable or sending a message to its Handler
+ * as appropriate). You can use this method for any items for which you
+ * would like to do processing without those other facilities.
+ *
+ * Use {@link MenuItem#getMenuInfo()} to get extra information set by the
+ * View that added this menu item.
+ *
+ * Derived classes should call through to the base class for it to perform
+ * the default menu handling.
+ *
+ * @param item The context menu item that was selected.
+ * @return boolean Return false to allow normal context menu processing to
+ * proceed, true to consume it here.
+ */
+ public boolean onContextItemSelected(@NonNull MenuItem item) {
+ return false;
+ }
+
+ /**
+ * When custom transitions are used with Fragments, the enter transition callback
+ * is called when this Fragment is attached or detached when not popping the back stack.
+ *
+ * @param callback Used to manipulate the shared element transitions on this Fragment
+ * when added not as a pop from the back stack.
+ */
+ public void setEnterSharedElementCallback(@Nullable SharedElementCallback callback) {
+ ensureAnimationInfo().mEnterTransitionCallback = callback;
+ }
+
+ /**
+ * When custom transitions are used with Fragments, the exit transition callback
+ * is called when this Fragment is attached or detached when popping the back stack.
+ *
+ * @param callback Used to manipulate the shared element transitions on this Fragment
+ * when added as a pop from the back stack.
+ */
+ public void setExitSharedElementCallback(@Nullable SharedElementCallback callback) {
+ ensureAnimationInfo().mExitTransitionCallback = callback;
+ }
+
+ /**
+ * Sets the Transition that will be used to move Views into the initial scene. The entering
+ * Views will be those that are regular Views or ViewGroups that have
+ * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as entering is governed by changing visibility from
+ * {@link View#INVISIBLE} to {@link View#VISIBLE}. If transition is null,
+ * entering Views will remain unaffected.
+ *
+ * @param transition The Transition to use to move Views into the initial Scene.
+ * transition must be an
+ * {@link android.transition.Transition} or
+ * {@link androidx.transition.Transition}.
+ */
+ public void setEnterTransition(@Nullable Object transition) {
+ ensureAnimationInfo().mEnterTransition = transition;
+ }
+
+ /**
+ * Returns the Transition that will be used to move Views into the initial scene. The entering
+ * Views will be those that are regular Views or ViewGroups that have
+ * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as entering is governed by changing visibility from
+ * {@link View#INVISIBLE} to {@link View#VISIBLE}.
+ *
+ * @return the Transition to use to move Views into the initial Scene.
+ */
+ @Nullable
+ public Object getEnterTransition() {
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mEnterTransition;
+ }
+
+ /**
+ * Sets the Transition that will be used to move Views out of the scene when the Fragment is
+ * preparing to be removed, hidden, or detached because of popping the back stack. The exiting
+ * Views will be those that are regular Views or ViewGroups that have
+ * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as entering is governed by changing visibility from
+ * {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null,
+ * entering Views will remain unaffected. If nothing is set, the default will be to
+ * use the same value as set in {@link #setEnterTransition(Object)}.
+ *
+ * @param transition The Transition to use to move Views out of the Scene when the Fragment
+ * is preparing to close due to popping the back stack. transition must be
+ * an {@link android.transition.Transition} or
+ * {@link androidx.transition.Transition}.
+ */
+ public void setReturnTransition(@Nullable Object transition) {
+ ensureAnimationInfo().mReturnTransition = transition;
+ }
+
+ /**
+ * Returns the Transition that will be used to move Views out of the scene when the Fragment is
+ * preparing to be removed, hidden, or detached because of popping the back stack. The exiting
+ * Views will be those that are regular Views or ViewGroups that have
+ * {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as entering is governed by changing visibility from
+ * {@link View#VISIBLE} to {@link View#INVISIBLE}. If nothing is set, the default will be to use
+ * the same transition as {@link #getEnterTransition()}.
+ *
+ * @return the Transition to use to move Views out of the Scene when the Fragment
+ * is preparing to close due to popping the back stack.
+ */
+ @Nullable
+ public Object getReturnTransition() {
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mReturnTransition == USE_DEFAULT_TRANSITION ? getEnterTransition()
+ : mAnimationInfo.mReturnTransition;
+ }
+
+ /**
+ * Sets the Transition that will be used to move Views out of the scene when the
+ * fragment is removed, hidden, or detached when not popping the back stack.
+ * The exiting Views will be those that are regular Views or ViewGroups that
+ * have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as exiting is governed by changing visibility
+ * from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null, the views will
+ * remain unaffected.
+ *
+ * @param transition The Transition to use to move Views out of the Scene when the Fragment
+ * is being closed not due to popping the back stack. transition
+ * must be an
+ * {@link android.transition.Transition} or
+ * {@link androidx.transition.Transition}.
+ */
+ public void setExitTransition(@Nullable Object transition) {
+ ensureAnimationInfo().mExitTransition = transition;
+ }
+
+ /**
+ * Returns the Transition that will be used to move Views out of the scene when the
+ * fragment is removed, hidden, or detached when not popping the back stack.
+ * The exiting Views will be those that are regular Views or ViewGroups that
+ * have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions will extend
+ * {@link android.transition.Visibility} as exiting is governed by changing visibility
+ * from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null, the views will
+ * remain unaffected.
+ *
+ * @return the Transition to use to move Views out of the Scene when the Fragment
+ * is being closed not due to popping the back stack.
+ */
+ @Nullable
+ public Object getExitTransition() {
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mExitTransition;
+ }
+
+ /**
+ * Sets the Transition that will be used to move Views in to the scene when returning due
+ * to popping a back stack. The entering Views will be those that are regular Views
+ * or ViewGroups that have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions
+ * will extend {@link android.transition.Visibility} as exiting is governed by changing
+ * visibility from {@link View#VISIBLE} to {@link View#INVISIBLE}. If transition is null,
+ * the views will remain unaffected. If nothing is set, the default will be to use the same
+ * transition as {@link #getExitTransition()}.
+ *
+ * @param transition The Transition to use to move Views into the scene when reentering from a
+ * previously-started Activity due to popping the back stack. transition
+ * must be an
+ * {@link android.transition.Transition} or
+ * {@link androidx.transition.Transition}.
+ */
+ public void setReenterTransition(@Nullable Object transition) {
+ ensureAnimationInfo().mReenterTransition = transition;
+ }
+
+ /**
+ * Returns the Transition that will be used to move Views in to the scene when returning due
+ * to popping a back stack. The entering Views will be those that are regular Views
+ * or ViewGroups that have {@link ViewGroup#isTransitionGroup} return true. Typical Transitions
+ * will extend {@link android.transition.Visibility} as exiting is governed by changing
+ * visibility from {@link View#VISIBLE} to {@link View#INVISIBLE}. If nothing is set, the
+ * default will be to use the same transition as {@link #getExitTransition()}.
+ *
+ * @return the Transition to use to move Views into the scene when reentering from a
+ * previously-started Activity due to popping the back stack.
+ */
+ @Nullable
+ public Object getReenterTransition() {
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mReenterTransition == USE_DEFAULT_TRANSITION ? getExitTransition()
+ : mAnimationInfo.mReenterTransition;
+ }
+
+ /**
+ * Sets the Transition that will be used for shared elements transferred into the content
+ * Scene. Typical Transitions will affect size and location, such as
+ * {@link android.transition.ChangeBounds}. A null
+ * value will cause transferred shared elements to blink to the final position.
+ *
+ * @param transition The Transition to use for shared elements transferred into the content
+ * Scene. transition must be an
+ * {@link android.transition.Transition android.transition.Transition} or
+ * {@link androidx.transition.Transition androidx.transition.Transition}.
+ */
+ public void setSharedElementEnterTransition(@Nullable Object transition) {
+ ensureAnimationInfo().mSharedElementEnterTransition = transition;
+ }
+
+ /**
+ * Returns the Transition that will be used for shared elements transferred into the content
+ * Scene. Typical Transitions will affect size and location, such as
+ * {@link android.transition.ChangeBounds}. A null
+ * value will cause transferred shared elements to blink to the final position.
+ *
+ * @return The Transition to use for shared elements transferred into the content
+ * Scene.
+ */
+ @Nullable
+ public Object getSharedElementEnterTransition() {
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mSharedElementEnterTransition;
+ }
+
+ /**
+ * Sets the Transition that will be used for shared elements transferred back during a
+ * pop of the back stack. This Transition acts in the leaving Fragment.
+ * Typical Transitions will affect size and location, such as
+ * {@link android.transition.ChangeBounds}. A null
+ * value will cause transferred shared elements to blink to the final position.
+ * If no value is set, the default will be to use the same value as
+ * {@link #setSharedElementEnterTransition(Object)}.
+ *
+ * @param transition The Transition to use for shared elements transferred out of the content
+ * Scene. transition must be an
+ * {@link android.transition.Transition android.transition.Transition} or
+ * {@link androidx.transition.Transition androidx.transition.Transition}.
+ */
+ public void setSharedElementReturnTransition(@Nullable Object transition) {
+ ensureAnimationInfo().mSharedElementReturnTransition = transition;
+ }
+
+ /**
+ * Return the Transition that will be used for shared elements transferred back during a
+ * pop of the back stack. This Transition acts in the leaving Fragment.
+ * Typical Transitions will affect size and location, such as
+ * {@link android.transition.ChangeBounds}. A null
+ * value will cause transferred shared elements to blink to the final position.
+ * If no value is set, the default will be to use the same value as
+ * {@link #setSharedElementEnterTransition(Object)}.
+ *
+ * @return The Transition to use for shared elements transferred out of the content
+ * Scene.
+ */
+ @Nullable
+ public Object getSharedElementReturnTransition() {
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mSharedElementReturnTransition == USE_DEFAULT_TRANSITION
+ ? getSharedElementEnterTransition()
+ : mAnimationInfo.mSharedElementReturnTransition;
+ }
+
+ /**
+ * Sets whether the the exit transition and enter transition overlap or not.
+ * When true, the enter transition will start as soon as possible. When false, the
+ * enter transition will wait until the exit transition completes before starting.
+ *
+ * @param allow true to start the enter transition when possible or false to
+ * wait until the exiting transition completes.
+ */
+ public void setAllowEnterTransitionOverlap(boolean allow) {
+ ensureAnimationInfo().mAllowEnterTransitionOverlap = allow;
+ }
+
+ /**
+ * Returns whether the the exit transition and enter transition overlap or not.
+ * When true, the enter transition will start as soon as possible. When false, the
+ * enter transition will wait until the exit transition completes before starting.
+ *
+ * @return true when the enter transition should start as soon as possible or false to
+ * when it should wait until the exiting transition completes.
+ */
+ public boolean getAllowEnterTransitionOverlap() {
+ return (mAnimationInfo == null || mAnimationInfo.mAllowEnterTransitionOverlap == null)
+ ? true : mAnimationInfo.mAllowEnterTransitionOverlap;
+ }
+
+ /**
+ * Sets whether the the return transition and reenter transition overlap or not.
+ * When true, the reenter transition will start as soon as possible. When false, the
+ * reenter transition will wait until the return transition completes before starting.
+ *
+ * @param allow true to start the reenter transition when possible or false to wait until the
+ * return transition completes.
+ */
+ public void setAllowReturnTransitionOverlap(boolean allow) {
+ ensureAnimationInfo().mAllowReturnTransitionOverlap = allow;
+ }
+
+ /**
+ * Returns whether the the return transition and reenter transition overlap or not.
+ * When true, the reenter transition will start as soon as possible. When false, the
+ * reenter transition will wait until the return transition completes before starting.
+ *
+ * @return true to start the reenter transition when possible or false to wait until the
+ * return transition completes.
+ */
+ public boolean getAllowReturnTransitionOverlap() {
+ return (mAnimationInfo == null || mAnimationInfo.mAllowReturnTransitionOverlap == null)
+ ? true : mAnimationInfo.mAllowReturnTransitionOverlap;
+ }
+
+ /**
+ * Postpone the entering Fragment transition until {@link #startPostponedEnterTransition()}
+ * or {@link FragmentManager#executePendingTransactions()} has been called.
+ *
+ * This method gives the Fragment the ability to delay Fragment animations
+ * until all data is loaded. Until then, the added, shown, and
+ * attached Fragments will be INVISIBLE and removed, hidden, and detached Fragments won't
+ * be have their Views removed. The transaction runs when all postponed added Fragments in the
+ * transaction have called {@link #startPostponedEnterTransition()}.
+ *
+ * This method should be called before being added to the FragmentTransaction or
+ * in {@link #onCreate(Bundle)}, {@link #onAttach(Context)}, or
+ * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}}.
+ * {@link #startPostponedEnterTransition()} must be called to allow the Fragment to
+ * start the transitions.
+ *
+ * When a FragmentTransaction is started that may affect a postponed FragmentTransaction,
+ * based on which containers are in their operations, the postponed FragmentTransaction
+ * will have its start triggered. The early triggering may result in faulty or nonexistent
+ * animations in the postponed transaction. FragmentTransactions that operate only on
+ * independent containers will not interfere with each other's postponement.
+ *
+ * Calling postponeEnterTransition on Fragments with a null View will not postpone the
+ * transition. Likewise, postponement only works if
+ * {@link FragmentTransaction#setReorderingAllowed(boolean) FragmentTransaction reordering} is
+ * enabled.
+ *
+ * @see Activity#postponeEnterTransition()
+ * @see FragmentTransaction#setReorderingAllowed(boolean)
+ */
+ public void postponeEnterTransition() {
+ ensureAnimationInfo().mEnterTransitionPostponed = true;
+ }
+
+ /**
+ * Postpone the entering Fragment transition for a given amount of time and then call
+ * {@link #startPostponedEnterTransition()}.
+ *
+ * This method gives the Fragment the ability to delay Fragment animations for a given amount
+ * of time. Until then, the added, shown, and attached Fragments will be INVISIBLE and removed,
+ * hidden, and detached Fragments won't be have their Views removed. The transaction runs when
+ * all postponed added Fragments in the transaction have called
+ * {@link #startPostponedEnterTransition()}.
+ *
+ * This method should be called before being added to the FragmentTransaction or
+ * in {@link #onCreate(Bundle)}, {@link #onAttach(Context)}, or
+ * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}}.
+ *
+ * When a FragmentTransaction is started that may affect a postponed FragmentTransaction,
+ * based on which containers are in their operations, the postponed FragmentTransaction
+ * will have its start triggered. The early triggering may result in faulty or nonexistent
+ * animations in the postponed transaction. FragmentTransactions that operate only on
+ * independent containers will not interfere with each other's postponement.
+ *
+ * Calling postponeEnterTransition on Fragments with a null View will not postpone the
+ * transition. Likewise, postponement only works if
+ * {@link FragmentTransaction#setReorderingAllowed(boolean) FragmentTransaction reordering} is
+ * enabled.
+ *
+ * @param duration The length of the delay in {@code timeUnit} units
+ * @param timeUnit The units of time for {@code duration}
+ * @see Activity#postponeEnterTransition()
+ * @see FragmentTransaction#setReorderingAllowed(boolean)
+ */
+ public final void postponeEnterTransition(long duration, @NonNull TimeUnit timeUnit) {
+ ensureAnimationInfo().mEnterTransitionPostponed = true;
+ Handler handler;
+ if (mFragmentManager != null) {
+ handler = mFragmentManager.mHost.getHandler();
+ } else {
+ handler = new Handler(Looper.getMainLooper());
+ }
+ handler.removeCallbacks(mPostponedDurationRunnable);
+ handler.postDelayed(mPostponedDurationRunnable, timeUnit.toMillis(duration));
+ }
+
+ /**
+ * Begin postponed transitions after {@link #postponeEnterTransition()} was called.
+ * If postponeEnterTransition() was called, you must call startPostponedEnterTransition()
+ * or {@link FragmentManager#executePendingTransactions()} to complete the FragmentTransaction.
+ * If postponement was interrupted with {@link FragmentManager#executePendingTransactions()},
+ * before {@code startPostponedEnterTransition()}, animations may not run or may execute
+ * improperly.
+ *
+ * @see Activity#startPostponedEnterTransition()
+ */
+ public void startPostponedEnterTransition() {
+ if (mFragmentManager == null || mFragmentManager.mHost == null) {
+ ensureAnimationInfo().mEnterTransitionPostponed = false;
+ } else if (Looper.myLooper() != mFragmentManager.mHost.getHandler().getLooper()) {
+ mFragmentManager.mHost.getHandler().postAtFrontOfQueue(new Runnable() {
+ @Override
+ public void run() {
+ callStartTransitionListener();
+ }
+ });
+ } else {
+ callStartTransitionListener();
+ }
+ }
+
+ /**
+ * Calls the start transition listener. This must be called on the UI thread.
+ */
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ void callStartTransitionListener() {
+ final OnStartEnterTransitionListener listener;
+ if (mAnimationInfo == null) {
+ listener = null;
+ } else {
+ mAnimationInfo.mEnterTransitionPostponed = false;
+ listener = mAnimationInfo.mStartEnterTransitionListener;
+ mAnimationInfo.mStartEnterTransitionListener = null;
+ }
+ if (listener != null) {
+ listener.onStartEnterTransition();
+ }
+ }
+
+ /**
+ * Print the Fragments'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 The PrintWriter to which you should dump your state. This will be
+ * closed for you after you return.
+ * @param args additional arguments to the dump request.
+ */
+ public void dump(@NonNull String prefix, @Nullable FileDescriptor fd,
+ @NonNull PrintWriter writer, @Nullable String[] args) {
+ writer.print(prefix); writer.print("mFragmentId=#");
+ writer.print(Integer.toHexString(mFragmentId));
+ writer.print(" mContainerId=#");
+ writer.print(Integer.toHexString(mContainerId));
+ writer.print(" mTag="); writer.println(mTag);
+ writer.print(prefix); writer.print("mState="); writer.print(mState);
+ writer.print(" mWho="); writer.print(mWho);
+ writer.print(" mBackStackNesting="); writer.println(mBackStackNesting);
+ writer.print(prefix); writer.print("mAdded="); writer.print(mAdded);
+ writer.print(" mRemoving="); writer.print(mRemoving);
+ writer.print(" mFromLayout="); writer.print(mFromLayout);
+ writer.print(" mInLayout="); writer.println(mInLayout);
+ writer.print(prefix); writer.print("mHidden="); writer.print(mHidden);
+ writer.print(" mDetached="); writer.print(mDetached);
+ writer.print(" mMenuVisible="); writer.print(mMenuVisible);
+ writer.print(" mHasMenu="); writer.println(mHasMenu);
+ writer.print(prefix); writer.print("mRetainInstance="); writer.print(mRetainInstance);
+ writer.print(" mUserVisibleHint="); writer.println(mUserVisibleHint);
+ if (mFragmentManager != null) {
+ writer.print(prefix); writer.print("mFragmentManager=");
+ writer.println(mFragmentManager);
+ }
+ if (mHost != null) {
+ writer.print(prefix); writer.print("mHost=");
+ writer.println(mHost);
+ }
+ if (mParentFragment != null) {
+ writer.print(prefix); writer.print("mParentFragment=");
+ writer.println(mParentFragment);
+ }
+ if (mArguments != null) {
+ writer.print(prefix); writer.print("mArguments="); writer.println(mArguments);
+ }
+ if (mSavedFragmentState != null) {
+ writer.print(prefix); writer.print("mSavedFragmentState=");
+ writer.println(mSavedFragmentState);
+ }
+ if (mSavedViewState != null) {
+ writer.print(prefix); writer.print("mSavedViewState=");
+ writer.println(mSavedViewState);
+ }
+ Fragment target = getTargetFragment();
+ if (target != null) {
+ writer.print(prefix); writer.print("mTarget="); writer.print(target);
+ writer.print(" mTargetRequestCode=");
+ writer.println(mTargetRequestCode);
+ }
+ if (getNextAnim() != 0) {
+ writer.print(prefix); writer.print("mNextAnim="); writer.println(getNextAnim());
+ }
+ if (mContainer != null) {
+ writer.print(prefix); writer.print("mContainer="); writer.println(mContainer);
+ }
+ if (mView != null) {
+ writer.print(prefix); writer.print("mView="); writer.println(mView);
+ }
+ if (mInnerView != null) {
+ writer.print(prefix); writer.print("mInnerView="); writer.println(mView);
+ }
+ if (getAnimatingAway() != null) {
+ writer.print(prefix);
+ writer.print("mAnimatingAway=");
+ writer.println(getAnimatingAway());
+ writer.print(prefix);
+ writer.print("mStateAfterAnimating=");
+ writer.println(getStateAfterAnimating());
+ }
+ if (getContext() != null) {
+ LoaderManager.getInstance(this).dump(prefix, fd, writer, args);
+ }
+ writer.print(prefix);
+ writer.println("Child " + mChildFragmentManager + ":");
+ mChildFragmentManager.dump(prefix + " ", fd, writer, args);
+ }
+
+ @Nullable
+ Fragment findFragmentByWho(@NonNull String who) {
+ if (who.equals(mWho)) {
+ return this;
+ }
+ return mChildFragmentManager.findFragmentByWho(who);
+ }
+
+ void performAttach() {
+ mChildFragmentManager.attachController(mHost, new FragmentContainer() {
+ @Override
+ @Nullable
+ public View onFindViewById(int id) {
+ if (mView == null) {
+ throw new IllegalStateException("Fragment " + this + " does not have a view");
+ }
+ return mView.findViewById(id);
+ }
+
+ @Override
+ public boolean onHasView() {
+ return (mView != null);
+ }
+ }, this);
+ mCalled = false;
+ onAttach(mHost.getContext());
+ if (!mCalled) {
+ throw new SuperNotCalledException("Fragment " + this
+ + " did not call through to super.onAttach()");
+ }
+ }
+
+ void performCreate(Bundle savedInstanceState) {
+ mChildFragmentManager.noteStateNotSaved();
+ mState = CREATED;
+ mCalled = false;
+ mSavedStateRegistryController.performRestore(savedInstanceState);
+ onCreate(savedInstanceState);
+ mIsCreated = true;
+ if (!mCalled) {
+ throw new SuperNotCalledException("Fragment " + this
+ + " did not call through to super.onCreate()");
+ }
+ mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
+ }
+
+ void performCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ mChildFragmentManager.noteStateNotSaved();
+ mPerformedCreateView = true;
+ mViewLifecycleOwner = new FragmentViewLifecycleOwner();
+ mView = onCreateView(inflater, container, savedInstanceState);
+ if (mView != null) {
+ // Initialize the view lifecycle
+ mViewLifecycleOwner.initialize();
+ // Then inform any Observers of the new LifecycleOwner
+ mViewLifecycleOwnerLiveData.setValue(mViewLifecycleOwner);
+ } else {
+ if (mViewLifecycleOwner.isInitialized()) {
+ throw new IllegalStateException("Called getViewLifecycleOwner() but "
+ + "onCreateView() returned null");
+ }
+ mViewLifecycleOwner = null;
+ }
+ }
+
+ void performActivityCreated(Bundle savedInstanceState) {
+ mChildFragmentManager.noteStateNotSaved();
+ mState = ACTIVITY_CREATED;
+ mCalled = false;
+ onActivityCreated(savedInstanceState);
+ if (!mCalled) {
+ throw new SuperNotCalledException("Fragment " + this
+ + " did not call through to super.onActivityCreated()");
+ }
+ mChildFragmentManager.dispatchActivityCreated();
+ }
+
+ void performStart() {
+ mChildFragmentManager.noteStateNotSaved();
+ mChildFragmentManager.execPendingActions();
+ mState = STARTED;
+ mCalled = false;
+ onStart();
+ if (!mCalled) {
+ throw new SuperNotCalledException("Fragment " + this
+ + " did not call through to super.onStart()");
+ }
+ mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ if (mView != null) {
+ mViewLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ }
+ mChildFragmentManager.dispatchStart();
+ }
+
+ void performResume() {
+ mChildFragmentManager.noteStateNotSaved();
+ mChildFragmentManager.execPendingActions();
+ mState = RESUMED;
+ mCalled = false;
+ onResume();
+ if (!mCalled) {
+ throw new SuperNotCalledException("Fragment " + this
+ + " did not call through to super.onResume()");
+ }
+ mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
+ if (mView != null) {
+ mViewLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
+ }
+ mChildFragmentManager.dispatchResume();
+ mChildFragmentManager.execPendingActions();
+ }
+
+ void noteStateNotSaved() {
+ mChildFragmentManager.noteStateNotSaved();
+ }
+
+ void performPrimaryNavigationFragmentChanged() {
+ boolean isPrimaryNavigationFragment = mFragmentManager.isPrimaryNavigation(this);
+ // Only send out the callback / dispatch if the state has changed
+ if (mIsPrimaryNavigationFragment == null
+ || mIsPrimaryNavigationFragment != isPrimaryNavigationFragment) {
+ mIsPrimaryNavigationFragment = isPrimaryNavigationFragment;
+ onPrimaryNavigationFragmentChanged(isPrimaryNavigationFragment);
+ mChildFragmentManager.dispatchPrimaryNavigationFragmentChanged();
+ }
+ }
+
+ void performMultiWindowModeChanged(boolean isInMultiWindowMode) {
+ onMultiWindowModeChanged(isInMultiWindowMode);
+ mChildFragmentManager.dispatchMultiWindowModeChanged(isInMultiWindowMode);
+ }
+
+ void performPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ onPictureInPictureModeChanged(isInPictureInPictureMode);
+ mChildFragmentManager.dispatchPictureInPictureModeChanged(isInPictureInPictureMode);
+ }
+
+ void performConfigurationChanged(@NonNull Configuration newConfig) {
+ onConfigurationChanged(newConfig);
+ mChildFragmentManager.dispatchConfigurationChanged(newConfig);
+ }
+
+ void performLowMemory() {
+ onLowMemory();
+ mChildFragmentManager.dispatchLowMemory();
+ }
+
+ /*
+ void performTrimMemory(int level) {
+ onTrimMemory(level);
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.dispatchTrimMemory(level);
+ }
+ }
+ */
+
+ boolean performCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
+ boolean show = false;
+ if (!mHidden) {
+ if (mHasMenu && mMenuVisible) {
+ show = true;
+ onCreateOptionsMenu(menu, inflater);
+ }
+ show |= mChildFragmentManager.dispatchCreateOptionsMenu(menu, inflater);
+ }
+ return show;
+ }
+
+ boolean performPrepareOptionsMenu(@NonNull Menu menu) {
+ boolean show = false;
+ if (!mHidden) {
+ if (mHasMenu && mMenuVisible) {
+ show = true;
+ onPrepareOptionsMenu(menu);
+ }
+ show |= mChildFragmentManager.dispatchPrepareOptionsMenu(menu);
+ }
+ return show;
+ }
+
+ boolean performOptionsItemSelected(@NonNull MenuItem item) {
+ if (!mHidden) {
+ if (mHasMenu && mMenuVisible) {
+ if (onOptionsItemSelected(item)) {
+ return true;
+ }
+ }
+ if (mChildFragmentManager.dispatchOptionsItemSelected(item)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean performContextItemSelected(@NonNull MenuItem item) {
+ if (!mHidden) {
+ if (onContextItemSelected(item)) {
+ return true;
+ }
+ if (mChildFragmentManager.dispatchContextItemSelected(item)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void performOptionsMenuClosed(@NonNull Menu menu) {
+ if (!mHidden) {
+ if (mHasMenu && mMenuVisible) {
+ onOptionsMenuClosed(menu);
+ }
+ mChildFragmentManager.dispatchOptionsMenuClosed(menu);
+ }
+ }
+
+ void performSaveInstanceState(Bundle outState) {
+ onSaveInstanceState(outState);
+ mSavedStateRegistryController.performSave(outState);
+ Parcelable p = mChildFragmentManager.saveAllState();
+ if (p != null) {
+ outState.putParcelable(FragmentActivity.FRAGMENTS_TAG, p);
+ }
+ }
+
+ void performPause() {
+ mChildFragmentManager.dispatchPause();
+ if (mView != null) {
+ mViewLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
+ }
+ mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
+ mState = STARTED;
+ mCalled = false;
+ onPause();
+ if (!mCalled) {
+ throw new SuperNotCalledException("Fragment " + this
+ + " did not call through to super.onPause()");
+ }
+ }
+
+ void performStop() {
+ mChildFragmentManager.dispatchStop();
+ if (mView != null) {
+ mViewLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+ }
+ mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+ mState = ACTIVITY_CREATED;
+ mCalled = false;
+ onStop();
+ if (!mCalled) {
+ throw new SuperNotCalledException("Fragment " + this
+ + " did not call through to super.onStop()");
+ }
+ }
+
+ void performDestroyView() {
+ mChildFragmentManager.dispatchDestroyView();
+ if (mView != null) {
+ mViewLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
+ }
+ mState = CREATED;
+ mCalled = false;
+ onDestroyView();
+ if (!mCalled) {
+ throw new SuperNotCalledException("Fragment " + this
+ + " did not call through to super.onDestroyView()");
+ }
+ // Handles the detach/reattach case where the view hierarchy
+ // is destroyed and recreated and an additional call to
+ // onLoadFinished may be needed to ensure the new view
+ // hierarchy is populated from data from the Loaders
+ LoaderManager.getInstance(this).markForRedelivery();
+ mPerformedCreateView = false;
+ }
+
+ void performDestroy() {
+ mChildFragmentManager.dispatchDestroy();
+ mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
+ mState = INITIALIZING;
+ mCalled = false;
+ mIsCreated = false;
+ onDestroy();
+ if (!mCalled) {
+ throw new SuperNotCalledException("Fragment " + this
+ + " did not call through to super.onDestroy()");
+ }
+ }
+
+ void performDetach() {
+ mCalled = false;
+ onDetach();
+ mLayoutInflater = null;
+ if (!mCalled) {
+ throw new SuperNotCalledException("Fragment " + this
+ + " did not call through to super.onDetach()");
+ }
+
+ // Destroy the child FragmentManager if we still have it here.
+ // This is normally done in performDestroy(), but is done here
+ // specifically if the Fragment is retained.
+ if (!mChildFragmentManager.isDestroyed()) {
+ mChildFragmentManager.dispatchDestroy();
+ mChildFragmentManager = new FragmentManagerImpl();
+ }
+ }
+
+ void setOnStartEnterTransitionListener(OnStartEnterTransitionListener listener) {
+ ensureAnimationInfo();
+ if (listener == mAnimationInfo.mStartEnterTransitionListener) {
+ return;
+ }
+ if (listener != null && mAnimationInfo.mStartEnterTransitionListener != null) {
+ throw new IllegalStateException("Trying to set a replacement "
+ + "startPostponedEnterTransition on " + this);
+ }
+ if (mAnimationInfo.mEnterTransitionPostponed) {
+ mAnimationInfo.mStartEnterTransitionListener = listener;
+ }
+ if (listener != null) {
+ listener.startListening();
+ }
+ }
+
+ private AnimationInfo ensureAnimationInfo() {
+ if (mAnimationInfo == null) {
+ mAnimationInfo = new AnimationInfo();
+ }
+ return mAnimationInfo;
+ }
+
+ int getNextAnim() {
+ if (mAnimationInfo == null) {
+ return 0;
+ }
+ return mAnimationInfo.mNextAnim;
+ }
+
+ void setNextAnim(int animResourceId) {
+ if (mAnimationInfo == null && animResourceId == 0) {
+ return; // no change!
+ }
+ ensureAnimationInfo().mNextAnim = animResourceId;
+ }
+
+ int getNextTransition() {
+ if (mAnimationInfo == null) {
+ return 0;
+ }
+ return mAnimationInfo.mNextTransition;
+ }
+
+ void setNextTransition(int nextTransition, int nextTransitionStyle) {
+ if (mAnimationInfo == null && nextTransition == 0 && nextTransitionStyle == 0) {
+ return; // no change!
+ }
+ ensureAnimationInfo();
+ mAnimationInfo.mNextTransition = nextTransition;
+ mAnimationInfo.mNextTransitionStyle = nextTransitionStyle;
+ }
+
+ int getNextTransitionStyle() {
+ if (mAnimationInfo == null) {
+ return 0;
+ }
+ return mAnimationInfo.mNextTransitionStyle;
+ }
+
+ SharedElementCallback getEnterTransitionCallback() {
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mEnterTransitionCallback;
+ }
+
+ SharedElementCallback getExitTransitionCallback() {
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mExitTransitionCallback;
+ }
+
+ View getAnimatingAway() {
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mAnimatingAway;
+ }
+
+ void setAnimatingAway(View view) {
+ ensureAnimationInfo().mAnimatingAway = view;
+ }
+
+ void setAnimator(Animator animator) {
+ ensureAnimationInfo().mAnimator = animator;
+ }
+
+ Animator getAnimator() {
+ if (mAnimationInfo == null) {
+ return null;
+ }
+ return mAnimationInfo.mAnimator;
+ }
+
+ int getStateAfterAnimating() {
+ if (mAnimationInfo == null) {
+ return 0;
+ }
+ return mAnimationInfo.mStateAfterAnimating;
+ }
+
+ void setStateAfterAnimating(int state) {
+ ensureAnimationInfo().mStateAfterAnimating = state;
+ }
+
+ boolean isPostponed() {
+ if (mAnimationInfo == null) {
+ return false;
+ }
+ return mAnimationInfo.mEnterTransitionPostponed;
+ }
+
+ boolean isHideReplaced() {
+ if (mAnimationInfo == null) {
+ return false;
+ }
+ return mAnimationInfo.mIsHideReplaced;
+ }
+
+ void setHideReplaced(boolean replaced) {
+ ensureAnimationInfo().mIsHideReplaced = replaced;
+ }
+
+ /**
+ * Used internally to be notified when {@link #startPostponedEnterTransition()} has
+ * been called. This listener will only be called once and then be removed from the
+ * listeners.
+ */
+ interface OnStartEnterTransitionListener {
+ void onStartEnterTransition();
+ void startListening();
+ }
+
+ /**
+ * Contains all the animation and transition information for a fragment. This will only
+ * be instantiated for Fragments that have Views.
+ */
+ static class AnimationInfo {
+ // Non-null if the fragment's view hierarchy is currently animating away,
+ // meaning we need to wait a bit on completely destroying it. This is the
+ // view that is animating.
+ View mAnimatingAway;
+
+ // Non-null if the fragment's view hierarchy is currently animating away with an
+ // animator instead of an animation.
+ Animator mAnimator;
+
+ // If mAnimatingAway != null, this is the state we should move to once the
+ // animation is done.
+ int mStateAfterAnimating;
+
+ // If app has requested a specific animation, this is the one to use.
+ int mNextAnim;
+
+ // If app has requested a specific transition, this is the one to use.
+ int mNextTransition;
+
+ // If app has requested a specific transition style, this is the one to use.
+ int mNextTransitionStyle;
+
+ Object mEnterTransition = null;
+ Object mReturnTransition = USE_DEFAULT_TRANSITION;
+ Object mExitTransition = null;
+ Object mReenterTransition = USE_DEFAULT_TRANSITION;
+ Object mSharedElementEnterTransition = null;
+ Object mSharedElementReturnTransition = USE_DEFAULT_TRANSITION;
+ Boolean mAllowReturnTransitionOverlap;
+ Boolean mAllowEnterTransitionOverlap;
+
+ SharedElementCallback mEnterTransitionCallback = null;
+ SharedElementCallback mExitTransitionCallback = null;
+
+ // True when postponeEnterTransition has been called and startPostponeEnterTransition
+ // hasn't been called yet.
+ boolean mEnterTransitionPostponed;
+
+ // Listener to wait for startPostponeEnterTransition. After being called, it will
+ // be set to null
+ OnStartEnterTransitionListener mStartEnterTransitionListener;
+
+ // True if the View was hidden, but the transition is handling the hide
+ boolean mIsHideReplaced;
+ }
+}
diff --git a/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentActivity.java b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentActivity.java
new file mode 100644
index 000000000..cafe39baa
--- /dev/null
+++ b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentActivity.java
@@ -0,0 +1,1013 @@
+/*
+ * 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 android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.Window;
+
+import androidx.activity.ComponentActivity;
+import androidx.activity.OnBackPressedDispatcher;
+import androidx.activity.OnBackPressedDispatcherOwner;
+import androidx.annotation.CallSuper;
+import androidx.annotation.ContentView;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.collection.SparseArrayCompat;
+import androidx.core.app.ActivityCompat;
+import androidx.core.app.SharedElementCallback;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.ViewModelStore;
+import androidx.lifecycle.ViewModelStoreOwner;
+import androidx.loader.app.LoaderManager;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Collection;
+
+/**
+ * Base class for activities that want to use the support-based
+ * {@link Fragment Fragments}.
+ *
+ *
Known limitations:
+ *
+ *
When using the <fragment> tag, this implementation can not
+ * use the parent view's ID as the new fragment's ID. You must explicitly
+ * specify an ID (or tag) in the <fragment>.
+ *
+ */
+public class FragmentActivity extends ComponentActivity implements
+ ActivityCompat.OnRequestPermissionsResultCallback,
+ ActivityCompat.RequestPermissionsRequestCodeValidator {
+ private static final String TAG = "FragmentActivity";
+
+ static final String FRAGMENTS_TAG = "android:support:fragments";
+ static final String NEXT_CANDIDATE_REQUEST_INDEX_TAG = "android:support:next_request_index";
+ static final String ALLOCATED_REQUEST_INDICIES_TAG = "android:support:request_indicies";
+ static final String REQUEST_FRAGMENT_WHO_TAG = "android:support:request_fragment_who";
+ static final int MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS = 0xffff - 1;
+
+ final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
+ /**
+ * A {@link Lifecycle} that is exactly nested outside of when the FragmentController
+ * has its state changed, providing the proper nesting of Lifecycle callbacks
+ *
+ * TODO(b/127528777) Drive Fragment Lifecycle with LifecycleObserver
+ */
+ final LifecycleRegistry mFragmentLifecycleRegistry = new LifecycleRegistry(this);
+
+ boolean mCreated;
+ boolean mResumed;
+ boolean mStopped = true;
+
+ boolean mRequestedPermissionsFromFragment;
+
+ // We need to keep track of whether startIntentSenderForResult originated from a Fragment, so we
+ // can conditionally check whether the requestCode collides with our reserved ID space for the
+ // request index (see above). Unfortunately we can't just call
+ // super.startIntentSenderForResult(...) to bypass the check when the call didn't come from a
+ // fragment, since we need to use the ActivityCompat version for backward compatibility.
+ boolean mStartedIntentSenderFromFragment;
+ // We need to keep track of whether startActivityForResult originated from a Fragment, so we
+ // can conditionally check whether the requestCode collides with our reserved ID space for the
+ // request index (see above). Unfortunately we can't just call
+ // super.startActivityForResult(...) to bypass the check when the call didn't come from a
+ // fragment, since we need to use the ActivityCompat version for backward compatibility.
+ boolean mStartedActivityFromFragment;
+
+ // A hint for the next candidate request index. Request indicies are ints between 0 and 2^16-1
+ // which are encoded into the upper 16 bits of the requestCode for
+ // Fragment.startActivityForResult(...) calls. This allows us to dispatch onActivityResult(...)
+ // to the appropriate Fragment. Request indicies are allocated by allocateRequestIndex(...).
+ int mNextCandidateRequestIndex;
+ // A map from request index to Fragment "who" (i.e. a Fragment's unique identifier). Used to
+ // keep track of the originating Fragment for Fragment.startActivityForResult(...) calls, so we
+ // can dispatch the onActivityResult(...) to the appropriate Fragment. Will only contain entries
+ // for startActivityForResult calls where a result has not yet been delivered.
+ SparseArrayCompat mPendingFragmentActivityResults;
+
+ /**
+ * Default constructor for FragmentActivity. All Activities must have a default constructor
+ * for API 27 and lower devices or when using the default
+ * {@link android.app.AppComponentFactory}.
+ */
+ public FragmentActivity() {
+ super();
+ }
+
+ /**
+ * Alternate constructor that can be used to provide a default layout
+ * that will be inflated as part of super.onCreate(savedInstanceState).
+ *
+ *
This should generally be called from your constructor that takes no parameters,
+ * as is required for API 27 and lower or when using the default
+ * {@link android.app.AppComponentFactory}.
+ *
+ * @see #FragmentActivity()
+ */
+ @ContentView
+ public FragmentActivity(@LayoutRes int contentLayoutId) {
+ super(contentLayoutId);
+ }
+
+ // ------------------------------------------------------------------------
+ // HOOKS INTO ACTIVITY
+ // ------------------------------------------------------------------------
+
+ /**
+ * Dispatch incoming result to the correct fragment.
+ */
+ @Override
+ @CallSuper
+ protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ mFragments.noteStateNotSaved();
+ int requestIndex = requestCode>>16;
+ if (requestIndex != 0) {
+ requestIndex--;
+
+ String who = mPendingFragmentActivityResults.get(requestIndex);
+ mPendingFragmentActivityResults.remove(requestIndex);
+ if (who == null) {
+ Log.w(TAG, "Activity result delivered for unknown Fragment.");
+ return;
+ }
+ Fragment targetFragment = mFragments.findFragmentByWho(who);
+ if (targetFragment == null) {
+ Log.w(TAG, "Activity result no fragment exists for who: " + who);
+ } else {
+ targetFragment.onActivityResult(requestCode & 0xffff, resultCode, data);
+ }
+ return;
+ }
+ ActivityCompat.PermissionCompatDelegate delegate =
+ ActivityCompat.getPermissionCompatDelegate();
+ if (delegate != null && delegate.onActivityResult(this, requestCode, resultCode, data)) {
+ // Delegate has handled the activity result
+ return;
+ }
+
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ /**
+ * Reverses the Activity Scene entry Transition and triggers the calling Activity
+ * to reverse its exit Transition. When the exit Transition completes,
+ * {@link #finish()} is called. If no entry Transition was used, finish() is called
+ * immediately and the Activity exit Transition is run.
+ *
+ *
On Android 4.4 or lower, this method only finishes the Activity with no
+ * special exit transition.
+ */
+ public void supportFinishAfterTransition() {
+ ActivityCompat.finishAfterTransition(this);
+ }
+
+ /**
+ * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
+ * android.view.View, String)} was used to start an Activity, callback
+ * will be called to handle shared elements on the launched Activity. This requires
+ * {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * @param callback Used to manipulate shared element transitions on the launched Activity.
+ */
+ public void setEnterSharedElementCallback(@Nullable SharedElementCallback callback) {
+ ActivityCompat.setEnterSharedElementCallback(this, callback);
+ }
+
+ /**
+ * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
+ * android.view.View, String)} was used to start an Activity, listener
+ * will be called to handle shared elements on the launching Activity. Most
+ * calls will only come when returning from the started Activity.
+ * This requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+ *
+ * @param listener Used to manipulate shared element transitions on the launching Activity.
+ */
+ public void setExitSharedElementCallback(@Nullable SharedElementCallback listener) {
+ ActivityCompat.setExitSharedElementCallback(this, listener);
+ }
+
+ /**
+ * Support library version of {@link android.app.Activity#postponeEnterTransition()} that works
+ * only on API 21 and later.
+ */
+ public void supportPostponeEnterTransition() {
+ ActivityCompat.postponeEnterTransition(this);
+ }
+
+ /**
+ * Support library version of {@link android.app.Activity#startPostponedEnterTransition()}
+ * that only works with API 21 and later.
+ */
+ public void supportStartPostponedEnterTransition() {
+ ActivityCompat.startPostponedEnterTransition(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ *
Note: If you override this method you must call
+ * super.onMultiWindowModeChanged to correctly dispatch the event
+ * to support fragments attached to this activity.
+ *
+ * @param isInMultiWindowMode True if the activity is in multi-window mode.
+ */
+ @Override
+ @CallSuper
+ public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
+ mFragments.dispatchMultiWindowModeChanged(isInMultiWindowMode);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ *
Note: If you override this method you must call
+ * super.onPictureInPictureModeChanged to correctly dispatch the event
+ * to support fragments attached to this activity.
+ *
+ * @param isInPictureInPictureMode True if the activity is in picture-in-picture mode.
+ */
+ @Override
+ @CallSuper
+ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ mFragments.dispatchPictureInPictureModeChanged(isInPictureInPictureMode);
+ }
+
+ /**
+ * Dispatch configuration change to all fragments.
+ */
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mFragments.noteStateNotSaved();
+ mFragments.dispatchConfigurationChanged(newConfig);
+ }
+
+ /**
+ * Perform initialization of all fragments.
+ */
+ @SuppressWarnings("deprecation")
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ mFragments.attachHost(null /*parent*/);
+
+ if (savedInstanceState != null) {
+ Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
+ mFragments.restoreSaveState(p);
+
+ // Check if there are any pending onActivityResult calls to descendent Fragments.
+ if (savedInstanceState.containsKey(NEXT_CANDIDATE_REQUEST_INDEX_TAG)) {
+ mNextCandidateRequestIndex =
+ savedInstanceState.getInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG);
+ int[] requestCodes = savedInstanceState.getIntArray(ALLOCATED_REQUEST_INDICIES_TAG);
+ String[] fragmentWhos = savedInstanceState.getStringArray(REQUEST_FRAGMENT_WHO_TAG);
+ if (requestCodes == null || fragmentWhos == null ||
+ requestCodes.length != fragmentWhos.length) {
+ Log.w(TAG, "Invalid requestCode mapping in savedInstanceState.");
+ } else {
+ mPendingFragmentActivityResults = new SparseArrayCompat<>(requestCodes.length);
+ for (int i = 0; i < requestCodes.length; i++) {
+ mPendingFragmentActivityResults.put(requestCodes[i], fragmentWhos[i]);
+ }
+ }
+ }
+ }
+
+ if (mPendingFragmentActivityResults == null) {
+ mPendingFragmentActivityResults = new SparseArrayCompat<>();
+ mNextCandidateRequestIndex = 0;
+ }
+
+ super.onCreate(savedInstanceState);
+
+ mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
+ mFragments.dispatchCreate();
+ }
+
+ /**
+ * Dispatch to Fragment.onCreateOptionsMenu().
+ */
+ @Override
+ public boolean onCreatePanelMenu(int featureId, @NonNull Menu menu) {
+ if (featureId == Window.FEATURE_OPTIONS_PANEL) {
+ boolean show = super.onCreatePanelMenu(featureId, menu);
+ show |= mFragments.dispatchCreateOptionsMenu(menu, getMenuInflater());
+ return show;
+ }
+ return super.onCreatePanelMenu(featureId, menu);
+ }
+
+ @Override
+ @Nullable
+ public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context,
+ @NonNull AttributeSet attrs) {
+ final View v = dispatchFragmentsOnCreateView(parent, name, context, attrs);
+ if (v == null) {
+ return super.onCreateView(parent, name, context, attrs);
+ }
+ return v;
+ }
+
+ @Override
+ @Nullable
+ public View onCreateView(@NonNull String name, @NonNull Context context,
+ @NonNull AttributeSet attrs) {
+ final View v = dispatchFragmentsOnCreateView(null, name, context, attrs);
+ if (v == null) {
+ return super.onCreateView(name, context, attrs);
+ }
+ return v;
+ }
+
+ @Nullable
+ final View dispatchFragmentsOnCreateView(@Nullable View parent, @NonNull String name,
+ @NonNull Context context, @NonNull AttributeSet attrs) {
+ return mFragments.onCreateView(parent, name, context, attrs);
+ }
+
+ /**
+ * Destroy all fragments.
+ */
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mFragments.dispatchDestroy();
+ mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
+ }
+
+ /**
+ * Dispatch onLowMemory() to all fragments.
+ */
+ @Override
+ public void onLowMemory() {
+ super.onLowMemory();
+ mFragments.dispatchLowMemory();
+ }
+
+ /**
+ * Dispatch context and options menu to fragments.
+ */
+ @Override
+ public boolean onMenuItemSelected(int featureId, @NonNull MenuItem item) {
+ if (super.onMenuItemSelected(featureId, item)) {
+ return true;
+ }
+
+ switch (featureId) {
+ case Window.FEATURE_OPTIONS_PANEL:
+ return mFragments.dispatchOptionsItemSelected(item);
+
+ case Window.FEATURE_CONTEXT_MENU:
+ return mFragments.dispatchContextItemSelected(item);
+
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Call onOptionsMenuClosed() on fragments.
+ */
+ @Override
+ public void onPanelClosed(int featureId, @NonNull Menu menu) {
+ switch (featureId) {
+ case Window.FEATURE_OPTIONS_PANEL:
+ mFragments.dispatchOptionsMenuClosed(menu);
+ break;
+ }
+ super.onPanelClosed(featureId, menu);
+ }
+
+ /**
+ * Dispatch onPause() to fragments.
+ */
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mResumed = false;
+ mFragments.dispatchPause();
+ mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
+ }
+
+ /**
+ * Handle onNewIntent() to inform the fragment manager that the
+ * state is not saved. If you are handling new intents and may be
+ * making changes to the fragment state, you want to be sure to call
+ * through to the super-class here first. Otherwise, if your state
+ * is saved but the activity is not stopped, you could get an
+ * onNewIntent() call which happens before onResume() and trying to
+ * perform fragment operations at that point will throw IllegalStateException
+ * because the fragment manager thinks the state is still saved.
+ */
+ @Override
+ @CallSuper
+ protected void onNewIntent(@SuppressLint("UnknownNullness") Intent intent) {
+ super.onNewIntent(intent);
+ mFragments.noteStateNotSaved();
+ }
+
+ /**
+ * Hook in to note that fragment state is no longer saved.
+ */
+ @Override
+ public void onStateNotSaved() {
+ mFragments.noteStateNotSaved();
+ }
+
+ /**
+ * Dispatch onResume() to fragments. Note that for better inter-operation
+ * with older versions of the platform, at the point of this call the
+ * fragments attached to the activity are not resumed.
+ */
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mResumed = true;
+ mFragments.noteStateNotSaved();
+ mFragments.execPendingActions();
+ }
+
+ /**
+ * Dispatch onResume() to fragments.
+ */
+ @Override
+ protected void onPostResume() {
+ super.onPostResume();
+ onResumeFragments();
+ }
+
+ /**
+ * This is the fragment-orientated version of {@link #onResume()} that you
+ * can override to perform operations in the Activity at the same point
+ * where its fragments are resumed. Be sure to always call through to
+ * the super-class.
+ */
+ protected void onResumeFragments() {
+ mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
+ mFragments.dispatchResume();
+ }
+
+ /**
+ * Dispatch onPrepareOptionsMenu() to fragments.
+ */
+ @Override
+ public boolean onPreparePanel(int featureId, @Nullable View view, @NonNull Menu menu) {
+ if (featureId == Window.FEATURE_OPTIONS_PANEL) {
+ boolean goforit = onPrepareOptionsPanel(view, menu);
+ goforit |= mFragments.dispatchPrepareOptionsMenu(menu);
+ return goforit;
+ }
+ return super.onPreparePanel(featureId, view, menu);
+ }
+
+ /**
+ * @hide
+ * @deprecated Override {@link #onPreparePanel(int, View, Menu)}.
+ */
+ @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @Deprecated
+ protected boolean onPrepareOptionsPanel(@Nullable View view, @NonNull Menu menu) {
+ return super.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, view, menu);
+ }
+
+ /**
+ * Save all appropriate fragment state.
+ */
+ @Override
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ markFragmentsCreated();
+ mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+ Parcelable p = mFragments.saveAllState();
+ if (p != null) {
+ outState.putParcelable(FRAGMENTS_TAG, p);
+ }
+ if (mPendingFragmentActivityResults.size() > 0) {
+ outState.putInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG, mNextCandidateRequestIndex);
+
+ int[] requestCodes = new int[mPendingFragmentActivityResults.size()];
+ String[] fragmentWhos = new String[mPendingFragmentActivityResults.size()];
+ for (int i = 0; i < mPendingFragmentActivityResults.size(); i++) {
+ requestCodes[i] = mPendingFragmentActivityResults.keyAt(i);
+ fragmentWhos[i] = mPendingFragmentActivityResults.valueAt(i);
+ }
+ outState.putIntArray(ALLOCATED_REQUEST_INDICIES_TAG, requestCodes);
+ outState.putStringArray(REQUEST_FRAGMENT_WHO_TAG, fragmentWhos);
+ }
+ }
+
+ /**
+ * Dispatch onStart() to all fragments.
+ */
+ @Override
+ protected void onStart() {
+ super.onStart();
+
+ mStopped = false;
+
+ if (!mCreated) {
+ mCreated = true;
+ mFragments.dispatchActivityCreated();
+ }
+
+ mFragments.noteStateNotSaved();
+ mFragments.execPendingActions();
+
+ // NOTE: HC onStart goes here.
+
+ mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
+ mFragments.dispatchStart();
+ }
+
+ /**
+ * Dispatch onStop() to all fragments.
+ */
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ mStopped = true;
+ markFragmentsCreated();
+
+ mFragments.dispatchStop();
+ mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
+ }
+
+ // ------------------------------------------------------------------------
+ // NEW METHODS
+ // ------------------------------------------------------------------------
+
+ /**
+ * Support library version of {@link Activity#invalidateOptionsMenu}.
+ *
+ *
Invalidate the activity's options menu. This will cause relevant presentations
+ * of the menu to fully update via calls to onCreateOptionsMenu and
+ * onPrepareOptionsMenu the next time the menu is requested.
+ *
+ * @deprecated Call {@link Activity#invalidateOptionsMenu} directly.
+ */
+ @Deprecated
+ public void supportInvalidateOptionsMenu() {
+ invalidateOptionsMenu();
+ }
+
+ /**
+ * Print the Activity's state into the given stream. This gets invoked if
+ * you run "adb shell dumpsys activity ".
+ *
+ * @param prefix Desired prefix to prepend at each line of output.
+ * @param fd The raw file descriptor that the dump is being sent to.
+ * @param writer The PrintWriter to which you should dump your state. This will be
+ * closed for you after you return.
+ * @param args additional arguments to the dump request.
+ */
+ @Override
+ public void dump(@NonNull String prefix, @Nullable FileDescriptor fd,
+ @NonNull PrintWriter writer, @Nullable String[] args) {
+ super.dump(prefix, fd, writer, args);
+ writer.print(prefix); writer.print("Local FragmentActivity ");
+ writer.print(Integer.toHexString(System.identityHashCode(this)));
+ writer.println(" State:");
+ String innerPrefix = prefix + " ";
+ writer.print(innerPrefix); writer.print("mCreated=");
+ writer.print(mCreated); writer.print(" mResumed=");
+ writer.print(mResumed); writer.print(" mStopped=");
+ writer.print(mStopped);
+
+ if (getApplication() != null) {
+ LoaderManager.getInstance(this).dump(innerPrefix, fd, writer, args);
+ }
+ mFragments.getSupportFragmentManager().dump(prefix, fd, writer, args);
+ }
+
+ // ------------------------------------------------------------------------
+ // FRAGMENT SUPPORT
+ // ------------------------------------------------------------------------
+
+ /**
+ * Called when a fragment is attached to the activity.
+ *
+ *
This is called after the attached fragment's onAttach and before
+ * the attached fragment's onCreate if the fragment has not yet had a previous
+ * call to onCreate.
+ */
+ @SuppressWarnings("unused")
+ public void onAttachFragment(@NonNull Fragment fragment) {
+ }
+
+ /**
+ * Return the FragmentManager for interacting with fragments associated
+ * with this activity.
+ */
+ @NonNull
+ public FragmentManager getSupportFragmentManager() {
+ return mFragments.getSupportFragmentManager();
+ }
+
+ /**
+ * @deprecated Use
+ * {@link LoaderManager#getInstance(LifecycleOwner) LoaderManager.getInstance(this)}.
+ */
+ @Deprecated
+ @NonNull
+ public LoaderManager getSupportLoaderManager() {
+ return LoaderManager.getInstance(this);
+ }
+
+ /**
+ * Modifies the standard behavior to allow results to be delivered to fragments.
+ * This imposes a restriction that requestCode be <= 0xffff.
+ */
+ @Override
+ public void startActivityForResult(@SuppressLint("UnknownNullness") Intent intent,
+ int requestCode) {
+ // If this was started from a Fragment we've already checked the upper 16 bits were not in
+ // use, and then repurposed them for the Fragment's index.
+ if (!mStartedActivityFromFragment) {
+ if (requestCode != -1) {
+ checkForValidRequestCode(requestCode);
+ }
+ }
+ super.startActivityForResult(intent, requestCode);
+ }
+
+ @Override
+ public void startActivityForResult(@SuppressLint("UnknownNullness") Intent intent,
+ int requestCode, @Nullable Bundle options) {
+ // If this was started from a Fragment we've already checked the upper 16 bits were not in
+ // use, and then repurposed them for the Fragment's index.
+ if (!mStartedActivityFromFragment) {
+ if (requestCode != -1) {
+ checkForValidRequestCode(requestCode);
+ }
+ }
+ super.startActivityForResult(intent, requestCode, options);
+ }
+
+ @Override
+ public void startIntentSenderForResult(@SuppressLint("UnknownNullness") IntentSender intent,
+ int requestCode, @Nullable Intent fillInIntent, int flagsMask,
+ int flagsValues, int extraFlags) throws IntentSender.SendIntentException {
+ // If this was started from a Fragment we've already checked the upper 16 bits were not in
+ // use, and then repurposed them for the Fragment's index.
+ if (!mStartedIntentSenderFromFragment) {
+ if (requestCode != -1) {
+ checkForValidRequestCode(requestCode);
+ }
+ }
+ super.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask, flagsValues,
+ extraFlags);
+ }
+
+ @Override
+ public void startIntentSenderForResult(@SuppressLint("UnknownNullness") IntentSender intent,
+ int requestCode, @Nullable Intent fillInIntent, int flagsMask, int flagsValues,
+ int extraFlags, @Nullable Bundle options) throws IntentSender.SendIntentException {
+ // If this was started from a Fragment we've already checked the upper 16 bits were not in
+ // use, and then repurposed them for the Fragment's index.
+ if (!mStartedIntentSenderFromFragment) {
+ if (requestCode != -1) {
+ checkForValidRequestCode(requestCode);
+ }
+ }
+ super.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask, flagsValues,
+ extraFlags, options);
+ }
+
+ /**
+ * Checks whether the given request code is a valid code by masking it with 0xffff0000. Throws
+ * an {@link IllegalArgumentException} if the code is not valid.
+ */
+ static void checkForValidRequestCode(int requestCode) {
+ if ((requestCode & 0xffff0000) != 0) {
+ throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
+ }
+ }
+
+ @Override
+ public final void validateRequestPermissionsRequestCode(int requestCode) {
+ // We use 16 bits of the request code to encode the fragment id when
+ // requesting permissions from a fragment. Hence, requestPermissions()
+ // should validate the code against that but we cannot override it as
+ // we can not then call super and also the ActivityCompat would call
+ // back to this override. To handle this we use dependency inversion
+ // where we are the validator of request codes when requesting
+ // permissions in ActivityCompat.
+ if (!mRequestedPermissionsFromFragment
+ && requestCode != -1) {
+ checkForValidRequestCode(requestCode);
+ }
+ }
+
+ /**
+ * Callback for the result from requesting permissions. This method
+ * is invoked for every call on {@link #requestPermissions(String[], int)}.
+ *
+ * Note: It is possible that the permissions request interaction
+ * with the user is interrupted. In this case you will receive empty permissions
+ * and results arrays which should be treated as a cancellation.
+ *
+ *
+ * @param requestCode The request code passed in {@link #requestPermissions(String[], int)}.
+ * @param permissions The requested permissions. Never null.
+ * @param grantResults The grant results for the corresponding permissions
+ * which is either {@link android.content.pm.PackageManager#PERMISSION_GRANTED}
+ * or {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null.
+ *
+ * @see #requestPermissions(String[], int)
+ */
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+ @NonNull int[] grantResults) {
+ mFragments.noteStateNotSaved();
+ int index = (requestCode >> 16) & 0xffff;
+ if (index != 0) {
+ index--;
+
+ String who = mPendingFragmentActivityResults.get(index);
+ mPendingFragmentActivityResults.remove(index);
+ if (who == null) {
+ Log.w(TAG, "Activity result delivered for unknown Fragment.");
+ return;
+ }
+ Fragment frag = mFragments.findFragmentByWho(who);
+ if (frag == null) {
+ Log.w(TAG, "Activity result no fragment exists for who: " + who);
+ } else {
+ frag.onRequestPermissionsResult(requestCode & 0xffff, permissions, grantResults);
+ }
+ }
+ }
+
+ /**
+ * Called by Fragment.startActivityForResult() to implement its behavior.
+ */
+ public void startActivityFromFragment(@NonNull Fragment fragment,
+ @SuppressLint("UnknownNullness") Intent intent, int requestCode) {
+ startActivityFromFragment(fragment, intent, requestCode, null);
+ }
+
+ /**
+ * Called by Fragment.startActivityForResult() to implement its behavior.
+ */
+ public void startActivityFromFragment(@NonNull Fragment fragment,
+ @SuppressLint("UnknownNullness") Intent intent, int requestCode,
+ @Nullable Bundle options) {
+ mStartedActivityFromFragment = true;
+ try {
+ if (requestCode == -1) {
+ ActivityCompat.startActivityForResult(this, intent, -1, options);
+ return;
+ }
+ checkForValidRequestCode(requestCode);
+ int requestIndex = allocateRequestIndex(fragment);
+ ActivityCompat.startActivityForResult(
+ this, intent, ((requestIndex + 1) << 16) + (requestCode & 0xffff), options);
+ } finally {
+ mStartedActivityFromFragment = false;
+ }
+ }
+
+ /**
+ * Called by Fragment.startIntentSenderForResult() to implement its behavior.
+ */
+ public void startIntentSenderFromFragment(@NonNull Fragment fragment,
+ @SuppressLint("UnknownNullness") IntentSender intent, int requestCode,
+ @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+ @Nullable Bundle options) throws IntentSender.SendIntentException {
+ mStartedIntentSenderFromFragment = true;
+ try {
+ if (requestCode == -1) {
+ ActivityCompat.startIntentSenderForResult(this, intent, requestCode, fillInIntent,
+ flagsMask, flagsValues, extraFlags, options);
+ return;
+ }
+ checkForValidRequestCode(requestCode);
+ int requestIndex = allocateRequestIndex(fragment);
+ ActivityCompat.startIntentSenderForResult(this, intent,
+ ((requestIndex + 1) << 16) + (requestCode & 0xffff), fillInIntent,
+ flagsMask, flagsValues, extraFlags, options);
+ } finally {
+ mStartedIntentSenderFromFragment = false;
+ }
+ }
+
+ // Allocates the next available startActivityForResult request index.
+ private int allocateRequestIndex(@NonNull Fragment fragment) {
+ // Sanity check that we havn't exhaused the request index space.
+ if (mPendingFragmentActivityResults.size() >= MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS) {
+ throw new IllegalStateException("Too many pending Fragment activity results.");
+ }
+
+ // Find an unallocated request index in the mPendingFragmentActivityResults map.
+ while (mPendingFragmentActivityResults.indexOfKey(mNextCandidateRequestIndex) >= 0) {
+ mNextCandidateRequestIndex =
+ (mNextCandidateRequestIndex + 1) % MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS;
+ }
+
+ int requestIndex = mNextCandidateRequestIndex;
+ mPendingFragmentActivityResults.put(requestIndex, fragment.mWho);
+ mNextCandidateRequestIndex =
+ (mNextCandidateRequestIndex + 1) % MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS;
+
+ return requestIndex;
+ }
+
+ /**
+ * Called by Fragment.requestPermissions() to implement its behavior.
+ */
+ void requestPermissionsFromFragment(@NonNull Fragment fragment, @NonNull String[] permissions,
+ int requestCode) {
+ if (requestCode == -1) {
+ ActivityCompat.requestPermissions(this, permissions, requestCode);
+ return;
+ }
+ checkForValidRequestCode(requestCode);
+ try {
+ mRequestedPermissionsFromFragment = true;
+ int requestIndex = allocateRequestIndex(fragment);
+ ActivityCompat.requestPermissions(this, permissions,
+ ((requestIndex + 1) << 16) + (requestCode & 0xffff));
+ } finally {
+ mRequestedPermissionsFromFragment = false;
+ }
+ }
+
+ class HostCallbacks extends FragmentHostCallback implements
+ ViewModelStoreOwner,
+ OnBackPressedDispatcherOwner {
+ public HostCallbacks() {
+ super(FragmentActivity.this /*fragmentActivity*/);
+ }
+
+ @NonNull
+ @Override
+ public Lifecycle getLifecycle() {
+ // Instead of directly using the Activity's Lifecycle, we
+ // use a LifecycleRegistry that is nested exactly outside of
+ // when Fragments get their lifecycle changed
+ // TODO(b/127528777) Drive Fragment Lifecycle with LifecycleObserver
+ return mFragmentLifecycleRegistry;
+ }
+
+ @NonNull
+ @Override
+ public ViewModelStore getViewModelStore() {
+ return FragmentActivity.this.getViewModelStore();
+ }
+
+ @NonNull
+ @Override
+ public OnBackPressedDispatcher getOnBackPressedDispatcher() {
+ return FragmentActivity.this.getOnBackPressedDispatcher();
+ }
+
+ @Override
+ public void onDump(@NonNull String prefix, @Nullable FileDescriptor fd,
+ @NonNull PrintWriter writer, @Nullable String[] args) {
+ FragmentActivity.this.dump(prefix, fd, writer, args);
+ }
+
+ @Override
+ public boolean onShouldSaveFragmentState(@NonNull Fragment fragment) {
+ return !isFinishing();
+ }
+
+ @Override
+ @NonNull
+ public LayoutInflater onGetLayoutInflater() {
+ return FragmentActivity.this.getLayoutInflater().cloneInContext(FragmentActivity.this);
+ }
+
+ @Override
+ public FragmentActivity onGetHost() {
+ return FragmentActivity.this;
+ }
+
+ @Override
+ public void onSupportInvalidateOptionsMenu() {
+ FragmentActivity.this.supportInvalidateOptionsMenu();
+ }
+
+ @Override
+ public void onStartActivityFromFragment(@NonNull Fragment fragment, Intent intent,
+ int requestCode) {
+ FragmentActivity.this.startActivityFromFragment(fragment, intent, requestCode);
+ }
+
+ @Override
+ public void onStartActivityFromFragment(@NonNull Fragment fragment, Intent intent,
+ int requestCode, @Nullable Bundle options) {
+ FragmentActivity.this.startActivityFromFragment(fragment, intent, requestCode, options);
+ }
+
+ @Override
+ public void onStartIntentSenderFromFragment(
+ @NonNull Fragment fragment, IntentSender intent, int requestCode,
+ @Nullable Intent fillInIntent, int flagsMask, int flagsValues,
+ int extraFlags, Bundle options) throws IntentSender.SendIntentException {
+ FragmentActivity.this.startIntentSenderFromFragment(fragment, intent, requestCode,
+ fillInIntent, flagsMask, flagsValues, extraFlags, options);
+ }
+
+ @Override
+ public void onRequestPermissionsFromFragment(@NonNull Fragment fragment,
+ @NonNull String[] permissions, int requestCode) {
+ FragmentActivity.this.requestPermissionsFromFragment(fragment, permissions,
+ requestCode);
+ }
+
+ @Override
+ public boolean onShouldShowRequestPermissionRationale(@NonNull String permission) {
+ return ActivityCompat.shouldShowRequestPermissionRationale(
+ FragmentActivity.this, permission);
+ }
+
+ @Override
+ public boolean onHasWindowAnimations() {
+ return getWindow() != null;
+ }
+
+ @Override
+ public int onGetWindowAnimations() {
+ final Window w = getWindow();
+ return (w == null) ? 0 : w.getAttributes().windowAnimations;
+ }
+
+ @Override
+ public void onAttachFragment(@NonNull Fragment fragment) {
+ FragmentActivity.this.onAttachFragment(fragment);
+ }
+
+ @Nullable
+ @Override
+ public View onFindViewById(int id) {
+ return FragmentActivity.this.findViewById(id);
+ }
+
+ @Override
+ public boolean onHasView() {
+ final Window w = getWindow();
+ return (w != null && w.peekDecorView() != null);
+ }
+ }
+
+ private void markFragmentsCreated() {
+ boolean reiterate;
+ do {
+ reiterate = markState(getSupportFragmentManager(), Lifecycle.State.CREATED);
+ } while (reiterate);
+ }
+
+ private static boolean markState(FragmentManager manager, Lifecycle.State state) {
+ boolean hadNotMarked = false;
+ Collection fragments = manager.getFragments();
+ for (Fragment fragment : fragments) {
+ if (fragment == null) {
+ continue;
+ }
+ if (fragment.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
+ fragment.mLifecycleRegistry.setCurrentState(state);
+ hadNotMarked = true;
+ }
+
+ if (fragment.getHost() != null) {
+ FragmentManager childFragmentManager = fragment.getChildFragmentManager();
+ hadNotMarked |= markState(childFragmentManager, state);
+ }
+ }
+ return hadNotMarked;
+ }
+}
diff --git a/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentContainer.java b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentContainer.java
new file mode 100644
index 000000000..2afb8f76e
--- /dev/null
+++ b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentContainer.java
@@ -0,0 +1,59 @@
+/*
+ * 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 android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+
+/**
+ * Callbacks to a {@link Fragment}'s container.
+ */
+public abstract class FragmentContainer {
+ /**
+ * Return the view with the given resource ID. May return {@code null} if the
+ * view is not a child of this container.
+ */
+ @Nullable
+ public abstract View onFindViewById(@IdRes int id);
+
+ /**
+ * Return {@code true} if the container holds any view.
+ */
+ public abstract boolean onHasView();
+
+
+ /**
+ * Creates an instance of the specified fragment, can be overridden to construct fragments
+ * with dependencies, or change the fragment being constructed. By default just calls
+ * {@link Fragment#instantiate(Context, String, Bundle)}.
+ * @deprecated Use {@link FragmentManager#setFragmentFactory} to control how Fragments are
+ * instantiated.
+ */
+ @SuppressWarnings("deprecation")
+ @Deprecated
+ @NonNull
+ public Fragment instantiate(@NonNull Context context, @NonNull String className,
+ @Nullable Bundle arguments) {
+ return Fragment.instantiate(context, className, arguments);
+ }
+}
diff --git a/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentController.java b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentController.java
new file mode 100644
index 000000000..5b5e07f76
--- /dev/null
+++ b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentController.java
@@ -0,0 +1,532 @@
+/*
+ * 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.core.util.Preconditions.checkNotNull;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.collection.SimpleArrayMap;
+import androidx.lifecycle.ViewModelStoreOwner;
+import androidx.loader.app.LoaderManager;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provides integration points with a {@link FragmentManager} for a fragment host.
+ *
+ * It is the responsibility of the host to take care of the Fragment's lifecycle.
+ * The methods provided by {@link FragmentController} are for that purpose.
+ */
+public class FragmentController {
+ private final FragmentHostCallback> mHost;
+
+ /**
+ * Returns a {@link FragmentController}.
+ */
+ @NonNull
+ public static FragmentController createController(@NonNull FragmentHostCallback> callbacks) {
+ return new FragmentController(checkNotNull(callbacks, "callbacks == null"));
+ }
+
+ private FragmentController(FragmentHostCallback> callbacks) {
+ mHost = callbacks;
+ }
+
+ /**
+ * Returns a {@link FragmentManager} for this controller.
+ */
+ @NonNull
+ public FragmentManager getSupportFragmentManager() {
+ return mHost.mFragmentManager;
+ }
+
+ /**
+ * Returns a {@link LoaderManager}.
+ *
+ * @deprecated Loaders are managed separately from FragmentController and this now throws an
+ * {@link UnsupportedOperationException}. Use {@link LoaderManager#getInstance} to obtain a
+ * LoaderManager.
+ * @see LoaderManager#getInstance
+ */
+ @Deprecated
+ @SuppressLint("UnknownNullness")
+ public LoaderManager getSupportLoaderManager() {
+ throw new UnsupportedOperationException("Loaders are managed separately from "
+ + "FragmentController, use LoaderManager.getInstance() to obtain a LoaderManager.");
+ }
+
+ /**
+ * Returns a fragment with the given identifier.
+ */
+ @Nullable
+ public Fragment findFragmentByWho(@NonNull String who) {
+ return mHost.mFragmentManager.findFragmentByWho(who);
+ }
+
+ /**
+ * Returns the number of active fragments.
+ */
+ public int getActiveFragmentsCount() {
+ return mHost.mFragmentManager.getActiveFragmentCount();
+ }
+
+ /**
+ * Returns the list of active fragments.
+ */
+ @NonNull
+ public List getActiveFragments(@SuppressLint("UnknownNullness")
+ List actives) {
+ return mHost.mFragmentManager.getActiveFragments();
+ }
+
+ /**
+ * Attaches the host to the FragmentManager for this controller. The host must be
+ * attached before the FragmentManager can be used to manage Fragments.
+ */
+ public void attachHost(@Nullable Fragment parent) {
+ mHost.mFragmentManager.attachController(
+ mHost, mHost /*container*/, parent);
+ }
+
+ /**
+ * Instantiates a Fragment's view.
+ *
+ * @param parent The parent that the created view will be placed
+ * in; note that this may be null.
+ * @param name Tag name to be inflated.
+ * @param context The context the view is being created in.
+ * @param attrs Inflation attributes as specified in XML file.
+ *
+ * @return view the newly created view
+ */
+ @Nullable
+ public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context,
+ @NonNull AttributeSet attrs) {
+ return mHost.mFragmentManager.onCreateView(parent, name, context, attrs);
+ }
+
+ /**
+ * Marks the fragment state as unsaved. This allows for "state loss" detection.
+ */
+ public void noteStateNotSaved() {
+ mHost.mFragmentManager.noteStateNotSaved();
+ }
+
+ /**
+ * Saves the state for all Fragments.
+ *
+ * @see #restoreSaveState(Parcelable)
+ */
+ @Nullable
+ public Parcelable saveAllState() {
+ return mHost.mFragmentManager.saveAllState();
+ }
+
+ /**
+ * Restores the saved state for all Fragments. The given Fragment list are Fragment
+ * instances retained across configuration changes.
+ *
+ * @deprecated Have your {@link FragmentHostCallback} implement {@link ViewModelStoreOwner}
+ * to automatically restore the Fragment's non configuration state and use
+ * {@link #restoreSaveState(Parcelable)} to restore the Fragment's save state.
+ */
+ @Deprecated
+ public void restoreAllState(@Nullable Parcelable state,
+ @Nullable List nonConfigList) {
+ mHost.mFragmentManager.restoreAllState(state,
+ new FragmentManagerNonConfig(nonConfigList, null, null));
+ }
+
+ /**
+ * Restores the saved state for all Fragments. The given FragmentManagerNonConfig are Fragment
+ * instances retained across configuration changes, including nested fragments
+ *
+ * @deprecated Have your {@link FragmentHostCallback} implement {@link ViewModelStoreOwner}
+ * to automatically restore the Fragment's non configuration state and use
+ * {@link #restoreSaveState(Parcelable)} to restore the Fragment's save state.
+ */
+ @Deprecated
+ public void restoreAllState(@Nullable Parcelable state,
+ @Nullable FragmentManagerNonConfig nonConfig) {
+ mHost.mFragmentManager.restoreAllState(state, nonConfig);
+ }
+
+ /**
+ * Restores the saved state for all Fragments.
+ *
+ * @param state the saved state containing the Parcelable returned by {@link #saveAllState()}
+ * @see #saveAllState()
+ */
+ public void restoreSaveState(@Nullable Parcelable state) {
+ if (!(mHost instanceof ViewModelStoreOwner)) {
+ throw new IllegalStateException("Your FragmentHostCallback must implement "
+ + "ViewModelStoreOwner to call restoreSaveState(). Call restoreAllState() "
+ + " if you're still using retainNestedNonConfig().");
+ }
+ mHost.mFragmentManager.restoreSaveState(state);
+ }
+
+ /**
+ * Returns a list of Fragments that have opted to retain their instance across
+ * configuration changes.
+ *
+ * @deprecated Have your {@link FragmentHostCallback} implement {@link ViewModelStoreOwner}
+ * to automatically retain the Fragment's non configuration state.
+ */
+ @Deprecated
+ @Nullable
+ public List retainNonConfig() {
+ FragmentManagerNonConfig nonconf = mHost.mFragmentManager.retainNonConfig();
+ return nonconf != null && nonconf.getFragments() != null
+ ? new ArrayList<>(nonconf.getFragments())
+ : null;
+ }
+
+ /**
+ * Returns a nested tree of Fragments that have opted to retain their instance across
+ * configuration changes.
+ *
+ * @deprecated Have your {@link FragmentHostCallback} implement {@link ViewModelStoreOwner}
+ * to automatically retain the Fragment's non configuration state.
+ */
+ @Deprecated
+ @Nullable
+ public FragmentManagerNonConfig retainNestedNonConfig() {
+ return mHost.mFragmentManager.retainNonConfig();
+ }
+
+ /**
+ * Moves all Fragments managed by the controller's FragmentManager
+ * into the create state.
+ *
Call when Fragments should be created.
+ *
+ * @see Fragment#onCreate(Bundle)
+ */
+ public void dispatchCreate() {
+ mHost.mFragmentManager.dispatchCreate();
+ }
+
+ /**
+ * Moves all Fragments managed by the controller's FragmentManager
+ * into the activity created state.
+ *
Call when Fragments should be informed their host has been created.
+ *
+ * @see Fragment#onActivityCreated(Bundle)
+ */
+ public void dispatchActivityCreated() {
+ mHost.mFragmentManager.dispatchActivityCreated();
+ }
+
+ /**
+ * Moves all Fragments managed by the controller's FragmentManager
+ * into the start state.
+ *
Call when Fragments should be started.
+ *
+ * @see Fragment#onStart()
+ */
+ public void dispatchStart() {
+ mHost.mFragmentManager.dispatchStart();
+ }
+
+ /**
+ * Moves all Fragments managed by the controller's FragmentManager
+ * into the resume state.
+ *
Call when Fragments should be resumed.
+ *
+ * @see Fragment#onResume()
+ */
+ public void dispatchResume() {
+ mHost.mFragmentManager.dispatchResume();
+ }
+
+ /**
+ * Moves all Fragments managed by the controller's FragmentManager
+ * into the pause state.
+ *
Call when Fragments should be paused.
+ *
+ * @see Fragment#onPause()
+ */
+ public void dispatchPause() {
+ mHost.mFragmentManager.dispatchPause();
+ }
+
+ /**
+ * Moves all Fragments managed by the controller's FragmentManager
+ * into the stop state.
+ *
Call when Fragments should be stopped.
+ *
+ * @see Fragment#onStop()
+ */
+ public void dispatchStop() {
+ mHost.mFragmentManager.dispatchStop();
+ }
+
+ /**
+ * @deprecated This functionality has been rolled into {@link #dispatchStop()}.
+ */
+ @Deprecated
+ public void dispatchReallyStop() {
+ }
+
+ /**
+ * Moves all Fragments managed by the controller's FragmentManager
+ * into the destroy view state.
+ *
Call when the Fragment's views should be destroyed.
+ *
+ * @see Fragment#onDestroyView()
+ */
+ public void dispatchDestroyView() {
+ mHost.mFragmentManager.dispatchDestroyView();
+ }
+
+ /**
+ * Moves Fragments managed by the controller's FragmentManager
+ * into the destroy state.
+ *
+ * If the {@link androidx.fragment.app.FragmentHostCallback} is an instance of {@link ViewModelStoreOwner},
+ * then retained Fragments and any other non configuration state such as any
+ * {@link androidx.lifecycle.ViewModel} attached to Fragments will only be destroyed if
+ * {@link androidx.lifecycle.ViewModelStore#clear()} is called prior to this method.
+ *
+ * Otherwise, the FragmentManager will look to see if the
+ * {@link FragmentHostCallback host's} Context is an {@link Activity}
+ * and if {@link Activity#isChangingConfigurations()} returns true. In only that case
+ * will non configuration state be retained.
+ *
Call when Fragments should be destroyed.
+ *
+ * @see Fragment#onDestroy()
+ */
+ public void dispatchDestroy() {
+ mHost.mFragmentManager.dispatchDestroy();
+ }
+
+ /**
+ * Lets all Fragments managed by the controller's FragmentManager know the multi-window mode of
+ * the activity changed.
+ *
Call when the multi-window mode of the activity changed.
+ *
+ * @see Fragment#onMultiWindowModeChanged
+ */
+ public void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode) {
+ mHost.mFragmentManager.dispatchMultiWindowModeChanged(isInMultiWindowMode);
+ }
+
+ /**
+ * Lets all Fragments managed by the controller's FragmentManager know the picture-in-picture
+ * mode of the activity changed.
+ *
Call when the picture-in-picture mode of the activity changed.
+ *
+ * @see Fragment#onPictureInPictureModeChanged
+ */
+ public void dispatchPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ mHost.mFragmentManager.dispatchPictureInPictureModeChanged(isInPictureInPictureMode);
+ }
+
+ /**
+ * Lets all Fragments managed by the controller's FragmentManager
+ * know a configuration change occurred.
+ *
Call when there is a configuration change.
+ *
+ * @see Fragment#onConfigurationChanged(Configuration)
+ */
+ public void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
+ mHost.mFragmentManager.dispatchConfigurationChanged(newConfig);
+ }
+
+ /**
+ * Lets all Fragments managed by the controller's FragmentManager
+ * know the device is in a low memory condition.
+ *
Call when the device is low on memory and Fragment's should trim
+ * their memory usage.
+ *
+ * @see Fragment#onLowMemory()
+ */
+ public void dispatchLowMemory() {
+ mHost.mFragmentManager.dispatchLowMemory();
+ }
+
+ /**
+ * Lets all Fragments managed by the controller's FragmentManager
+ * know they should create an options menu.
+ *
Call when the Fragment should create an options menu.
+ *
+ * @return {@code true} if the options menu contains items to display
+ * @see Fragment#onCreateOptionsMenu(Menu, MenuInflater)
+ */
+ public boolean dispatchCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
+ return mHost.mFragmentManager.dispatchCreateOptionsMenu(menu, inflater);
+ }
+
+ /**
+ * Lets all Fragments managed by the controller's FragmentManager
+ * know they should prepare their options menu for display.
+ *
Call immediately before displaying the Fragment's options menu.
+ *
+ * @return {@code true} if the options menu contains items to display
+ * @see Fragment#onPrepareOptionsMenu(Menu)
+ */
+ public boolean dispatchPrepareOptionsMenu(@NonNull Menu menu) {
+ return mHost.mFragmentManager.dispatchPrepareOptionsMenu(menu);
+ }
+
+ /**
+ * Sends an option item selection event to the Fragments managed by the
+ * controller's FragmentManager. Once the event has been consumed,
+ * no additional handling will be performed.
+ *
Call immediately after an options menu item has been selected
+ *
+ * @return {@code true} if the options menu selection event was consumed
+ * @see Fragment#onOptionsItemSelected(MenuItem)
+ */
+ public boolean dispatchOptionsItemSelected(@NonNull MenuItem item) {
+ return mHost.mFragmentManager.dispatchOptionsItemSelected(item);
+ }
+
+ /**
+ * Sends a context item selection event to the Fragments managed by the
+ * controller's FragmentManager. Once the event has been consumed,
+ * no additional handling will be performed.
+ *
Call immediately after an options menu item has been selected
+ *
+ * @return {@code true} if the context menu selection event was consumed
+ * @see Fragment#onContextItemSelected(MenuItem)
+ */
+ public boolean dispatchContextItemSelected(@NonNull MenuItem item) {
+ return mHost.mFragmentManager.dispatchContextItemSelected(item);
+ }
+
+ /**
+ * Lets all Fragments managed by the controller's FragmentManager
+ * know their options menu has closed.
+ *
Call immediately after closing the Fragment's options menu.
+ *
+ * @see Fragment#onOptionsMenuClosed(Menu)
+ */
+ public void dispatchOptionsMenuClosed(@NonNull Menu menu) {
+ mHost.mFragmentManager.dispatchOptionsMenuClosed(menu);
+ }
+
+ /**
+ * Execute any pending actions for the Fragments managed by the
+ * controller's FragmentManager.
+ *
Call when queued actions can be performed [eg when the
+ * Fragment moves into a start or resume state].
+ * @return {@code true} if queued actions were performed
+ */
+ public boolean execPendingActions() {
+ return mHost.mFragmentManager.execPendingActions();
+ }
+
+ /**
+ * Starts the loaders.
+ *
+ * @deprecated Loaders are managed separately from FragmentController
+ */
+ @Deprecated
+ public void doLoaderStart() {
+ }
+
+ /**
+ * Stops the loaders, optionally retaining their state. This is useful for keeping the
+ * loader state across configuration changes.
+ *
+ * @param retain When {@code true}, the loaders aren't stopped, but, their instances
+ * are retained in a started state
+ *
+ * @deprecated Loaders are managed separately from FragmentController
+ */
+ @Deprecated
+ public void doLoaderStop(boolean retain) {
+ }
+
+ /**
+ * Retains the state of each of the loaders.
+ *
+ * @deprecated Loaders are managed separately from FragmentController
+ */
+ @Deprecated
+ public void doLoaderRetain() {
+ }
+
+ /**
+ * Destroys the loaders and, if their state is not being retained, removes them.
+ *
+ * @deprecated Loaders are managed separately from FragmentController
+ */
+ @Deprecated
+ public void doLoaderDestroy() {
+ }
+
+ /**
+ * Lets the loaders know the host is ready to receive notifications.
+ *
+ * @deprecated Loaders are managed separately from FragmentController
+ */
+ @Deprecated
+ public void reportLoaderStart() {
+ }
+
+ /**
+ * Returns a list of LoaderManagers that have opted to retain their instance across
+ * configuration changes.
+ *
+ * @deprecated Loaders are managed separately from FragmentController
+ */
+ @Deprecated
+ @Nullable
+ public SimpleArrayMap retainLoaderNonConfig() {
+ return null;
+ }
+
+ /**
+ * Restores the saved state for all LoaderManagers. The given LoaderManager list are
+ * LoaderManager instances retained across configuration changes.
+ *
+ * @see #retainLoaderNonConfig()
+ *
+ * @deprecated Loaders are managed separately from FragmentController
+ */
+ @Deprecated
+ public void restoreLoaderNonConfig(@SuppressLint("UnknownNullness")
+ SimpleArrayMap loaderManagers) {
+ }
+
+ /**
+ * Dumps the current state of the loaders.
+ *
+ * @deprecated Loaders are managed separately from FragmentController
+ */
+ @Deprecated
+ public void dumpLoaders(@NonNull String prefix, @Nullable FileDescriptor fd,
+ @NonNull PrintWriter writer, @Nullable String[] args) {
+ }
+}
diff --git a/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentFactory.java b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentFactory.java
new file mode 100644
index 000000000..8214fc96b
--- /dev/null
+++ b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentFactory.java
@@ -0,0 +1,132 @@
+/*
+ * 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 androidx.annotation.NonNull;
+import androidx.collection.SimpleArrayMap;
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Interface used to control the instantiation of {@link Fragment} instances.
+ * Implementations can be registered with a {@link FragmentManager} via
+ * {@link FragmentManager#setFragmentFactory(FragmentFactory)}.
+ *
+ * @see FragmentManager#setFragmentFactory(FragmentFactory)
+ */
+public class FragmentFactory {
+ private static final SimpleArrayMap> sClassMap = new SimpleArrayMap<>();
+
+ /**
+ * Determine if the given fragment name is a support library fragment class.
+ *
+ * @param classLoader The default classloader to use for loading the Class
+ * @param className Class name of the fragment to load
+ * @return Returns the parsed Class
+ */
+ @NonNull
+ private static Class> loadClass(@NonNull ClassLoader classLoader,
+ @NonNull String className) throws ClassNotFoundException {
+ Class> clazz = sClassMap.get(className);
+ if (clazz == null) {
+ // Class not found in the cache, see if it's real, and try to add it
+ clazz = Class.forName(className, false, classLoader);
+ sClassMap.put(className, clazz);
+ }
+ return clazz;
+ }
+
+ /**
+ * Determine if the given fragment name is a valid Fragment class.
+ *
+ * @param classLoader The default classloader to use for loading the Class
+ * @param className Class name of the fragment to test
+ * @return true if className is androidx.fragment.app.Fragment
+ * or a subclass, false otherwise.
+ */
+ static boolean isFragmentClass(@NonNull ClassLoader classLoader,
+ @NonNull String className) {
+ try {
+ Class> clazz = loadClass(classLoader, className);
+ return Fragment.class.isAssignableFrom(clazz);
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Parse a Fragment Class from the given class name. The resulting Class is kept in a global
+ * cache, bypassing the {@link Class#forName(String)} calls when passed the same
+ * class name again.
+ *
+ * @param classLoader The default classloader to use for loading the Class
+ * @param className The class name of the fragment to parse.
+ * @return Returns the parsed Fragment Class
+ * @throws Fragment.InstantiationException If there is a failure in parsing
+ * the given fragment class. This is a runtime exception; it is not
+ * normally expected to happen.
+ */
+ @SuppressWarnings("unchecked")
+ @NonNull
+ public static Class extends Fragment> loadFragmentClass(@NonNull ClassLoader classLoader,
+ @NonNull String className) {
+ try {
+ Class> clazz = loadClass(classLoader, className);
+ return (Class extends Fragment>) clazz;
+ } catch (ClassNotFoundException e) {
+ throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
+ + ": make sure class name exists", e);
+ } catch (ClassCastException e) {
+ throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
+ + ": make sure class is a valid subclass of Fragment", e);
+ }
+ }
+
+ /**
+ * Create a new instance of a Fragment with the given class name. This uses
+ * {@link #loadFragmentClass(ClassLoader, String)} and the empty
+ * constructor of the resulting Class by default.
+ *
+ * @param classLoader The default classloader to use for instantiation
+ * @param className The class name of the fragment to instantiate.
+ * @return Returns a new fragment instance.
+ * @throws Fragment.InstantiationException If there is a failure in instantiating
+ * the given fragment class. This is a runtime exception; it is not
+ * normally expected to happen.
+ */
+ @NonNull
+ public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
+ try {
+ Class extends Fragment> cls = loadFragmentClass(classLoader, className);
+ return cls.getConstructor().newInstance();
+ } catch (java.lang.InstantiationException e) {
+ throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
+ + ": make sure class name exists, is public, and has an"
+ + " empty constructor that is public", e);
+ } catch (IllegalAccessException e) {
+ throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
+ + ": make sure class name exists, is public, and has an"
+ + " empty constructor that is public", e);
+ } catch (NoSuchMethodException e) {
+ throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
+ + ": could not find Fragment constructor", e);
+ } catch (InvocationTargetException e) {
+ throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
+ + ": calling Fragment constructor caused an exception", e);
+ }
+ }
+}
diff --git a/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentHostCallback.java b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentHostCallback.java
new file mode 100644
index 000000000..f9d820f08
--- /dev/null
+++ b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentHostCallback.java
@@ -0,0 +1,210 @@
+/*
+ * 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 android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.app.ActivityCompat;
+import androidx.core.util.Preconditions;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Integration points with the Fragment host.
+ *
+ * Fragments may be hosted by any object; such as an {@link Activity}. In order to
+ * host fragments, implement {@link FragmentHostCallback}, overriding the methods
+ * applicable to the host.
+ */
+public abstract class FragmentHostCallback extends FragmentContainer {
+ @Nullable private final Activity mActivity;
+ @NonNull private final Context mContext;
+ @NonNull private final Handler mHandler;
+ private final int mWindowAnimations;
+ final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();
+
+ public FragmentHostCallback(@NonNull Context context, @NonNull Handler handler,
+ int windowAnimations) {
+ this(context instanceof Activity ? (Activity) context : null, context, handler,
+ windowAnimations);
+ }
+
+ FragmentHostCallback(@NonNull FragmentActivity activity) {
+ this(activity, activity /*context*/, new Handler(), 0 /*windowAnimations*/);
+ }
+
+ FragmentHostCallback(@Nullable Activity activity, @NonNull Context context,
+ @NonNull Handler handler, int windowAnimations) {
+ mActivity = activity;
+ mContext = Preconditions.checkNotNull(context, "context == null");
+ mHandler = Preconditions.checkNotNull(handler, "handler == null");
+ mWindowAnimations = windowAnimations;
+ }
+
+ /**
+ * Print internal state into the given stream.
+ *
+ * @param prefix Desired prefix to prepend at each line of output.
+ * @param fd The raw file descriptor that the dump is being sent to.
+ * @param writer The PrintWriter to which you should dump your state. This will be closed
+ * for you after you return.
+ * @param args additional arguments to the dump request.
+ */
+ public void onDump(@NonNull String prefix, @Nullable FileDescriptor fd,
+ @NonNull PrintWriter writer, @Nullable String[] args) {
+ }
+
+ /**
+ * Return {@code true} if the fragment's state needs to be saved.
+ */
+ public boolean onShouldSaveFragmentState(@NonNull Fragment fragment) {
+ return true;
+ }
+
+ /**
+ * Return a {@link LayoutInflater}.
+ * See {@link Activity#getLayoutInflater()}.
+ */
+ @NonNull
+ public LayoutInflater onGetLayoutInflater() {
+ return LayoutInflater.from(mContext);
+ }
+
+ /**
+ * Return the object that's currently hosting the fragment. If a {@link Fragment}
+ * is hosted by a {@link FragmentActivity}, the object returned here should be
+ * the same object returned from {@link Fragment#getActivity()}.
+ */
+ @Nullable
+ public abstract E onGetHost();
+
+ /**
+ * Invalidates the activity's options menu.
+ * See {@link FragmentActivity#supportInvalidateOptionsMenu()}
+ */
+ public void onSupportInvalidateOptionsMenu() {
+ }
+
+ /**
+ * Starts a new {@link Activity} from the given fragment.
+ * See {@link FragmentActivity#startActivityForResult(Intent, int)}.
+ */
+ public void onStartActivityFromFragment(@NonNull Fragment fragment,
+ @SuppressLint("UnknownNullness") Intent intent, int requestCode) {
+ onStartActivityFromFragment(fragment, intent, requestCode, null);
+ }
+
+ /**
+ * Starts a new {@link Activity} from the given fragment.
+ * See {@link FragmentActivity#startActivityForResult(Intent, int, Bundle)}.
+ */
+ public void onStartActivityFromFragment(
+ @NonNull Fragment fragment, @SuppressLint("UnknownNullness") Intent intent,
+ int requestCode, @Nullable Bundle options) {
+ if (requestCode != -1) {
+ throw new IllegalStateException(
+ "Starting activity with a requestCode requires a FragmentActivity host");
+ }
+ mContext.startActivity(intent);
+ }
+
+ /**
+ * Starts a new {@link IntentSender} from the given fragment.
+ * See {@link Activity#startIntentSender(IntentSender, Intent, int, int, int, Bundle)}.
+ */
+ public void onStartIntentSenderFromFragment(@NonNull Fragment fragment,
+ @SuppressLint("UnknownNullness") IntentSender intent, int requestCode,
+ @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+ @Nullable Bundle options) throws IntentSender.SendIntentException {
+ if (requestCode != -1) {
+ throw new IllegalStateException(
+ "Starting intent sender with a requestCode requires a FragmentActivity host");
+ }
+ ActivityCompat.startIntentSenderForResult(mActivity, intent, requestCode, fillInIntent,
+ flagsMask, flagsValues, extraFlags, options);
+ }
+
+ /**
+ * Requests permissions from the given fragment.
+ * See {@link FragmentActivity#requestPermissions(String[], int)}
+ */
+ public void onRequestPermissionsFromFragment(@NonNull Fragment fragment,
+ @NonNull String[] permissions, int requestCode) {
+ }
+
+ /**
+ * Checks whether to show permission rationale UI from a fragment.
+ * See {@link FragmentActivity#shouldShowRequestPermissionRationale(String)}
+ */
+ public boolean onShouldShowRequestPermissionRationale(@NonNull String permission) {
+ return false;
+ }
+
+ /**
+ * Return {@code true} if there are window animations.
+ */
+ public boolean onHasWindowAnimations() {
+ return true;
+ }
+
+ /**
+ * Return the window animations.
+ */
+ public int onGetWindowAnimations() {
+ return mWindowAnimations;
+ }
+
+ @Nullable
+ @Override
+ public View onFindViewById(int id) {
+ return null;
+ }
+
+ @Override
+ public boolean onHasView() {
+ return true;
+ }
+
+ @Nullable
+ Activity getActivity() {
+ return mActivity;
+ }
+
+ @NonNull
+ Context getContext() {
+ return mContext;
+ }
+
+ @NonNull
+ Handler getHandler() {
+ return mHandler;
+ }
+
+ void onAttachFragment(@NonNull Fragment fragment) {
+ }
+}
diff --git a/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentManager.java b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentManager.java
new file mode 100644
index 000000000..446002c06
--- /dev/null
+++ b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -0,0 +1,601 @@
+/*
+ * 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 android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.StringRes;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * 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.
+ *
+ *
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 {
+ static final FragmentFactory DEFAULT_FACTORY = new FragmentFactory();
+
+ /**
+ * 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()}.
+ *
+ *
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.
+ */
+ public int getId();
+
+ /**
+ * Get the name that was supplied to
+ * {@link FragmentTransaction#addToBackStack(String)
+ * FragmentTransaction.addToBackStack(String)} when creating this entry.
+ */
+ @Nullable
+ public String getName();
+
+ /**
+ * Return the full bread crumb title resource identifier for the entry,
+ * or 0 if it does not have one.
+ */
+ @StringRes
+ public int getBreadCrumbTitleRes();
+
+ /**
+ * Return the short bread crumb title resource identifier for the entry,
+ * or 0 if it does not have one.
+ */
+ @StringRes
+ public int getBreadCrumbShortTitleRes();
+
+ /**
+ * Return the full bread crumb title for the entry, or null if it
+ * does not have one.
+ */
+ @Nullable
+ public CharSequence getBreadCrumbTitle();
+
+ /**
+ * Return the short bread crumb title for the entry, or null if it
+ * does not have one.
+ */
+ @Nullable
+ public CharSequence getBreadCrumbShortTitle();
+ }
+
+ /**
+ * Interface to watch for changes to the back stack.
+ */
+ public interface OnBackStackChangedListener {
+ /**
+ * Called whenever the contents of the back stack change.
+ */
+ public void onBackStackChanged();
+ }
+
+ private FragmentFactory mFragmentFactory = null;
+
+ /**
+ * 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 {@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.
+ */
+ @NonNull
+ public abstract FragmentTransaction beginTransaction();
+
+ /**
+ * @hide -- remove once prebuilts are in.
+ * @deprecated
+ */
+ @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @Deprecated
+ @NonNull
+ public FragmentTransaction openTransaction() {
+ return beginTransaction();
+ }
+
+ /**
+ * 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.
+ *
+ *
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.
+ *
+ * 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.
+ */
+ public abstract boolean executePendingTransactions();
+
+ /**
+ * 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 abstract Fragment findFragmentById(@IdRes int 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.
+ * @return The fragment if found or null otherwise.
+ */
+ @Nullable
+ public abstract Fragment findFragmentByTag(@Nullable String tag);
+
+ /**
+ * 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<<0;
+
+ /**
+ * 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 abstract void popBackStack();
+
+ /**
+ * 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.
+ */
+ public abstract boolean popBackStackImmediate();
+
+ /**
+ * 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 abstract void popBackStack(@Nullable String name, int flags);
+
+ /**
+ * 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.
+ */
+ public abstract boolean popBackStackImmediate(@Nullable String name, 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.
+ *
+ * @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 abstract void popBackStack(int id, int flags);
+
+ /**
+ * 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 abstract boolean popBackStackImmediate(int id, int flags);
+
+ /**
+ * Return the number of entries currently in the back stack.
+ */
+ public abstract int getBackStackEntryCount();
+
+ /**
+ * Return the BackStackEntry at index index in the back stack;
+ * entries start index 0 being the bottom of the stack.
+ */
+ @NonNull
+ public abstract BackStackEntry getBackStackEntryAt(int index);
+
+ /**
+ * Add a new listener for changes to the fragment back stack.
+ */
+ public abstract void addOnBackStackChangedListener(
+ @NonNull OnBackStackChangedListener listener);
+
+ /**
+ * Remove a listener that was previously added with
+ * {@link #addOnBackStackChangedListener(OnBackStackChangedListener)}.
+ */
+ public abstract void removeOnBackStackChangedListener(
+ @NonNull OnBackStackChangedListener listener);
+
+ /**
+ * 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 abstract void putFragment(@NonNull Bundle bundle, @NonNull String key,
+ @NonNull Fragment fragment);
+
+ /**
+ * 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 abstract Fragment getFragment(@NonNull Bundle bundle, @NonNull String key);
+
+ /**
+ * 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.
+ *
+ * @return A list of all fragments that are added to the FragmentManager.
+ */
+ @NonNull
+ public abstract List getFragments();
+
+ /**
+ * 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 {@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.
+ *
+ *
+ * @param f 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 abstract Fragment.SavedState saveFragmentInstanceState(@NonNull Fragment f);
+
+ /**
+ * 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 abstract boolean isDestroyed();
+
+ /**
+ * 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 abstract void registerFragmentLifecycleCallbacks(@NonNull FragmentLifecycleCallbacks cb,
+ boolean 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 abstract void unregisterFragmentLifecycleCallbacks(
+ @NonNull FragmentLifecycleCallbacks cb);
+
+ /**
+ * Return the currently active primary navigation fragment for this FragmentManager.
+ * The primary navigation fragment is set by fragment transactions using
+ * {@link FragmentTransaction#setPrimaryNavigationFragment(Fragment)}.
+ *
+ *
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.
+ *
+ * @return the fragment designated as the primary navigation fragment
+ */
+ @Nullable
+ public abstract Fragment getPrimaryNavigationFragment();
+
+ /**
+ * Set a {@link FragmentFactory} for this FragmentManager that will be used
+ * to create new Fragment instances from this point onward.
+ *
+ * @param fragmentFactory the factory to use to create new Fragment instances
+ */
+ public void setFragmentFactory(@NonNull FragmentFactory fragmentFactory) {
+ mFragmentFactory = fragmentFactory;
+ }
+
+ /**
+ * Gets the current {@link FragmentFactory} used to instantiate new Fragment instances.
+ *
+ * @return the current FragmentFactory
+ */
+ @NonNull
+ public FragmentFactory getFragmentFactory() {
+ if (mFragmentFactory == null) {
+ mFragmentFactory = DEFAULT_FACTORY;
+ }
+ return mFragmentFactory;
+ }
+
+ /**
+ * 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 abstract void dump(@NonNull String prefix, @Nullable FileDescriptor fd,
+ @NonNull PrintWriter writer, @Nullable String[] args);
+
+ /**
+ * 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.
+ */
+ public static void enableDebugLogging(boolean enabled) {
+ FragmentManagerImpl.DEBUG = enabled;
+ }
+
+ /**
+ * 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 abstract boolean isStateSaved();
+
+ /**
+ * Callback interface for listening to fragment state changes that happen
+ * within a given FragmentManager.
+ */
+ 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
+ * onAttachFragment 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
+ */
+ 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) {}
+ }
+}
+
diff --git a/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentManagerImpl.java b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentManagerImpl.java
new file mode 100644
index 000000000..a5f974f0d
--- /dev/null
+++ b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentManagerImpl.java
@@ -0,0 +1,3484 @@
+/*
+ * 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 android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorListenerAdapter;
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+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.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.Transformation;
+
+import androidx.activity.OnBackPressedCallback;
+import androidx.activity.OnBackPressedDispatcher;
+import androidx.activity.OnBackPressedDispatcherOwner;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.collection.ArraySet;
+import androidx.core.util.DebugUtils;
+import androidx.core.util.LogWriter;
+import androidx.core.view.OneShotPreDrawListener;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.ViewModelStore;
+import androidx.lifecycle.ViewModelStoreOwner;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Container for fragments associated with an activity.
+ */
+final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {
+ static boolean DEBUG = false;
+ static final String TAG = "FragmentManager";
+
+ static final String TARGET_REQUEST_CODE_STATE_TAG = "android:target_req_state";
+ static final String TARGET_STATE_TAG = "android:target_state";
+ static final String VIEW_STATE_TAG = "android:view_state";
+ static final String USER_VISIBLE_HINT_TAG = "android:user_visible_hint";
+
+ private static final class FragmentLifecycleCallbacksHolder {
+ final FragmentLifecycleCallbacks mCallback;
+ final boolean mRecursive;
+
+ FragmentLifecycleCallbacksHolder(FragmentLifecycleCallbacks callback, boolean recursive) {
+ mCallback = callback;
+ mRecursive = recursive;
+ }
+ }
+
+ ArrayList mPendingActions;
+ boolean mExecutingActions;
+
+ int mNextFragmentIndex = 0;
+
+ final ArrayList mAdded = new ArrayList<>();
+ final HashMap mActive = new HashMap<>();
+ ArrayList mBackStack;
+ ArrayList mCreatedMenus;
+ private OnBackPressedDispatcher mOnBackPressedDispatcher;
+ private final OnBackPressedCallback mOnBackPressedCallback =
+ new OnBackPressedCallback(false) {
+ @Override
+ public void handleOnBackPressed() {
+ FragmentManagerImpl.this.handleOnBackPressed();
+ }
+ };
+
+ // Must be accessed while locked.
+ ArrayList mBackStackIndices;
+ ArrayList mAvailBackStackIndices;
+
+ ArrayList mBackStackChangeListeners;
+ private final CopyOnWriteArrayList
+ mLifecycleCallbacks = new CopyOnWriteArrayList<>();
+
+ int mCurState = Fragment.INITIALIZING;
+ FragmentHostCallback mHost;
+ FragmentContainer mContainer;
+ Fragment mParent;
+ @Nullable
+ Fragment mPrimaryNav;
+
+ boolean mNeedMenuInvalidate;
+ boolean mStateSaved;
+ boolean mStopped;
+ boolean mDestroyed;
+ boolean mHavePendingDeferredStart;
+
+ // Temporary vars for removing redundant operations in BackStackRecords:
+ ArrayList mTmpRecords;
+ ArrayList mTmpIsPop;
+ ArrayList mTmpAddedFragments;
+
+ // Temporary vars for state save and restore.
+ Bundle mStateBundle = null;
+ SparseArray mStateArray = null;
+
+ // Postponed transactions.
+ ArrayList mPostponedTransactions;
+
+ private FragmentManagerViewModel mNonConfig;
+
+ Runnable mExecCommit = new Runnable() {
+ @Override
+ public void run() {
+ execPendingActions();
+ }
+ };
+
+ 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;
+ }
+
+ @NonNull
+ @Override
+ public FragmentTransaction beginTransaction() {
+ return new BackStackRecord(this);
+ }
+
+ @Override
+ public boolean executePendingTransactions() {
+ boolean updates = execPendingActions();
+ 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
+ if (mPendingActions != null && !mPendingActions.isEmpty()) {
+ mOnBackPressedCallback.setEnabled(true);
+ 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.
+ mOnBackPressedCallback.setEnabled(getBackStackEntryCount() > 0
+ && isPrimaryNavigation(mParent));
+ }
+
+ /**
+ * 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;
+ }
+ FragmentManagerImpl 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 == primaryNavigationFragment
+ && isPrimaryNavigation(parentFragmentManager.mParent);
+ }
+
+ @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();
+ if (mOnBackPressedCallback.isEnabled()) {
+ // We still have a back stack, so we can pop
+ popBackStackImmediate();
+ } else {
+ // 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();
+ }
+ }
+
+ @Override
+ public void popBackStack() {
+ enqueueAction(new PopBackStackState(null, -1, 0), false);
+ }
+
+ @Override
+ public boolean popBackStackImmediate() {
+ checkStateLoss();
+ return popBackStackImmediate(null, -1, 0);
+ }
+
+ @Override
+ public void popBackStack(@Nullable final String name, final int flags) {
+ enqueueAction(new PopBackStackState(name, -1, flags), false);
+ }
+
+ @Override
+ public boolean popBackStackImmediate(@Nullable String name, int flags) {
+ checkStateLoss();
+ return popBackStackImmediate(name, -1, flags);
+ }
+
+ @Override
+ public void popBackStack(final int id, final int flags) {
+ if (id < 0) {
+ throw new IllegalArgumentException("Bad id: " + id);
+ }
+ enqueueAction(new PopBackStackState(null, id, flags), false);
+ }
+
+ @Override
+ public boolean popBackStackImmediate(int id, int flags) {
+ checkStateLoss();
+ execPendingActions();
+ 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(String name, int id, int flags) {
+ execPendingActions();
+ 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();
+ burpActive();
+ return executePop;
+ }
+
+ @Override
+ public int getBackStackEntryCount() {
+ return mBackStack != null ? mBackStack.size() : 0;
+ }
+
+ @Override
+ public BackStackEntry getBackStackEntryAt(int index) {
+ return mBackStack.get(index);
+ }
+
+ @Override
+ public void addOnBackStackChangedListener(OnBackStackChangedListener listener) {
+ if (mBackStackChangeListeners == null) {
+ mBackStackChangeListeners = new ArrayList();
+ }
+ mBackStackChangeListeners.add(listener);
+ }
+
+ @Override
+ public void removeOnBackStackChangedListener(OnBackStackChangedListener listener) {
+ if (mBackStackChangeListeners != null) {
+ mBackStackChangeListeners.remove(listener);
+ }
+ }
+
+ @Override
+ public void putFragment(Bundle bundle, String key, Fragment fragment) {
+ if (fragment.mFragmentManager != this) {
+ throwException(new IllegalStateException("Fragment " + fragment
+ + " is not currently in the FragmentManager"));
+ }
+ bundle.putString(key, fragment.mWho);
+ }
+
+ @Override
+ @Nullable
+ public Fragment getFragment(Bundle bundle, String key) {
+ String who = bundle.getString(key);
+ if (who == null) {
+ return null;
+ }
+ Fragment f = mActive.get(who);
+ if (f == null) {
+ throwException(new IllegalStateException("Fragment no longer exists for key "
+ + key + ": unique id " + who));
+ }
+ return f;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public List getFragments() {
+ if (mAdded.isEmpty()) {
+ return Collections.emptyList();
+ }
+ synchronized (mAdded) {
+ return (List) mAdded.clone();
+ }
+ }
+
+ @NonNull
+ ViewModelStore getViewModelStore(@NonNull Fragment f) {
+ return mNonConfig.getViewModelStore(f);
+ }
+
+ @NonNull
+ FragmentManagerViewModel getChildNonConfig(@NonNull Fragment f) {
+ return mNonConfig.getChildNonConfig(f);
+ }
+
+ void addRetainedFragment(@NonNull Fragment f) {
+ if (isStateSaved()) {
+ if (FragmentManagerImpl.DEBUG) {
+ Log.v(TAG, "Ignoring addRetainedFragment as the state is already saved");
+ }
+ return;
+ }
+ boolean added = mNonConfig.addRetainedFragment(f);
+ if (added && FragmentManagerImpl.DEBUG) {
+ Log.v(TAG, "Updating retained Fragments: Added " + f);
+ }
+ }
+
+ void removeRetainedFragment(@NonNull Fragment f) {
+ if (isStateSaved()) {
+ if (FragmentManagerImpl.DEBUG) {
+ Log.v(TAG, "Ignoring removeRetainedFragment as the state is already saved");
+ }
+ return;
+ }
+ boolean removed = mNonConfig.removeRetainedFragment(f);
+ if (removed && FragmentManagerImpl.DEBUG) {
+ Log.v(TAG, "Updating retained Fragments: Removed " + 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 getActiveFragments() {
+ return new ArrayList<>(mActive.values());
+ }
+
+ /**
+ * Used by FragmentController to get the number of Active Fragments.
+ *
+ * @return The number of active fragments.
+ */
+ int getActiveFragmentCount() {
+ return mActive.size();
+ }
+
+ @Override
+ @Nullable
+ public Fragment.SavedState saveFragmentInstanceState(@NonNull Fragment fragment) {
+ if (fragment.mFragmentManager != this) {
+ throwException( new IllegalStateException("Fragment " + fragment
+ + " is not currently in the FragmentManager"));
+ }
+ if (fragment.mState > Fragment.INITIALIZING) {
+ Bundle result = saveFragmentBasicState(fragment);
+ return result != null ? new Fragment.SavedState(result) : null;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isDestroyed() {
+ return mDestroyed;
+ }
+
+ @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) {
+ DebugUtils.buildShortClassTag(mParent, sb);
+ } else {
+ DebugUtils.buildShortClassTag(mHost, sb);
+ }
+ sb.append("}}");
+ return sb.toString();
+ }
+
+ @Override
+ public void dump(@NonNull String prefix, @Nullable FileDescriptor fd,
+ @NonNull PrintWriter writer, @Nullable String[] args) {
+ String innerPrefix = prefix + " ";
+
+ if (!mActive.isEmpty()) {
+ writer.print(prefix); writer.print("Active Fragments in ");
+ writer.print(Integer.toHexString(System.identityHashCode(this)));
+ writer.println(":");
+ for (Fragment f : mActive.values()) {
+ writer.print(prefix); writer.println(f);
+ if (f != null) {
+ f.dump(innerPrefix, fd, writer, args);
+ }
+ }
+ }
+
+ int N = mAdded.size();
+ if (N > 0) {
+ writer.print(prefix); writer.println("Added Fragments:");
+ for (int i = 0; i < N; i++) {
+ Fragment f = mAdded.get(i);
+ writer.print(prefix);
+ writer.print(" #");
+ writer.print(i);
+ writer.print(": ");
+ writer.println(f.toString());
+ }
+ }
+
+ if (mCreatedMenus != null) {
+ N = mCreatedMenus.size();
+ if (N > 0) {
+ writer.print(prefix); writer.println("Fragments Created Menus:");
+ for (int i=0; i 0) {
+ writer.print(prefix); writer.println("Back Stack:");
+ for (int i=0; i 0) {
+ writer.print(prefix); writer.println("Back Stack Indices:");
+ for (int i=0; i 0) {
+ writer.print(prefix); writer.print("mAvailBackStackIndices: ");
+ writer.println(Arrays.toString(mAvailBackStackIndices.toArray()));
+ }
+ }
+
+ if (mPendingActions != null) {
+ N = mPendingActions.size();
+ if (N > 0) {
+ writer.print(prefix); writer.println("Pending Actions:");
+ for (int i=0; i= state;
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ void moveToState(Fragment f, int newState, int transit, int transitionStyle,
+ boolean keepActive) {
+ // Fragments that are not currently added will sit in the onCreate() state.
+ if ((!f.mAdded || f.mDetached) && newState > Fragment.CREATED) {
+ newState = Fragment.CREATED;
+ }
+ if (f.mRemoving && newState > f.mState) {
+ if (f.mState == Fragment.INITIALIZING && f.isInBackStack()) {
+ // Allow the fragment to be created so that it can be saved later.
+ newState = Fragment.CREATED;
+ } else {
+ // While removing a fragment, we can't change it to a higher state.
+ newState = f.mState;
+ }
+ }
+ // Defer start if requested; don't allow it to move to STARTED or higher
+ // if it's not already started.
+ if (f.mDeferStart && f.mState < Fragment.STARTED && newState > Fragment.ACTIVITY_CREATED) {
+ newState = Fragment.ACTIVITY_CREATED;
+ }
+ // Don't allow the Fragment to go above its max lifecycle state
+ // Ensure that Fragments are capped at CREATED instead of ACTIVITY_CREATED.
+ if (f.mMaxState == Lifecycle.State.CREATED) {
+ newState = Math.min(newState, Fragment.CREATED);
+ } else {
+ newState = Math.min(newState, f.mMaxState.ordinal());
+ }
+ if (f.mState <= newState) {
+ // For fragments that are created from a layout, when restoring from
+ // state we don't want to allow them to be created until they are
+ // being reloaded from the layout.
+ if (f.mFromLayout && !f.mInLayout) {
+ return;
+ }
+ if (f.getAnimatingAway() != null || f.getAnimator() != null) {
+ // The fragment is currently being animated... but! Now we
+ // want to move our state back up. Give up on waiting for the
+ // animation, move to whatever the final state should be once
+ // the animation is done, and then we can proceed from there.
+ f.setAnimatingAway(null);
+ f.setAnimator(null);
+ moveToState(f, f.getStateAfterAnimating(), 0, 0, true);
+ }
+ switch (f.mState) {
+ case Fragment.INITIALIZING:
+ if (newState > Fragment.INITIALIZING) {
+ if (DEBUG) Log.v(TAG, "moveto CREATED: " + f);
+ if (f.mSavedFragmentState != null) {
+ f.mSavedFragmentState.setClassLoader(mHost.getContext()
+ .getClassLoader());
+ f.mSavedViewState = f.mSavedFragmentState.getSparseParcelableArray(
+ FragmentManagerImpl.VIEW_STATE_TAG);
+ Fragment target = getFragment(f.mSavedFragmentState,
+ FragmentManagerImpl.TARGET_STATE_TAG);
+ f.mTargetWho = target != null ? target.mWho : null;
+ if (f.mTargetWho != null) {
+ f.mTargetRequestCode = f.mSavedFragmentState.getInt(
+ FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0);
+ }
+ if (f.mSavedUserVisibleHint != null) {
+ f.mUserVisibleHint = f.mSavedUserVisibleHint;
+ f.mSavedUserVisibleHint = null;
+ } else {
+ f.mUserVisibleHint = f.mSavedFragmentState.getBoolean(
+ FragmentManagerImpl.USER_VISIBLE_HINT_TAG, true);
+ }
+ if (!f.mUserVisibleHint) {
+ f.mDeferStart = true;
+ if (newState > Fragment.ACTIVITY_CREATED) {
+ newState = Fragment.ACTIVITY_CREATED;
+ }
+ }
+ }
+
+ f.mHost = mHost;
+ f.mParentFragment = mParent;
+ f.mFragmentManager = mParent != null
+ ? mParent.mChildFragmentManager : mHost.mFragmentManager;
+
+ // If we have a target fragment, push it along to at least CREATED
+ // so that this one can rely on it as an initialized dependency.
+ if (f.mTarget != null) {
+ if (mActive.get(f.mTarget.mWho) != f.mTarget) {
+ throw new IllegalStateException("Fragment " + f
+ + " declared target fragment " + f.mTarget
+ + " that does not belong to this FragmentManager!");
+ }
+ if (f.mTarget.mState < Fragment.CREATED) {
+ moveToState(f.mTarget, Fragment.CREATED, 0, 0, true);
+ }
+ f.mTargetWho = f.mTarget.mWho;
+ f.mTarget = null;
+ }
+ if (f.mTargetWho != null) {
+ Fragment target = mActive.get(f.mTargetWho);
+ if (target == null) {
+ throw new IllegalStateException("Fragment " + f
+ + " declared target fragment " + f.mTargetWho
+ + " that does not belong to this FragmentManager!");
+ }
+ if (target.mState < Fragment.CREATED) {
+ moveToState(target, Fragment.CREATED, 0, 0, true);
+ }
+ }
+
+ dispatchOnFragmentPreAttached(f, mHost.getContext(), false);
+ f.performAttach();
+ if (f.mParentFragment == null) {
+ mHost.onAttachFragment(f);
+ } else {
+ f.mParentFragment.onAttachFragment(f);
+ }
+ dispatchOnFragmentAttached(f, mHost.getContext(), false);
+
+ if (!f.mIsCreated) {
+ dispatchOnFragmentPreCreated(f, f.mSavedFragmentState, false);
+ f.performCreate(f.mSavedFragmentState);
+ dispatchOnFragmentCreated(f, f.mSavedFragmentState, false);
+ } else {
+ f.restoreChildFragmentState(f.mSavedFragmentState);
+ f.mState = Fragment.CREATED;
+ }
+ }
+ // fall through
+ case Fragment.CREATED:
+ // We want to unconditionally run this anytime we do a moveToState that
+ // moves the Fragment above INITIALIZING, including cases such as when
+ // we move from CREATED => CREATED as part of the case fall through above.
+ if (newState > Fragment.INITIALIZING) {
+ ensureInflatedFragmentView(f);
+ }
+
+ if (newState > Fragment.CREATED) {
+ if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f);
+ if (!f.mFromLayout) {
+ ViewGroup container = null;
+ if (f.mContainerId != 0) {
+ if (f.mContainerId == View.NO_ID) {
+ throwException(new IllegalArgumentException(
+ "Cannot create fragment "
+ + f
+ + " for a container view with no id"));
+ }
+ container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
+ if (container == null && !f.mRestored) {
+ String resName;
+ try {
+ resName = f.getResources().getResourceName(f.mContainerId);
+ } catch (Resources.NotFoundException e) {
+ resName = "unknown";
+ }
+ throwException(new IllegalArgumentException(
+ "No view found for id 0x"
+ + Integer.toHexString(f.mContainerId) + " ("
+ + resName
+ + ") for fragment " + f));
+ }
+ }
+ f.mContainer = container;
+ f.performCreateView(f.performGetLayoutInflater(
+ f.mSavedFragmentState), container, f.mSavedFragmentState);
+ if (f.mView != null) {
+ f.mInnerView = f.mView;
+ f.mView.setSaveFromParentEnabled(false);
+ if (container != null) {
+ container.addView(f.mView);
+ }
+ if (f.mHidden) {
+ f.mView.setVisibility(View.GONE);
+ }
+ f.onViewCreated(f.mView, f.mSavedFragmentState);
+ dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState,
+ false);
+ // Only animate the view if it is visible. This is done after
+ // dispatchOnFragmentViewCreated in case visibility is changed
+ f.mIsNewlyAdded = (f.mView.getVisibility() == View.VISIBLE)
+ && f.mContainer != null;
+ } else {
+ f.mInnerView = null;
+ }
+ }
+
+ f.performActivityCreated(f.mSavedFragmentState);
+ dispatchOnFragmentActivityCreated(f, f.mSavedFragmentState, false);
+ if (f.mView != null) {
+ f.restoreViewState(f.mSavedFragmentState);
+ }
+ f.mSavedFragmentState = null;
+ }
+ // fall through
+ case Fragment.ACTIVITY_CREATED:
+ if (newState > Fragment.ACTIVITY_CREATED) {
+ if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
+ f.performStart();
+ dispatchOnFragmentStarted(f, false);
+ }
+ // fall through
+ case Fragment.STARTED:
+ if (newState > Fragment.STARTED) {
+ if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f);
+ f.performResume();
+ dispatchOnFragmentResumed(f, false);
+ f.mSavedFragmentState = null;
+ f.mSavedViewState = null;
+ }
+ }
+ } else if (f.mState > newState) {
+ switch (f.mState) {
+ case Fragment.RESUMED:
+ if (newState < Fragment.RESUMED) {
+ if (DEBUG) Log.v(TAG, "movefrom RESUMED: " + f);
+ f.performPause();
+ dispatchOnFragmentPaused(f, false);
+ }
+ // fall through
+ case Fragment.STARTED:
+ if (newState < Fragment.STARTED) {
+ if (DEBUG) Log.v(TAG, "movefrom STARTED: " + f);
+ f.performStop();
+ dispatchOnFragmentStopped(f, false);
+ }
+ // fall through
+ case Fragment.ACTIVITY_CREATED:
+ if (newState < Fragment.ACTIVITY_CREATED) {
+ if (DEBUG) Log.v(TAG, "movefrom ACTIVITY_CREATED: " + f);
+ if (f.mView != null) {
+ // Need to save the current view state if not
+ // done already.
+ if (mHost.onShouldSaveFragmentState(f) && f.mSavedViewState == null) {
+ saveFragmentViewState(f);
+ }
+ }
+ f.performDestroyView();
+ dispatchOnFragmentViewDestroyed(f, false);
+ if (f.mView != null && f.mContainer != null) {
+ // Stop any current animations:
+ f.mContainer.endViewTransition(f.mView);
+ f.mView.clearAnimation();
+ AnimationOrAnimator anim = null;
+ // If parent is being removed, no need to handle child animations.
+ if (f.getParentFragment() == null || !f.getParentFragment().mRemoving) {
+ if (mCurState > Fragment.INITIALIZING && !mDestroyed
+ && f.mView.getVisibility() == View.VISIBLE
+ && f.mPostponedAlpha >= 0) {
+ anim = loadAnimation(f, transit, false,
+ transitionStyle);
+ }
+ f.mPostponedAlpha = 0;
+ if (anim != null) {
+ animateRemoveFragment(f, anim, newState);
+ }
+ f.mContainer.removeView(f.mView);
+ }
+ }
+ f.mContainer = null;
+ f.mView = null;
+ // Set here to ensure that Observers are called after
+ // the Fragment's view is set to null
+ f.mViewLifecycleOwner = null;
+ f.mViewLifecycleOwnerLiveData.setValue(null);
+ f.mInnerView = null;
+ f.mInLayout = false;
+ }
+ // fall through
+ case Fragment.CREATED:
+ if (newState < Fragment.CREATED) {
+ if (mDestroyed) {
+ // The fragment's containing activity is
+ // being destroyed, but this fragment is
+ // currently animating away. Stop the
+ // animation right now -- it is not needed,
+ // and we can't wait any more on destroying
+ // the fragment.
+ if (f.getAnimatingAway() != null) {
+ View v = f.getAnimatingAway();
+ f.setAnimatingAway(null);
+ v.clearAnimation();
+ } else if (f.getAnimator() != null) {
+ Animator animator = f.getAnimator();
+ f.setAnimator(null);
+ animator.cancel();
+ }
+ }
+ if (f.getAnimatingAway() != null || f.getAnimator() != null) {
+ // We are waiting for the fragment's view to finish
+ // animating away. Just make a note of the state
+ // the fragment now should move to once the animation
+ // is done.
+ f.setStateAfterAnimating(newState);
+ newState = Fragment.CREATED;
+ } else {
+ if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f);
+ boolean beingRemoved = f.mRemoving && !f.isInBackStack();
+ if (beingRemoved || mNonConfig.shouldDestroy(f)) {
+ boolean shouldClear;
+ if (mHost instanceof ViewModelStoreOwner) {
+ shouldClear = mNonConfig.isCleared();
+ } else if (mHost.getContext() instanceof Activity) {
+ Activity activity = (Activity) mHost.getContext();
+ shouldClear = !activity.isChangingConfigurations();
+ } else {
+ shouldClear = true;
+ }
+ if (beingRemoved || shouldClear) {
+ mNonConfig.clearNonConfigState(f);
+ }
+ f.performDestroy();
+ dispatchOnFragmentDestroyed(f, false);
+ } else {
+ f.mState = Fragment.INITIALIZING;
+ }
+
+ f.performDetach();
+ dispatchOnFragmentDetached(f, false);
+ if (!keepActive) {
+ if (beingRemoved || mNonConfig.shouldDestroy(f)) {
+ makeInactive(f);
+ } else {
+ f.mHost = null;
+ f.mParentFragment = null;
+ f.mFragmentManager = null;
+ if (f.mTargetWho != null) {
+ Fragment target = mActive.get(f.mTargetWho);
+ if (target != null && target.getRetainInstance()) {
+ // Only keep references to other retained Fragments
+ // to avoid developers accessing Fragments that
+ // are never coming back
+ f.mTarget = target;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (f.mState != newState) {
+ Log.w(TAG, "moveToState: Fragment state for " + f + " not updated inline; "
+ + "expected state " + newState + " found " + f.mState);
+ f.mState = newState;
+ }
+ }
+
+ /**
+ * Animates the removal of a fragment with the given animator or animation. After animating,
+ * the fragment's view will be removed from the hierarchy.
+ *
+ * @param fragment The fragment to animate out
+ * @param anim The animator or animation to run on the fragment's view
+ * @param newState The final state after animating.
+ */
+ private void animateRemoveFragment(@NonNull final Fragment fragment,
+ @NonNull AnimationOrAnimator anim, final int newState) {
+ final View viewToAnimate = fragment.mView;
+ final ViewGroup container = fragment.mContainer;
+ container.startViewTransition(viewToAnimate);
+ fragment.setStateAfterAnimating(newState);
+ if (anim.animation != null) {
+ Animation animation =
+ new EndViewTransitionAnimation(anim.animation, container, viewToAnimate);
+ fragment.setAnimatingAway(fragment.mView);
+ animation.setAnimationListener(new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ // onAnimationEnd() comes during draw(), so there can still be some
+ // draw events happening after this call. We don't want to detach
+ // the view until after the onAnimationEnd()
+ container.post(new Runnable() {
+ @Override
+ public void run() {
+ if (fragment.getAnimatingAway() != null) {
+ fragment.setAnimatingAway(null);
+ moveToState(fragment, fragment.getStateAfterAnimating(), 0, 0,
+ false);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+ });
+ fragment.mView.startAnimation(animation);
+ } else {
+ Animator animator = anim.animator;
+ fragment.setAnimator(anim.animator);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator anim) {
+ container.endViewTransition(viewToAnimate);
+ // If an animator ends immediately, we can just pretend there is no animation.
+ // When that happens the the fragment's view won't have been removed yet.
+ Animator animator = fragment.getAnimator();
+ fragment.setAnimator(null);
+ if (animator != null && container.indexOfChild(viewToAnimate) < 0) {
+ moveToState(fragment, fragment.getStateAfterAnimating(), 0, 0, false);
+ }
+ }
+ });
+ animator.setTarget(fragment.mView);
+ animator.start();
+ }
+ }
+
+ void moveToState(Fragment f) {
+ moveToState(f, mCurState, 0, 0, false);
+ }
+
+ void ensureInflatedFragmentView(Fragment f) {
+ if (f.mFromLayout && !f.mPerformedCreateView) {
+ f.performCreateView(f.performGetLayoutInflater(
+ f.mSavedFragmentState), null, f.mSavedFragmentState);
+ if (f.mView != null) {
+ f.mInnerView = f.mView;
+ f.mView.setSaveFromParentEnabled(false);
+ if (f.mHidden) f.mView.setVisibility(View.GONE);
+ f.onViewCreated(f.mView, f.mSavedFragmentState);
+ dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState, false);
+ } else {
+ f.mInnerView = null;
+ }
+ }
+ }
+
+ /**
+ * Fragments that have been shown or hidden don't have their visibility changed or
+ * animations run during the {@link #showFragment(Fragment)} or {@link #hideFragment(Fragment)}
+ * calls. After fragments are brought to their final state in
+ * {@link #moveFragmentToExpectedState(Fragment)} the fragments that have been shown or
+ * hidden must have their visibility changed and their animations started here.
+ *
+ * @param fragment The fragment with mHiddenChanged = true that should change its View's
+ * visibility and start the show or hide animation.
+ */
+ void completeShowHideFragment(final Fragment fragment) {
+ if (fragment.mView != null) {
+ AnimationOrAnimator anim = loadAnimation(fragment, fragment.getNextTransition(),
+ !fragment.mHidden, fragment.getNextTransitionStyle());
+ if (anim != null && anim.animator != null) {
+ anim.animator.setTarget(fragment.mView);
+ if (fragment.mHidden) {
+ if (fragment.isHideReplaced()) {
+ fragment.setHideReplaced(false);
+ } else {
+ final ViewGroup container = fragment.mContainer;
+ final View animatingView = fragment.mView;
+ container.startViewTransition(animatingView);
+ // Delay the actual hide operation until the animation finishes,
+ // otherwise the fragment will just immediately disappear
+ anim.animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ container.endViewTransition(animatingView);
+ animation.removeListener(this);
+ if (fragment.mView != null && fragment.mHidden) {
+ fragment.mView.setVisibility(View.GONE);
+ }
+ }
+ });
+ }
+ } else {
+ fragment.mView.setVisibility(View.VISIBLE);
+ }
+ anim.animator.start();
+ } else {
+ if (anim != null) {
+ fragment.mView.startAnimation(anim.animation);
+ anim.animation.start();
+ }
+ final int visibility = fragment.mHidden && !fragment.isHideReplaced()
+ ? View.GONE
+ : View.VISIBLE;
+ fragment.mView.setVisibility(visibility);
+ if (fragment.isHideReplaced()) {
+ fragment.setHideReplaced(false);
+ }
+ }
+ }
+ if (fragment.mAdded && isMenuAvailable(fragment)) {
+ mNeedMenuInvalidate = true;
+ }
+ fragment.mHiddenChanged = false;
+ fragment.onHiddenChanged(fragment.mHidden);
+ }
+
+ /**
+ * Moves a fragment to its expected final state or the fragment manager's state, depending
+ * on whether the fragment manager's state is raised properly.
+ *
+ * @param f The fragment to change.
+ */
+ void moveFragmentToExpectedState(Fragment f) {
+ if (f == null) {
+ return;
+ }
+ if (!mActive.containsKey(f.mWho)) {
+ if (DEBUG) {
+ Log.v(TAG, "Ignoring moving " + f + " to state " + mCurState
+ + "since it is not added to " + this);
+ }
+ return;
+ }
+ int nextState = mCurState;
+ if (f.mRemoving) {
+ if (f.isInBackStack()) {
+ nextState = Math.min(nextState, Fragment.CREATED);
+ } else {
+ nextState = Math.min(nextState, Fragment.INITIALIZING);
+ }
+ }
+ moveToState(f, nextState, f.getNextTransition(), f.getNextTransitionStyle(), false);
+
+ if (f.mView != null) {
+ // Move the view if it is out of order
+ Fragment underFragment = findFragmentUnder(f);
+ if (underFragment != null) {
+ final View underView = underFragment.mView;
+ // make sure this fragment is in the right order.
+ final ViewGroup container = f.mContainer;
+ int underIndex = container.indexOfChild(underView);
+ int viewIndex = container.indexOfChild(f.mView);
+ if (viewIndex < underIndex) {
+ container.removeViewAt(viewIndex);
+ container.addView(f.mView, underIndex);
+ }
+ }
+ if (f.mIsNewlyAdded && f.mContainer != null) {
+ // Make it visible and run the animations
+ if (f.mPostponedAlpha > 0f) {
+ f.mView.setAlpha(f.mPostponedAlpha);
+ }
+ f.mPostponedAlpha = 0f;
+ f.mIsNewlyAdded = false;
+ // run animations:
+ AnimationOrAnimator anim = loadAnimation(f, f.getNextTransition(), true,
+ f.getNextTransitionStyle());
+ if (anim != null) {
+ if (anim.animation != null) {
+ f.mView.startAnimation(anim.animation);
+ } else {
+ anim.animator.setTarget(f.mView);
+ anim.animator.start();
+ }
+ }
+ }
+ }
+ if (f.mHiddenChanged) {
+ completeShowHideFragment(f);
+ }
+ }
+
+ /**
+ * 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;
+
+ // Must add them in the proper order. mActive fragments may be out of order
+ final int numAdded = mAdded.size();
+ for (int i = 0; i < numAdded; i++) {
+ Fragment f = mAdded.get(i);
+ moveFragmentToExpectedState(f);
+ }
+
+ // Now iterate through all active fragments. These will include those that are removed
+ // and detached.
+ for (Fragment f : mActive.values()) {
+ if (f != null && (f.mRemoving || f.mDetached) && !f.mIsNewlyAdded) {
+ moveFragmentToExpectedState(f);
+ }
+ }
+
+ startPendingDeferredFragments();
+
+ if (mNeedMenuInvalidate && mHost != null && mCurState == Fragment.RESUMED) {
+ mHost.onSupportInvalidateOptionsMenu();
+ mNeedMenuInvalidate = false;
+ }
+ }
+
+ void startPendingDeferredFragments() {
+ for (Fragment f : mActive.values()) {
+ if (f != null) {
+ performPendingDeferredStart(f);
+ }
+ }
+ }
+
+ void makeActive(Fragment f) {
+ if (mActive.get(f.mWho) != null) {
+ return;
+ }
+
+ mActive.put(f.mWho, f);
+ if (f.mRetainInstanceChangedWhileDetached) {
+ if (f.mRetainInstance) {
+ addRetainedFragment(f);
+ } else {
+ removeRetainedFragment(f);
+ }
+ f.mRetainInstanceChangedWhileDetached = false;
+ }
+ if (DEBUG) Log.v(TAG, "Added fragment to active set " + f);
+ }
+
+ void makeInactive(Fragment f) {
+ if (mActive.get(f.mWho) == null) {
+ return;
+ }
+
+ if (DEBUG) Log.v(TAG, "Removed fragment from active set " + f);
+ // Ensure that any Fragment that had this Fragment as its
+ // target Fragment retains a reference to the Fragment
+ for (Fragment fragment : mActive.values()) {
+ if (fragment != null && f.mWho.equals(fragment.mTargetWho)) {
+ fragment.mTarget = f;
+ fragment.mTargetWho = null;
+ }
+ }
+ // Don't remove yet. That happens in burpActive(). This prevents
+ // concurrent modification while iterating over mActive
+ mActive.put(f.mWho, null);
+ removeRetainedFragment(f);
+
+ if (f.mTargetWho != null) {
+ // Restore the target Fragment so that it can be accessed
+ // even after the Fragment is removed.
+ f.mTarget = mActive.get(f.mTargetWho);
+ }
+ f.initState();
+ }
+
+ public void addFragment(Fragment fragment, boolean moveToStateNow) {
+ if (DEBUG) Log.v(TAG, "add: " + fragment);
+ makeActive(fragment);
+ if (!fragment.mDetached) {
+ if (mAdded.contains(fragment)) {
+ throw new IllegalStateException("Fragment already added: " + fragment);
+ }
+ synchronized (mAdded) {
+ mAdded.add(fragment);
+ }
+ fragment.mAdded = true;
+ fragment.mRemoving = false;
+ if (fragment.mView == null) {
+ fragment.mHiddenChanged = false;
+ }
+ if (isMenuAvailable(fragment)) {
+ mNeedMenuInvalidate = true;
+ }
+ if (moveToStateNow) {
+ moveToState(fragment);
+ }
+ }
+ }
+
+ public void removeFragment(Fragment fragment) {
+ if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting);
+ final boolean inactive = !fragment.isInBackStack();
+ if (!fragment.mDetached || inactive) {
+ synchronized (mAdded) {
+ mAdded.remove(fragment);
+ }
+ if (isMenuAvailable(fragment)) {
+ mNeedMenuInvalidate = true;
+ }
+ fragment.mAdded = false;
+ fragment.mRemoving = true;
+ }
+ }
+
+ /**
+ * Marks a fragment as hidden to be later animated in with
+ * {@link #completeShowHideFragment(Fragment)}.
+ *
+ * @param fragment The fragment to be shown.
+ */
+ public void hideFragment(Fragment fragment) {
+ if (DEBUG) 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;
+ }
+ }
+
+ /**
+ * Marks a fragment as shown to be later animated in with
+ * {@link #completeShowHideFragment(Fragment)}.
+ *
+ * @param fragment The fragment to be shown.
+ */
+ public void showFragment(Fragment fragment) {
+ if (DEBUG) 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;
+ }
+ }
+
+ public void detachFragment(Fragment fragment) {
+ if (DEBUG) 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 (DEBUG) Log.v(TAG, "remove from detach: " + fragment);
+ synchronized (mAdded) {
+ mAdded.remove(fragment);
+ }
+ if (isMenuAvailable(fragment)) {
+ mNeedMenuInvalidate = true;
+ }
+ fragment.mAdded = false;
+ }
+ }
+ }
+
+ public void attachFragment(Fragment fragment) {
+ if (DEBUG) Log.v(TAG, "attach: " + fragment);
+ if (fragment.mDetached) {
+ fragment.mDetached = false;
+ if (!fragment.mAdded) {
+ if (mAdded.contains(fragment)) {
+ throw new IllegalStateException("Fragment already added: " + fragment);
+ }
+ if (DEBUG) Log.v(TAG, "add from attach: " + fragment);
+ synchronized (mAdded) {
+ mAdded.add(fragment);
+ }
+ fragment.mAdded = true;
+ if (isMenuAvailable(fragment)) {
+ mNeedMenuInvalidate = true;
+ }
+ }
+ }
+ }
+
+ @Override
+ @Nullable
+ public Fragment findFragmentById(int id) {
+ // First look through added fragments.
+ for (int i = mAdded.size() - 1; i >= 0; i--) {
+ Fragment f = mAdded.get(i);
+ if (f != null && f.mFragmentId == id) {
+ return f;
+ }
+ }
+ // Now for any known fragment.
+ for (Fragment f : mActive.values()) {
+ if (f != null && f.mFragmentId == id) {
+ return f;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ @Nullable
+ public Fragment findFragmentByTag(@Nullable String tag) {
+ if (tag != null) {
+ // First look through added fragments.
+ for (int i=mAdded.size()-1; i>=0; i--) {
+ Fragment f = mAdded.get(i);
+ if (f != null && tag.equals(f.mTag)) {
+ return f;
+ }
+ }
+ }
+ if (tag != null) {
+ // Now for any known fragment.
+ for (Fragment f : mActive.values()) {
+ if (f != null && tag.equals(f.mTag)) {
+ return f;
+ }
+ }
+ }
+ return null;
+ }
+
+ public Fragment findFragmentByWho(@NonNull String who) {
+ for (Fragment f : mActive.values()) {
+ if (f != null && (f = f.findFragmentByWho(who)) != null) {
+ return f;
+ }
+ }
+ return null;
+ }
+
+ private void checkStateLoss() {
+ if (isStateSaved()) {
+ // MOD: Fix fragment state loss exception
+ //throw new IllegalStateException(
+ // "Can not perform this action after onSaveInstanceState");
+ }
+ }
+
+ @Override
+ 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
+ */
+ public void enqueueAction(OpGenerator action, boolean allowStateLoss) {
+ if (!allowStateLoss) {
+ checkStateLoss();
+ }
+ synchronized (this) {
+ if (mDestroyed || mHost == null) {
+ if (allowStateLoss) {
+ // This FragmentManager isn't attached, so drop the entire transaction.
+ return;
+ }
+ throw new IllegalStateException("Activity has been destroyed");
+ }
+ if (mPendingActions == null) {
+ mPendingActions = new ArrayList<>();
+ }
+ 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 (this) {
+ boolean postponeReady =
+ mPostponedTransactions != null && !mPostponedTransactions.isEmpty();
+ boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1;
+ if (postponeReady || pendingReady) {
+ mHost.getHandler().removeCallbacks(mExecCommit);
+ mHost.getHandler().post(mExecCommit);
+ updateOnBackPressedCallbackEnabled();
+ }
+ }
+ }
+
+ public int allocBackStackIndex(BackStackRecord bse) {
+ synchronized (this) {
+ if (mAvailBackStackIndices == null || mAvailBackStackIndices.size() <= 0) {
+ if (mBackStackIndices == null) {
+ mBackStackIndices = new ArrayList();
+ }
+ int index = mBackStackIndices.size();
+ if (DEBUG) Log.v(TAG, "Setting back stack index " + index + " to " + bse);
+ mBackStackIndices.add(bse);
+ return index;
+
+ } else {
+ int index = mAvailBackStackIndices.remove(mAvailBackStackIndices.size()-1);
+ if (DEBUG) Log.v(TAG, "Adding back stack index " + index + " with " + bse);
+ mBackStackIndices.set(index, bse);
+ return index;
+ }
+ }
+ }
+
+ public void setBackStackIndex(int index, BackStackRecord bse) {
+ synchronized (this) {
+ if (mBackStackIndices == null) {
+ mBackStackIndices = new ArrayList();
+ }
+ int N = mBackStackIndices.size();
+ if (index < N) {
+ if (DEBUG) Log.v(TAG, "Setting back stack index " + index + " to " + bse);
+ mBackStackIndices.set(index, bse);
+ } else {
+ while (N < index) {
+ mBackStackIndices.add(null);
+ if (mAvailBackStackIndices == null) {
+ mAvailBackStackIndices = new ArrayList();
+ }
+ if (DEBUG) Log.v(TAG, "Adding available back stack index " + N);
+ mAvailBackStackIndices.add(N);
+ N++;
+ }
+ if (DEBUG) Log.v(TAG, "Adding back stack index " + index + " with " + bse);
+ mBackStackIndices.add(bse);
+ }
+ }
+ }
+
+ public void freeBackStackIndex(int index) {
+ synchronized (this) {
+ mBackStackIndices.set(index, null);
+ if (mAvailBackStackIndices == null) {
+ mAvailBackStackIndices = new ArrayList();
+ }
+ if (DEBUG) Log.v(TAG, "Freeing back stack index " + index);
+ mAvailBackStackIndices.add(index);
+ }
+ }
+
+ /**
+ * 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) {
+ throw new IllegalStateException("Fragment host has been destroyed");
+ }
+
+ 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<>();
+ }
+ mExecutingActions = true;
+ try {
+ executePostponedTransaction(null, null);
+ } finally {
+ mExecutingActions = false;
+ }
+ }
+
+ public void execSingleAction(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();
+ 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!
+ */
+ public boolean execPendingActions() {
+ ensureExecReady(true);
+
+ boolean didSomething = false;
+ while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) {
+ mExecutingActions = true;
+ try {
+ removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop);
+ } finally {
+ cleanupExec();
+ }
+ didSomething = true;
+ }
+
+ updateOnBackPressedCallbackEnabled();
+ doPendingDeferredStart();
+ burpActive();
+
+ return didSomething;
+ }
+
+ /**
+ * Complete the execution of transactions that have previously been postponed, but are
+ * now ready.
+ */
+ private void executePostponedTransaction(ArrayList records,
+ ArrayList isRecordPop) {
+ int numPostponed = mPostponedTransactions == null ? 0 : mPostponedTransactions.size();
+ for (int i = 0; i < numPostponed; i++) {
+ StartEnterTransitionListener listener = mPostponedTransactions.get(i);
+ if (records != null && !listener.mIsBack) {
+ int index = records.indexOf(listener.mRecord);
+ if (index != -1 && isRecordPop.get(index)) {
+ mPostponedTransactions.remove(i);
+ i--;
+ numPostponed--;
+ listener.cancelTransaction();
+ continue;
+ }
+ }
+ if (listener.isReady() || (records != null
+ && listener.mRecord.interactsWith(records, 0, records.size()))) {
+ mPostponedTransactions.remove(i);
+ i--;
+ numPostponed--;
+ int index;
+ if (records != null && !listener.mIsBack
+ && (index = records.indexOf(listener.mRecord)) != -1
+ && isRecordPop.get(index)) {
+ // This is popping a postponed transaction
+ listener.cancelTransaction();
+ } else {
+ listener.completeTransaction();
+ }
+ }
+ }
+ }
+
+ /**
+ * Remove redundant BackStackRecord operations and executes them. This method merges operations
+ * of proximate records that allow reordering. See
+ * {@link FragmentTransaction#setReorderingAllowed(boolean)}.
+ *
+ * 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.
+ *
+ * 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(ArrayList records,
+ ArrayList isRecordPop) {
+ if (records == null || records.isEmpty()) {
+ return;
+ }
+
+ if (isRecordPop == null || records.size() != isRecordPop.size()) {
+ throw new IllegalStateException("Internal error with the back stack records");
+ }
+
+ // Force start of any postponed transactions that interact with scheduled transactions:
+ executePostponedTransaction(records, isRecordPop);
+
+ 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 records to be executed
+ * @param endIndex One more than the final record index in records to executed.
+ */
+ private void executeOpsTogether(ArrayList records,
+ ArrayList 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(mAdded);
+ 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) {
+ FragmentTransition.startTransitions(this, records, isRecordPop, startIndex, endIndex,
+ false);
+ }
+ executeOps(records, isRecordPop, startIndex, endIndex);
+
+ int postponeIndex = endIndex;
+ if (allowReordering) {
+ ArraySet addedFragments = new ArraySet<>();
+ addAddedFragments(addedFragments);
+ postponeIndex = postponePostponableTransactions(records, isRecordPop,
+ startIndex, endIndex, addedFragments);
+ makeRemovedFragmentsInvisible(addedFragments);
+ }
+
+ if (postponeIndex != startIndex && allowReordering) {
+ // need to run something now
+ FragmentTransition.startTransitions(this, records, isRecordPop, startIndex,
+ postponeIndex, true);
+ moveToState(mCurState, true);
+ }
+
+ for (int recordNum = startIndex; recordNum < endIndex; recordNum++) {
+ final BackStackRecord record = records.get(recordNum);
+ final boolean isPop = isRecordPop.get(recordNum);
+ if (isPop && record.mIndex >= 0) {
+ freeBackStackIndex(record.mIndex);
+ record.mIndex = -1;
+ }
+ record.runOnCommitRunnables();
+ }
+ if (addToBackStack) {
+ reportBackStackChanged();
+ }
+ }
+
+ /**
+ * Any fragments that were removed because they have been postponed should have their views
+ * made invisible by setting their alpha to 0.
+ *
+ * @param fragments The fragments that were added during operation execution. Only the ones
+ * that are no longer added will have their alpha changed.
+ */
+ private void makeRemovedFragmentsInvisible(ArraySet fragments) {
+ final int numAdded = fragments.size();
+ for (int i = 0; i < numAdded; i++) {
+ final Fragment fragment = fragments.valueAt(i);
+ if (!fragment.mAdded) {
+ final View view = fragment.requireView();
+ fragment.mPostponedAlpha = view.getAlpha();
+ view.setAlpha(0f);
+ }
+ }
+ }
+
+ /**
+ * Examine all transactions and determine which ones are marked as postponed. Those will
+ * have their operations rolled back and moved to the end of the record list (up to endIndex).
+ * It will also add the postponed transaction to the queue.
+ *
+ * @param records A list of BackStackRecords that should be checked.
+ * @param isRecordPop The direction that these records are being run.
+ * @param startIndex The index of the first record in records to be checked
+ * @param endIndex One more than the final record index in records to be checked.
+ * @return The index of the first postponed transaction or endIndex if no transaction was
+ * postponed.
+ */
+ private int postponePostponableTransactions(ArrayList records,
+ ArrayList isRecordPop, int startIndex, int endIndex,
+ ArraySet added) {
+ int postponeIndex = endIndex;
+ for (int i = endIndex - 1; i >= startIndex; i--) {
+ final BackStackRecord record = records.get(i);
+ final boolean isPop = isRecordPop.get(i);
+ boolean isPostponed = record.isPostponed()
+ && !record.interactsWith(records, i + 1, endIndex);
+ if (isPostponed) {
+ if (mPostponedTransactions == null) {
+ mPostponedTransactions = new ArrayList<>();
+ }
+ StartEnterTransitionListener listener =
+ new StartEnterTransitionListener(record, isPop);
+ mPostponedTransactions.add(listener);
+ record.setOnStartPostponedListener(listener);
+
+ // roll back the transaction
+ if (isPop) {
+ record.executeOps();
+ } else {
+ record.executePopOps(false);
+ }
+
+ // move to the end
+ postponeIndex--;
+ if (i != postponeIndex) {
+ records.remove(i);
+ records.add(postponeIndex, record);
+ }
+
+ // different views may be visible now
+ addAddedFragments(added);
+ }
+ }
+ return postponeIndex;
+ }
+
+ /**
+ * When a postponed transaction is ready to be started, this completes the transaction,
+ * removing, hiding, or showing views as well as starting the animations and transitions.
+ *
+ * {@code runtransitions} is set to false when the transaction postponement was interrupted
+ * abnormally -- normally by a new transaction being started that affects the postponed
+ * transaction.
+ *
+ * @param record The transaction to run
+ * @param isPop true if record is popping or false if it is adding
+ * @param runTransitions true if the fragment transition should be run or false otherwise.
+ * @param moveToState true if the state should be changed after executing the operations.
+ * This is false when the transaction is canceled when a postponed
+ * transaction is popped.
+ */
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ void completeExecute(BackStackRecord record, boolean isPop, boolean runTransitions,
+ boolean moveToState) {
+ if (isPop) {
+ record.executePopOps(moveToState);
+ } else {
+ record.executeOps();
+ }
+ ArrayList records = new ArrayList<>(1);
+ ArrayList isRecordPop = new ArrayList<>(1);
+ records.add(record);
+ isRecordPop.add(isPop);
+ if (runTransitions) {
+ FragmentTransition.startTransitions(this, records, isRecordPop, 0, 1, true);
+ }
+ if (moveToState) {
+ moveToState(mCurState, true);
+ }
+
+ for (Fragment fragment : mActive.values()) {
+ // Allow added fragments to be removed during the pop since we aren't going
+ // to move them to the final state with moveToState(mCurState).
+ if (fragment != null && fragment.mView != null && fragment.mIsNewlyAdded
+ && record.interactsWith(fragment.mContainerId)) {
+ if (fragment.mPostponedAlpha > 0) {
+ fragment.mView.setAlpha(fragment.mPostponedAlpha);
+ }
+ if (moveToState) {
+ fragment.mPostponedAlpha = 0;
+ } else {
+ fragment.mPostponedAlpha = -1;
+ fragment.mIsNewlyAdded = false;
+ }
+ }
+ }
+ }
+
+ /**
+ * Find a fragment within the fragment's container whose View should be below the passed
+ * fragment. {@code null} is returned when the fragment has no View or if there should be
+ * no fragment with a View below the given fragment.
+ *
+ * As an example, if mAdded has two Fragments with Views sharing the same container:
+ * FragmentA
+ * FragmentB
+ *
+ * Then, when processing FragmentB, FragmentA will be returned. If, however, FragmentA
+ * had no View, null would be returned.
+ *
+ * @param f The fragment that may be on top of another fragment.
+ * @return The fragment with a View under f, if one exists or null if f has no View or
+ * there are no fragments with Views in the same container.
+ */
+ private Fragment findFragmentUnder(Fragment f) {
+ final ViewGroup container = f.mContainer;
+ final View view = f.mView;
+
+ if (container == null || view == null) {
+ return null;
+ }
+
+ final int fragmentIndex = mAdded.indexOf(f);
+ for (int i = fragmentIndex - 1; i >= 0; i--) {
+ Fragment underFragment = mAdded.get(i);
+ if (underFragment.mContainer == container && underFragment.mView != null) {
+ // Found the fragment under this one
+ return underFragment;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 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(ArrayList records,
+ ArrayList 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);
+ // Only execute the add operations at the end of
+ // all transactions.
+ boolean moveToState = i == (endIndex - 1);
+ record.executePopOps(moveToState);
+ } else {
+ record.bumpBackStackNesting(1);
+ record.executeOps();
+ }
+ }
+ }
+
+ /**
+ * Ensure that fragments that are added are moved to at least the CREATED state.
+ * Any newly-added Views are inserted into {@code added} so that the Transaction can be
+ * postponed with {@link Fragment#postponeEnterTransition()}. They will later be made
+ * invisible (by setting their alpha to 0) if they have been removed when postponed.
+ */
+ private void addAddedFragments(ArraySet added) {
+ if (mCurState < Fragment.CREATED) {
+ return;
+ }
+ // We want to leave the fragment in the started state
+ final int state = Math.min(mCurState, Fragment.STARTED);
+ final int numAdded = mAdded.size();
+ for (int i = 0; i < numAdded; i++) {
+ Fragment fragment = mAdded.get(i);
+ if (fragment.mState < state) {
+ moveToState(fragment, state, fragment.getNextAnim(), fragment.getNextTransition(),
+ false);
+ if (fragment.mView != null && !fragment.mHidden && fragment.mIsNewlyAdded) {
+ added.add(fragment);
+ }
+ }
+ }
+ }
+
+ /**
+ * Starts all postponed transactions regardless of whether they are ready or not.
+ */
+ private void forcePostponedTransactions() {
+ if (mPostponedTransactions != null) {
+ while (!mPostponedTransactions.isEmpty()) {
+ mPostponedTransactions.remove(0).completeTransaction();
+ }
+ }
+ }
+
+ /**
+ * 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() {
+ for (Fragment fragment : mActive.values()) {
+ if (fragment != null) {
+ if (fragment.getAnimatingAway() != null) {
+ // Give up waiting for the animation and just end it.
+ final int stateAfterAnimating = fragment.getStateAfterAnimating();
+ final View animatingAway = fragment.getAnimatingAway();
+ Animation animation = animatingAway.getAnimation();
+ if (animation != null) {
+ animation.cancel();
+ // force-clear the animation, as Animation#cancel() doesn't work prior to N,
+ // and will instead cause the animation to infinitely loop
+ animatingAway.clearAnimation();
+ }
+ fragment.setAnimatingAway(null);
+ moveToState(fragment, stateAfterAnimating, 0, 0, false);
+ } else if (fragment.getAnimator() != null) {
+ fragment.getAnimator().end();
+ }
+ }
+ }
+ }
+
+ /**
+ * 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(ArrayList records,
+ ArrayList isPop) {
+ boolean didSomething = false;
+ synchronized (this) {
+ if (mPendingActions == null || mPendingActions.size() == 0) {
+ return false;
+ }
+
+ final int numActions = mPendingActions.size();
+ for (int i = 0; i < numActions; i++) {
+ didSomething |= mPendingActions.get(i).generateOps(records, isPop);
+ }
+ mPendingActions.clear();
+ mHost.getHandler().removeCallbacks(mExecCommit);
+ }
+ return didSomething;
+ }
+
+ void doPendingDeferredStart() {
+ if (mHavePendingDeferredStart) {
+ mHavePendingDeferredStart = false;
+ startPendingDeferredFragments();
+ }
+ }
+
+ void reportBackStackChanged() {
+ if (mBackStackChangeListeners != null) {
+ for (int i=0; i();
+ }
+ mBackStack.add(state);
+ }
+
+ @SuppressWarnings("unused")
+ boolean popBackStackState(ArrayList records, ArrayList isRecordPop,
+ String name, int id, int flags) {
+ if (mBackStack == null) {
+ return false;
+ }
+ if (name == null && id < 0 && (flags & POP_BACK_STACK_INCLUSIVE) == 0) {
+ int last = mBackStack.size() - 1;
+ if (last < 0) {
+ return false;
+ }
+ records.add(mBackStack.remove(last));
+ isRecordPop.add(true);
+ } else {
+ int index = -1;
+ if (name != null || id >= 0) {
+ // If a name or ID is specified, look for that place in
+ // the stack.
+ 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 false;
+ }
+ if ((flags&POP_BACK_STACK_INCLUSIVE) != 0) {
+ index--;
+ // Consume all following entries that match.
+ while (index >= 0) {
+ BackStackRecord bss = mBackStack.get(index);
+ if ((name != null && name.equals(bss.getName()))
+ || (id >= 0 && id == bss.mIndex)) {
+ index--;
+ continue;
+ }
+ break;
+ }
+ }
+ }
+ if (index == mBackStack.size()-1) {
+ return false;
+ }
+ for (int i = mBackStack.size() - 1; i > index; i--) {
+ records.add(mBackStack.remove(i));
+ isRecordPop.add(true);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @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();
+ }
+
+ void saveFragmentViewState(Fragment f) {
+ if (f.mInnerView == null) {
+ return;
+ }
+ if (mStateArray == null) {
+ mStateArray = new SparseArray();
+ } else {
+ mStateArray.clear();
+ }
+ f.mInnerView.saveHierarchyState(mStateArray);
+ if (mStateArray.size() > 0) {
+ f.mSavedViewState = mStateArray;
+ mStateArray = null;
+ }
+ }
+
+ Bundle saveFragmentBasicState(Fragment f) {
+ Bundle result = null;
+
+ if (mStateBundle == null) {
+ mStateBundle = new Bundle();
+ }
+ f.performSaveInstanceState(mStateBundle);
+ dispatchOnFragmentSaveInstanceState(f, mStateBundle, false);
+ if (!mStateBundle.isEmpty()) {
+ result = mStateBundle;
+ mStateBundle = null;
+ }
+
+ if (f.mView != null) {
+ saveFragmentViewState(f);
+ }
+ if (f.mSavedViewState != null) {
+ if (result == null) {
+ result = new Bundle();
+ }
+ result.putSparseParcelableArray(
+ FragmentManagerImpl.VIEW_STATE_TAG, f.mSavedViewState);
+ }
+ if (!f.mUserVisibleHint) {
+ if (result == null) {
+ result = new Bundle();
+ }
+ // Only add this if it's not the default value
+ result.putBoolean(FragmentManagerImpl.USER_VISIBLE_HINT_TAG, f.mUserVisibleHint);
+ }
+
+ return result;
+ }
+
+ Parcelable saveAllState() {
+ // Make sure all pending operations have now been executed to get
+ // our state update-to-date.
+ forcePostponedTransactions();
+ endAnimatingAwayFragments();
+ execPendingActions();
+
+ mStateSaved = true;
+
+ if (mActive.isEmpty()) {
+ return null;
+ }
+
+ // First collect all active fragments.
+ int size = mActive.size();
+ ArrayList active = new ArrayList<>(size);
+ boolean haveFragments = false;
+ for (Fragment f : mActive.values()) {
+ if (f != null) {
+ if (f.mFragmentManager != this) {
+ throwException(new IllegalStateException(
+ "Failure saving state: active " + f
+ + " was removed from the FragmentManager"));
+ }
+
+ haveFragments = true;
+
+ FragmentState fs = new FragmentState(f);
+ active.add(fs);
+
+ if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) {
+ fs.mSavedFragmentState = saveFragmentBasicState(f);
+
+ if (f.mTargetWho != null) {
+ Fragment target = mActive.get(f.mTargetWho);
+ if (target == null) {
+ throwException(new IllegalStateException(
+ "Failure saving state: " + f
+ + " has target not in fragment manager: "
+ + f.mTargetWho));
+ }
+ if (fs.mSavedFragmentState == null) {
+ fs.mSavedFragmentState = new Bundle();
+ }
+ putFragment(fs.mSavedFragmentState,
+ FragmentManagerImpl.TARGET_STATE_TAG, target);
+ if (f.mTargetRequestCode != 0) {
+ fs.mSavedFragmentState.putInt(
+ FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG,
+ f.mTargetRequestCode);
+ }
+ }
+
+ } else {
+ fs.mSavedFragmentState = f.mSavedFragmentState;
+ }
+
+ if (DEBUG) Log.v(TAG, "Saved state of " + f + ": "
+ + fs.mSavedFragmentState);
+ }
+ }
+
+ if (!haveFragments) {
+ if (DEBUG) Log.v(TAG, "saveAllState: no fragments!");
+ return null;
+ }
+
+ ArrayList added = null;
+ BackStackState[] backStack = null;
+
+ // Build list of currently added fragments.
+ size = mAdded.size();
+ if (size > 0) {
+ added = new ArrayList<>(size);
+ for (Fragment f : mAdded) {
+ added.add(f.mWho);
+ if (f.mFragmentManager != this) {
+ throwException(new IllegalStateException(
+ "Failure saving state: active " + f
+ + " was removed from the FragmentManager"));
+ }
+ if (DEBUG) {
+ Log.v(TAG, "saveAllState: adding fragment (" + f.mWho
+ + "): " + f);
+ }
+ }
+ }
+
+ // Now save back stack.
+ if (mBackStack != null) {
+ size = mBackStack.size();
+ if (size > 0) {
+ backStack = new BackStackState[size];
+ for (int i = 0; i < size; i++) {
+ backStack[i] = new BackStackState(mBackStack.get(i));
+ if (DEBUG) Log.v(TAG, "saveAllState: adding back stack #" + i
+ + ": " + mBackStack.get(i));
+ }
+ }
+ }
+
+ FragmentManagerState fms = new FragmentManagerState();
+ fms.mActive = active;
+ fms.mAdded = added;
+ fms.mBackStack = backStack;
+ if (mPrimaryNav != null) {
+ fms.mPrimaryNavActiveWho = mPrimaryNav.mWho;
+ }
+ fms.mNextFragmentIndex = mNextFragmentIndex;
+ return fms;
+ }
+
+ void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
+ if (mHost instanceof ViewModelStoreOwner) {
+ throwException(new IllegalStateException("You must use restoreSaveState when your "
+ + "FragmentHostCallback implements ViewModelStoreOwner"));
+ }
+ mNonConfig.restoreFromSnapshot(nonConfig);
+ restoreSaveState(state);
+ }
+
+ void restoreSaveState(Parcelable state) {
+ // If there is no saved state at all, then there's nothing else to do
+ if (state == null) return;
+ FragmentManagerState fms = (FragmentManagerState)state;
+ if (fms.mActive == null) return;
+
+ // First re-attach any non-config instances we are retaining back
+ // to their saved state, so we don't try to instantiate them again.
+ for (Fragment f : mNonConfig.getRetainedFragments()) {
+ if (DEBUG) Log.v(TAG, "restoreSaveState: re-attaching retained " + f);
+ FragmentState fs = null;
+ for (FragmentState fragmentState : fms.mActive) {
+ if (fragmentState.mWho.equals(f.mWho)) {
+ fs = fragmentState;
+ break;
+ }
+ }
+ if (fs == null) {
+ if (DEBUG) {
+ Log.v(TAG, "Discarding retained Fragment " + f
+ + " that was not found in the set of active Fragments " + fms.mActive);
+ }
+ // 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.
+ moveToState(f, Fragment.CREATED, 0, 0, false);
+ f.mRemoving = true;
+ moveToState(f, Fragment.INITIALIZING, 0, 0, false);
+ continue;
+ }
+ fs.mInstance = f;
+ f.mSavedViewState = null;
+ f.mBackStackNesting = 0;
+ f.mInLayout = false;
+ f.mAdded = false;
+ f.mTargetWho = f.mTarget != null ? f.mTarget.mWho : null;
+ f.mTarget = null;
+ if (fs.mSavedFragmentState != null) {
+ fs.mSavedFragmentState.setClassLoader(mHost.getContext().getClassLoader());
+ f.mSavedViewState = fs.mSavedFragmentState.getSparseParcelableArray(
+ FragmentManagerImpl.VIEW_STATE_TAG);
+ f.mSavedFragmentState = fs.mSavedFragmentState;
+ }
+ }
+
+ // Build the full list of active fragments, instantiating them from
+ // their saved state.
+ mActive.clear();
+ for (FragmentState fs : fms.mActive) {
+ if (fs != null) {
+ Fragment f = fs.instantiate(mHost.getContext().getClassLoader(),
+ getFragmentFactory());
+ f.mFragmentManager = this;
+ if (DEBUG) Log.v(TAG, "restoreSaveState: active (" + f.mWho + "): " + f);
+ mActive.put(f.mWho, f);
+ // Now that the fragment is instantiated (or came from being
+ // retained above), clear mInstance in case we end up re-restoring
+ // from this FragmentState again.
+ fs.mInstance = null;
+ }
+ }
+
+ // Build the list of currently added fragments.
+ mAdded.clear();
+ if (fms.mAdded != null) {
+ for (String who : fms.mAdded) {
+ Fragment f = mActive.get(who);
+ if (f == null) {
+ throwException(new IllegalStateException(
+ "No instantiated fragment for (" + who + ")"));
+ }
+ f.mAdded = true;
+ if (DEBUG) Log.v(TAG, "restoreSaveState: added (" + who + "): " + f);
+ if (mAdded.contains(f)) {
+ throw new IllegalStateException("Already added " + f);
+ }
+ synchronized (mAdded) {
+ mAdded.add(f);
+ }
+ }
+ }
+
+ // Build the back stack.
+ if (fms.mBackStack != null) {
+ mBackStack = new ArrayList(fms.mBackStack.length);
+ for (int i=0; i= 0) {
+ setBackStackIndex(bse.mIndex, bse);
+ }
+ }
+ } else {
+ mBackStack = null;
+ }
+
+ if (fms.mPrimaryNavActiveWho != null) {
+ mPrimaryNav = mActive.get(fms.mPrimaryNavActiveWho);
+ dispatchParentPrimaryNavigationFragmentChanged(mPrimaryNav);
+ }
+ this.mNextFragmentIndex = fms.mNextFragmentIndex;
+ }
+
+ /**
+ * To prevent list modification errors, mActive sets values to null instead of
+ * removing them when the Fragment becomes inactive. This cleans up the list at the
+ * end of executing the transactions.
+ */
+ private void burpActive() {
+ Collection values = mActive.values();
+ // values() provides a view into the map, so removing elements from it
+ // removes the relevant pairs in the Map
+ values.removeAll(Collections.singleton(null));
+ }
+
+ public 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;
+ 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);
+ }
+ }
+
+ public void noteStateNotSaved() {
+ mStateSaved = false;
+ mStopped = false;
+ final int addedCount = mAdded.size();
+ for (int i = 0; i < addedCount; i++) {
+ Fragment fragment = mAdded.get(i);
+ if (fragment != null) {
+ fragment.noteStateNotSaved();
+ }
+ }
+ }
+
+ public void dispatchCreate() {
+ mStateSaved = false;
+ mStopped = false;
+ dispatchStateChange(Fragment.CREATED);
+ }
+
+ public void dispatchActivityCreated() {
+ mStateSaved = false;
+ mStopped = false;
+ dispatchStateChange(Fragment.ACTIVITY_CREATED);
+ }
+
+ public void dispatchStart() {
+ mStateSaved = false;
+ mStopped = false;
+ dispatchStateChange(Fragment.STARTED);
+ }
+
+ public void dispatchResume() {
+ mStateSaved = false;
+ mStopped = false;
+ dispatchStateChange(Fragment.RESUMED);
+ }
+
+ public void dispatchPause() {
+ dispatchStateChange(Fragment.STARTED);
+ }
+
+ public void dispatchStop() {
+ mStopped = true;
+ dispatchStateChange(Fragment.ACTIVITY_CREATED);
+ }
+
+ public void dispatchDestroyView() {
+ dispatchStateChange(Fragment.CREATED);
+ }
+
+ public void dispatchDestroy() {
+ mDestroyed = true;
+ execPendingActions();
+ dispatchStateChange(Fragment.INITIALIZING);
+ 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;
+ }
+ }
+
+ private void dispatchStateChange(int nextState) {
+ try {
+ mExecutingActions = true;
+ moveToState(nextState, false);
+ } finally {
+ mExecutingActions = false;
+ }
+ execPendingActions();
+ }
+
+ public void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode) {
+ for (int i = mAdded.size() - 1; i >= 0; --i) {
+ final Fragment f = mAdded.get(i);
+ if (f != null) {
+ f.performMultiWindowModeChanged(isInMultiWindowMode);
+ }
+ }
+ }
+
+ public void dispatchPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ for (int i = mAdded.size() - 1; i >= 0; --i) {
+ final Fragment f = mAdded.get(i);
+ if (f != null) {
+ f.performPictureInPictureModeChanged(isInPictureInPictureMode);
+ }
+ }
+ }
+
+ public void dispatchConfigurationChanged(@NonNull Configuration newConfig) {
+ for (int i = 0; i < mAdded.size(); i++) {
+ Fragment f = mAdded.get(i);
+ if (f != null) {
+ f.performConfigurationChanged(newConfig);
+ }
+ }
+ }
+
+ public void dispatchLowMemory() {
+ for (int i = 0; i < mAdded.size(); i++) {
+ Fragment f = mAdded.get(i);
+ if (f != null) {
+ f.performLowMemory();
+ }
+ }
+ }
+
+ public boolean dispatchCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
+ if (mCurState < Fragment.CREATED) {
+ return false;
+ }
+ boolean show = false;
+ ArrayList newMenus = null;
+ for (int i = 0; i < mAdded.size(); i++) {
+ Fragment f = mAdded.get(i);
+ if (f != null) {
+ if (f.performCreateOptionsMenu(menu, inflater)) {
+ show = true;
+ if (newMenus == null) {
+ newMenus = new ArrayList();
+ }
+ newMenus.add(f);
+ }
+ }
+ }
+
+ if (mCreatedMenus != null) {
+ for (int i=0; i records, ArrayList 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(String name, int id, int flags) {
+ mName = name;
+ mId = id;
+ mFlags = flags;
+ }
+
+ @Override
+ public boolean generateOps(ArrayList records,
+ ArrayList 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);
+ }
+ }
+
+ /**
+ * A listener for a postponed transaction. This waits until
+ * {@link Fragment#startPostponedEnterTransition()} is called or a transaction is started
+ * that interacts with this one, based on interactions with the fragment container.
+ */
+ static class StartEnterTransitionListener
+ implements Fragment.OnStartEnterTransitionListener {
+ final boolean mIsBack;
+ final BackStackRecord mRecord;
+ private int mNumPostponed;
+
+ StartEnterTransitionListener(BackStackRecord record, boolean isBack) {
+ mIsBack = isBack;
+ mRecord = record;
+ }
+
+ /**
+ * Called from {@link Fragment#startPostponedEnterTransition()}, this decreases the
+ * number of Fragments that are postponed. This may cause the transaction to schedule
+ * to finish running and run transitions and animations.
+ */
+ @Override
+ public void onStartEnterTransition() {
+ mNumPostponed--;
+ if (mNumPostponed != 0) {
+ return;
+ }
+ mRecord.mManager.scheduleCommit();
+ }
+
+ /**
+ * Called from {@link Fragment#
+ * setOnStartEnterTransitionListener(Fragment.OnStartEnterTransitionListener)}, this
+ * increases the number of fragments that are postponed as part of this transaction.
+ */
+ @Override
+ public void startListening() {
+ mNumPostponed++;
+ }
+
+ /**
+ * @return true if there are no more postponed fragments as part of the transaction.
+ */
+ public boolean isReady() {
+ return mNumPostponed == 0;
+ }
+
+ /**
+ * Completes the transaction and start the animations and transitions. This may skip
+ * the transitions if this is called before all fragments have called
+ * {@link Fragment#startPostponedEnterTransition()}.
+ */
+ public void completeTransaction() {
+ final boolean canceled;
+ canceled = mNumPostponed > 0;
+ FragmentManagerImpl manager = mRecord.mManager;
+ final int numAdded = manager.mAdded.size();
+ for (int i = 0; i < numAdded; i++) {
+ final Fragment fragment = manager.mAdded.get(i);
+ fragment.setOnStartEnterTransitionListener(null);
+ if (canceled && fragment.isPostponed()) {
+ fragment.startPostponedEnterTransition();
+ }
+ }
+ mRecord.mManager.completeExecute(mRecord, mIsBack, !canceled, true);
+ }
+
+ /**
+ * Cancels this transaction instead of completing it. That means that the state isn't
+ * changed, so the pop results in no change to the state.
+ */
+ public void cancelTransaction() {
+ mRecord.mManager.completeExecute(mRecord, mIsBack, false, false);
+ }
+ }
+
+ /**
+ * Contains either an animator or animation. One of these should be null.
+ */
+ private static class AnimationOrAnimator {
+ public final Animation animation;
+ public final Animator animator;
+
+ AnimationOrAnimator(Animation animation) {
+ this.animation = animation;
+ this.animator = null;
+ if (animation == null) {
+ throw new IllegalStateException("Animation cannot be null");
+ }
+ }
+
+ AnimationOrAnimator(Animator animator) {
+ this.animation = null;
+ this.animator = animator;
+ if (animator == null) {
+ throw new IllegalStateException("Animator cannot be null");
+ }
+ }
+ }
+
+ /**
+ * We must call endViewTransition() before the animation ends or else the parent doesn't
+ * get nulled out. We use both startViewTransition() and startAnimation() to solve a problem
+ * with Views remaining in the hierarchy as disappearing children after the view has been
+ * removed in some edge cases.
+ */
+ private static class EndViewTransitionAnimation extends AnimationSet implements Runnable {
+ private final ViewGroup mParent;
+ private final View mChild;
+ private boolean mEnded;
+ private boolean mTransitionEnded;
+ private boolean mAnimating = true;
+
+ EndViewTransitionAnimation(@NonNull Animation animation,
+ @NonNull ViewGroup parent, @NonNull View child) {
+ super(false);
+ mParent = parent;
+ mChild = child;
+ addAnimation(animation);
+ // We must call endViewTransition() even if the animation was never run or it
+ // is interrupted in a way that can't be detected easily (app put in background)
+ mParent.post(this);
+ }
+
+ @Override
+ public boolean getTransformation(long currentTime, Transformation t) {
+ mAnimating = true;
+ if (mEnded) {
+ return !mTransitionEnded;
+ }
+ boolean more = super.getTransformation(currentTime, t);
+ if (!more) {
+ mEnded = true;
+ OneShotPreDrawListener.add(mParent, this);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean getTransformation(long currentTime, Transformation outTransformation,
+ float scale) {
+ mAnimating = true;
+ if (mEnded) {
+ return !mTransitionEnded;
+ }
+ boolean more = super.getTransformation(currentTime, outTransformation, scale);
+ if (!more) {
+ mEnded = true;
+ OneShotPreDrawListener.add(mParent, this);
+ }
+ return true;
+ }
+
+ @Override
+ public void run() {
+ if (!mEnded && mAnimating) {
+ mAnimating = false;
+ // Called while animating, so we'll check again on next cycle
+ mParent.post(this);
+ } else {
+ mParent.endViewTransition(mChild);
+ mTransitionEnded = true;
+ }
+ }
+ }
+}
diff --git a/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentManagerNonConfig.java b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentManagerNonConfig.java
new file mode 100644
index 000000000..d4352f16d
--- /dev/null
+++ b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentManagerNonConfig.java
@@ -0,0 +1,86 @@
+/*
+ * 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 android.os.Parcelable;
+
+import androidx.annotation.Nullable;
+import androidx.lifecycle.ViewModelStore;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * FragmentManagerNonConfig stores the retained instance fragments across
+ * activity recreation events.
+ *
+ *
Apps should treat objects of this type as opaque, returned by
+ * and passed to the state save and restore process for fragments in
+ * {@link FragmentController#retainNestedNonConfig()} and
+ * {@link FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.
+ *
+ * @deprecated Have your {@link FragmentHostCallback} implement
+ * {@link androidx.lifecycle.ViewModelStoreOwner} to automatically retain the Fragment's
+ * non configuration state.
+ */
+@Deprecated
+public class FragmentManagerNonConfig {
+ private final @Nullable Collection mFragments;
+ private final @Nullable Map mChildNonConfigs;
+ private final @Nullable Map mViewModelStores;
+
+ FragmentManagerNonConfig(@Nullable Collection fragments,
+ @Nullable Map childNonConfigs,
+ @Nullable Map viewModelStores) {
+ mFragments = fragments;
+ mChildNonConfigs = childNonConfigs;
+ mViewModelStores = viewModelStores;
+ }
+
+ @SuppressWarnings("BooleanMethodIsAlwaysInverted")
+ boolean isRetaining(Fragment f) {
+ if (mFragments == null) {
+ return false;
+ }
+ return mFragments.contains(f);
+ }
+
+ /**
+ * @return the retained instance fragments returned by a FragmentManager
+ */
+ @Nullable
+ Collection getFragments() {
+ return mFragments;
+ }
+
+ /**
+ * @return the FragmentManagerNonConfigs from any applicable fragment's child FragmentManager
+ */
+ @Nullable
+ Map getChildNonConfigs() {
+ return mChildNonConfigs;
+ }
+
+ /**
+ * @return the ViewModelStores for all fragments associated with the FragmentManager
+ */
+ @Nullable
+ Map getViewModelStores() {
+ return mViewModelStores;
+ }
+}
diff --git a/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentManagerState.java b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentManagerState.java
new file mode 100644
index 000000000..969c3bc20
--- /dev/null
+++ b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentManagerState.java
@@ -0,0 +1,70 @@
+/*
+ * 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 android.annotation.SuppressLint;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+
+@SuppressLint("BanParcelableUsage")
+final class FragmentManagerState implements Parcelable {
+ ArrayList mActive;
+ ArrayList mAdded;
+ BackStackState[] mBackStack;
+ String mPrimaryNavActiveWho = null;
+ int mNextFragmentIndex;
+
+ public FragmentManagerState() {
+ }
+
+ public FragmentManagerState(Parcel in) {
+ mActive = in.createTypedArrayList(FragmentState.CREATOR);
+ mAdded = in.createStringArrayList();
+ mBackStack = in.createTypedArray(BackStackState.CREATOR);
+ mPrimaryNavActiveWho = in.readString();
+ mNextFragmentIndex = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedList(mActive);
+ dest.writeStringList(mAdded);
+ dest.writeTypedArray(mBackStack, flags);
+ dest.writeString(mPrimaryNavActiveWho);
+ dest.writeInt(mNextFragmentIndex);
+ }
+
+ public static final Parcelable.Creator CREATOR
+ = new Parcelable.Creator() {
+ @Override
+ public FragmentManagerState createFromParcel(Parcel in) {
+ return new FragmentManagerState(in);
+ }
+
+ @Override
+ public FragmentManagerState[] newArray(int size) {
+ return new FragmentManagerState[size];
+ }
+ };
+}
diff --git a/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentManagerViewModel.java b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentManagerViewModel.java
new file mode 100644
index 000000000..b466f6d0c
--- /dev/null
+++ b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentManagerViewModel.java
@@ -0,0 +1,280 @@
+/*
+ * 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 android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.lifecycle.ViewModelStore;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * FragmentManagerViewModel is the always up to date view of the Fragment's
+ * non configuration state
+ */
+class FragmentManagerViewModel extends ViewModel {
+
+ private static final ViewModelProvider.Factory FACTORY = new ViewModelProvider.Factory() {
+ @NonNull
+ @Override
+ @SuppressWarnings("unchecked")
+ public T create(@NonNull Class modelClass) {
+ FragmentManagerViewModel viewModel = new FragmentManagerViewModel(true);
+ return (T) viewModel;
+ }
+ };
+
+ @NonNull
+ static FragmentManagerViewModel getInstance(ViewModelStore viewModelStore) {
+ ViewModelProvider viewModelProvider = new ViewModelProvider(viewModelStore,
+ FACTORY);
+ return viewModelProvider.get(FragmentManagerViewModel.class);
+ }
+
+ private final HashSet mRetainedFragments = new HashSet<>();
+ private final HashMap mChildNonConfigs = new HashMap<>();
+ private final HashMap mViewModelStores = new HashMap<>();
+
+ private final boolean mStateAutomaticallySaved;
+ // Only used when mStateAutomaticallySaved is true
+ private boolean mHasBeenCleared = false;
+ // Only used when mStateAutomaticallySaved is false
+ private boolean mHasSavedSnapshot = false;
+
+ /**
+ * FragmentManagerViewModel simultaneously supports two modes:
+ *
+ *
Automatically saved: in this model, it is assumed that the ViewModel is added to
+ * an appropriate {@link ViewModelStore} that has the same lifecycle as the
+ * FragmentManager and that {@link #onCleared()} indicates that the Fragment's host
+ * is being permanently destroyed.
+ *
Not automatically saved: in this model, the FragmentManager is responsible for
+ * calling {@link #getSnapshot()} and later restoring the ViewModel with
+ * {@link #restoreFromSnapshot(FragmentManagerNonConfig)}.
+ *
+ * These states are mutually exclusive.
+ *
+ * @param stateAutomaticallySaved Whether the ViewModel will be automatically saved.
+ */
+ FragmentManagerViewModel(boolean stateAutomaticallySaved) {
+ mStateAutomaticallySaved = stateAutomaticallySaved;
+ }
+
+ @Override
+ protected void onCleared() {
+ if (FragmentManagerImpl.DEBUG) {
+ Log.d(FragmentManagerImpl.TAG, "onCleared called for " + this);
+ }
+ mHasBeenCleared = true;
+ }
+
+ boolean isCleared() {
+ return mHasBeenCleared;
+ }
+
+ boolean addRetainedFragment(@NonNull Fragment fragment) {
+ return mRetainedFragments.add(fragment);
+ }
+
+ @NonNull
+ Collection getRetainedFragments() {
+ return mRetainedFragments;
+ }
+
+ boolean shouldDestroy(@NonNull Fragment fragment) {
+ if (!mRetainedFragments.contains(fragment)) {
+ // Always destroy non-retained Fragments
+ return true;
+ }
+ if (mStateAutomaticallySaved) {
+ // If we automatically save our state, then only
+ // destroy a retained Fragment when we've been cleared
+ return mHasBeenCleared;
+ } else {
+ // Else, only destroy retained Fragments if they've
+ // been reaped before the state has been saved
+ return !mHasSavedSnapshot;
+ }
+ }
+
+ boolean removeRetainedFragment(@NonNull Fragment fragment) {
+ return mRetainedFragments.remove(fragment);
+ }
+
+ @NonNull
+ FragmentManagerViewModel getChildNonConfig(@NonNull Fragment f) {
+ FragmentManagerViewModel childNonConfig = mChildNonConfigs.get(f.mWho);
+ if (childNonConfig == null) {
+ childNonConfig = new FragmentManagerViewModel(mStateAutomaticallySaved);
+ mChildNonConfigs.put(f.mWho, childNonConfig);
+ }
+ return childNonConfig;
+ }
+
+ @NonNull
+ ViewModelStore getViewModelStore(@NonNull Fragment f) {
+ ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
+ if (viewModelStore == null) {
+ viewModelStore = new ViewModelStore();
+ mViewModelStores.put(f.mWho, viewModelStore);
+ }
+ return viewModelStore;
+ }
+
+ void clearNonConfigState(@NonNull Fragment f) {
+ if (FragmentManagerImpl.DEBUG) {
+ Log.d(FragmentManagerImpl.TAG, "Clearing non-config state for " + f);
+ }
+ // Clear and remove the Fragment's child non config state
+ FragmentManagerViewModel childNonConfig = mChildNonConfigs.get(f.mWho);
+ if (childNonConfig != null) {
+ childNonConfig.onCleared();
+ mChildNonConfigs.remove(f.mWho);
+ }
+ // Clear and remove the Fragment's ViewModelStore
+ ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
+ if (viewModelStore != null) {
+ viewModelStore.clear();
+ mViewModelStores.remove(f.mWho);
+ }
+ }
+
+ /**
+ * @deprecated Ideally, we only support mStateAutomaticallySaved = true and remove this
+ * code, alongside
+ * {@link FragmentController#restoreAllState(android.os.Parcelable, FragmentManagerNonConfig)}.
+ */
+ @Deprecated
+ void restoreFromSnapshot(@Nullable FragmentManagerNonConfig nonConfig) {
+ mRetainedFragments.clear();
+ mChildNonConfigs.clear();
+ mViewModelStores.clear();
+ if (nonConfig != null) {
+ Collection fragments = nonConfig.getFragments();
+ if (fragments != null) {
+ mRetainedFragments.addAll(fragments);
+ }
+ Map childNonConfigs = nonConfig.getChildNonConfigs();
+ if (childNonConfigs != null) {
+ for (Map.Entry entry :
+ childNonConfigs.entrySet()) {
+ FragmentManagerViewModel childViewModel =
+ new FragmentManagerViewModel(mStateAutomaticallySaved);
+ childViewModel.restoreFromSnapshot(entry.getValue());
+ mChildNonConfigs.put(entry.getKey(), childViewModel);
+ }
+ }
+ Map viewModelStores = nonConfig.getViewModelStores();
+ if (viewModelStores != null) {
+ mViewModelStores.putAll(viewModelStores);
+ }
+ }
+ mHasSavedSnapshot = false;
+ }
+
+ /**
+ * @deprecated Ideally, we only support mStateAutomaticallySaved = true and remove this
+ * code, alongside {@link FragmentController#retainNestedNonConfig()}.
+ */
+ @Deprecated
+ @Nullable
+ FragmentManagerNonConfig getSnapshot() {
+ if (mRetainedFragments.isEmpty() && mChildNonConfigs.isEmpty()
+ && mViewModelStores.isEmpty()) {
+ return null;
+ }
+ HashMap childNonConfigs = new HashMap<>();
+ for (Map.Entry entry : mChildNonConfigs.entrySet()) {
+ FragmentManagerNonConfig childNonConfig = entry.getValue().getSnapshot();
+ if (childNonConfig != null) {
+ childNonConfigs.put(entry.getKey(), childNonConfig);
+ }
+ }
+
+ mHasSavedSnapshot = true;
+ if (mRetainedFragments.isEmpty() && childNonConfigs.isEmpty()
+ && mViewModelStores.isEmpty()) {
+ return null;
+ }
+ return new FragmentManagerNonConfig(
+ new ArrayList<>(mRetainedFragments),
+ childNonConfigs,
+ new HashMap<>(mViewModelStores));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ FragmentManagerViewModel that = (FragmentManagerViewModel) o;
+
+ return mRetainedFragments.equals(that.mRetainedFragments)
+ && mChildNonConfigs.equals(that.mChildNonConfigs)
+ && mViewModelStores.equals(that.mViewModelStores);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mRetainedFragments.hashCode();
+ result = 31 * result + mChildNonConfigs.hashCode();
+ result = 31 * result + mViewModelStores.hashCode();
+ return result;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("FragmentManagerViewModel{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append("} Fragments (");
+ Iterator fragmentIterator = mRetainedFragments.iterator();
+ while (fragmentIterator.hasNext()) {
+ sb.append(fragmentIterator.next());
+ if (fragmentIterator.hasNext()) {
+ sb.append(", ");
+ }
+ }
+ sb.append(") Child Non Config (");
+ Iterator childNonConfigIterator = mChildNonConfigs.keySet().iterator();
+ while (childNonConfigIterator.hasNext()) {
+ sb.append(childNonConfigIterator.next());
+ if (childNonConfigIterator.hasNext()) {
+ sb.append(", ");
+ }
+ }
+ sb.append(") ViewModelStores (");
+ Iterator viewModelStoreIterator = mViewModelStores.keySet().iterator();
+ while (viewModelStoreIterator.hasNext()) {
+ sb.append(viewModelStoreIterator.next());
+ if (viewModelStoreIterator.hasNext()) {
+ sb.append(", ");
+ }
+ }
+ sb.append(')');
+ return sb.toString();
+ }
+}
diff --git a/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentPagerAdapter.java b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentPagerAdapter.java
new file mode 100644
index 000000000..d23a10757
--- /dev/null
+++ b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentPagerAdapter.java
@@ -0,0 +1,271 @@
+/*
+ * 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 android.os.Parcelable;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.Lifecycle;
+import androidx.viewpager.widget.PagerAdapter;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Implementation of {@link PagerAdapter} that
+ * represents each page as a {@link Fragment} that is persistently
+ * kept in the fragment manager as long as the user can return to the page.
+ *
+ *
This version of the pager is best for use when there are a handful of
+ * typically more static fragments to be paged through, such as a set of tabs.
+ * The fragment of each page the user visits will be kept in memory, though its
+ * view hierarchy may be destroyed when not visible. This can result in using
+ * a significant amount of memory since fragment instances can hold on to an
+ * arbitrary amount of state. For larger sets of pages, consider
+ * {@link FragmentStatePagerAdapter}.
+ *
+ *
When using FragmentPagerAdapter the host ViewPager must have a
+ * valid ID set.
+ *
+ *
Subclasses only need to implement {@link #getItem(int)}
+ * and {@link #getCount()} to have a working adapter.
+ *
+ *
Here is an example implementation of a pager containing fragments of
+ * lists:
+ *
+ * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentPagerSupport.java
+ * complete}
+ *
+ *
The R.layout.fragment_pager resource of the top-level fragment is:
+ *
+ * {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_pager.xml
+ * complete}
+ *
+ *
The R.layout.fragment_pager_list resource containing each
+ * individual fragment's layout is:
+ *
+ * {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_pager_list.xml
+ * complete}
+ */
+@SuppressWarnings("deprecation")
+public abstract class FragmentPagerAdapter extends PagerAdapter {
+ private static final String TAG = "FragmentPagerAdapter";
+ private static final boolean DEBUG = false;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({BEHAVIOR_SET_USER_VISIBLE_HINT, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT})
+ private @interface Behavior { }
+
+ /**
+ * Indicates that {@link Fragment#setUserVisibleHint(boolean)} will be called when the current
+ * fragment changes.
+ *
+ * @deprecated This behavior relies on the deprecated
+ * {@link Fragment#setUserVisibleHint(boolean)} API. Use
+ * {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} to switch to its replacement,
+ * {@link FragmentTransaction#setMaxLifecycle}.
+ * @see #FragmentPagerAdapter(FragmentManager, int)
+ */
+ @Deprecated
+ public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0;
+
+ /**
+ * Indicates that only the current fragment will be in the {@link Lifecycle.State#RESUMED}
+ * state. All other Fragments are capped at {@link Lifecycle.State#STARTED}.
+ *
+ * @see #FragmentPagerAdapter(FragmentManager, int)
+ */
+ public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;
+
+ private final FragmentManager mFragmentManager;
+ private final int mBehavior;
+ private FragmentTransaction mCurTransaction = null;
+ private Fragment mCurrentPrimaryItem = null;
+
+ /**
+ * Constructor for {@link FragmentPagerAdapter} that sets the fragment manager for the adapter.
+ * This is the equivalent of calling {@link #FragmentPagerAdapter(FragmentManager, int)} and
+ * passing in {@link #BEHAVIOR_SET_USER_VISIBLE_HINT}.
+ *
+ *
Fragments will have {@link Fragment#setUserVisibleHint(boolean)} called whenever the
+ * current Fragment changes.
+ *
+ * @param fm fragment manager that will interact with this adapter
+ * @deprecated use {@link #FragmentPagerAdapter(FragmentManager, int)} with
+ * {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT}
+ */
+ @Deprecated
+ public FragmentPagerAdapter(@NonNull FragmentManager fm) {
+ this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
+ }
+
+ /**
+ * Constructor for {@link FragmentPagerAdapter}.
+ *
+ * If {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} is passed in, then only the current
+ * Fragment is in the {@link Lifecycle.State#RESUMED} state. All other fragments are capped at
+ * {@link Lifecycle.State#STARTED}. If {@link #BEHAVIOR_SET_USER_VISIBLE_HINT} is passed, all
+ * fragments are in the {@link Lifecycle.State#RESUMED} state and there will be callbacks to
+ * {@link Fragment#setUserVisibleHint(boolean)}.
+ *
+ * @param fm fragment manager that will interact with this adapter
+ * @param behavior determines if only current fragments are in a resumed state
+ */
+ public FragmentPagerAdapter(@NonNull FragmentManager fm,
+ @Behavior int behavior) {
+ mFragmentManager = fm;
+ mBehavior = behavior;
+ }
+
+ /**
+ * Return the Fragment associated with a specified position.
+ */
+ @NonNull
+ public abstract Fragment getItem(int position);
+
+ @Override
+ public void startUpdate(@NonNull ViewGroup container) {
+ if (container.getId() == View.NO_ID) {
+ throw new IllegalStateException("ViewPager with adapter " + this
+ + " requires a view id");
+ }
+ }
+
+ @SuppressWarnings({"ReferenceEquality", "deprecation"})
+ @NonNull
+ @Override
+ public Object instantiateItem(@NonNull ViewGroup container, int position) {
+ if (mCurTransaction == null) {
+ mCurTransaction = mFragmentManager.beginTransaction();
+ }
+
+ final long itemId = getItemId(position);
+
+ // Do we already have this fragment?
+ String name = makeFragmentName(container.getId(), itemId);
+ Fragment fragment = mFragmentManager.findFragmentByTag(name);
+ if (fragment != null) {
+ if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
+ mCurTransaction.attach(fragment);
+ } else {
+ fragment = getItem(position);
+ if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
+ mCurTransaction.add(container.getId(), fragment,
+ makeFragmentName(container.getId(), itemId));
+ }
+ if (fragment != mCurrentPrimaryItem) {
+ fragment.setMenuVisibility(false);
+ if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
+ mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
+ } else {
+ fragment.setUserVisibleHint(false);
+ }
+ }
+
+ return fragment;
+ }
+
+ @Override
+ public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
+ Fragment fragment = (Fragment) object;
+
+ if (mCurTransaction == null) {
+ mCurTransaction = mFragmentManager.beginTransaction();
+ }
+ if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
+ + " v=" + (fragment.getView()));
+ mCurTransaction.detach(fragment);
+ if (fragment == mCurrentPrimaryItem) {
+ mCurrentPrimaryItem = null;
+ }
+ }
+
+ @SuppressWarnings({"ReferenceEquality", "deprecation"})
+ @Override
+ public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
+ Fragment fragment = (Fragment)object;
+ if (fragment != mCurrentPrimaryItem) {
+ if (mCurrentPrimaryItem != null) {
+ mCurrentPrimaryItem.setMenuVisibility(false);
+ if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
+ if (mCurTransaction == null) {
+ mCurTransaction = mFragmentManager.beginTransaction();
+ }
+ mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
+ } else {
+ mCurrentPrimaryItem.setUserVisibleHint(false);
+ }
+ }
+ fragment.setMenuVisibility(true);
+ if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
+ if (mCurTransaction == null) {
+ mCurTransaction = mFragmentManager.beginTransaction();
+ }
+ mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
+ } else {
+ fragment.setUserVisibleHint(true);
+ }
+
+ mCurrentPrimaryItem = fragment;
+ }
+ }
+
+ @Override
+ public void finishUpdate(@NonNull ViewGroup container) {
+ if (mCurTransaction != null) {
+ mCurTransaction.commitNowAllowingStateLoss();
+ mCurTransaction = null;
+ }
+ }
+
+ @Override
+ public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
+ return ((Fragment)object).getView() == view;
+ }
+
+ @Override
+ @Nullable
+ public Parcelable saveState() {
+ return null;
+ }
+
+ @Override
+ public void restoreState(@Nullable Parcelable state, @Nullable ClassLoader loader) {
+ }
+
+ /**
+ * Return a unique identifier for the item at the given position.
+ *
+ *
The default implementation returns the given position.
+ * Subclasses should override this method if the positions of items can change.
+ *
+ * @param position Position within this adapter
+ * @return Unique identifier for the item at position
+ */
+ public long getItemId(int position) {
+ return position;
+ }
+
+ private static String makeFragmentName(int viewId, long id) {
+ return "android:switcher:" + viewId + ":" + id;
+ }
+}
diff --git a/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentState.java b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentState.java
new file mode 100644
index 000000000..f8947c3f0
--- /dev/null
+++ b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentState.java
@@ -0,0 +1,185 @@
+/*
+ * 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 android.annotation.SuppressLint;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.Lifecycle;
+
+@SuppressLint("BanParcelableUsage")
+final class FragmentState implements Parcelable {
+ final String mClassName;
+ final String mWho;
+ final boolean mFromLayout;
+ final int mFragmentId;
+ final int mContainerId;
+ final String mTag;
+ final boolean mRetainInstance;
+ final boolean mRemoving;
+ final boolean mDetached;
+ final Bundle mArguments;
+ final boolean mHidden;
+ final int mMaxLifecycleState;
+
+ Bundle mSavedFragmentState;
+
+ Fragment mInstance;
+
+ FragmentState(Fragment frag) {
+ mClassName = frag.getClass().getName();
+ mWho = frag.mWho;
+ mFromLayout = frag.mFromLayout;
+ mFragmentId = frag.mFragmentId;
+ mContainerId = frag.mContainerId;
+ mTag = frag.mTag;
+ mRetainInstance = frag.mRetainInstance;
+ mRemoving = frag.mRemoving;
+ mDetached = frag.mDetached;
+ mArguments = frag.mArguments;
+ mHidden = frag.mHidden;
+ mMaxLifecycleState = frag.mMaxState.ordinal();
+ }
+
+ FragmentState(Parcel in) {
+ mClassName = in.readString();
+ mWho = in.readString();
+ mFromLayout = in.readInt() != 0;
+ mFragmentId = in.readInt();
+ mContainerId = in.readInt();
+ mTag = in.readString();
+ mRetainInstance = in.readInt() != 0;
+ mRemoving = in.readInt() != 0;
+ mDetached = in.readInt() != 0;
+ mArguments = in.readBundle();
+ mHidden = in.readInt() != 0;
+ mSavedFragmentState = in.readBundle();
+ mMaxLifecycleState = in.readInt();
+ }
+
+ public Fragment instantiate(@NonNull ClassLoader classLoader,
+ @NonNull FragmentFactory factory) {
+ if (mInstance == null) {
+ if (mArguments != null) {
+ mArguments.setClassLoader(classLoader);
+ }
+
+ mInstance = factory.instantiate(classLoader, mClassName);
+ mInstance.setArguments(mArguments);
+
+ if (mSavedFragmentState != null) {
+ mSavedFragmentState.setClassLoader(classLoader);
+ mInstance.mSavedFragmentState = mSavedFragmentState;
+ } else {
+ // When restoring a Fragment, always ensure we have a
+ // non-null Bundle so that developers have a signal for
+ // when the Fragment is being restored
+ mInstance.mSavedFragmentState = new Bundle();
+ }
+ mInstance.mWho = mWho;
+ mInstance.mFromLayout = mFromLayout;
+ mInstance.mRestored = true;
+ mInstance.mFragmentId = mFragmentId;
+ mInstance.mContainerId = mContainerId;
+ mInstance.mTag = mTag;
+ mInstance.mRetainInstance = mRetainInstance;
+ mInstance.mRemoving = mRemoving;
+ mInstance.mDetached = mDetached;
+ mInstance.mHidden = mHidden;
+ mInstance.mMaxState = Lifecycle.State.values()[mMaxLifecycleState];
+
+ if (FragmentManagerImpl.DEBUG) {
+ Log.v(FragmentManagerImpl.TAG, "Instantiated fragment " + mInstance);
+ }
+ }
+ return mInstance;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("FragmentState{");
+ sb.append(mClassName);
+ sb.append(" (");
+ sb.append(mWho);
+ sb.append(")}:");
+ if (mFromLayout) {
+ sb.append(" fromLayout");
+ }
+ if (mContainerId != 0) {
+ sb.append(" id=0x");
+ sb.append(Integer.toHexString(mContainerId));
+ }
+ if (mTag != null && !mTag.isEmpty()) {
+ sb.append(" tag=");
+ sb.append(mTag);
+ }
+ if (mRetainInstance) {
+ sb.append(" retainInstance");
+ }
+ if (mRemoving) {
+ sb.append(" removing");
+ }
+ if (mDetached) {
+ sb.append(" detached");
+ }
+ if (mHidden) {
+ sb.append(" hidden");
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mClassName);
+ dest.writeString(mWho);
+ dest.writeInt(mFromLayout ? 1 : 0);
+ dest.writeInt(mFragmentId);
+ dest.writeInt(mContainerId);
+ dest.writeString(mTag);
+ dest.writeInt(mRetainInstance ? 1 : 0);
+ dest.writeInt(mRemoving ? 1 : 0);
+ dest.writeInt(mDetached ? 1 : 0);
+ dest.writeBundle(mArguments);
+ dest.writeInt(mHidden ? 1 : 0);
+ dest.writeBundle(mSavedFragmentState);
+ dest.writeInt(mMaxLifecycleState);
+ }
+
+ public static final Parcelable.Creator CREATOR =
+ new Parcelable.Creator() {
+ @Override
+ public FragmentState createFromParcel(Parcel in) {
+ return new FragmentState(in);
+ }
+
+ @Override
+ public FragmentState[] newArray(int size) {
+ return new FragmentState[size];
+ }
+ };
+}
diff --git a/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentStatePagerAdapter.java b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentStatePagerAdapter.java
new file mode 100644
index 000000000..1e3eef059
--- /dev/null
+++ b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentStatePagerAdapter.java
@@ -0,0 +1,323 @@
+/*
+ * 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 android.os.Bundle;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.Lifecycle;
+import androidx.viewpager.widget.PagerAdapter;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+
+/**
+ * Implementation of {@link PagerAdapter} that
+ * uses a {@link Fragment} to manage each page. This class also handles
+ * saving and restoring of fragment's state.
+ *
+ *
This version of the pager is more useful when there are a large number
+ * of pages, working more like a list view. When pages are not visible to
+ * the user, their entire fragment may be destroyed, only keeping the saved
+ * state of that fragment. This allows the pager to hold on to much less
+ * memory associated with each visited page as compared to
+ * {@link FragmentPagerAdapter} at the cost of potentially more overhead when
+ * switching between pages.
+ *
+ *
When using FragmentPagerAdapter the host ViewPager must have a
+ * valid ID set.
+ *
+ *
Subclasses only need to implement {@link #getItem(int)}
+ * and {@link #getCount()} to have a working adapter.
+ *
+ *
Here is an example implementation of a pager containing fragments of
+ * lists:
+ *
+ * {@sample frameworks/support/samples/Support4Demos/src/main/java/com/example/android/supportv4/app/FragmentStatePagerSupport.java
+ * complete}
+ *
+ *
The R.layout.fragment_pager resource of the top-level fragment is:
+ *
+ * {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_pager.xml
+ * complete}
+ *
+ *
The R.layout.fragment_pager_list resource containing each
+ * individual fragment's layout is:
+ *
+ * {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_pager_list.xml
+ * complete}
+ */
+@SuppressWarnings("deprecation")
+public abstract class FragmentStatePagerAdapter extends PagerAdapter {
+ private static final String TAG = "FragmentStatePagerAdapt";
+ private static final boolean DEBUG = false;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({BEHAVIOR_SET_USER_VISIBLE_HINT, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT})
+ private @interface Behavior { }
+
+ /**
+ * Indicates that {@link Fragment#setUserVisibleHint(boolean)} will be called when the current
+ * fragment changes.
+ *
+ * @deprecated This behavior relies on the deprecated
+ * {@link Fragment#setUserVisibleHint(boolean)} API. Use
+ * {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} to switch to its replacement,
+ * {@link FragmentTransaction#setMaxLifecycle}.
+ * @see #FragmentStatePagerAdapter(FragmentManager, int)
+ */
+ @Deprecated
+ public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0;
+
+ /**
+ * Indicates that only the current fragment will be in the {@link Lifecycle.State#RESUMED}
+ * state. All other Fragments are capped at {@link Lifecycle.State#STARTED}.
+ *
+ * @see #FragmentStatePagerAdapter(FragmentManager, int)
+ */
+ public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;
+
+ private final FragmentManager mFragmentManager;
+ private final int mBehavior;
+ private FragmentTransaction mCurTransaction = null;
+
+ private ArrayList mSavedState = new ArrayList();
+ private ArrayList mFragments = new ArrayList();
+ private Fragment mCurrentPrimaryItem = null;
+
+ /**
+ * Constructor for {@link FragmentStatePagerAdapter} that sets the fragment manager for the
+ * adapter. This is the equivalent of calling
+ * {@link #FragmentStatePagerAdapter(FragmentManager, int)} and passing in
+ * {@link #BEHAVIOR_SET_USER_VISIBLE_HINT}.
+ *
+ *
Fragments will have {@link Fragment#setUserVisibleHint(boolean)} called whenever the
+ * current Fragment changes.
+ *
+ * @param fm fragment manager that will interact with this adapter
+ * @deprecated use {@link #FragmentStatePagerAdapter(FragmentManager, int)} with
+ * {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT}
+ */
+ @Deprecated
+ public FragmentStatePagerAdapter(@NonNull FragmentManager fm) {
+ this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
+ }
+
+ /**
+ * Constructor for {@link FragmentStatePagerAdapter}.
+ *
+ * If {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} is passed in, then only the current
+ * Fragment is in the {@link Lifecycle.State#RESUMED} state, while all other fragments are
+ * capped at {@link Lifecycle.State#STARTED}. If {@link #BEHAVIOR_SET_USER_VISIBLE_HINT} is
+ * passed, all fragments are in the {@link Lifecycle.State#RESUMED} state and there will be
+ * callbacks to {@link Fragment#setUserVisibleHint(boolean)}.
+ *
+ * @param fm fragment manager that will interact with this adapter
+ * @param behavior determines if only current fragments are in a resumed state
+ */
+ public FragmentStatePagerAdapter(@NonNull FragmentManager fm,
+ @Behavior int behavior) {
+ mFragmentManager = fm;
+ mBehavior = behavior;
+ }
+
+ /**
+ * Return the Fragment associated with a specified position.
+ */
+ @NonNull
+ public abstract Fragment getItem(int position);
+
+ @Override
+ public void startUpdate(@NonNull ViewGroup container) {
+ if (container.getId() == View.NO_ID) {
+ throw new IllegalStateException("ViewPager with adapter " + this
+ + " requires a view id");
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ @NonNull
+ @Override
+ public Object instantiateItem(@NonNull ViewGroup container, int position) {
+ // If we already have this item instantiated, there is nothing
+ // to do. This can happen when we are restoring the entire pager
+ // from its saved state, where the fragment manager has already
+ // taken care of restoring the fragments we previously had instantiated.
+ if (mFragments.size() > position) {
+ Fragment f = mFragments.get(position);
+ if (f != null) {
+ return f;
+ }
+ }
+
+ if (mCurTransaction == null) {
+ mCurTransaction = mFragmentManager.beginTransaction();
+ }
+
+ Fragment fragment = getItem(position);
+ if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
+ if (mSavedState.size() > position) {
+ Fragment.SavedState fss = mSavedState.get(position);
+ if (fss != null) {
+ fragment.setInitialSavedState(fss);
+ }
+ }
+ while (mFragments.size() <= position) {
+ mFragments.add(null);
+ }
+ fragment.setMenuVisibility(false);
+ if (mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) {
+ fragment.setUserVisibleHint(false);
+ }
+
+ mFragments.set(position, fragment);
+ mCurTransaction.add(container.getId(), fragment);
+
+ if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
+ mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
+ }
+
+ return fragment;
+ }
+
+ @Override
+ public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
+ Fragment fragment = (Fragment) object;
+
+ if (mCurTransaction == null) {
+ mCurTransaction = mFragmentManager.beginTransaction();
+ }
+ if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
+ + " v=" + ((Fragment)object).getView());
+ while (mSavedState.size() <= position) {
+ mSavedState.add(null);
+ }
+ mSavedState.set(position, fragment.isAdded()
+ ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
+ mFragments.set(position, null);
+
+ mCurTransaction.remove(fragment);
+ if (fragment == mCurrentPrimaryItem) {
+ mCurrentPrimaryItem = null;
+ }
+ }
+
+ @Override
+ @SuppressWarnings({"ReferenceEquality", "deprecation"})
+ public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
+ Fragment fragment = (Fragment)object;
+ if (fragment != mCurrentPrimaryItem) {
+ if (mCurrentPrimaryItem != null) {
+ mCurrentPrimaryItem.setMenuVisibility(false);
+ if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
+ if (mCurTransaction == null) {
+ mCurTransaction = mFragmentManager.beginTransaction();
+ }
+ mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
+ } else {
+ mCurrentPrimaryItem.setUserVisibleHint(false);
+ }
+ }
+ fragment.setMenuVisibility(true);
+ if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
+ if (mCurTransaction == null) {
+ mCurTransaction = mFragmentManager.beginTransaction();
+ }
+ mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
+ } else {
+ fragment.setUserVisibleHint(true);
+ }
+
+ mCurrentPrimaryItem = fragment;
+ }
+ }
+
+ @Override
+ public void finishUpdate(@NonNull ViewGroup container) {
+ if (mCurTransaction != null) {
+ mCurTransaction.commitNowAllowingStateLoss();
+ mCurTransaction = null;
+ }
+ }
+
+ @Override
+ public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
+ return ((Fragment)object).getView() == view;
+ }
+
+ @Override
+ @Nullable
+ public Parcelable saveState() {
+ Bundle state = null;
+ if (mSavedState.size() > 0) {
+ state = new Bundle();
+ Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
+ mSavedState.toArray(fss);
+ state.putParcelableArray("states", fss);
+ }
+ for (int i=0; i keys = bundle.keySet();
+ for (String key: keys) {
+ if (key.startsWith("f")) {
+ int index = Integer.parseInt(key.substring(1));
+ Fragment f = mFragmentManager.getFragment(bundle, key);
+ if (f != null) {
+ while (mFragments.size() <= index) {
+ mFragments.add(null);
+ }
+ f.setMenuVisibility(false);
+ mFragments.set(index, f);
+ } else {
+ Log.w(TAG, "Bad fragment at key " + key);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentTabHost.java b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentTabHost.java
new file mode 100644
index 000000000..f006b7004
--- /dev/null
+++ b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentTabHost.java
@@ -0,0 +1,441 @@
+/*
+ * 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 android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TabHost;
+import android.widget.TabWidget;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+
+/**
+ * Special TabHost that allows the use of {@link Fragment} objects for
+ * its tab content. When placing this in a view hierarchy, after inflating
+ * the hierarchy you must call {@link #setup(Context, FragmentManager, int)}
+ * to complete the initialization of the tab host.
+ *
+ * @deprecated Use
+ * TabLayout and ViewPager instead.
+ */
+@Deprecated
+public class FragmentTabHost extends TabHost
+ implements TabHost.OnTabChangeListener {
+ private final ArrayList mTabs = new ArrayList<>();
+
+ private FrameLayout mRealTabContent;
+ private Context mContext;
+ private FragmentManager mFragmentManager;
+ private int mContainerId;
+ private TabHost.OnTabChangeListener mOnTabChangeListener;
+ private TabInfo mLastTab;
+ private boolean mAttached;
+
+ static final class TabInfo {
+ final @NonNull String tag;
+ final @NonNull Class> clss;
+ final @Nullable Bundle args;
+ Fragment fragment;
+
+ TabInfo(@NonNull String _tag, @NonNull Class> _class, @Nullable Bundle _args) {
+ tag = _tag;
+ clss = _class;
+ args = _args;
+ }
+ }
+
+ static class DummyTabFactory implements TabHost.TabContentFactory {
+ private final Context mContext;
+
+ public DummyTabFactory(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public View createTabContent(String tag) {
+ View v = new View(mContext);
+ v.setMinimumWidth(0);
+ v.setMinimumHeight(0);
+ return v;
+ }
+ }
+
+ static class SavedState extends BaseSavedState {
+ String curTab;
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ SavedState(Parcel in) {
+ super(in);
+ curTab = in.readString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeString(curTab);
+ }
+
+ @Override
+ public String toString() {
+ return "FragmentTabHost.SavedState{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " curTab=" + curTab + "}";
+ }
+
+ public static final Parcelable.Creator CREATOR
+ = new Parcelable.Creator() {
+ @Override
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ /**
+ * @deprecated Use
+ *
+ * TabLayout and ViewPager instead.
+ */
+ @Deprecated
+ public FragmentTabHost(@NonNull Context context) {
+ // Note that we call through to the version that takes an AttributeSet,
+ // because the simple Context construct can result in a broken object!
+ super(context, null);
+ initFragmentTabHost(context, null);
+ }
+
+ /**
+ * @deprecated Use
+ *
+ * TabLayout and ViewPager instead.
+ */
+ @Deprecated
+ public FragmentTabHost(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ initFragmentTabHost(context, attrs);
+ }
+
+ private void initFragmentTabHost(Context context, AttributeSet attrs) {
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ new int[] { android.R.attr.inflatedId }, 0, 0);
+ mContainerId = a.getResourceId(0, 0);
+ a.recycle();
+
+ super.setOnTabChangedListener(this);
+ }
+
+ private void ensureHierarchy(Context context) {
+ // If owner hasn't made its own view hierarchy, then as a convenience
+ // we will construct a standard one here.
+ if (findViewById(android.R.id.tabs) == null) {
+ LinearLayout ll = new LinearLayout(context);
+ ll.setOrientation(LinearLayout.VERTICAL);
+ addView(ll, new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+
+ TabWidget tw = new TabWidget(context);
+ tw.setId(android.R.id.tabs);
+ tw.setOrientation(TabWidget.HORIZONTAL);
+ ll.addView(tw, new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT, 0));
+
+ FrameLayout fl = new FrameLayout(context);
+ fl.setId(android.R.id.tabcontent);
+ ll.addView(fl, new LinearLayout.LayoutParams(0, 0, 0));
+
+ mRealTabContent = fl = new FrameLayout(context);
+ mRealTabContent.setId(mContainerId);
+ ll.addView(fl, new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT, 0, 1));
+ }
+ }
+
+ /**
+ * @deprecated Use
+ *
+ * TabLayout and ViewPager instead.
+ */
+ @Override @Deprecated
+ public void setup() {
+ throw new IllegalStateException(
+ "Must call setup() that takes a Context and FragmentManager");
+ }
+
+ /**
+ * Set up the FragmentTabHost to use the given FragmentManager
+ *
+ * @deprecated Use
+ *
+ * TabLayout and ViewPager instead.
+ */
+ @Deprecated
+ public void setup(@NonNull Context context, @NonNull FragmentManager manager) {
+ ensureHierarchy(context); // Ensure views required by super.setup()
+ super.setup();
+ mContext = context;
+ mFragmentManager = manager;
+ ensureContent();
+ }
+
+ /**
+ * Set up the FragmentTabHost to use the given FragmentManager
+ *
+ * @deprecated Use
+ *
+ * TabLayout and ViewPager instead.
+ */
+ @Deprecated
+ public void setup(@NonNull Context context, @NonNull FragmentManager manager,
+ int containerId) {
+ ensureHierarchy(context); // Ensure views required by super.setup()
+ super.setup();
+ mContext = context;
+ mFragmentManager = manager;
+ mContainerId = containerId;
+ ensureContent();
+ mRealTabContent.setId(containerId);
+
+ // We must have an ID to be able to save/restore our state. If
+ // the owner hasn't set one at this point, we will set it ourselves.
+ if (getId() == View.NO_ID) {
+ setId(android.R.id.tabhost);
+ }
+ }
+
+ private void ensureContent() {
+ if (mRealTabContent == null) {
+ mRealTabContent = (FrameLayout)findViewById(mContainerId);
+ if (mRealTabContent == null) {
+ throw new IllegalStateException(
+ "No tab content FrameLayout found for id " + mContainerId);
+ }
+ }
+ }
+
+ /**
+ * @deprecated Use
+ *
+ * TabLayout and ViewPager instead.
+ */
+ @Deprecated
+ @Override
+ public void setOnTabChangedListener(@Nullable OnTabChangeListener l) {
+ mOnTabChangeListener = l;
+ }
+
+ /**
+ * @deprecated Use
+ *
+ * TabLayout and ViewPager instead.
+ */
+ @Deprecated
+ public void addTab(@NonNull TabHost.TabSpec tabSpec, @NonNull Class> clss,
+ @Nullable Bundle args) {
+ tabSpec.setContent(new DummyTabFactory(mContext));
+
+ final String tag = tabSpec.getTag();
+ final TabInfo info = new TabInfo(tag, clss, args);
+
+ if (mAttached) {
+ // If we are already attached to the window, then check to make
+ // sure this tab's fragment is inactive if it exists. This shouldn't
+ // normally happen.
+ info.fragment = mFragmentManager.findFragmentByTag(tag);
+ if (info.fragment != null && !info.fragment.isDetached()) {
+ final FragmentTransaction ft = mFragmentManager.beginTransaction();
+ ft.detach(info.fragment);
+ ft.commit();
+ }
+ }
+
+ mTabs.add(info);
+ addTab(tabSpec);
+ }
+
+ /**
+ * @deprecated Use
+ *
+ * TabLayout and ViewPager instead.
+ */
+ @Deprecated
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ final String currentTag = getCurrentTabTag();
+
+ // Go through all tabs and make sure their fragments match
+ // the correct state.
+ FragmentTransaction ft = null;
+ for (int i = 0, count = mTabs.size(); i < count; i++) {
+ final TabInfo tab = mTabs.get(i);
+ tab.fragment = mFragmentManager.findFragmentByTag(tab.tag);
+ if (tab.fragment != null && !tab.fragment.isDetached()) {
+ if (tab.tag.equals(currentTag)) {
+ // The fragment for this tab is already there and
+ // active, and it is what we really want to have
+ // as the current tab. Nothing to do.
+ mLastTab = tab;
+ } else {
+ // This fragment was restored in the active state,
+ // but is not the current tab. Deactivate it.
+ if (ft == null) {
+ ft = mFragmentManager.beginTransaction();
+ }
+ ft.detach(tab.fragment);
+ }
+ }
+ }
+
+ // We are now ready to go. Make sure we are switched to the
+ // correct tab.
+ mAttached = true;
+ ft = doTabChanged(currentTag, ft);
+ if (ft != null) {
+ ft.commit();
+ mFragmentManager.executePendingTransactions();
+ }
+ }
+
+ /**
+ * @deprecated Use
+ *
+ * TabLayout and ViewPager instead.
+ */
+ @Deprecated
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mAttached = false;
+ }
+
+ /**
+ * @deprecated Use
+ *
+ * TabLayout and ViewPager instead.
+ */
+ @Deprecated
+ @Override
+ @NonNull
+ protected Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+ SavedState ss = new SavedState(superState);
+ ss.curTab = getCurrentTabTag();
+ return ss;
+ }
+
+ /**
+ * @deprecated Use
+ *
+ * TabLayout and ViewPager instead.
+ */
+ @Deprecated
+ @Override
+ protected void onRestoreInstanceState(@SuppressLint("UnknownNullness") Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+ SavedState ss = (SavedState) state;
+ super.onRestoreInstanceState(ss.getSuperState());
+ setCurrentTabByTag(ss.curTab);
+ }
+
+ /**
+ * @deprecated Use
+ *
+ * TabLayout and ViewPager instead.
+ */
+ @Deprecated
+ @Override
+ public void onTabChanged(@Nullable String tabId) {
+ if (mAttached) {
+ final FragmentTransaction ft = doTabChanged(tabId, null);
+ if (ft != null) {
+ ft.commit();
+ }
+ }
+ if (mOnTabChangeListener != null) {
+ mOnTabChangeListener.onTabChanged(tabId);
+ }
+ }
+
+ @Nullable
+ private FragmentTransaction doTabChanged(@Nullable String tag,
+ @Nullable FragmentTransaction ft) {
+ final TabInfo newTab = getTabInfoForTag(tag);
+ if (mLastTab != newTab) {
+ if (ft == null) {
+ ft = mFragmentManager.beginTransaction();
+ }
+
+ if (mLastTab != null) {
+ if (mLastTab.fragment != null) {
+ ft.detach(mLastTab.fragment);
+ }
+ }
+
+ if (newTab != null) {
+ if (newTab.fragment == null) {
+ newTab.fragment = mFragmentManager.getFragmentFactory().instantiate(
+ mContext.getClassLoader(), newTab.clss.getName());
+ newTab.fragment.setArguments(newTab.args);
+ ft.add(mContainerId, newTab.fragment, newTab.tag);
+ } else {
+ ft.attach(newTab.fragment);
+ }
+ }
+
+ mLastTab = newTab;
+ }
+
+ return ft;
+ }
+
+ @Nullable
+ private TabInfo getTabInfoForTag(String tabId) {
+ for (int i = 0, count = mTabs.size(); i < count; i++) {
+ final TabInfo tab = mTabs.get(i);
+ if (tab.tag.equals(tabId)) {
+ return tab;
+ }
+ }
+ return null;
+ }
+}
diff --git a/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentTransaction.java b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentTransaction.java
new file mode 100644
index 000000000..af91cf4d1
--- /dev/null
+++ b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentTransaction.java
@@ -0,0 +1,731 @@
+/*
+ * 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 android.os.Bundle;
+import android.view.View;
+
+import androidx.annotation.AnimRes;
+import androidx.annotation.AnimatorRes;
+import androidx.annotation.IdRes;
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.StringRes;
+import androidx.annotation.StyleRes;
+import androidx.core.view.ViewCompat;
+import androidx.lifecycle.Lifecycle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+
+/**
+ * Static library support version of the framework's {@link android.app.FragmentTransaction}.
+ * 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 SDK
+ * documentation for a class overview.
+ */
+public abstract class FragmentTransaction {
+
+ static final int OP_NULL = 0;
+ static final int OP_ADD = 1;
+ static final int OP_REPLACE = 2;
+ static final int OP_REMOVE = 3;
+ static final int OP_HIDE = 4;
+ static final int OP_SHOW = 5;
+ static final int OP_DETACH = 6;
+ static final int OP_ATTACH = 7;
+ static final int OP_SET_PRIMARY_NAV = 8;
+ static final int OP_UNSET_PRIMARY_NAV = 9;
+ static final int OP_SET_MAX_LIFECYCLE = 10;
+
+ static final class Op {
+ int mCmd;
+ Fragment mFragment;
+ int mEnterAnim;
+ int mExitAnim;
+ int mPopEnterAnim;
+ int mPopExitAnim;
+ Lifecycle.State mOldMaxState;
+ Lifecycle.State mCurrentMaxState;
+
+ Op() {
+ }
+
+ Op(int cmd, Fragment fragment) {
+ this.mCmd = cmd;
+ this.mFragment = fragment;
+ this.mOldMaxState = Lifecycle.State.RESUMED;
+ this.mCurrentMaxState = Lifecycle.State.RESUMED;
+ }
+
+ Op(int cmd, @NonNull Fragment fragment, Lifecycle.State state) {
+ this.mCmd = cmd;
+ this.mFragment = fragment;
+ this.mOldMaxState = fragment.mMaxState;
+ this.mCurrentMaxState = state;
+ }
+ }
+
+ ArrayList mOps = new ArrayList<>();
+ int mEnterAnim;
+ int mExitAnim;
+ int mPopEnterAnim;
+ int mPopExitAnim;
+ int mTransition;
+ int mTransitionStyle;
+ boolean mAddToBackStack;
+ boolean mAllowAddToBackStack = true;
+ @Nullable String mName;
+
+ int mBreadCrumbTitleRes;
+ CharSequence mBreadCrumbTitleText;
+ int mBreadCrumbShortTitleRes;
+ CharSequence mBreadCrumbShortTitleText;
+
+ ArrayList mSharedElementSourceNames;
+ ArrayList mSharedElementTargetNames;
+ boolean mReorderingAllowed = false;
+
+ ArrayList mCommitRunnables;
+
+ void addOp(Op op) {
+ mOps.add(op);
+ op.mEnterAnim = mEnterAnim;
+ op.mExitAnim = mExitAnim;
+ op.mPopEnterAnim = mPopEnterAnim;
+ op.mPopExitAnim = mPopExitAnim;
+ }
+
+ /**
+ * Calls {@link #add(int, Fragment, String)} with a 0 containerViewId.
+ */
+ @NonNull
+ public FragmentTransaction add(@NonNull Fragment fragment, @Nullable String tag) {
+ doAddOp(0, fragment, tag, OP_ADD);
+ return this;
+ }
+
+ /**
+ * Calls {@link #add(int, Fragment, String)} with a null tag.
+ */
+ @NonNull
+ public FragmentTransaction add(@IdRes int containerViewId, @NonNull Fragment fragment) {
+ doAddOp(containerViewId, fragment, null, OP_ADD);
+ return this;
+ }
+
+ /**
+ * Add a fragment to the activity state. This fragment may optionally
+ * also have its view (if {@link Fragment#onCreateView Fragment.onCreateView}
+ * returns non-null) into a container view of the activity.
+ *
+ * @param containerViewId Optional identifier of the container this fragment is
+ * to be placed in. If 0, it will not be placed in a container.
+ * @param fragment The fragment to be added. This fragment must not already
+ * be added to the activity.
+ * @param tag Optional tag name for the fragment, to later retrieve the
+ * fragment with {@link FragmentManager#findFragmentByTag(String)
+ * FragmentManager.findFragmentByTag(String)}.
+ *
+ * @return Returns the same FragmentTransaction instance.
+ */
+ @NonNull
+ public FragmentTransaction add(@IdRes int containerViewId, @NonNull Fragment fragment,
+ @Nullable String tag) {
+ doAddOp(containerViewId, fragment, tag, OP_ADD);
+ return this;
+ }
+
+ void doAddOp(int containerViewId, Fragment fragment, @Nullable String tag, int opcmd) {
+ final Class> fragmentClass = fragment.getClass();
+ final int modifiers = fragmentClass.getModifiers();
+ if (fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)
+ || (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers))) {
+ throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName()
+ + " must be a public static class to be properly recreated from"
+ + " instance state.");
+ }
+
+ if (tag != null) {
+ if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
+ throw new IllegalStateException("Can't change tag of fragment "
+ + fragment + ": was " + fragment.mTag
+ + " now " + tag);
+ }
+ fragment.mTag = tag;
+ }
+
+ if (containerViewId != 0) {
+ if (containerViewId == View.NO_ID) {
+ throw new IllegalArgumentException("Can't add fragment "
+ + fragment + " with tag " + tag + " to container view with no id");
+ }
+ if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
+ throw new IllegalStateException("Can't change container ID of fragment "
+ + fragment + ": was " + fragment.mFragmentId
+ + " now " + containerViewId);
+ }
+ fragment.mContainerId = fragment.mFragmentId = containerViewId;
+ }
+
+ addOp(new Op(opcmd, fragment));
+ }
+
+ /**
+ * Calls {@link #replace(int, Fragment, String)} with a null tag.
+ */
+ @NonNull
+ public FragmentTransaction replace(@IdRes int containerViewId, @NonNull Fragment fragment) {
+ return replace(containerViewId, fragment, null);
+ }
+
+ /**
+ * Replace an existing fragment that was added to a container. This is
+ * essentially the same as calling {@link #remove(Fragment)} for all
+ * currently added fragments that were added with the same containerViewId
+ * and then {@link #add(int, Fragment, String)} with the same arguments
+ * given here.
+ *
+ * @param containerViewId Identifier of the container whose fragment(s) are
+ * to be replaced.
+ * @param fragment The new fragment to place in the container.
+ * @param tag Optional tag name for the fragment, to later retrieve the
+ * fragment with {@link FragmentManager#findFragmentByTag(String)
+ * FragmentManager.findFragmentByTag(String)}.
+ *
+ * @return Returns the same FragmentTransaction instance.
+ */
+ @NonNull
+ public FragmentTransaction replace(@IdRes int containerViewId, @NonNull Fragment fragment,
+ @Nullable String tag) {
+ if (containerViewId == 0) {
+ throw new IllegalArgumentException("Must use non-zero containerViewId");
+ }
+ doAddOp(containerViewId, fragment, tag, OP_REPLACE);
+ return this;
+ }
+
+ /**
+ * Remove an existing fragment. If it was added to a container, its view
+ * is also removed from that container.
+ *
+ * @param fragment The fragment to be removed.
+ *
+ * @return Returns the same FragmentTransaction instance.
+ */
+ @NonNull
+ public FragmentTransaction remove(@NonNull Fragment fragment) {
+ addOp(new Op(OP_REMOVE, fragment));
+
+ return this;
+ }
+
+ /**
+ * Hides an existing fragment. This is only relevant for fragments whose
+ * views have been added to a container, as this will cause the view to
+ * be hidden.
+ *
+ * @param fragment The fragment to be hidden.
+ *
+ * @return Returns the same FragmentTransaction instance.
+ */
+ @NonNull
+ public FragmentTransaction hide(@NonNull Fragment fragment) {
+ addOp(new Op(OP_HIDE, fragment));
+
+ return this;
+ }
+
+ /**
+ * Shows a previously hidden fragment. This is only relevant for fragments whose
+ * views have been added to a container, as this will cause the view to
+ * be shown.
+ *
+ * @param fragment The fragment to be shown.
+ *
+ * @return Returns the same FragmentTransaction instance.
+ */
+ @NonNull
+ public FragmentTransaction show(@NonNull Fragment fragment) {
+ addOp(new Op(OP_SHOW, fragment));
+
+ return this;
+ }
+
+ /**
+ * Detach the given fragment from the UI. This is the same state as
+ * when it is put on the back stack: the fragment is removed from
+ * the UI, however its state is still being actively managed by the
+ * fragment manager. When going into this state its view hierarchy
+ * is destroyed.
+ *
+ * @param fragment The fragment to be detached.
+ *
+ * @return Returns the same FragmentTransaction instance.
+ */
+ @NonNull
+ public FragmentTransaction detach(@NonNull Fragment fragment) {
+ addOp(new Op(OP_DETACH, fragment));
+
+ return this;
+ }
+
+ /**
+ * Re-attach a fragment after it had previously been detached from
+ * the UI with {@link #detach(Fragment)}. This
+ * causes its view hierarchy to be re-created, attached to the UI,
+ * and displayed.
+ *
+ * @param fragment The fragment to be attached.
+ *
+ * @return Returns the same FragmentTransaction instance.
+ */
+ @NonNull
+ public FragmentTransaction attach(@NonNull Fragment fragment) {
+ addOp(new Op(OP_ATTACH, fragment));
+
+ return this;
+ }
+
+ /**
+ * Set a currently active fragment in this FragmentManager as the primary navigation fragment.
+ *
+ *
The primary navigation fragment's
+ * {@link Fragment#getChildFragmentManager() child FragmentManager} will be called first
+ * to process delegated navigation actions such as {@link FragmentManager#popBackStack()}
+ * if no ID or transaction name is provided to pop to. Navigation operations outside of the
+ * fragment system may choose to delegate those actions to the primary navigation fragment
+ * as returned by {@link FragmentManager#getPrimaryNavigationFragment()}.
+ *
+ *
The fragment provided must currently be added to the FragmentManager to be set as
+ * a primary navigation fragment, or previously added as part of this transaction.
+ *
+ * @param fragment the fragment to set as the primary navigation fragment
+ * @return the same FragmentTransaction instance
+ */
+ @NonNull
+ public FragmentTransaction setPrimaryNavigationFragment(@Nullable Fragment fragment) {
+ addOp(new Op(OP_SET_PRIMARY_NAV, fragment));
+
+ return this;
+ }
+
+ /**
+ * Set a ceiling for the state of an active fragment in this FragmentManager. If fragment is
+ * already above the received state, it will be forced down to the correct state.
+ *
+ *
The fragment provided must currently be added to the FragmentManager to have it's
+ * Lifecycle state capped, or previously added as part of this transaction. The
+ * {@link Lifecycle.State} passed in must at least be {@link Lifecycle.State#CREATED}, otherwise
+ * an {@link IllegalArgumentException} will be thrown.
+ *
+ * @param fragment the fragment to have it's state capped.
+ * @param state the ceiling state for the fragment.
+ * @return the same FragmentTransaction instance
+ */
+ @NonNull
+ public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
+ @NonNull Lifecycle.State state) {
+ addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state));
+ return this;
+ }
+
+ /**
+ * @return true if this transaction contains no operations,
+ * false otherwise.
+ */
+ public boolean isEmpty() {
+ return mOps.isEmpty();
+ }
+
+ /**
+ * Bit mask that is set for all enter transitions.
+ */
+ public static final int TRANSIT_ENTER_MASK = 0x1000;
+
+ /**
+ * Bit mask that is set for all exit transitions.
+ */
+ public static final int TRANSIT_EXIT_MASK = 0x2000;
+
+ /** @hide */
+ @RestrictTo(LIBRARY_GROUP_PREFIX)
+ @IntDef({TRANSIT_NONE, TRANSIT_FRAGMENT_OPEN, TRANSIT_FRAGMENT_CLOSE, TRANSIT_FRAGMENT_FADE})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface Transit {}
+
+ /** Not set up for a transition. */
+ public static final int TRANSIT_UNSET = -1;
+ /** No animation for transition. */
+ public static final int TRANSIT_NONE = 0;
+ /** Fragment is being added onto the stack */
+ public static final int TRANSIT_FRAGMENT_OPEN = 1 | TRANSIT_ENTER_MASK;
+ /** Fragment is being removed from the stack */
+ public static final int TRANSIT_FRAGMENT_CLOSE = 2 | TRANSIT_EXIT_MASK;
+ /** Fragment should simply fade in or out; that is, no strong navigation associated
+ * with it except that it is appearing or disappearing for some reason. */
+ public static final int TRANSIT_FRAGMENT_FADE = 3 | TRANSIT_ENTER_MASK;
+
+ /**
+ * Set specific animation resources to run for the fragments that are
+ * entering and exiting in this transaction. These animations will not be
+ * played when popping the back stack.
+ *
+ * @param enter An animation or animator resource ID used for the enter animation on the
+ * view of the fragment being added or attached.
+ * @param exit An animation or animator resource ID used for the exit animation on the
+ * view of the fragment being removed or detached.
+ */
+ @NonNull
+ public FragmentTransaction setCustomAnimations(@AnimatorRes @AnimRes int enter,
+ @AnimatorRes @AnimRes int exit) {
+ return setCustomAnimations(enter, exit, 0, 0);
+ }
+
+ /**
+ * Set specific animation resources to run for the fragments that are
+ * entering and exiting in this transaction. The popEnter
+ * and popExit animations will be played for enter/exit
+ * operations specifically when popping the back stack.
+ *
+ * @param enter An animation or animator resource ID used for the enter animation on the
+ * view of the fragment being added or attached.
+ * @param exit An animation or animator resource ID used for the exit animation on the
+ * view of the fragment being removed or detached.
+ * @param popEnter An animation or animator resource ID used for the enter animation on the
+ * view of the fragment being readded or reattached caused by
+ * {@link FragmentManager#popBackStack()} or similar methods.
+ * @param popExit An animation or animator resource ID used for the enter animation on the
+ * view of the fragment being removed or detached caused by
+ * {@link FragmentManager#popBackStack()} or similar methods.
+ */
+ @NonNull
+ public FragmentTransaction setCustomAnimations(@AnimatorRes @AnimRes int enter,
+ @AnimatorRes @AnimRes int exit, @AnimatorRes @AnimRes int popEnter,
+ @AnimatorRes @AnimRes int popExit) {
+ mEnterAnim = enter;
+ mExitAnim = exit;
+ mPopEnterAnim = popEnter;
+ mPopExitAnim = popExit;
+ return this;
+ }
+
+ /**
+ * Used with custom Transitions to map a View from a removed or hidden
+ * Fragment to a View from a shown or added Fragment.
+ * sharedElement must have a unique transitionName in the View hierarchy.
+ *
+ * @param sharedElement A View in a disappearing Fragment to match with a View in an
+ * appearing Fragment.
+ * @param name The transitionName for a View in an appearing Fragment to match to the shared
+ * element.
+ * @see Fragment#setSharedElementReturnTransition(Object)
+ * @see Fragment#setSharedElementEnterTransition(Object)
+ */
+ @NonNull
+ public FragmentTransaction addSharedElement(@NonNull View sharedElement, @NonNull String name) {
+ if (FragmentTransition.supportsTransition()) {
+ String transitionName = ViewCompat.getTransitionName(sharedElement);
+ if (transitionName == null) {
+ throw new IllegalArgumentException("Unique transitionNames are required for all"
+ + " sharedElements");
+ }
+ if (mSharedElementSourceNames == null) {
+ mSharedElementSourceNames = new ArrayList();
+ mSharedElementTargetNames = new ArrayList();
+ } else if (mSharedElementTargetNames.contains(name)) {
+ throw new IllegalArgumentException("A shared element with the target name '"
+ + name + "' has already been added to the transaction.");
+ } else if (mSharedElementSourceNames.contains(transitionName)) {
+ throw new IllegalArgumentException("A shared element with the source name '"
+ + transitionName + "' has already been added to the transaction.");
+ }
+
+ mSharedElementSourceNames.add(transitionName);
+ mSharedElementTargetNames.add(name);
+ }
+ return this;
+ }
+
+ /**
+ * Select a standard transition animation for this transaction. May be
+ * one of {@link #TRANSIT_NONE}, {@link #TRANSIT_FRAGMENT_OPEN},
+ * {@link #TRANSIT_FRAGMENT_CLOSE}, or {@link #TRANSIT_FRAGMENT_FADE}.
+ */
+ @NonNull
+ public FragmentTransaction setTransition(@Transit int transition) {
+ mTransition = transition;
+ return this;
+ }
+
+ /**
+ * Set a custom style resource that will be used for resolving transit
+ * animations.
+ */
+ @NonNull
+ public FragmentTransaction setTransitionStyle(@StyleRes int styleRes) {
+ mTransitionStyle = styleRes;
+ return this;
+ }
+
+ /**
+ * Add this transaction to the back stack. This means that the transaction
+ * will be remembered after it is committed, and will reverse its operation
+ * when later popped off the stack.
+ *
+ * {@link #setReorderingAllowed(boolean)} must be set to true
+ * in the same transaction as addToBackStack() to allow the pop of that
+ * transaction to be reordered.
+ *
+ * @param name An optional name for this back stack state, or null.
+ */
+ @NonNull
+ public FragmentTransaction addToBackStack(@Nullable String name) {
+ if (!mAllowAddToBackStack) {
+ throw new IllegalStateException(
+ "This FragmentTransaction is not allowed to be added to the back stack.");
+ }
+ mAddToBackStack = true;
+ mName = name;
+ return this;
+ }
+
+ /**
+ * Returns true if this FragmentTransaction is allowed to be added to the back
+ * stack. If this method would return false, {@link #addToBackStack(String)}
+ * will throw {@link IllegalStateException}.
+ *
+ * @return True if {@link #addToBackStack(String)} is permitted on this transaction.
+ */
+ public boolean isAddToBackStackAllowed() {
+ return mAllowAddToBackStack;
+ }
+
+ /**
+ * Disallow calls to {@link #addToBackStack(String)}. Any future calls to
+ * addToBackStack will throw {@link IllegalStateException}. If addToBackStack
+ * has already been called, this method will throw IllegalStateException.
+ */
+ @NonNull
+ public FragmentTransaction disallowAddToBackStack() {
+ if (mAddToBackStack) {
+ throw new IllegalStateException(
+ "This transaction is already being added to the back stack");
+ }
+ mAllowAddToBackStack = false;
+ return this;
+ }
+
+ /**
+ * Set the full title to show as a bread crumb when this transaction
+ * is on the back stack.
+ *
+ * @param res A string resource containing the title.
+ */
+ @NonNull
+ public FragmentTransaction setBreadCrumbTitle(@StringRes int res) {
+ mBreadCrumbTitleRes = res;
+ mBreadCrumbTitleText = null;
+ return this;
+ }
+
+ /**
+ * Like {@link #setBreadCrumbTitle(int)} but taking a raw string; this
+ * method is not recommended, as the string can not be changed
+ * later if the locale changes.
+ */
+ @NonNull
+ public FragmentTransaction setBreadCrumbTitle(@Nullable CharSequence text) {
+ mBreadCrumbTitleRes = 0;
+ mBreadCrumbTitleText = text;
+ return this;
+ }
+
+ /**
+ * Set the short title to show as a bread crumb when this transaction
+ * is on the back stack.
+ *
+ * @param res A string resource containing the title.
+ */
+ @NonNull
+ public FragmentTransaction setBreadCrumbShortTitle(@StringRes int res) {
+ mBreadCrumbShortTitleRes = res;
+ mBreadCrumbShortTitleText = null;
+ return this;
+ }
+
+ /**
+ * Like {@link #setBreadCrumbShortTitle(int)} but taking a raw string; this
+ * method is not recommended, as the string can not be changed
+ * later if the locale changes.
+ */
+ @NonNull
+ public FragmentTransaction setBreadCrumbShortTitle(@Nullable CharSequence text) {
+ mBreadCrumbShortTitleRes = 0;
+ mBreadCrumbShortTitleText = text;
+ return this;
+ }
+
+ /**
+ * Sets whether or not to allow optimizing operations within and across
+ * transactions. This will remove redundant operations, eliminating
+ * operations that cancel. For example, if two transactions are executed
+ * together, one that adds a fragment A and the next replaces it with fragment B,
+ * the operations will cancel and only fragment B will be added. That means that
+ * fragment A may not go through the creation/destruction lifecycle.
+ *
+ * The side effect of removing redundant operations is that fragments may have state changes
+ * out of the expected order. For example, one transaction adds fragment A,
+ * a second adds fragment B, then a third removes fragment A. Without removing the redundant
+ * operations, fragment B could expect that while it is being created, fragment A will also
+ * exist because fragment A will be removed after fragment B was added.
+ * With removing redundant operations, fragment B cannot expect fragment A to exist when
+ * it has been created because fragment A's add/remove will be optimized out.
+ *
+ * It can also reorder the state changes of Fragments to allow for better Transitions.
+ * Added Fragments may have {@link Fragment#onCreate(Bundle)} called before replaced
+ * Fragments have {@link Fragment#onDestroy()} called.
+ *
+ * The default is {@code false}.
+ *
+ * @param reorderingAllowed {@code true} to enable optimizing out redundant operations
+ * or {@code false} to disable optimizing out redundant
+ * operations on this transaction.
+ */
+ @NonNull
+ public FragmentTransaction setReorderingAllowed(boolean reorderingAllowed) {
+ mReorderingAllowed = reorderingAllowed;
+ return this;
+ }
+
+ /**
+ * @deprecated This has been renamed {@link #setReorderingAllowed(boolean)}.
+ */
+ @Deprecated
+ @NonNull
+ public FragmentTransaction setAllowOptimization(boolean allowOptimization) {
+ return setReorderingAllowed(allowOptimization);
+ }
+
+ /**
+ * Add a Runnable to this transaction that will be run after this transaction has
+ * been committed. If fragment transactions are {@link #setReorderingAllowed(boolean) optimized}
+ * this may be after other subsequent fragment operations have also taken place, or operations
+ * in this transaction may have been optimized out due to the presence of a subsequent
+ * fragment transaction in the batch.
+ *
+ *
If a transaction is committed using {@link #commitAllowingStateLoss()} this runnable
+ * may be executed when the FragmentManager is in a state where new transactions may not
+ * be committed without allowing state loss.
+ *
+ *
runOnCommit may not be used with transactions
+ * {@link #addToBackStack(String) added to the back stack} as Runnables cannot be persisted
+ * with back stack state. {@link IllegalStateException} will be thrown if
+ * {@link #addToBackStack(String)} has been previously called for this transaction
+ * or if it is called after a call to runOnCommit.
+ *
+ * @param runnable Runnable to add
+ * @return this FragmentTransaction
+ * @throws IllegalStateException if {@link #addToBackStack(String)} has been called
+ */
+ @NonNull
+ public FragmentTransaction runOnCommit(@NonNull Runnable runnable) {
+ disallowAddToBackStack();
+ if (mCommitRunnables == null) {
+ mCommitRunnables = new ArrayList<>();
+ }
+ mCommitRunnables.add(runnable);
+ return this;
+ }
+
+ /**
+ * Schedules a commit of this transaction. The commit does
+ * not happen immediately; it will be scheduled as work on the main thread
+ * to be done the next time that thread is ready.
+ *
+ *
A transaction can only be committed with this method
+ * prior to its containing activity saving its state. If the commit is
+ * attempted after that point, an exception will be thrown. This is
+ * because the state after the commit can be lost if the activity needs to
+ * be restored from its state. See {@link #commitAllowingStateLoss()} for
+ * situations where it may be okay to lose the commit.
+ *
+ * @return Returns the identifier of this transaction's back stack entry,
+ * if {@link #addToBackStack(String)} had been called. Otherwise, returns
+ * a negative number.
+ */
+ public abstract int commit();
+
+ /**
+ * Like {@link #commit} but allows the commit to be executed after an
+ * activity's state is saved. This is dangerous because the commit can
+ * be lost if the activity needs to later be restored from its state, so
+ * this should only be used for cases where it is okay for the UI state
+ * to change unexpectedly on the user.
+ */
+ public abstract int commitAllowingStateLoss();
+
+ /**
+ * Commits this transaction synchronously. Any added fragments will be
+ * initialized and brought completely to the lifecycle state of their host
+ * and any removed fragments will be torn down accordingly before this
+ * call returns. Committing a transaction in this way allows fragments
+ * to be added as dedicated, encapsulated components that monitor the
+ * lifecycle state of their host while providing firmer ordering guarantees
+ * around when those fragments are fully initialized and ready. Fragments
+ * that manage views will have those views created and attached.
+ *
+ *
Calling commitNow is preferable to calling
+ * {@link #commit()} followed by {@link FragmentManager#executePendingTransactions()}
+ * as the latter will have the side effect of attempting to commit all
+ * currently pending transactions whether that is the desired behavior
+ * or not.
+ *
+ *
Transactions committed in this way may not be added to the
+ * FragmentManager's back stack, as doing so would break other expected
+ * ordering guarantees for other asynchronously committed transactions.
+ * This method will throw {@link IllegalStateException} if the transaction
+ * previously requested to be added to the back stack with
+ * {@link #addToBackStack(String)}.
+ *
+ *
A transaction can only be committed with this method
+ * prior to its containing activity saving its state. If the commit is
+ * attempted after that point, an exception will be thrown. This is
+ * because the state after the commit can be lost if the activity needs to
+ * be restored from its state. See {@link #commitAllowingStateLoss()} for
+ * situations where it may be okay to lose the commit.
+ */
+ public abstract void commitNow();
+
+ /**
+ * Like {@link #commitNow} but allows the commit to be executed after an
+ * activity's state is saved. This is dangerous because the commit can
+ * be lost if the activity needs to later be restored from its state, so
+ * this should only be used for cases where it is okay for the UI state
+ * to change unexpectedly on the user.
+ */
+ public abstract void commitNowAllowingStateLoss();
+}
diff --git a/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentTransition.java b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentTransition.java
new file mode 100644
index 000000000..997902e00
--- /dev/null
+++ b/fragment-1.1.0/src/main/java/androidx/fragment/app/FragmentTransition.java
@@ -0,0 +1,1267 @@
+/*
+ * 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 android.graphics.Rect;
+import android.os.Build;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.collection.ArrayMap;
+import androidx.core.app.SharedElementCallback;
+import androidx.core.view.OneShotPreDrawListener;
+import androidx.core.view.ViewCompat;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Contains the Fragment Transition functionality for both ordered and reordered
+ * Fragment Transactions. With reordered fragment transactions, all Views have been
+ * added to the View hierarchy prior to calling startTransitions. With ordered
+ * fragment transactions, Views will be removed and added after calling startTransitions.
+ */
+class FragmentTransition {
+ /**
+ * The inverse of all BackStackRecord operation commands. This assumes that
+ * REPLACE operations have already been replaced by add/remove operations.
+ */
+ private static final int[] INVERSE_OPS = {
+ BackStackRecord.OP_NULL, // inverse of OP_NULL (error)
+ BackStackRecord.OP_REMOVE, // inverse of OP_ADD
+ BackStackRecord.OP_NULL, // inverse of OP_REPLACE (error)
+ BackStackRecord.OP_ADD, // inverse of OP_REMOVE
+ BackStackRecord.OP_SHOW, // inverse of OP_HIDE
+ BackStackRecord.OP_HIDE, // inverse of OP_SHOW
+ BackStackRecord.OP_ATTACH, // inverse of OP_DETACH
+ BackStackRecord.OP_DETACH, // inverse of OP_ATTACH
+ BackStackRecord.OP_UNSET_PRIMARY_NAV, // inverse of OP_SET_PRIMARY_NAV
+ BackStackRecord.OP_SET_PRIMARY_NAV, // inverse of OP_UNSET_PRIMARY_NAV
+ BackStackRecord.OP_SET_MAX_LIFECYCLE
+ };
+
+ private static final FragmentTransitionImpl PLATFORM_IMPL = Build.VERSION.SDK_INT >= 21
+ ? new FragmentTransitionCompat21()
+ : null;
+
+ private static final FragmentTransitionImpl SUPPORT_IMPL = resolveSupportImpl();
+
+ private static FragmentTransitionImpl resolveSupportImpl() {
+ try {
+ @SuppressWarnings("unchecked")
+ Class impl = (Class) Class.forName(
+ "androidx.transition.FragmentTransitionSupport");
+ return impl.getDeclaredConstructor().newInstance();
+ } catch (Exception ignored) {
+ // support-transition is not loaded; ignore
+ }
+ return null;
+ }
+
+ /**
+ * The main entry point for Fragment Transitions, this starts the transitions
+ * set on the leaving Fragment's {@link Fragment#getExitTransition()}, the
+ * entering Fragment's {@link Fragment#getEnterTransition()} and
+ * {@link Fragment#getSharedElementEnterTransition()}. When popping,
+ * the leaving Fragment's {@link Fragment#getReturnTransition()} and
+ * {@link Fragment#getSharedElementReturnTransition()} and the entering
+ * {@link Fragment#getReenterTransition()} will be run.
+ *
+ * With reordered Fragment Transitions, all Views have been added to the
+ * View hierarchy prior to calling this method. The incoming Fragment's Views
+ * will be INVISIBLE. With ordered Fragment Transitions, this method
+ * is called before any change has been made to the hierarchy. That means
+ * that the added Fragments have not created their Views yet and the hierarchy
+ * is unknown.
+ *
+ * @param fragmentManager The executing FragmentManagerImpl
+ * @param records The list of transactions being executed.
+ * @param isRecordPop For each transaction, whether it is a pop transaction or not.
+ * @param startIndex The first index into records and isRecordPop to execute as
+ * part of this transition.
+ * @param endIndex One past the last index into records and isRecordPop to execute
+ * as part of this transition.
+ * @param isReordered true if this is a reordered transaction, meaning that the
+ * Views of incoming fragments have been added. false if the
+ * transaction has yet to be run and Views haven't been created.
+ */
+ static void startTransitions(FragmentManagerImpl fragmentManager,
+ ArrayList records, ArrayList isRecordPop,
+ int startIndex, int endIndex, boolean isReordered) {
+ if (fragmentManager.mCurState < Fragment.CREATED) {
+ return;
+ }
+
+ SparseArray transitioningFragments =
+ new SparseArray<>();
+ for (int i = startIndex; i < endIndex; i++) {
+ final BackStackRecord record = records.get(i);
+ final boolean isPop = isRecordPop.get(i);
+ if (isPop) {
+ calculatePopFragments(record, transitioningFragments, isReordered);
+ } else {
+ calculateFragments(record, transitioningFragments, isReordered);
+ }
+ }
+
+ if (transitioningFragments.size() != 0) {
+ final View nonExistentView = new View(fragmentManager.mHost.getContext());
+ final int numContainers = transitioningFragments.size();
+ for (int i = 0; i < numContainers; i++) {
+ int containerId = transitioningFragments.keyAt(i);
+ ArrayMap nameOverrides = calculateNameOverrides(containerId,
+ records, isRecordPop, startIndex, endIndex);
+
+ FragmentContainerTransition containerTransition =
+ transitioningFragments.valueAt(i);
+
+ if (isReordered) {
+ configureTransitionsReordered(fragmentManager, containerId,
+ containerTransition, nonExistentView, nameOverrides);
+ } else {
+ configureTransitionsOrdered(fragmentManager, containerId,
+ containerTransition, nonExistentView, nameOverrides);
+ }
+ }
+ }
+ }
+
+ /**
+ * Iterates through the transactions that affect a given fragment container
+ * and tracks the shared element names across transactions. This is most useful
+ * in pop transactions where the names of shared elements are known.
+ *
+ * @param containerId The container ID that is executing the transition.
+ * @param records The list of transactions being executed.
+ * @param isRecordPop For each transaction, whether it is a pop transaction or not.
+ * @param startIndex The first index into records and isRecordPop to execute as
+ * part of this transition.
+ * @param endIndex One past the last index into records and isRecordPop to execute
+ * as part of this transition.
+ * @return A map from the initial shared element name to the final shared element name
+ * before any onMapSharedElements is run.
+ */
+ private static ArrayMap calculateNameOverrides(int containerId,
+ ArrayList records, ArrayList isRecordPop,
+ int startIndex, int endIndex) {
+ ArrayMap nameOverrides = new ArrayMap<>();
+ for (int recordNum = endIndex - 1; recordNum >= startIndex; recordNum--) {
+ final BackStackRecord record = records.get(recordNum);
+ if (!record.interactsWith(containerId)) {
+ continue;
+ }
+ final boolean isPop = isRecordPop.get(recordNum);
+ if (record.mSharedElementSourceNames != null) {
+ final int numSharedElements = record.mSharedElementSourceNames.size();
+ final ArrayList sources;
+ final ArrayList targets;
+ if (isPop) {
+ targets = record.mSharedElementSourceNames;
+ sources = record.mSharedElementTargetNames;
+ } else {
+ sources = record.mSharedElementSourceNames;
+ targets = record.mSharedElementTargetNames;
+ }
+ for (int i = 0; i < numSharedElements; i++) {
+ String sourceName = sources.get(i);
+ String targetName = targets.get(i);
+ String previousTarget = nameOverrides.remove(targetName);
+ if (previousTarget != null) {
+ nameOverrides.put(sourceName, previousTarget);
+ } else {
+ nameOverrides.put(sourceName, targetName);
+ }
+ }
+ }
+ }
+ return nameOverrides;
+ }
+
+ /**
+ * Configures a transition for a single fragment container for which the transaction was
+ * reordered. That means that all Fragment Views have been added and incoming fragment
+ * Views are marked invisible.
+ *
+ * @param fragmentManager The executing FragmentManagerImpl
+ * @param containerId The container ID that is executing the transition.
+ * @param fragments A structure holding the transitioning fragments in this container.
+ * @param nonExistentView A View that does not exist in the hierarchy. This is used to
+ * prevent transitions from acting on other Views when there is no
+ * other target.
+ * @param nameOverrides A map of the shared element names from the starting fragment to
+ * the final fragment's Views as given in
+ * {@link FragmentTransaction#addSharedElement(View, String)}.
+ */
+ private static void configureTransitionsReordered(FragmentManagerImpl fragmentManager,
+ int containerId, FragmentContainerTransition fragments,
+ View nonExistentView, ArrayMap nameOverrides) {
+ ViewGroup sceneRoot = null;
+ if (fragmentManager.mContainer.onHasView()) {
+ sceneRoot = (ViewGroup) fragmentManager.mContainer.onFindViewById(containerId);
+ }
+ if (sceneRoot == null) {
+ return;
+ }
+ final Fragment inFragment = fragments.lastIn;
+ final Fragment outFragment = fragments.firstOut;
+ final FragmentTransitionImpl impl = chooseImpl(outFragment, inFragment);
+ if (impl == null) {
+ return;
+ }
+ final boolean inIsPop = fragments.lastInIsPop;
+ final boolean outIsPop = fragments.firstOutIsPop;
+
+ ArrayList sharedElementsIn = new ArrayList<>();
+ ArrayList sharedElementsOut = new ArrayList<>();
+ Object enterTransition = getEnterTransition(impl, inFragment, inIsPop);
+ Object exitTransition = getExitTransition(impl, outFragment, outIsPop);
+
+ Object sharedElementTransition = configureSharedElementsReordered(impl, sceneRoot,
+ nonExistentView, nameOverrides, fragments, sharedElementsOut, sharedElementsIn,
+ enterTransition, exitTransition);
+
+ if (enterTransition == null && sharedElementTransition == null
+ && exitTransition == null) {
+ return; // no transitions!
+ }
+
+ ArrayList exitingViews = configureEnteringExitingViews(impl, exitTransition,
+ outFragment, sharedElementsOut, nonExistentView);
+
+ ArrayList enteringViews = configureEnteringExitingViews(impl, enterTransition,
+ inFragment, sharedElementsIn, nonExistentView);
+
+ setViewVisibility(enteringViews, View.INVISIBLE);
+
+ Object transition = mergeTransitions(impl, enterTransition, exitTransition,
+ sharedElementTransition, inFragment, inIsPop);
+
+ if (transition != null) {
+ replaceHide(impl, exitTransition, outFragment, exitingViews);
+ ArrayList inNames =
+ impl.prepareSetNameOverridesReordered(sharedElementsIn);
+ impl.scheduleRemoveTargets(transition,
+ enterTransition, enteringViews, exitTransition, exitingViews,
+ sharedElementTransition, sharedElementsIn);
+ impl.beginDelayedTransition(sceneRoot, transition);
+ impl.setNameOverridesReordered(sceneRoot, sharedElementsOut,
+ sharedElementsIn, inNames, nameOverrides);
+ setViewVisibility(enteringViews, View.VISIBLE);
+ impl.swapSharedElementTargets(sharedElementTransition,
+ sharedElementsOut, sharedElementsIn);
+ }
+ }
+
+ /**
+ * Replace hide operations with visibility changes on the exiting views. Instead of making
+ * the entire fragment's view GONE, make each exiting view INVISIBLE. At the end of the
+ * transition, make the fragment's view GONE.
+ */
+ private static void replaceHide(FragmentTransitionImpl impl,
+ Object exitTransition, Fragment exitingFragment,
+ final ArrayList exitingViews) {
+ if (exitingFragment != null && exitTransition != null && exitingFragment.mAdded
+ && exitingFragment.mHidden && exitingFragment.mHiddenChanged) {
+ exitingFragment.setHideReplaced(true);
+ impl.scheduleHideFragmentView(exitTransition,
+ exitingFragment.getView(), exitingViews);
+ final ViewGroup container = exitingFragment.mContainer;
+ OneShotPreDrawListener.add(container, new Runnable() {
+ @Override
+ public void run() {
+ setViewVisibility(exitingViews, View.INVISIBLE);
+ }
+ });
+ }
+ }
+
+ /**
+ * Configures a transition for a single fragment container for which the transaction was
+ * ordered. That means that the transaction has not been executed yet, so incoming
+ * Views are not yet known.
+ *
+ * @param fragmentManager The executing FragmentManagerImpl
+ * @param containerId The container ID that is executing the transition.
+ * @param fragments A structure holding the transitioning fragments in this container.
+ * @param nonExistentView A View that does not exist in the hierarchy. This is used to
+ * prevent transitions from acting on other Views when there is no
+ * other target.
+ * @param nameOverrides A map of the shared element names from the starting fragment to
+ * the final fragment's Views as given in
+ * {@link FragmentTransaction#addSharedElement(View, String)}.
+ */
+ private static void configureTransitionsOrdered(FragmentManagerImpl fragmentManager,
+ int containerId, FragmentContainerTransition fragments,
+ View nonExistentView, ArrayMap nameOverrides) {
+ ViewGroup sceneRoot = null;
+ if (fragmentManager.mContainer.onHasView()) {
+ sceneRoot = (ViewGroup) fragmentManager.mContainer.onFindViewById(containerId);
+ }
+ if (sceneRoot == null) {
+ return;
+ }
+ final Fragment inFragment = fragments.lastIn;
+ final Fragment outFragment = fragments.firstOut;
+ final FragmentTransitionImpl impl = chooseImpl(outFragment, inFragment);
+ if (impl == null) {
+ return;
+ }
+ final boolean inIsPop = fragments.lastInIsPop;
+ final boolean outIsPop = fragments.firstOutIsPop;
+
+ Object enterTransition = getEnterTransition(impl, inFragment, inIsPop);
+ Object exitTransition = getExitTransition(impl, outFragment, outIsPop);
+
+ ArrayList sharedElementsOut = new ArrayList<>();
+ ArrayList sharedElementsIn = new ArrayList<>();
+
+ Object sharedElementTransition = configureSharedElementsOrdered(impl, sceneRoot,
+ nonExistentView, nameOverrides, fragments, sharedElementsOut, sharedElementsIn,
+ enterTransition, exitTransition);
+
+ if (enterTransition == null && sharedElementTransition == null
+ && exitTransition == null) {
+ return; // no transitions!
+ }
+
+ ArrayList exitingViews = configureEnteringExitingViews(impl, exitTransition,
+ outFragment, sharedElementsOut, nonExistentView);
+
+ if (exitingViews == null || exitingViews.isEmpty()) {
+ exitTransition = null;
+ }
+
+ // Ensure the entering transition doesn't target anything until the views are made
+ // visible
+ impl.addTarget(enterTransition, nonExistentView);
+
+ Object transition = mergeTransitions(impl, enterTransition, exitTransition,
+ sharedElementTransition, inFragment, fragments.lastInIsPop);
+
+ if (transition != null) {
+ final ArrayList enteringViews = new ArrayList<>();
+ impl.scheduleRemoveTargets(transition,
+ enterTransition, enteringViews, exitTransition, exitingViews,
+ sharedElementTransition, sharedElementsIn);
+ scheduleTargetChange(impl, sceneRoot, inFragment, nonExistentView, sharedElementsIn,
+ enterTransition, enteringViews, exitTransition, exitingViews);
+ impl.setNameOverridesOrdered(sceneRoot, sharedElementsIn, nameOverrides);
+
+ impl.beginDelayedTransition(sceneRoot, transition);
+ impl.scheduleNameReset(sceneRoot, sharedElementsIn, nameOverrides);
+ }
+ }
+
+ /**
+ * This method is used for fragment transitions for ordrered transactions to change the
+ * enter and exit transition targets after the call to
+ * {@link FragmentTransitionCompat21#beginDelayedTransition(ViewGroup, Object)}. The exit
+ * transition must ensure that it does not target any Views and the enter transition must start
+ * targeting the Views of the incoming Fragment.
+ *
+ * @param sceneRoot The fragment container View
+ * @param inFragment The last fragment that is entering
+ * @param nonExistentView A view that does not exist in the hierarchy that is used as a
+ * transition target to ensure no View is targeted.
+ * @param sharedElementsIn The shared element Views of the incoming fragment
+ * @param enterTransition The enter transition of the incoming fragment
+ * @param enteringViews The entering Views of the incoming fragment
+ * @param exitTransition The exit transition of the outgoing fragment
+ * @param exitingViews The exiting views of the outgoing fragment
+ */
+ private static void scheduleTargetChange(final FragmentTransitionImpl impl,
+ final ViewGroup sceneRoot,
+ final Fragment inFragment, final View nonExistentView,
+ final ArrayList sharedElementsIn,
+ final Object enterTransition, final ArrayList enteringViews,
+ final Object exitTransition, final ArrayList exitingViews) {
+ OneShotPreDrawListener.add(sceneRoot, new Runnable() {
+ @Override
+ public void run() {
+ if (enterTransition != null) {
+ impl.removeTarget(enterTransition,
+ nonExistentView);
+ ArrayList views = configureEnteringExitingViews(impl,
+ enterTransition, inFragment, sharedElementsIn, nonExistentView);
+ enteringViews.addAll(views);
+ }
+
+ if (exitingViews != null) {
+ if (exitTransition != null) {
+ ArrayList tempExiting = new ArrayList<>();
+ tempExiting.add(nonExistentView);
+ impl.replaceTargets(exitTransition, exitingViews,
+ tempExiting);
+ }
+ exitingViews.clear();
+ exitingViews.add(nonExistentView);
+ }
+ }
+ });
+ }
+
+ /**
+ * Chooses the appropriate implementation depending on the Transition instances hold by the
+ * Fragments.
+ */
+ private static FragmentTransitionImpl chooseImpl(Fragment outFragment, Fragment inFragment) {
+ // Collect all transition instances
+ final ArrayList