add modded androidx.fragment lib

This commit is contained in:
Yuriy Liskov
2023-10-14 21:10:02 +03:00
parent 41d261d1fd
commit 97a0a5cd41
33 changed files with 15363 additions and 6 deletions

View File

@@ -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 {

View File

@@ -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')

1
fragment-1.1.0/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View File

@@ -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
}

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2014 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="androidx.fragment" >
<!--<uses-sdk android:minSdkVersion="17" />-->
</manifest>

View File

@@ -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<BackStackRecord> records, ArrayList<Boolean> 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<BackStackRecord> 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.
*
* <p>Removes all OP_REPLACE ops and replaces them with the proper add and remove
* operations that are equivalent to the replace.</p>
*
* <p>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()}.</p>
*
* @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<Fragment> 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<Fragment> 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();
}
}

View File

@@ -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<String> 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<String> mSharedElementSourceNames;
final ArrayList<String> 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<BackStackState> CREATOR
= new Parcelable.Creator<BackStackState>() {
@Override
public BackStackState createFromParcel(Parcel in) {
return new BackStackState(in);
}
@Override
public BackStackState[] newArray(int size) {
return new BackStackState[size];
}
};
}

View File

@@ -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 <em>not</em> add the transaction to the fragment back stack. When the fragment
* is dismissed, a new transaction will be executed to remove it from
* the activity.
* @param manager The FragmentManager this fragment will be added to.
* @param tag The tag for this fragment, as per
* {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
*/
public void show(@NonNull FragmentManager manager, @Nullable String tag) {
mDismissed = false;
mShownByMe = true;
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
ft.commit();
}
/**
* Display the dialog, adding the fragment using an existing transaction
* and then {@link FragmentTransaction#commit() committing} the transaction.
* @param transaction An existing transaction in which to add the fragment.
* @param tag The tag for this fragment, as per
* {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
* @return Returns the identifier of the committed transaction, as per
* {@link FragmentTransaction#commit() FragmentTransaction.commit()}.
*/
public int show(@NonNull FragmentTransaction transaction, @Nullable String tag) {
mDismissed = false;
mShownByMe = true;
transaction.add(this, tag);
mViewDestroyed = false;
mBackStackId = transaction.commit();
return mBackStackId;
}
/**
* Display the dialog, immediately adding the fragment to the given FragmentManager. This
* is a convenience for explicitly creating a transaction, adding the
* fragment to it with the given tag, and calling {@link FragmentTransaction#commitNow()}.
* This does <em>not</em> add the transaction to the fragment back stack. When the fragment
* is dismissed, a new transaction will be executed to remove it from
* the activity.
* @param manager The FragmentManager this fragment will be added to.
* @param tag The tag for this fragment, as per
* {@link FragmentTransaction#add(Fragment, String) FragmentTransaction.add}.
*/
public void showNow(@NonNull FragmentManager manager, @Nullable String tag) {
mDismissed = false;
mShownByMe = true;
FragmentTransaction ft = manager.beginTransaction();
ft.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).
*
* <p>This is normally set for you based on whether the fragment is
* associated with a container view ID passed to
* {@link FragmentTransaction#add(int, Fragment) FragmentTransaction.add(int, Fragment)}.
* If the fragment was added with a container, setShowsDialog will be
* initialized to false; otherwise, it will be true.
*
* @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.
*
* <p>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.
*
* <p><em>Note: DialogFragment own the {@link Dialog#setOnCancelListener
* Dialog.setOnCancelListener} and {@link Dialog#setOnDismissListener
* Dialog.setOnDismissListener} callbacks. You must not set them yourself.</em>
* To find out about these events, override {@link #onCancel(DialogInterface)}
* and {@link #onDismiss(DialogInterface)}.</p>
*
* @param savedInstanceState The last saved instance state of the Fragment,
* or null if this is a freshly created Fragment.
*
* @return Return a new Dialog instance to be displayed by the Fragment.
*/
@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;
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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);
}
}

View File

@@ -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.
* <p>
* 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<Fragment> getActiveFragments(@SuppressLint("UnknownNullness")
List<Fragment> 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; <em>note that this may be null</em>.
* @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<Fragment> 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<Fragment> 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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>
* 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.
* <p>
* 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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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.
* <p>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<String, LoaderManager> 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<String, LoaderManager> 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) {
}
}

View File

@@ -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<String, Class<?>> 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 <code>className</code> is <code>androidx.fragment.app.Fragment</code>
* 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);
}
}
}

View File

@@ -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.
* <p>
* 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<E> 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) {
}
}

View File

@@ -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.
*
* <p>Your activity must derive from {@link FragmentActivity} to use this. From such an activity,
* you can acquire the {@link FragmentManager} by calling
* {@link FragmentActivity#getSupportFragmentManager}.
*/
public abstract class FragmentManager {
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()}.
*
* <p>Note that you should never hold on to a BackStackEntry object;
* the identifier as returned by {@link #getId} is the only thing that
* will be persisted across activity instances.
*/
public interface BackStackEntry {
/**
* Return the unique identifier for the entry. This is the only
* representation of the entry that will persist across activity
* instances.
*/
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.
*
* <p>Note: A fragment transaction can only be created/committed prior
* to an activity saving its state. If you try to commit a transaction
* after {@link FragmentActivity#onSaveInstanceState FragmentActivity.onSaveInstanceState()}
* (and prior to a following {@link FragmentActivity#onStart FragmentActivity.onStart}
* or {@link FragmentActivity#onResume FragmentActivity.onResume()}, you will get an error.
* This is because the framework takes care of saving your current fragments
* in the state, and if changes are made after the state is saved then they
* will be lost.</p>
*/
@NonNull
public 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.
*
* <p>If you are committing a single transaction that does not modify the
* fragment back stack, strongly consider using
* {@link FragmentTransaction#commitNow()} instead. This can help avoid
* unwanted side effects when other code in your app has pending committed
* transactions that expect different timing.</p>
* <p>
* This also forces the start of any postponed Transactions where
* {@link Fragment#postponeEnterTransition()} has been called.
*
* @return Returns true if there were any pending transactions to be
* executed.
*/
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 <var>index</var> 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.
* <p>
* The order of the fragments in the list is the order in which they were
* added or attached.
*
* @return A list of all fragments that are added to the FragmentManager.
*/
@NonNull
public abstract List<Fragment> 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:
*
* <ul>
* <li>The Fragment must currently be attached to the FragmentManager.
* <li>A new Fragment created using this saved state must be the same class
* type as the Fragment it was created from.
* <li>The saved state can not contain dependencies on other fragments --
* that is it can't use {@link #putFragment(Bundle, String, Fragment)} to
* store a fragment reference because that reference may not be valid when
* this saved state is later used. Likewise the Fragment's target and
* result code are not included in this state.
* </ul>
*
* @param 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)}.
*
* <p>The primary navigation fragment's
* {@link Fragment#getChildFragmentManager() child FragmentManager} will be called first
* to process delegated navigation actions such as {@link #popBackStack()} if no ID
* or transaction name is provided to pop to.</p>
*
* @return the fragment designated as the primary navigation fragment
*/
@Nullable
public 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
* <code>onAttachFragment</code> called before this call happens.
*
* @param fm Host FragmentManager
* @param f Fragment changing state
* @param context Context that the Fragment was attached to
*/
public void onFragmentAttached(@NonNull FragmentManager fm, @NonNull Fragment f,
@NonNull Context context) {}
/**
* Called right before the fragment's {@link Fragment#onCreate(Bundle)} method is called.
* This is a good time to inject any required dependencies or perform other configuration
* for the fragment.
*
* @param fm Host FragmentManager
* @param f Fragment changing state
* @param savedInstanceState Saved instance bundle from a previous instance
*/
public void onFragmentPreCreated(@NonNull FragmentManager fm, @NonNull Fragment f,
@Nullable Bundle savedInstanceState) {}
/**
* Called after the fragment has returned from the FragmentManager's call to
* {@link Fragment#onCreate(Bundle)}. This will only happen once for any given
* fragment instance, though the fragment may be attached and detached multiple times.
*
* @param fm Host FragmentManager
* @param f Fragment changing state
* @param savedInstanceState Saved instance bundle from a previous instance
*/
public void onFragmentCreated(@NonNull FragmentManager fm, @NonNull Fragment f,
@Nullable Bundle savedInstanceState) {}
/**
* Called after the fragment has returned from the FragmentManager's call to
* {@link Fragment#onActivityCreated(Bundle)}. This will only happen once for any given
* fragment instance, though the fragment may be attached and detached multiple times.
*
* @param fm Host FragmentManager
* @param f Fragment changing state
* @param savedInstanceState Saved instance bundle from a previous instance
*/
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) {}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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.
*
* <p>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)}.</p>
*
* @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<Fragment> mFragments;
private final @Nullable Map<String, FragmentManagerNonConfig> mChildNonConfigs;
private final @Nullable Map<String, ViewModelStore> mViewModelStores;
FragmentManagerNonConfig(@Nullable Collection<Fragment> fragments,
@Nullable Map<String, FragmentManagerNonConfig> childNonConfigs,
@Nullable Map<String, ViewModelStore> 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<Fragment> getFragments() {
return mFragments;
}
/**
* @return the FragmentManagerNonConfigs from any applicable fragment's child FragmentManager
*/
@Nullable
Map<String, FragmentManagerNonConfig> getChildNonConfigs() {
return mChildNonConfigs;
}
/**
* @return the ViewModelStores for all fragments associated with the FragmentManager
*/
@Nullable
Map<String, ViewModelStore> getViewModelStores() {
return mViewModelStores;
}
}

View File

@@ -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<FragmentState> mActive;
ArrayList<String> 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<FragmentManagerState> CREATOR
= new Parcelable.Creator<FragmentManagerState>() {
@Override
public FragmentManagerState createFromParcel(Parcel in) {
return new FragmentManagerState(in);
}
@Override
public FragmentManagerState[] newArray(int size) {
return new FragmentManagerState[size];
}
};
}

View File

@@ -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 extends ViewModel> T create(@NonNull Class<T> 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<Fragment> mRetainedFragments = new HashSet<>();
private final HashMap<String, FragmentManagerViewModel> mChildNonConfigs = new HashMap<>();
private final HashMap<String, ViewModelStore> 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:
* <ol>
* <li>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.</li>
* <li>Not automatically saved: in this model, the FragmentManager is responsible for
* calling {@link #getSnapshot()} and later restoring the ViewModel with
* {@link #restoreFromSnapshot(FragmentManagerNonConfig)}.</li>
* </ol>
* 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<Fragment> 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<Fragment> fragments = nonConfig.getFragments();
if (fragments != null) {
mRetainedFragments.addAll(fragments);
}
Map<String, FragmentManagerNonConfig> childNonConfigs = nonConfig.getChildNonConfigs();
if (childNonConfigs != null) {
for (Map.Entry<String, FragmentManagerNonConfig> entry :
childNonConfigs.entrySet()) {
FragmentManagerViewModel childViewModel =
new FragmentManagerViewModel(mStateAutomaticallySaved);
childViewModel.restoreFromSnapshot(entry.getValue());
mChildNonConfigs.put(entry.getKey(), childViewModel);
}
}
Map<String, ViewModelStore> 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<String, FragmentManagerNonConfig> childNonConfigs = new HashMap<>();
for (Map.Entry<String, FragmentManagerViewModel> 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<Fragment> fragmentIterator = mRetainedFragments.iterator();
while (fragmentIterator.hasNext()) {
sb.append(fragmentIterator.next());
if (fragmentIterator.hasNext()) {
sb.append(", ");
}
}
sb.append(") Child Non Config (");
Iterator<String> childNonConfigIterator = mChildNonConfigs.keySet().iterator();
while (childNonConfigIterator.hasNext()) {
sb.append(childNonConfigIterator.next());
if (childNonConfigIterator.hasNext()) {
sb.append(", ");
}
}
sb.append(") ViewModelStores (");
Iterator<String> viewModelStoreIterator = mViewModelStores.keySet().iterator();
while (viewModelStoreIterator.hasNext()) {
sb.append(viewModelStoreIterator.next());
if (viewModelStoreIterator.hasNext()) {
sb.append(", ");
}
}
sb.append(')');
return sb.toString();
}
}

View File

@@ -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.
*
* <p>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}.
*
* <p>When using FragmentPagerAdapter the host ViewPager must have a
* valid ID set.</p>
*
* <p>Subclasses only need to implement {@link #getItem(int)}
* and {@link #getCount()} to have a working adapter.
*
* <p>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}
*
* <p>The <code>R.layout.fragment_pager</code> resource of the top-level fragment is:
*
* {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_pager.xml
* complete}
*
* <p>The <code>R.layout.fragment_pager_list</code> 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}.
*
* <p>Fragments will have {@link Fragment#setUserVisibleHint(boolean)} called whenever the
* current Fragment changes.</p>
*
* @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.
*
* <p>The default implementation returns the given position.
* Subclasses should override this method if the positions of items can change.</p>
*
* @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;
}
}

View File

@@ -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<FragmentState> CREATOR =
new Parcelable.Creator<FragmentState>() {
@Override
public FragmentState createFromParcel(Parcel in) {
return new FragmentState(in);
}
@Override
public FragmentState[] newArray(int size) {
return new FragmentState[size];
}
};
}

View File

@@ -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.
*
* <p>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.
*
* <p>When using FragmentPagerAdapter the host ViewPager must have a
* valid ID set.</p>
*
* <p>Subclasses only need to implement {@link #getItem(int)}
* and {@link #getCount()} to have a working adapter.
*
* <p>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}
*
* <p>The <code>R.layout.fragment_pager</code> resource of the top-level fragment is:
*
* {@sample frameworks/support/samples/Support4Demos/src/main/res/layout/fragment_pager.xml
* complete}
*
* <p>The <code>R.layout.fragment_pager_list</code> 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<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
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}.
*
* <p>Fragments will have {@link Fragment#setUserVisibleHint(boolean)} called whenever the
* current Fragment changes.</p>
*
* @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<mFragments.size(); i++) {
Fragment f = mFragments.get(i);
if (f != null && f.isAdded()) {
if (state == null) {
state = new Bundle();
}
String key = "f" + i;
mFragmentManager.putFragment(state, key, f);
}
}
return state;
}
@Override
public void restoreState(@Nullable Parcelable state, @Nullable ClassLoader loader) {
if (state != null) {
Bundle bundle = (Bundle)state;
bundle.setClassLoader(loader);
Parcelable[] fss = bundle.getParcelableArray("states");
mSavedState.clear();
mFragments.clear();
if (fss != null) {
for (int i=0; i<fss.length; i++) {
mSavedState.add((Fragment.SavedState)fss[i]);
}
}
Iterable<String> 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);
}
}
}
}
}
}

View File

@@ -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 <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
* TabLayout and ViewPager</a> instead.
*/
@Deprecated
public class FragmentTabHost extends TabHost
implements TabHost.OnTabChangeListener {
private final ArrayList<TabInfo> 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<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
/**
* @deprecated Use
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
* TabLayout and ViewPager</a> 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
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
* TabLayout and ViewPager</a> 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
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
* TabLayout and ViewPager</a> 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
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
* TabLayout and ViewPager</a> 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
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
* TabLayout and ViewPager</a> 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
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
* TabLayout and ViewPager</a> instead.
*/
@Deprecated
@Override
public void setOnTabChangedListener(@Nullable OnTabChangeListener l) {
mOnTabChangeListener = l;
}
/**
* @deprecated Use
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
* TabLayout and ViewPager</a> 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
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
* TabLayout and ViewPager</a> 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
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
* TabLayout and ViewPager</a> instead.
*/
@Deprecated
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mAttached = false;
}
/**
* @deprecated Use
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
* TabLayout and ViewPager</a> instead.
*/
@Deprecated
@Override
@NonNull
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.curTab = getCurrentTabTag();
return ss;
}
/**
* @deprecated Use
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
* TabLayout and ViewPager</a> 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
* <a href="https://developer.android.com/guide/navigation/navigation-swipe-view ">
* TabLayout and ViewPager</a> 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;
}
}

View File

@@ -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<Op> 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<String> mSharedElementSourceNames;
ArrayList<String> mSharedElementTargetNames;
boolean mReorderingAllowed = false;
ArrayList<Runnable> 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.
*
* <p>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()}.</p>
*
* <p>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.</p>
*
* @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.
*
* <p>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.</p>
*
* @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 <code>true</code> if this transaction contains no operations,
* <code>false</code> 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 <code>popEnter</code>
* and <code>popExit</code> 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.
* <var>sharedElement</var> 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<String>();
mSharedElementTargetNames = new ArrayList<String>();
} 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.
* <p>
* {@link #setReorderingAllowed(boolean)} must be set to <code>true</code>
* 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 <em>not</em> 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 <em>not</em> 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* {@link Fragment#postponeEnterTransition()} requires {@code setReorderingAllowed(true)}.
* <p>
* 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.
*
* <p>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.</p>
*
* <p><code>runOnCommit</code> 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 <code>runOnCommit</code>.</p>
*
* @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.
*
* <p class="note">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.</p>
*
* @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.
*
* <p>Calling <code>commitNow</code> is preferable to calling
* {@link #commit()} followed by {@link FragmentManager#executePendingTransactions()}
* as the latter will have the side effect of attempting to commit <em>all</em>
* currently pending transactions whether that is the desired behavior
* or not.</p>
*
* <p>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)}.</p>
*
* <p class="note">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.</p>
*/
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();
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,318 @@
/*
* 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.transition.Transition;
import android.transition.TransitionManager;
import android.transition.TransitionSet;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.RequiresApi;
import java.util.ArrayList;
import java.util.List;
@RequiresApi(21)
class FragmentTransitionCompat21 extends FragmentTransitionImpl {
@Override
public boolean canHandle(Object transition) {
return transition instanceof Transition;
}
@Override
public Object cloneTransition(Object transition) {
Transition copy = null;
if (transition != null) {
copy = ((Transition) transition).clone();
}
return copy;
}
@Override
public Object wrapTransitionInSet(Object transition) {
if (transition == null) {
return null;
}
TransitionSet transitionSet = new TransitionSet();
transitionSet.addTransition((Transition) transition);
return transitionSet;
}
@Override
public void setSharedElementTargets(Object transitionObj,
View nonExistentView, ArrayList<View> sharedViews) {
TransitionSet transition = (TransitionSet) transitionObj;
final List<View> views = transition.getTargets();
views.clear();
final int count = sharedViews.size();
for (int i = 0; i < count; i++) {
final View view = sharedViews.get(i);
bfsAddViewChildren(views, view);
}
views.add(nonExistentView);
sharedViews.add(nonExistentView);
addTargets(transition, sharedViews);
}
@Override
public void setEpicenter(Object transitionObj, View view) {
if (view != null) {
Transition transition = (Transition) transitionObj;
final Rect epicenter = new Rect();
getBoundsOnScreen(view, epicenter);
transition.setEpicenterCallback(new Transition.EpicenterCallback() {
@Override
public Rect onGetEpicenter(Transition transition) {
return epicenter;
}
});
}
}
@Override
public void addTargets(Object transitionObj, ArrayList<View> views) {
Transition transition = (Transition) transitionObj;
if (transition == null) {
return;
}
if (transition instanceof TransitionSet) {
TransitionSet set = (TransitionSet) transition;
int numTransitions = set.getTransitionCount();
for (int i = 0; i < numTransitions; i++) {
Transition child = set.getTransitionAt(i);
addTargets(child, views);
}
} else if (!hasSimpleTarget(transition)) {
List<View> targets = transition.getTargets();
if (isNullOrEmpty(targets)) {
// We can just add the target views
int numViews = views.size();
for (int i = 0; i < numViews; i++) {
transition.addTarget(views.get(i));
}
}
}
}
/**
* Returns true if there are any targets based on ID, transition or type.
*/
private static boolean hasSimpleTarget(Transition transition) {
return !isNullOrEmpty(transition.getTargetIds())
|| !isNullOrEmpty(transition.getTargetNames())
|| !isNullOrEmpty(transition.getTargetTypes());
}
@Override
public Object mergeTransitionsTogether(Object transition1, Object transition2,
Object transition3) {
TransitionSet transitionSet = new TransitionSet();
if (transition1 != null) {
transitionSet.addTransition((Transition) transition1);
}
if (transition2 != null) {
transitionSet.addTransition((Transition) transition2);
}
if (transition3 != null) {
transitionSet.addTransition((Transition) transition3);
}
return transitionSet;
}
@Override
public void scheduleHideFragmentView(Object exitTransitionObj, final View fragmentView,
final ArrayList<View> exitingViews) {
Transition exitTransition = (Transition) exitTransitionObj;
exitTransition.addListener(new Transition.TransitionListener() {
@Override
public void onTransitionStart(Transition transition) {
}
@Override
public void onTransitionEnd(Transition transition) {
transition.removeListener(this);
fragmentView.setVisibility(View.GONE);
final int numViews = exitingViews.size();
for (int i = 0; i < numViews; i++) {
exitingViews.get(i).setVisibility(View.VISIBLE);
}
}
@Override
public void onTransitionCancel(Transition transition) {
}
@Override
public void onTransitionPause(Transition transition) {
}
@Override
public void onTransitionResume(Transition transition) {
}
});
}
@Override
public Object mergeTransitionsInSequence(Object exitTransitionObj,
Object enterTransitionObj, Object sharedElementTransitionObj) {
// First do exit, then enter, but allow shared element transition to happen
// during both.
Transition staggered = null;
final Transition exitTransition = (Transition) exitTransitionObj;
final Transition enterTransition = (Transition) enterTransitionObj;
final Transition sharedElementTransition = (Transition) sharedElementTransitionObj;
if (exitTransition != null && enterTransition != null) {
staggered = new TransitionSet()
.addTransition(exitTransition)
.addTransition(enterTransition)
.setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
} else if (exitTransition != null) {
staggered = exitTransition;
} else if (enterTransition != null) {
staggered = enterTransition;
}
if (sharedElementTransition != null) {
TransitionSet together = new TransitionSet();
if (staggered != null) {
together.addTransition(staggered);
}
together.addTransition(sharedElementTransition);
return together;
} else {
return staggered;
}
}
@Override
public void beginDelayedTransition(ViewGroup sceneRoot, Object transition) {
TransitionManager.beginDelayedTransition(sceneRoot, (Transition) transition);
}
@Override
public void scheduleRemoveTargets(final Object overallTransitionObj,
final Object enterTransition, final ArrayList<View> enteringViews,
final Object exitTransition, final ArrayList<View> exitingViews,
final Object sharedElementTransition, final ArrayList<View> sharedElementsIn) {
final Transition overallTransition = (Transition) overallTransitionObj;
overallTransition.addListener(new Transition.TransitionListener() {
@Override
public void onTransitionStart(Transition transition) {
if (enterTransition != null) {
replaceTargets(enterTransition, enteringViews, null);
}
if (exitTransition != null) {
replaceTargets(exitTransition, exitingViews, null);
}
if (sharedElementTransition != null) {
replaceTargets(sharedElementTransition, sharedElementsIn, null);
}
}
@Override
public void onTransitionEnd(Transition transition) {
transition.removeListener(this);
}
@Override
public void onTransitionCancel(Transition transition) {
}
@Override
public void onTransitionPause(Transition transition) {
}
@Override
public void onTransitionResume(Transition transition) {
}
});
}
@Override
public void swapSharedElementTargets(Object sharedElementTransitionObj,
ArrayList<View> sharedElementsOut, ArrayList<View> sharedElementsIn) {
TransitionSet sharedElementTransition = (TransitionSet) sharedElementTransitionObj;
if (sharedElementTransition != null) {
sharedElementTransition.getTargets().clear();
sharedElementTransition.getTargets().addAll(sharedElementsIn);
replaceTargets(sharedElementTransition, sharedElementsOut, sharedElementsIn);
}
}
@Override
public void replaceTargets(Object transitionObj, ArrayList<View> oldTargets,
ArrayList<View> newTargets) {
Transition transition = (Transition) transitionObj;
if (transition instanceof TransitionSet) {
TransitionSet set = (TransitionSet) transition;
int numTransitions = set.getTransitionCount();
for (int i = 0; i < numTransitions; i++) {
Transition child = set.getTransitionAt(i);
replaceTargets(child, oldTargets, newTargets);
}
} else if (!hasSimpleTarget(transition)) {
List<View> targets = transition.getTargets();
if (targets != null && targets.size() == oldTargets.size()
&& targets.containsAll(oldTargets)) {
// We have an exact match. We must have added these earlier in addTargets
final int targetCount = newTargets == null ? 0 : newTargets.size();
for (int i = 0; i < targetCount; i++) {
transition.addTarget(newTargets.get(i));
}
for (int i = oldTargets.size() - 1; i >= 0; i--) {
transition.removeTarget(oldTargets.get(i));
}
}
}
}
@Override
public void addTarget(Object transitionObj, View view) {
if (transitionObj != null) {
Transition transition = (Transition) transitionObj;
transition.addTarget(view);
}
}
@Override
public void removeTarget(Object transitionObj, View view) {
if (transitionObj != null) {
Transition transition = (Transition) transitionObj;
transition.removeTarget(view);
}
}
@Override
public void setEpicenter(Object transitionObj, final Rect epicenter) {
if (transitionObj != null) {
Transition transition = (Transition) transitionObj;
transition.setEpicenterCallback(new Transition.EpicenterCallback() {
@Override
public Rect onGetEpicenter(Transition transition) {
if (epicenter == null || epicenter.isEmpty()) {
return null;
}
return epicenter;
}
});
}
}
}

View File

@@ -0,0 +1,368 @@
/*
* 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.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.core.view.OneShotPreDrawListener;
import androidx.core.view.ViewCompat;
import androidx.core.view.ViewGroupCompat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@SuppressLint("UnknownNullness")
public abstract class FragmentTransitionImpl {
/**
* Returns {@code true} if this implementation can handle the specified {@link transition}.
*/
public abstract boolean canHandle(Object transition);
/**
* Returns a clone of a transition or null if it is null
*/
public abstract Object cloneTransition(Object transition);
/**
* Wraps a transition in a TransitionSet and returns the set. If transition is null, null is
* returned.
*/
public abstract Object wrapTransitionInSet(Object transition);
/**
* Finds all children of the shared elements and sets the wrapping TransitionSet
* targets to point to those. It also limits transitions that have no targets to the
* specific shared elements. This allows developers to target child views of the
* shared elements specifically, but this doesn't happen by default.
*/
public abstract void setSharedElementTargets(Object transitionObj,
View nonExistentView, ArrayList<View> sharedViews);
/**
* Sets a transition epicenter to the rectangle of a given View.
*/
public abstract void setEpicenter(Object transitionObj, View view);
/**
* Replacement for view.getBoundsOnScreen because that is not public. This returns a rect
* containing the bounds relative to the screen that the view is in.
*/
protected void getBoundsOnScreen(View view, Rect epicenter) {
int[] loc = new int[2];
view.getLocationOnScreen(loc);
epicenter.set(loc[0], loc[1], loc[0] + view.getWidth(), loc[1] + view.getHeight());
}
/**
* This method adds views as targets to the transition, but only if the transition
* doesn't already have a target. It is best for views to contain one View object
* that does not exist in the view hierarchy (state.nonExistentView) so that
* when they are removed later, a list match will suffice to remove the targets.
* Otherwise, if you happened to have targeted the exact views for the transition,
* the replaceTargets call will remove them unexpectedly.
*/
public abstract void addTargets(Object transitionObj, ArrayList<View> views);
/**
* Creates a TransitionSet that plays all passed transitions together. Any null
* transitions passed will not be added to the set. If all are null, then an empty
* TransitionSet will be returned.
*/
public abstract Object mergeTransitionsTogether(Object transition1, Object transition2,
Object transition3);
/**
* After the transition completes, the fragment's view is set to GONE and the exiting
* views are set to VISIBLE.
*/
public abstract void scheduleHideFragmentView(Object exitTransitionObj, View fragmentView,
ArrayList<View> exitingViews);
/**
* Combines enter, exit, and shared element transition so that they play in the proper
* sequence. First the exit transition plays along with the shared element transition.
* When the exit transition completes, the enter transition starts. The shared element
* transition can continue running while the enter transition plays.
*
* @return A TransitionSet with all of enter, exit, and shared element transitions in
* it (modulo null values), ordered such that they play in the proper sequence.
*/
public abstract Object mergeTransitionsInSequence(Object exitTransitionObj,
Object enterTransitionObj, Object sharedElementTransitionObj);
/**
* Calls {@code TransitionManager#beginDelayedTransition(ViewGroup, Transition)}.
*/
public abstract void beginDelayedTransition(ViewGroup sceneRoot, Object transition);
/**
* Prepares for setting the shared element names by gathering the names of the incoming
* shared elements and clearing them. {@link #setNameOverridesReordered(View, ArrayList,
* ArrayList, ArrayList, Map)} must be called after this to complete setting the shared element
* name overrides. This must be called before
* {@link #beginDelayedTransition(ViewGroup, Object)}.
*/
ArrayList<String> prepareSetNameOverridesReordered(ArrayList<View> sharedElementsIn) {
final ArrayList<String> names = new ArrayList<>();
final int numSharedElements = sharedElementsIn.size();
for (int i = 0; i < numSharedElements; i++) {
final View view = sharedElementsIn.get(i);
names.add(ViewCompat.getTransitionName(view));
ViewCompat.setTransitionName(view, null);
}
return names;
}
/**
* Changes the shared element names for the incoming shared elements to match those of the
* outgoing shared elements. This also temporarily clears the shared element names of the
* outgoing shared elements. Must be called after
* {@link #beginDelayedTransition(ViewGroup, Object)}.
*/
void setNameOverridesReordered(final View sceneRoot,
final ArrayList<View> sharedElementsOut, final ArrayList<View> sharedElementsIn,
final ArrayList<String> inNames, final Map<String, String> nameOverrides) {
final int numSharedElements = sharedElementsIn.size();
final ArrayList<String> outNames = new ArrayList<>();
for (int i = 0; i < numSharedElements; i++) {
final View view = sharedElementsOut.get(i);
final String name = ViewCompat.getTransitionName(view);
outNames.add(name);
if (name == null) {
continue;
}
ViewCompat.setTransitionName(view, null);
final String inName = nameOverrides.get(name);
for (int j = 0; j < numSharedElements; j++) {
if (inName.equals(inNames.get(j))) {
ViewCompat.setTransitionName(sharedElementsIn.get(j), name);
break;
}
}
}
OneShotPreDrawListener.add(sceneRoot, new Runnable() {
@Override
public void run() {
for (int i = 0; i < numSharedElements; i++) {
ViewCompat.setTransitionName(sharedElementsIn.get(i), inNames.get(i));
ViewCompat.setTransitionName(sharedElementsOut.get(i), outNames.get(i));
}
}
});
}
/**
* Gets the Views in the hierarchy affected by entering and exiting Activity Scene transitions.
*
* @param transitioningViews This View will be added to transitioningViews if it is VISIBLE and
* a normal View or a ViewGroup with
* {@link android.view.ViewGroup#isTransitionGroup()} true.
* @param view The base of the view hierarchy to look in.
*/
void captureTransitioningViews(ArrayList<View> transitioningViews, View view) {
if (view.getVisibility() == View.VISIBLE) {
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
if (ViewGroupCompat.isTransitionGroup(viewGroup)) {
transitioningViews.add(viewGroup);
} else {
int count = viewGroup.getChildCount();
for (int i = 0; i < count; i++) {
View child = viewGroup.getChildAt(i);
captureTransitioningViews(transitioningViews, child);
}
}
} else {
transitioningViews.add(view);
}
}
}
/**
* Finds all views that have transition names in the hierarchy under the given view and
* stores them in {@code namedViews} map with the name as the key.
*/
void findNamedViews(Map<String, View> namedViews, @NonNull View view) {
if (view.getVisibility() == View.VISIBLE) {
String transitionName = ViewCompat.getTransitionName(view);
if (transitionName != null) {
namedViews.put(transitionName, view);
}
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
int count = viewGroup.getChildCount();
for (int i = 0; i < count; i++) {
View child = viewGroup.getChildAt(i);
findNamedViews(namedViews, child);
}
}
}
}
/**
*Applies the prepared {@code nameOverrides} to the view hierarchy.
*/
void setNameOverridesOrdered(final View sceneRoot,
final ArrayList<View> sharedElementsIn, final Map<String, String> nameOverrides) {
OneShotPreDrawListener.add(sceneRoot, new Runnable() {
@Override
public void run() {
final int numSharedElements = sharedElementsIn.size();
for (int i = 0; i < numSharedElements; i++) {
View view = sharedElementsIn.get(i);
String name = ViewCompat.getTransitionName(view);
if (name != null) {
String inName = findKeyForValue(nameOverrides, name);
ViewCompat.setTransitionName(view, inName);
}
}
}
});
}
/**
* After the transition has started, remove all targets that we added to the transitions
* so that the transitions are left in a clean state.
*/
public abstract void scheduleRemoveTargets(Object overallTransitionObj,
Object enterTransition, ArrayList<View> enteringViews,
Object exitTransition, ArrayList<View> exitingViews,
Object sharedElementTransition, ArrayList<View> sharedElementsIn);
/**
* Swap the targets for the shared element transition from those Views in sharedElementsOut
* to those in sharedElementsIn
*/
public abstract void swapSharedElementTargets(Object sharedElementTransitionObj,
ArrayList<View> sharedElementsOut, ArrayList<View> sharedElementsIn);
/**
* This method removes the views from transitions that target ONLY those views and
* replaces them with the new targets list.
* The views list should match those added in addTargets and should contain
* one view that is not in the view hierarchy (state.nonExistentView).
*/
public abstract void replaceTargets(Object transitionObj, ArrayList<View> oldTargets,
ArrayList<View> newTargets);
/**
* Adds a View target to a transition. If transitionObj is null, nothing is done.
*/
public abstract void addTarget(Object transitionObj, View view);
/**
* Remove a View target to a transition. If transitionObj is null, nothing is done.
*/
public abstract void removeTarget(Object transitionObj, View view);
/**
* Sets the epicenter of a transition to a rect object. The object can be modified until
* the transition runs.
*/
public abstract void setEpicenter(Object transitionObj, Rect epicenter);
void scheduleNameReset(final ViewGroup sceneRoot,
final ArrayList<View> sharedElementsIn, final Map<String, String> nameOverrides) {
OneShotPreDrawListener.add(sceneRoot, new Runnable() {
@Override
public void run() {
final int numSharedElements = sharedElementsIn.size();
for (int i = 0; i < numSharedElements; i++) {
final View view = sharedElementsIn.get(i);
final String name = ViewCompat.getTransitionName(view);
final String inName = nameOverrides.get(name);
ViewCompat.setTransitionName(view, inName);
}
}
});
}
/**
* Uses a breadth-first scheme to add startView and all of its children to views.
* It won't add a child if it is already in views.
*/
protected static void bfsAddViewChildren(final List<View> views, final View startView) {
final int startIndex = views.size();
if (containedBeforeIndex(views, startView, startIndex)) {
return; // This child is already in the list, so all its children are also.
}
views.add(startView);
for (int index = startIndex; index < views.size(); index++) {
final View view = views.get(index);
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
final int childCount = viewGroup.getChildCount();
for (int childIndex = 0; childIndex < childCount; childIndex++) {
final View child = viewGroup.getChildAt(childIndex);
if (!containedBeforeIndex(views, child, startIndex)) {
views.add(child);
}
}
}
}
}
/**
* Does a linear search through views for view, limited to maxIndex.
*/
private static boolean containedBeforeIndex(final List<View> views, final View view,
final int maxIndex) {
for (int i = 0; i < maxIndex; i++) {
if (views.get(i) == view) {
return true;
}
}
return false;
}
/**
* Simple utility to detect if a list is null or has no elements.
*/
protected static boolean isNullOrEmpty(List list) {
return list == null || list.isEmpty();
}
/**
* Utility to find the String key in {@code map} that maps to {@code value}.
*/
@SuppressWarnings("WeakerAccess")
static String findKeyForValue(Map<String, String> map, String value) {
for (Map.Entry<String, String> entry : map.entrySet()) {
if (value.equals(entry.getValue())) {
return entry.getKey();
}
}
return null;
}
}

View File

@@ -0,0 +1,53 @@
/*
* 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.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
class FragmentViewLifecycleOwner implements LifecycleOwner {
private LifecycleRegistry mLifecycleRegistry = null;
/**
* Initializes the underlying Lifecycle if it hasn't already been created.
*/
void initialize() {
if (mLifecycleRegistry == null) {
mLifecycleRegistry = new LifecycleRegistry(this);
}
}
/**
* @return True if the Lifecycle has been initialized.
*/
boolean isInitialized() {
return mLifecycleRegistry != null;
}
@NonNull
@Override
public Lifecycle getLifecycle() {
initialize();
return mLifecycleRegistry;
}
void handleLifecycleEvent(@NonNull Lifecycle.Event event) {
mLifecycleRegistry.handleLifecycleEvent(event);
}
}

View File

@@ -0,0 +1,401 @@
/*
* 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.os.Handler;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* Static library support version of the framework's {@link android.app.ListFragment}.
* 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 ListFragment extends Fragment {
static final int INTERNAL_EMPTY_ID = 0x00ff0001;
static final int INTERNAL_PROGRESS_CONTAINER_ID = 0x00ff0002;
static final int INTERNAL_LIST_CONTAINER_ID = 0x00ff0003;
final private Handler mHandler = new Handler();
final private Runnable mRequestFocus = new Runnable() {
@Override
public void run() {
mList.focusableViewAvailable(mList);
}
};
final private AdapterView.OnItemClickListener mOnClickListener
= new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
onListItemClick((ListView)parent, v, position, id);
}
};
ListAdapter mAdapter;
ListView mList;
View mEmptyView;
TextView mStandardEmptyView;
View mProgressContainer;
View mListContainer;
CharSequence mEmptyText;
boolean mListShown;
public ListFragment() {
}
/**
* Provide default implementation to return a simple list view. Subclasses
* can override to replace with their own layout. If doing so, the
* returned view hierarchy <em>must</em> have a ListView whose id
* is {@link android.R.id#list android.R.id.list} and can optionally
* have a sibling view id {@link android.R.id#empty android.R.id.empty}
* that is to be shown when the list is empty.
*
* <p>If you are overriding this method with your own custom content,
* consider including the standard layout {@link android.R.layout#list_content}
* in your layout file, so that you continue to retain all of the standard
* behavior of ListFragment. In particular, this is currently the only
* way to have the built-in indeterminant progress state be shown.
*/
@Override
@Nullable
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
final Context context = requireContext();
FrameLayout root = new FrameLayout(context);
// ------------------------------------------------------------------
LinearLayout pframe = new LinearLayout(context);
pframe.setId(INTERNAL_PROGRESS_CONTAINER_ID);
pframe.setOrientation(LinearLayout.VERTICAL);
pframe.setVisibility(View.GONE);
pframe.setGravity(Gravity.CENTER);
ProgressBar progress = new ProgressBar(context, null,
android.R.attr.progressBarStyleLarge);
pframe.addView(progress, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
root.addView(pframe, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
// ------------------------------------------------------------------
FrameLayout lframe = new FrameLayout(context);
lframe.setId(INTERNAL_LIST_CONTAINER_ID);
TextView tv = new TextView(context);
tv.setId(INTERNAL_EMPTY_ID);
tv.setGravity(Gravity.CENTER);
lframe.addView(tv, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
ListView lv = new ListView(context);
lv.setId(android.R.id.list);
lv.setDrawSelectorOnTop(false);
lframe.addView(lv, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
root.addView(lframe, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
// ------------------------------------------------------------------
root.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return root;
}
/**
* Attach to list view once the view hierarchy has been created.
*/
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ensureList();
}
/**
* Detach from list view.
*/
@Override
public void onDestroyView() {
mHandler.removeCallbacks(mRequestFocus);
mList = null;
mListShown = false;
mEmptyView = mProgressContainer = mListContainer = null;
mStandardEmptyView = null;
super.onDestroyView();
}
/**
* This method will be called when an item in the list is selected.
* Subclasses should override. Subclasses can call
* getListView().getItemAtPosition(position) if they need to access the
* data associated with the selected item.
*
* @param l The ListView where the click happened
* @param v The view that was clicked within the ListView
* @param position The position of the view in the list
* @param id The row id of the item that was clicked
*/
public void onListItemClick(@NonNull ListView l, @NonNull View v, int position, long id) {
}
/**
* Provide the cursor for the list view.
*/
public void setListAdapter(@Nullable ListAdapter adapter) {
boolean hadAdapter = mAdapter != null;
mAdapter = adapter;
if (mList != null) {
mList.setAdapter(adapter);
if (!mListShown && !hadAdapter) {
// The list was hidden, and previously didn't have an
// adapter. It is now time to show it.
setListShown(true, requireView().getWindowToken() != null);
}
}
}
/**
* Set the currently selected list item to the specified
* position with the adapter's data
*
* @param position
*/
public void setSelection(int position) {
ensureList();
mList.setSelection(position);
}
/**
* Get the position of the currently selected list item.
*/
public int getSelectedItemPosition() {
ensureList();
return mList.getSelectedItemPosition();
}
/**
* Get the cursor row ID of the currently selected list item.
*/
public long getSelectedItemId() {
ensureList();
return mList.getSelectedItemId();
}
/**
* Get the fragment's list view widget.
*/
@NonNull
public ListView getListView() {
ensureList();
return mList;
}
/**
* The default content for a ListFragment has a TextView that can
* be shown when the list is empty. If you would like to have it
* shown, call this method to supply the text it should use.
*/
public void setEmptyText(@Nullable CharSequence text) {
ensureList();
if (mStandardEmptyView == null) {
throw new IllegalStateException("Can't be used with a custom content view");
}
mStandardEmptyView.setText(text);
if (mEmptyText == null) {
mList.setEmptyView(mStandardEmptyView);
}
mEmptyText = text;
}
/**
* Control whether the list is being displayed. You can make it not
* displayed if you are waiting for the initial data to show in it. During
* this time an indeterminant progress indicator will be shown instead.
*
* <p>Applications do not normally need to use this themselves. The default
* behavior of ListFragment is to start with the list not being shown, only
* showing it once an adapter is given with {@link #setListAdapter(ListAdapter)}.
* If the list at that point had not been shown, when it does get shown
* it will be do without the user ever seeing the hidden state.
*
* @param shown If true, the list view is shown; if false, the progress
* indicator. The initial value is true.
*/
public void setListShown(boolean shown) {
setListShown(shown, true);
}
/**
* Like {@link #setListShown(boolean)}, but no animation is used when
* transitioning from the previous state.
*/
public void setListShownNoAnimation(boolean shown) {
setListShown(shown, false);
}
/**
* Control whether the list is being displayed. You can make it not
* displayed if you are waiting for the initial data to show in it. During
* this time an indeterminant progress indicator will be shown instead.
*
* @param shown If true, the list view is shown; if false, the progress
* indicator. The initial value is true.
* @param animate If true, an animation will be used to transition to the
* new state.
*/
private void setListShown(boolean shown, boolean animate) {
ensureList();
if (mProgressContainer == null) {
throw new IllegalStateException("Can't be used with a custom content view");
}
if (mListShown == shown) {
return;
}
mListShown = shown;
if (shown) {
if (animate) {
mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
getContext(), android.R.anim.fade_out));
mListContainer.startAnimation(AnimationUtils.loadAnimation(
getContext(), android.R.anim.fade_in));
} else {
mProgressContainer.clearAnimation();
mListContainer.clearAnimation();
}
mProgressContainer.setVisibility(View.GONE);
mListContainer.setVisibility(View.VISIBLE);
} else {
if (animate) {
mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
getContext(), android.R.anim.fade_in));
mListContainer.startAnimation(AnimationUtils.loadAnimation(
getContext(), android.R.anim.fade_out));
} else {
mProgressContainer.clearAnimation();
mListContainer.clearAnimation();
}
mProgressContainer.setVisibility(View.VISIBLE);
mListContainer.setVisibility(View.GONE);
}
}
/**
* Get the ListAdapter associated with this fragment's ListView.
*
* @see #requireListAdapter()
*/
@Nullable
public ListAdapter getListAdapter() {
return mAdapter;
}
/**
* Get the ListAdapter associated with this fragment's ListView.
*
* @throws IllegalStateException if no ListAdapter has been set.
* @see #getListAdapter()
*/
@NonNull
public final ListAdapter requireListAdapter() {
ListAdapter listAdapter = getListAdapter();
if (listAdapter == null) {
throw new IllegalStateException("ListFragment " + this
+ " does not have a ListAdapter.");
}
return listAdapter;
}
private void ensureList() {
if (mList != null) {
return;
}
View root = getView();
if (root == null) {
throw new IllegalStateException("Content view not yet created");
}
if (root instanceof ListView) {
mList = (ListView)root;
} else {
mStandardEmptyView = (TextView)root.findViewById(INTERNAL_EMPTY_ID);
if (mStandardEmptyView == null) {
mEmptyView = root.findViewById(android.R.id.empty);
} else {
mStandardEmptyView.setVisibility(View.GONE);
}
mProgressContainer = root.findViewById(INTERNAL_PROGRESS_CONTAINER_ID);
mListContainer = root.findViewById(INTERNAL_LIST_CONTAINER_ID);
View rawListView = root.findViewById(android.R.id.list);
if (!(rawListView instanceof ListView)) {
if (rawListView == null) {
throw new RuntimeException(
"Your content must have a ListView whose id attribute is " +
"'android.R.id.list'");
}
throw new RuntimeException(
"Content has view with id attribute 'android.R.id.list' "
+ "that is not a ListView class");
}
mList = (ListView)rawListView;
if (mEmptyView != null) {
mList.setEmptyView(mEmptyView);
} else if (mEmptyText != null) {
mStandardEmptyView.setText(mEmptyText);
mList.setEmptyView(mStandardEmptyView);
}
}
mListShown = true;
mList.setOnItemClickListener(mOnClickListener);
if (mAdapter != null) {
ListAdapter adapter = mAdapter;
mAdapter = null;
setListAdapter(adapter);
} else {
// We are starting without an adapter, so assume we won't
// have our data right away and start with the progress indicator.
if (mProgressContainer != null) {
setListShown(false, false);
}
}
mHandler.post(mRequestFocus);
}
}

View File

@@ -0,0 +1,25 @@
/*
* 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.AndroidRuntimeException;
final class SuperNotCalledException extends AndroidRuntimeException {
public SuperNotCalledException(String msg) {
super(msg);
}
}

View File

@@ -34,11 +34,12 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.annotation:annotation:' + annotationXLibraryVersion
implementation 'androidx.core:core:1.6.0'
implementation 'androidx.interpolator:interpolator:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'androidx.fragment:fragment:1.1.0'
implementation 'androidx.media:media:1.2.0'
implementation 'androidx.core:core:' + kotlinCoreVersion
implementation 'androidx.recyclerview:recyclerview:' + recyclerviewXLibraryVersion
implementation 'androidx.fragment:fragment:' + annotationXLibraryVersion
implementation 'androidx.media:media:' + mediaXLibraryVersion
implementation 'androidx.interpolator:interpolator:' + leanbackXLibraryVersion
implementation project(':fragment-1.1.0')
implementation project(':sharedutils')
}

View File

@@ -1,4 +1,4 @@
include ':smarttubetv', ':common', ':chatkit', ':leanbackassistant', ':leanback-1.0.0'
include ':smarttubetv', ':common', ':chatkit', ':leanbackassistant', ':leanback-1.0.0', ':fragment-1.1.0'
def rootDir = settingsDir

View File

@@ -206,6 +206,7 @@ dependencies {
androidTestImplementation 'androidx.test:rules:' + testXSupportLibraryVersion
androidTestImplementation 'androidx.test.espresso:espresso-core:' + espressoVersion
implementation project(':fragment-1.1.0')
implementation project(':leanback-1.0.0')
implementation project(':common')
implementation project(':sharedutils')