-
Notifications
You must be signed in to change notification settings - Fork 946
/
FragmentManager.java
3881 lines (3580 loc) · 160 KB
/
FragmentManager.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.fragment.app;
import static androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult.EXTRA_ACTIVITY_OPTIONS_BUNDLE;
import static androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult.ACTION_INTENT_SENDER_REQUEST;
import static androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult.EXTRA_INTENT_SENDER_REQUEST;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
import androidx.activity.OnBackPressedDispatcherOwner;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.ActivityResultRegistry;
import androidx.activity.result.ActivityResultRegistryOwner;
import androidx.activity.result.IntentSenderRequest;
import androidx.activity.result.contract.ActivityResultContract;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.IdRes;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.StringRes;
import androidx.collection.ArraySet;
import androidx.core.os.CancellationSignal;
import androidx.fragment.R;
import androidx.fragment.app.strictmode.FragmentStrictMode;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelStore;
import androidx.lifecycle.ViewModelStoreOwner;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Static library support version of the framework's {@link android.app.FragmentManager}.
* Used to write apps that run on platforms prior to Android 3.0. When running
* on Android 3.0 or above, this implementation is still used; it does not try
* to switch to the framework's implementation. See the framework {@link FragmentManager}
* documentation for a class overview.
*
* <p>Your activity must derive from {@link FragmentActivity} to use this. From such an activity,
* you can acquire the {@link FragmentManager} by calling
* {@link FragmentActivity#getSupportFragmentManager}.
*/
public abstract class FragmentManager implements FragmentResultOwner {
private static boolean DEBUG = false;
static final String TAG = "FragmentManager";
static boolean USE_STATE_MANAGER = true;
/**
* Control whether FragmentManager uses the new state manager that is responsible for:
* <ul>
* <li>Moving Fragments through their lifecycle methods</li>
* <li>Running animations and transitions</li>
* <li>Handling postponed transactions</li>
* </ul>
*
* This must only be changed <strong>before</strong> any fragment transactions are done
* (i.e., in your <code>Application</code> class or prior to <code>super.onCreate()</code>
* in every activity with the same value for all activities). Changing it after that point
* is <strong>not</strong> supported and can result in fragments not moving to their
* expected state.
* <p>
* This is <strong>enabled</strong> by default. Disabling it should only be used in
* cases where you are debugging a potential regression and as part of
* <a href="https://issuetracker.google.com/issues/new?component=460964">filing
* an issue</a> to verify and fix the regression.
*
* @param enabled Whether the new state manager should be enabled.
*/
@FragmentStateManagerControl
public static void enableNewStateManager(boolean enabled) {
FragmentManager.USE_STATE_MANAGER = enabled;
}
/**
* Control whether the framework's internal fragment manager debugging
* logs are turned on. If enabled, you will see output in logcat as
* the framework performs fragment operations.
* @deprecated FragmentManager now respects {@link Log#isLoggable(String, int)} for debug
* logging, allowing you to use <code>adb shell setprop log.tag.FragmentManager VERBOSE</code>.
* @see Log#isLoggable(String, int)
*/
@Deprecated
public static void enableDebugLogging(boolean enabled) {
FragmentManager.DEBUG = enabled;
}
static boolean isLoggingEnabled(int level) {
return DEBUG || Log.isLoggable(TAG, level);
}
/**
* Flag for {@link #popBackStack(String, int)}
* and {@link #popBackStack(int, int)}: If set, and the name or ID of
* a back stack entry has been supplied, then all matching entries will
* be consumed until one that doesn't match is found or the bottom of
* the stack is reached. Otherwise, all entries up to but not including that entry
* will be removed.
*/
public static final int POP_BACK_STACK_INCLUSIVE = 1;
/**
* Representation of an entry on the fragment back stack, as created
* with {@link FragmentTransaction#addToBackStack(String)
* FragmentTransaction.addToBackStack()}. Entries can later be
* retrieved with {@link FragmentManager#getBackStackEntryAt(int)
* FragmentManager.getBackStackEntryAt()}.
*
* <p>Note that you should never hold on to a BackStackEntry object;
* the identifier as returned by {@link #getId} is the only thing that
* will be persisted across activity instances.
*/
public interface BackStackEntry {
/**
* Return the unique identifier for the entry. This is the only
* representation of the entry that will persist across activity
* instances.
*/
int getId();
/**
* Get the name that was supplied to
* {@link FragmentTransaction#addToBackStack(String)
* FragmentTransaction.addToBackStack(String)} when creating this entry.
*/
@Nullable
String getName();
/**
* Return the full bread crumb title resource identifier for the entry,
* or 0 if it does not have one.
* @deprecated Store breadcrumb titles separately from back stack entries. For example,
* by using an <code>android:label</code> on a fragment in a navigation graph.
*/
@Deprecated
@StringRes
int getBreadCrumbTitleRes();
/**
* Return the short bread crumb title resource identifier for the entry,
* or 0 if it does not have one.
* @deprecated Store breadcrumb short titles separately from back stack entries. For
* example, by using an <code>android:label</code> on a fragment in a navigation graph.
*/
@Deprecated
@StringRes
int getBreadCrumbShortTitleRes();
/**
* Return the full bread crumb title for the entry, or null if it
* does not have one.
* @deprecated Store breadcrumb titles separately from back stack entries. For example,
* * by using an <code>android:label</code> on a fragment in a navigation graph.
*/
@Deprecated
@Nullable
CharSequence getBreadCrumbTitle();
/**
* Return the short bread crumb title for the entry, or null if it
* does not have one.
* @deprecated Store breadcrumb short titles separately from back stack entries. For
* example, by using an <code>android:label</code> on a fragment in a navigation graph.
*/
@Deprecated
@Nullable
CharSequence getBreadCrumbShortTitle();
}
/**
* Interface to watch for changes to the back stack.
*/
public interface OnBackStackChangedListener {
/**
* Called whenever the contents of the back stack change.
*/
@MainThread
void onBackStackChanged();
}
/**
* A {@link FragmentResultListener} that is lifecycle aware so that
* the listener can be fired when the lifecycle is {@link Lifecycle.State#STARTED}.
*/
private static class LifecycleAwareResultListener implements FragmentResultListener {
private final Lifecycle mLifecycle;
private final FragmentResultListener mListener;
private final LifecycleEventObserver mObserver;
LifecycleAwareResultListener(@NonNull Lifecycle lifecycle,
@NonNull FragmentResultListener listener,
@NonNull LifecycleEventObserver observer) {
mLifecycle = lifecycle;
mListener = listener;
mObserver = observer;
}
public boolean isAtLeast(Lifecycle.State state) {
return mLifecycle.getCurrentState().isAtLeast(state);
}
@Override
public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle result) {
mListener.onFragmentResult(requestKey, result);
}
public void removeObserver() {
mLifecycle.removeObserver(mObserver);
}
}
/**
* Callback interface for listening to fragment state changes that happen
* within a given FragmentManager.
*/
@SuppressWarnings("unused")
public abstract static class FragmentLifecycleCallbacks {
/**
* Called right before the fragment's {@link Fragment#onAttach(Context)} method is called.
* This is a good time to inject any required dependencies or perform other configuration
* for the fragment before any of the fragment's lifecycle methods are invoked.
*
* @param fm Host FragmentManager
* @param f Fragment changing state
* @param context Context that the Fragment is being attached to
*/
public void onFragmentPreAttached(@NonNull FragmentManager fm, @NonNull Fragment f,
@NonNull Context context) {}
/**
* Called after the fragment has been attached to its host. Its host will have had
* <code>onAttachFragment</code> called before this call happens.
*
* @param fm Host FragmentManager
* @param f Fragment changing state
* @param context Context that the Fragment was attached to
*/
public void onFragmentAttached(@NonNull FragmentManager fm, @NonNull Fragment f,
@NonNull Context context) {}
/**
* Called right before the fragment's {@link Fragment#onCreate(Bundle)} method is called.
* This is a good time to inject any required dependencies or perform other configuration
* for the fragment.
*
* @param fm Host FragmentManager
* @param f Fragment changing state
* @param savedInstanceState Saved instance bundle from a previous instance
*/
public void onFragmentPreCreated(@NonNull FragmentManager fm, @NonNull Fragment f,
@Nullable Bundle savedInstanceState) {}
/**
* Called after the fragment has returned from the FragmentManager's call to
* {@link Fragment#onCreate(Bundle)}. This will only happen once for any given
* fragment instance, though the fragment may be attached and detached multiple times.
*
* @param fm Host FragmentManager
* @param f Fragment changing state
* @param savedInstanceState Saved instance bundle from a previous instance
*/
public void onFragmentCreated(@NonNull FragmentManager fm, @NonNull Fragment f,
@Nullable Bundle savedInstanceState) {}
/**
* Called after the fragment has returned from the FragmentManager's call to
* {@link Fragment#onActivityCreated(Bundle)}. This will only happen once for any given
* fragment instance, though the fragment may be attached and detached multiple times.
*
* @param fm Host FragmentManager
* @param f Fragment changing state
* @param savedInstanceState Saved instance bundle from a previous instance
*
* @deprecated To get a callback specifically when a Fragment activity's
* {@link android.app.Activity#onCreate(Bundle)} is called, register a
* {@link androidx.lifecycle.LifecycleObserver} on the Activity's {@link Lifecycle} in
* {@link #onFragmentAttached(FragmentManager, Fragment, Context)}, removing it when it
* receives the {@link Lifecycle.State#CREATED} callback.
*/
@Deprecated
public void onFragmentActivityCreated(@NonNull FragmentManager fm, @NonNull Fragment f,
@Nullable Bundle savedInstanceState) {}
/**
* Called after the fragment has returned a non-null view from the FragmentManager's
* request to {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}.
*
* @param fm Host FragmentManager
* @param f Fragment that created and owns the view
* @param v View returned by the fragment
* @param savedInstanceState Saved instance bundle from a previous instance
*/
public void onFragmentViewCreated(@NonNull FragmentManager fm, @NonNull Fragment f,
@NonNull View v, @Nullable Bundle savedInstanceState) {}
/**
* Called after the fragment has returned from the FragmentManager's call to
* {@link Fragment#onStart()}.
*
* @param fm Host FragmentManager
* @param f Fragment changing state
*/
public void onFragmentStarted(@NonNull FragmentManager fm, @NonNull Fragment f) {}
/**
* Called after the fragment has returned from the FragmentManager's call to
* {@link Fragment#onResume()}.
*
* @param fm Host FragmentManager
* @param f Fragment changing state
*/
public void onFragmentResumed(@NonNull FragmentManager fm, @NonNull Fragment f) {}
/**
* Called after the fragment has returned from the FragmentManager's call to
* {@link Fragment#onPause()}.
*
* @param fm Host FragmentManager
* @param f Fragment changing state
*/
public void onFragmentPaused(@NonNull FragmentManager fm, @NonNull Fragment f) {}
/**
* Called after the fragment has returned from the FragmentManager's call to
* {@link Fragment#onStop()}.
*
* @param fm Host FragmentManager
* @param f Fragment changing state
*/
public void onFragmentStopped(@NonNull FragmentManager fm, @NonNull Fragment f) {}
/**
* Called after the fragment has returned from the FragmentManager's call to
* {@link Fragment#onSaveInstanceState(Bundle)}.
*
* @param fm Host FragmentManager
* @param f Fragment changing state
* @param outState Saved state bundle for the fragment
*/
public void onFragmentSaveInstanceState(@NonNull FragmentManager fm, @NonNull Fragment f,
@NonNull Bundle outState) {}
/**
* Called after the fragment has returned from the FragmentManager's call to
* {@link Fragment#onDestroyView()}.
*
* @param fm Host FragmentManager
* @param f Fragment changing state
*/
public void onFragmentViewDestroyed(@NonNull FragmentManager fm, @NonNull Fragment f) {}
/**
* Called after the fragment has returned from the FragmentManager's call to
* {@link Fragment#onDestroy()}.
*
* @param fm Host FragmentManager
* @param f Fragment changing state
*/
public void onFragmentDestroyed(@NonNull FragmentManager fm, @NonNull Fragment f) {}
/**
* Called after the fragment has returned from the FragmentManager's call to
* {@link Fragment#onDetach()}.
*
* @param fm Host FragmentManager
* @param f Fragment changing state
*/
public void onFragmentDetached(@NonNull FragmentManager fm, @NonNull Fragment f) {}
}
private final ArrayList<OpGenerator> mPendingActions = new ArrayList<>();
private boolean mExecutingActions;
private final FragmentStore mFragmentStore = new FragmentStore();
ArrayList<BackStackRecord> mBackStack;
private ArrayList<Fragment> mCreatedMenus;
private final FragmentLayoutInflaterFactory mLayoutInflaterFactory =
new FragmentLayoutInflaterFactory(this);
private OnBackPressedDispatcher mOnBackPressedDispatcher;
private final OnBackPressedCallback mOnBackPressedCallback =
new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
FragmentManager.this.handleOnBackPressed();
}
};
private final AtomicInteger mBackStackIndex = new AtomicInteger();
private final Map<String, BackStackState> mBackStackStates =
Collections.synchronizedMap(new HashMap<String, BackStackState>());
private final Map<String, Bundle> mResults =
Collections.synchronizedMap(new HashMap<String, Bundle>());
private final Map<String, LifecycleAwareResultListener> mResultListeners =
Collections.synchronizedMap(new HashMap<String, LifecycleAwareResultListener>());
private ArrayList<OnBackStackChangedListener> mBackStackChangeListeners;
private Map<Fragment, HashSet<CancellationSignal>> mExitAnimationCancellationSignals =
Collections.synchronizedMap(new HashMap<Fragment, HashSet<CancellationSignal>>());
private final FragmentTransition.Callback mFragmentTransitionCallback =
new FragmentTransition.Callback() {
@Override
public void onStart(@NonNull Fragment fragment,
@NonNull CancellationSignal signal) {
addCancellationSignal(fragment, signal);
}
@Override
public void onComplete(@NonNull Fragment f, @NonNull CancellationSignal signal) {
if (!signal.isCanceled()) {
removeCancellationSignal(f, signal);
}
}
};
private final FragmentLifecycleCallbacksDispatcher mLifecycleCallbacksDispatcher =
new FragmentLifecycleCallbacksDispatcher(this);
private final CopyOnWriteArrayList<FragmentOnAttachListener> mOnAttachListeners =
new CopyOnWriteArrayList<>();
int mCurState = Fragment.INITIALIZING;
private FragmentHostCallback<?> mHost;
private FragmentContainer mContainer;
private Fragment mParent;
@SuppressWarnings("WeakerAccess") /* synthetic access */
@Nullable
Fragment mPrimaryNav;
private FragmentFactory mFragmentFactory = null;
private FragmentFactory mHostFragmentFactory = new FragmentFactory() {
@SuppressWarnings("deprecation")
@NonNull
@Override
public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
return getHost().instantiate(getHost().getContext(), className, null);
}
};
private SpecialEffectsControllerFactory mSpecialEffectsControllerFactory = null;
private SpecialEffectsControllerFactory mDefaultSpecialEffectsControllerFactory =
new SpecialEffectsControllerFactory() {
@NonNull
@Override
public SpecialEffectsController createController(@NonNull ViewGroup container) {
return new DefaultSpecialEffectsController(container);
}
};
private ActivityResultLauncher<Intent> mStartActivityForResult;
private ActivityResultLauncher<IntentSenderRequest> mStartIntentSenderForResult;
private ActivityResultLauncher<String[]> mRequestPermissions;
ArrayDeque<LaunchedFragmentInfo> mLaunchedFragments = new ArrayDeque<>();
private static final String EXTRA_CREATED_FILLIN_INTENT = "androidx.fragment"
+ ".extra.ACTIVITY_OPTIONS_BUNDLE";
private boolean mNeedMenuInvalidate;
private boolean mStateSaved;
private boolean mStopped;
private boolean mDestroyed;
private boolean mHavePendingDeferredStart;
// Temporary vars for removing redundant operations in BackStackRecords:
private ArrayList<BackStackRecord> mTmpRecords;
private ArrayList<Boolean> mTmpIsPop;
private ArrayList<Fragment> mTmpAddedFragments;
// Postponed transactions.
private ArrayList<StartEnterTransitionListener> mPostponedTransactions;
private FragmentManagerViewModel mNonConfig;
private FragmentStrictMode.Policy mStrictModePolicy;
private Runnable mExecCommit = new Runnable() {
@Override
public void run() {
execPendingActions(true);
}
};
private void throwException(RuntimeException ex) {
Log.e(TAG, ex.getMessage());
Log.e(TAG, "Activity state:");
LogWriter logw = new LogWriter(TAG);
PrintWriter pw = new PrintWriter(logw);
if (mHost != null) {
try {
mHost.onDump(" ", null, pw, new String[] { });
} catch (Exception e) {
Log.e(TAG, "Failed dumping state", e);
}
} else {
try {
dump(" ", null, pw, new String[] { });
} catch (Exception e) {
Log.e(TAG, "Failed dumping state", e);
}
}
throw ex;
}
/**
* @hide -- remove once prebuilts are in.
* @deprecated Use {@link #beginTransaction()}.
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
@Deprecated
@NonNull
public FragmentTransaction openTransaction() {
return beginTransaction();
}
/**
* Start a series of edit operations on the Fragments associated with
* this FragmentManager.
*
* <p>Note: A fragment transaction can only be created/committed prior
* to an activity saving its state. If you try to commit a transaction
* after {@link FragmentActivity#onSaveInstanceState FragmentActivity.onSaveInstanceState()}
* (and prior to a following {@link FragmentActivity#onStart FragmentActivity.onStart}
* or {@link FragmentActivity#onResume FragmentActivity.onResume()}, you will get an error.
* This is because the framework takes care of saving your current fragments
* in the state, and if changes are made after the state is saved then they
* will be lost.</p>
*/
@NonNull
public FragmentTransaction beginTransaction() {
return new BackStackRecord(this);
}
/**
* After a {@link FragmentTransaction} is committed with
* {@link FragmentTransaction#commit FragmentTransaction.commit()}, it
* is scheduled to be executed asynchronously on the process's main thread.
* If you want to immediately executing any such pending operations, you
* can call this function (only from the main thread) to do so. Note that
* all callbacks and other related behavior will be done from within this
* call, so be careful about where this is called from.
*
* <p>If you are committing a single transaction that does not modify the
* fragment back stack, strongly consider using
* {@link FragmentTransaction#commitNow()} instead. This can help avoid
* unwanted side effects when other code in your app has pending committed
* transactions that expect different timing.</p>
* <p>
* This also forces the start of any postponed Transactions where
* {@link Fragment#postponeEnterTransition()} has been called.
*
* @return Returns true if there were any pending transactions to be
* executed.
*/
public boolean executePendingTransactions() {
boolean updates = execPendingActions(true);
forcePostponedTransactions();
return updates;
}
private void updateOnBackPressedCallbackEnabled() {
// Always enable the callback if we have pending actions
// as we don't know if they'll change the back stack entry count.
// See handleOnBackPressed() for more explanation
synchronized (mPendingActions) {
if (!mPendingActions.isEmpty()) {
mOnBackPressedCallback.setEnabled(true);
return;
}
}
// This FragmentManager needs to have a back stack for this to be enabled
// And the parent fragment, if it exists, needs to be the primary navigation
// fragment.
mOnBackPressedCallback.setEnabled(getBackStackEntryCount() > 0
&& isPrimaryNavigation(mParent));
}
/**
* Recursively check up the FragmentManager hierarchy of primary
* navigation Fragments to ensure that all of the parent Fragments are the
* primary navigation Fragment for their associated FragmentManager
*/
boolean isPrimaryNavigation(@Nullable Fragment parent) {
// If the parent is null, then we're at the root host
// and we're always the primary navigation
if (parent == null) {
return true;
}
FragmentManager parentFragmentManager = parent.mFragmentManager;
Fragment primaryNavigationFragment = parentFragmentManager
.getPrimaryNavigationFragment();
// The parent Fragment needs to be the primary navigation Fragment
// and, if it has a parent itself, that parent also needs to be
// the primary navigation fragment, recursively up the stack
return parent.equals(primaryNavigationFragment)
&& isPrimaryNavigation(parentFragmentManager.mParent);
}
/**
* Recursively check up the FragmentManager hierarchy of Fragments to see
* if the menus are all visible.
*/
boolean isParentMenuVisible(@Nullable Fragment parent) {
if (parent == null) {
return true;
}
return parent.isMenuVisible();
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
void handleOnBackPressed() {
// First, execute any pending actions to make sure we're in an
// up to date view of the world just in case anyone is queuing
// up transactions that change the back stack then immediately
// calling onBackPressed()
execPendingActions(true);
if (mOnBackPressedCallback.isEnabled()) {
// We still have a back stack, so we can pop
popBackStackImmediate();
} else {
// Sigh. Due to FragmentManager's asynchronicity, we can
// get into cases where we *think* we can handle the back
// button but because of frame perfect dispatch, we fell
// on our face. Since our callback is disabled, we can
// re-trigger the onBackPressed() to dispatch to the next
// enabled callback
mOnBackPressedDispatcher.onBackPressed();
}
}
/**
* Save the back stack. While this functions similarly to
* {@link #popBackStack(String, int)}, it <strong>does not</strong> throw away the
* state of any fragments that were added through those transactions. Instead, the
* back stack that is saved by this method can later be restored with its state
* in tact.
* <p>
* This function is asynchronous -- it enqueues the
* request to pop, but the action will not be performed until the application
* returns to its event loop.
*
* @param name The name set by {@link FragmentTransaction#addToBackStack(String)}.
*/
public void saveBackStack(@NonNull String name) {
enqueueAction(new SaveBackStackState(name), false);
}
/**
* Pop the top state off the back stack. This function is asynchronous -- it enqueues the
* request to pop, but the action will not be performed until the application
* returns to its event loop.
*/
public void popBackStack() {
enqueueAction(new PopBackStackState(null, -1, 0), false);
}
/**
* Like {@link #popBackStack()}, but performs the operation immediately
* inside of the call. This is like calling {@link #executePendingTransactions()}
* afterwards without forcing the start of postponed Transactions.
* @return Returns true if there was something popped, else false.
*/
public boolean popBackStackImmediate() {
return popBackStackImmediate(null, -1, 0);
}
/**
* Pop the last fragment transition from the manager's fragment
* back stack.
* This function is asynchronous -- it enqueues the
* request to pop, but the action will not be performed until the application
* returns to its event loop.
*
* @param name If non-null, this is the name of a previous back state
* to look for; if found, all states up to that state will be popped. The
* {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether
* the named state itself is popped. If null, only the top state is popped.
* @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}.
*/
public void popBackStack(@Nullable final String name, final int flags) {
enqueueAction(new PopBackStackState(name, -1, flags), false);
}
/**
* Like {@link #popBackStack(String, int)}, but performs the operation immediately
* inside of the call. This is like calling {@link #executePendingTransactions()}
* afterwards without forcing the start of postponed Transactions.
* @return Returns true if there was something popped, else false.
*/
public boolean popBackStackImmediate(@Nullable String name, int flags) {
return popBackStackImmediate(name, -1, flags);
}
/**
* Pop all back stack states up to the one with the given identifier.
* This function is asynchronous -- it enqueues the
* request to pop, but the action will not be performed until the application
* returns to its event loop.
*
* @param id Identifier of the stated to be popped. If no identifier exists,
* false is returned.
* The identifier is the number returned by
* {@link FragmentTransaction#commit() FragmentTransaction.commit()}. The
* {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether
* the named state itself is popped.
* @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}.
*/
public void popBackStack(final int id, final int flags) {
if (id < 0) {
throw new IllegalArgumentException("Bad id: " + id);
}
enqueueAction(new PopBackStackState(null, id, flags), false);
}
/**
* Like {@link #popBackStack(int, int)}, but performs the operation immediately
* inside of the call. This is like calling {@link #executePendingTransactions()}
* afterwards without forcing the start of postponed Transactions.
* @return Returns true if there was something popped, else false.
*/
public boolean popBackStackImmediate(int id, int flags) {
if (id < 0) {
throw new IllegalArgumentException("Bad id: " + id);
}
return popBackStackImmediate(null, id, flags);
}
/**
* Used by all public popBackStackImmediate methods, this executes pending transactions and
* returns true if the pop action did anything, regardless of what other pending
* transactions did.
*
* @return true if the pop operation did anything or false otherwise.
*/
private boolean popBackStackImmediate(@Nullable String name, int id, int flags) {
execPendingActions(false);
ensureExecReady(true);
if (mPrimaryNav != null // We have a primary nav fragment
&& id < 0 // No valid id (since they're local)
&& name == null) { // no name to pop to (since they're local)
final FragmentManager childManager = mPrimaryNav.getChildFragmentManager();
if (childManager.popBackStackImmediate()) {
// We did something, just not to this specific FragmentManager. Return true.
return true;
}
}
boolean executePop = popBackStackState(mTmpRecords, mTmpIsPop, name, id, flags);
if (executePop) {
mExecutingActions = true;
try {
removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop);
} finally {
cleanupExec();
}
}
updateOnBackPressedCallbackEnabled();
doPendingDeferredStart();
mFragmentStore.burpActive();
return executePop;
}
/**
* Return the number of entries currently in the back stack.
*/
public int getBackStackEntryCount() {
return mBackStack != null ? mBackStack.size() : 0;
}
/**
* Return the BackStackEntry at index <var>index</var> in the back stack;
* entries start index 0 being the bottom of the stack.
*/
@NonNull
public BackStackEntry getBackStackEntryAt(int index) {
return mBackStack.get(index);
}
/**
* Add a new listener for changes to the fragment back stack.
*/
public void addOnBackStackChangedListener(@NonNull OnBackStackChangedListener listener) {
if (mBackStackChangeListeners == null) {
mBackStackChangeListeners = new ArrayList<>();
}
mBackStackChangeListeners.add(listener);
}
/**
* Remove a listener that was previously added with
* {@link #addOnBackStackChangedListener(OnBackStackChangedListener)}.
*/
public void removeOnBackStackChangedListener(@NonNull OnBackStackChangedListener listener) {
if (mBackStackChangeListeners != null) {
mBackStackChangeListeners.remove(listener);
}
}
/**
* Add new {@link CancellationSignal} for exit animation cancel callbacks
*/
void addCancellationSignal(@NonNull Fragment f, @NonNull CancellationSignal signal) {
if (mExitAnimationCancellationSignals.get(f) == null) {
mExitAnimationCancellationSignals.put(f, new HashSet<CancellationSignal>());
}
mExitAnimationCancellationSignals.get(f).add(signal);
}
/**
* Remove a {@link CancellationSignal} that was previously added with
* {@link #addCancellationSignal(Fragment, CancellationSignal)}.
*
* Destroy the view of the Fragment associated with that listener and move it to the proper
* state.
*/
void removeCancellationSignal(@NonNull Fragment f, @NonNull CancellationSignal signal) {
HashSet<CancellationSignal> signals = mExitAnimationCancellationSignals.get(f);
if (signals != null && signals.remove(signal) && signals.isEmpty()) {
mExitAnimationCancellationSignals.remove(f);
// The Fragment state must be below STARTED before destroying the view to ensure we
// support hide/show
if (f.mState < Fragment.STARTED) {
destroyFragmentView(f);
moveToState(f);
}
}
}
@Override
public final void setFragmentResult(@NonNull String requestKey, @NonNull Bundle result) {
// Check if there is a listener waiting for a result with this key
LifecycleAwareResultListener resultListener = mResultListeners.get(requestKey);
// if there is and it is started, fire the callback
if (resultListener != null && resultListener.isAtLeast(Lifecycle.State.STARTED)) {
resultListener.onFragmentResult(requestKey, result);
} else {
// else, save the result for later
mResults.put(requestKey, result);
}
}
@Override
public final void clearFragmentResult(@NonNull String requestKey) {
mResults.remove(requestKey);
}
@SuppressLint("SyntheticAccessor")
@Override
public final void setFragmentResultListener(@NonNull final String requestKey,
@NonNull final LifecycleOwner lifecycleOwner,
@NonNull final FragmentResultListener listener) {
final Lifecycle lifecycle = lifecycleOwner.getLifecycle();
if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {
return;
}
LifecycleEventObserver observer = new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_START) {
// once we are started, check for any stored results
Bundle storedResult = mResults.get(requestKey);
if (storedResult != null) {
// if there is a result, fire the callback
listener.onFragmentResult(requestKey, storedResult);
// and clear the result
clearFragmentResult(requestKey);
}
}
if (event == Lifecycle.Event.ON_DESTROY) {
lifecycle.removeObserver(this);
mResultListeners.remove(requestKey);
}
}
};
lifecycle.addObserver(observer);
LifecycleAwareResultListener storedListener = mResultListeners.put(requestKey,
new LifecycleAwareResultListener(lifecycle, listener, observer));
if (storedListener != null) {
storedListener.removeObserver();
}
}
@Override
public final void clearFragmentResultListener(@NonNull String requestKey) {
LifecycleAwareResultListener listener = mResultListeners.remove(requestKey);
if (listener != null) {
listener.removeObserver();
}
}
/**
* Put a reference to a fragment in a Bundle. This Bundle can be
* persisted as saved state, and when later restoring
* {@link #getFragment(Bundle, String)} will return the current
* instance of the same fragment.
*
* @param bundle The bundle in which to put the fragment reference.
* @param key The name of the entry in the bundle.
* @param fragment The Fragment whose reference is to be stored.
*/
public void putFragment(@NonNull Bundle bundle, @NonNull String key,
@NonNull Fragment fragment) {
if (fragment.mFragmentManager != this) {
throwException(new IllegalStateException("Fragment " + fragment
+ " is not currently in the FragmentManager"));
}
bundle.putString(key, fragment.mWho);
}
/**
* Retrieve the current Fragment instance for a reference previously
* placed with {@link #putFragment(Bundle, String, Fragment)}.
*
* @param bundle The bundle from which to retrieve the fragment reference.
* @param key The name of the entry in the bundle.
* @return Returns the current Fragment instance that is associated with
* the given reference.
*/
@Nullable
public Fragment getFragment(@NonNull Bundle bundle, @NonNull String key) {
String who = bundle.getString(key);
if (who == null) {
return null;
}
Fragment f = findActiveFragment(who);
if (f == null) {
throwException(new IllegalStateException("Fragment no longer exists for key "
+ key + ": unique id " + who));
}
return f;
}
/**
* Find a {@link Fragment} associated with the given {@link View}.
*
* This method will locate the {@link Fragment} associated with this view. This is automatically
* populated for the View returned by {@link Fragment#onCreateView} and its children.
*
* @param view the view to search from
* @return the locally scoped {@link Fragment} to the given view
* @throws IllegalStateException if the given view does not correspond with a
* {@link Fragment}.
*/
@NonNull