[go: nahoru, domu]

blob: 866d69dcb429a931af7c6c56a5cb139fb4b29b14 [file] [log] [blame]
jbwoodsb1757aa2019-06-12 11:17:06 -07001/*
2 * Copyright 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package androidx.fragment.app
18
19import android.animation.LayoutTransition
jbwoods2534c732019-06-20 12:42:37 -070020import android.content.Context
21import android.graphics.Canvas
jbwoodsd46ea742019-06-14 16:18:00 -070022import android.graphics.Insets
jbwoods2534c732019-06-20 12:42:37 -070023import android.os.Bundle
jbwoods2534c732019-06-20 12:42:37 -070024import android.view.LayoutInflater
25import android.view.View
26import android.view.ViewGroup
jbwoodsd46ea742019-06-14 16:18:00 -070027import android.view.WindowInsets
Jeremy Woods991f2682019-08-08 10:33:56 -070028import android.view.animation.Animation
jbwoodsb1757aa2019-06-12 11:17:06 -070029import androidx.fragment.app.test.FragmentTestActivity
30import androidx.fragment.test.R
31import androidx.test.ext.junit.runners.AndroidJUnit4
Jeremy Woodsf6472c52019-09-20 12:30:06 -070032import androidx.test.filters.MediumTest
jbwoodsd46ea742019-06-14 16:18:00 -070033import androidx.test.filters.SdkSuppress
jbwoodsb1757aa2019-06-12 11:17:06 -070034import androidx.test.rule.ActivityTestRule
jbwoods2534c732019-06-20 12:42:37 -070035import androidx.testutils.waitForExecution
jbwoodsb1757aa2019-06-12 11:17:06 -070036import com.google.common.truth.Truth.assertThat
37import com.google.common.truth.Truth.assertWithMessage
Jeremy Woods6c5151d2019-07-26 16:58:58 -070038import org.junit.Assert.fail
jbwoodsb1757aa2019-06-12 11:17:06 -070039import org.junit.Before
40import org.junit.Rule
41import org.junit.Test
42import org.junit.runner.RunWith
Jeremy Woodsf441f192019-07-22 10:52:10 -070043import java.util.concurrent.CountDownLatch
Ian Lake325987e2020-02-20 09:42:08 -080044import java.util.concurrent.TimeUnit
jbwoodsb1757aa2019-06-12 11:17:06 -070045
Jeremy Woodsf6472c52019-09-20 12:30:06 -070046@MediumTest
jbwoodsb1757aa2019-06-12 11:17:06 -070047@RunWith(AndroidJUnit4::class)
48class FragmentContainerViewTest {
49 @get:Rule
50 var activityRule = ActivityTestRule(FragmentTestActivity::class.java)
Jeremy Woods6c5151d2019-07-26 16:58:58 -070051 lateinit var context: Context
jbwoodsb1757aa2019-06-12 11:17:06 -070052
53 @Before
54 fun setupContainer() {
55 activityRule.setContentView(R.layout.fragment_container_view)
Jeremy Woods6c5151d2019-07-26 16:58:58 -070056 context = activityRule.activity.applicationContext
jbwoodsb1757aa2019-06-12 11:17:06 -070057 }
58
Jeremy Woods2882e142019-09-17 13:56:12 -070059 @SdkSuppress(minSdkVersion = 18) // androidx.transition needs setLayoutTransition for API < 18
jbwoodsb1757aa2019-06-12 11:17:06 -070060 @Test
61 fun setLayoutTransitionUnsupported() {
62 val activity = activityRule.activity
63 val layout = FragmentContainerView(activity.applicationContext)
64
65 try {
66 layout.layoutTransition = LayoutTransition()
SatoShunfb9531d2019-09-23 02:40:31 +000067 fail("setLayoutTransition should throw UnsupportedOperationException")
jbwoodsb1757aa2019-06-12 11:17:06 -070068 } catch (e: UnsupportedOperationException) {
69 assertThat(e)
70 .hasMessageThat()
71 .contains("FragmentContainerView does not support Layout Transitions or " +
72 "animateLayoutChanges=\"true\".")
73 }
74 }
75
Jeremy Woods2882e142019-09-17 13:56:12 -070076 @SdkSuppress(maxSdkVersion = 17) // androidx.transition needs setLayoutTransition for API < 18
77 @Test
78 fun setLayoutTransitionAllowed() {
79 val emptyLayoutTransition = LayoutTransition()
80 emptyLayoutTransition.setAnimator(LayoutTransition.APPEARING, null)
81 emptyLayoutTransition.setAnimator(LayoutTransition.CHANGE_APPEARING, null)
82 emptyLayoutTransition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, null)
83 emptyLayoutTransition.setAnimator(LayoutTransition.DISAPPEARING, null)
84 emptyLayoutTransition.setAnimator(4 /*LayoutTransition.Changing*/, null)
85
86 val containerView = FragmentContainerView(context)
87 containerView.layoutTransition = emptyLayoutTransition
88 }
89
jbwoodsb1757aa2019-06-12 11:17:06 -070090 // If view sets animateLayoutChanges to true, throw UnsupportedOperationException
91 @Test
92 fun animateLayoutChangesTrueUnsupported() {
93 try {
94 StrictViewFragment(R.layout.fragment_container_view_unsupported_operation)
95 } catch (e: UnsupportedOperationException) {
96 assertThat(e)
97 .hasMessageThat()
98 .contains("FragmentContainerView does not support Layout Transitions or " +
99 "animateLayoutChanges=\"true\".")
100 }
101 }
102
103 @Test
104 fun createFragmentWithFragmentContainerView() {
105 val activity = activityRule.activity
106 val fm = activity.supportFragmentManager
107
108 val fragment = StrictViewFragment(R.layout.fragment_container_view)
109 fm.beginTransaction()
110 .add(R.id.fragment_container_view, fragment)
111 .commit()
112 activityRule.runOnUiThread { fm.executePendingTransactions() }
113
114 assertWithMessage("Fragment View should be a FragmentContainerView")
115 .that(fragment.view)
116 .isInstanceOf(FragmentContainerView::class.java)
117 }
jbwoods2534c732019-06-20 12:42:37 -0700118
jbwoodsd46ea742019-06-14 16:18:00 -0700119 @SdkSuppress(minSdkVersion = 29) // WindowInsets.Builder requires API 29
120 @Test
121 fun windowInsetsDispatchToChildren() {
jbwoodsd46ea742019-06-14 16:18:00 -0700122 val parentView = FragmentContainerView(context)
123 val childView = FragmentContainerView(context)
124
125 parentView.fitsSystemWindows = true
126
127 val sentInsets = WindowInsets.Builder()
128 .setSystemWindowInsets(Insets.of(4, 3, 2, 1))
129 .build()
130
131 var dispatchedToChild = false
132 childView.setOnApplyWindowInsetsListener { _, insets ->
133 // Ensure insets received by child are not consumed at all by the parent
134 assertThat(insets.systemWindowInsets).isEqualTo(sentInsets.systemWindowInsets)
135 dispatchedToChild = true
136 insets
137 }
138
Jeremy Woods6c5151d2019-07-26 16:58:58 -0700139 childView.setTag(R.id.fragment_container_view_tag, Fragment())
140
jbwoodsd46ea742019-06-14 16:18:00 -0700141 parentView.addView(childView)
142 parentView.dispatchApplyWindowInsets(sentInsets)
143
144 assertThat(dispatchedToChild).isTrue()
145 }
146
jbwoods2534c732019-06-20 12:42:37 -0700147 @Test
Jeremy Woods6c5151d2019-07-26 16:58:58 -0700148 fun addView() {
Jeremy Woods6c5151d2019-07-26 16:58:58 -0700149 val view = View(context)
150 val fragment = Fragment()
151 fragment.mView = view
152
Ian Lake20f7c622019-10-04 10:42:27 -0700153 // Mimic what FragmentStateManager.createView() does
154 fragment.mView.setTag(androidx.fragment.R.id.fragment_container_view_tag, fragment)
Jeremy Woods6c5151d2019-07-26 16:58:58 -0700155
156 val fragmentContainerView = FragmentContainerView(context)
157
158 assertWithMessage("FragmentContainerView should have no child views")
159 .that(fragmentContainerView.childCount).isEqualTo(0)
160
161 fragmentContainerView.addView(view)
162
163 assertWithMessage("FragmentContainerView should have one child view")
164 .that(fragmentContainerView.childCount).isEqualTo(1)
165 }
166
167 @Test
168 fun addViewNotAssociatedWithFragment() {
169 val view = View(context)
170
171 try {
172 FragmentContainerView(context).addView(view, 0, null)
173 fail("View without a Fragment added to FragmentContainerView should throw an exception")
174 } catch (e: IllegalStateException) {
175 assertThat(e)
176 .hasMessageThat().contains(
177 "Views added to a FragmentContainerView must be associated with a Fragment. " +
178 "View " + view + " is not associated with a Fragment."
179 )
180 }
181 }
182
183 @Test
184 fun addViewInLayoutNotAssociatedWithFragment() {
185 val view = View(context)
186
187 try {
188 FragmentContainerView(context).addViewInLayout(view, 0, null, false)
189 fail("View without a Fragment added to FragmentContainerView should throw an exception")
190 } catch (e: IllegalStateException) {
191 assertThat(e)
192 .hasMessageThat().contains(
193 "Views added to a FragmentContainerView must be associated with a Fragment. " +
194 "View " + view + " is not associated with a Fragment."
195 )
196 }
197 }
198
199 @Test
jbwoods2534c732019-06-20 12:42:37 -0700200 fun removeViewAt() {
jbwoods2534c732019-06-20 12:42:37 -0700201 val childView2 = FragmentContainerView(context)
202
Jeremy Woods640fd632019-07-27 21:24:42 -0700203 val view = setupRemoveTestsView(FragmentContainerView(context), childView2)
jbwoods2534c732019-06-20 12:42:37 -0700204
205 view.removeViewAt(0)
206
207 assertThat(view.childCount).isEqualTo(1)
208 assertThat(view.getChildAt(0)).isEqualTo(childView2)
209 }
210
211 @Test
212 fun removeViewInLayout() {
jbwoods2534c732019-06-20 12:42:37 -0700213 val childView1 = FragmentContainerView(context)
214 val childView2 = FragmentContainerView(context)
215
Jeremy Woods640fd632019-07-27 21:24:42 -0700216 val view = setupRemoveTestsView(childView1, childView2)
jbwoods2534c732019-06-20 12:42:37 -0700217
218 view.removeViewInLayout(childView1)
219
220 assertThat(view.childCount).isEqualTo(1)
221 assertThat(view.getChildAt(0)).isEqualTo(childView2)
222 }
223
224 @Test
225 fun removeView() {
jbwoods2534c732019-06-20 12:42:37 -0700226 val childView1 = FragmentContainerView(context)
227 val childView2 = FragmentContainerView(context)
228
Jeremy Woods640fd632019-07-27 21:24:42 -0700229 val view = setupRemoveTestsView(childView1, childView2)
jbwoods2534c732019-06-20 12:42:37 -0700230
231 view.removeView(childView1)
232
233 assertThat(view.getChildAt(0)).isEqualTo(childView2)
234 }
235
236 @Test
237 fun removeViews() {
Jeremy Woods640fd632019-07-27 21:24:42 -0700238 val view = setupRemoveTestsView(
239 FragmentContainerView(context),
240 FragmentContainerView(context)
241 )
jbwoods2534c732019-06-20 12:42:37 -0700242
243 view.removeViews(1, 1)
244
245 assertThat(view.childCount).isEqualTo(1)
246 }
247
248 @Test
249 fun removeViewsInLayout() {
Jeremy Woods640fd632019-07-27 21:24:42 -0700250 val view = setupRemoveTestsView(
251 FragmentContainerView(context),
252 FragmentContainerView(context)
253 )
jbwoods2534c732019-06-20 12:42:37 -0700254
255 view.removeViewsInLayout(1, 1)
256
257 assertThat(view.childCount).isEqualTo(1)
258 }
259
260 @Test
261 fun removeAllViewsInLayout() {
Jeremy Woods991f2682019-08-08 10:33:56 -0700262 val removingView1 = ChildView(context)
263 val removingView2 = ChildView(context)
264
Jeremy Woods640fd632019-07-27 21:24:42 -0700265 val view = setupRemoveTestsView(
Jeremy Woods991f2682019-08-08 10:33:56 -0700266 removingView1,
267 removingView2
Jeremy Woods640fd632019-07-27 21:24:42 -0700268 )
jbwoods2534c732019-06-20 12:42:37 -0700269
270 view.removeAllViewsInLayout()
271
Jeremy Woods991f2682019-08-08 10:33:56 -0700272 assertThat(removingView1.getAnimationCount).isEqualTo(2)
273 assertThat(removingView2.getAnimationCount).isEqualTo(2)
jbwoods2534c732019-06-20 12:42:37 -0700274 assertThat(view.childCount).isEqualTo(0)
275 }
276
277 // removeDetachedView should not actually remove the view
278 @Test
279 fun removeDetachedView() {
jbwoods2534c732019-06-20 12:42:37 -0700280 val childView1 = FragmentContainerView(context)
281 val childView2 = FragmentContainerView(context)
282
Jeremy Woods640fd632019-07-27 21:24:42 -0700283 val view = setupRemoveTestsView(childView1, childView2)
Jeremy Woods6c5151d2019-07-26 16:58:58 -0700284
285 view.removeDetachedView(childView1, false)
jbwoods2534c732019-06-20 12:42:37 -0700286
287 assertThat(view.childCount).isEqualTo(2)
288 assertThat(view.getChildAt(1)).isEqualTo(childView2)
Jeremy Woods6c5151d2019-07-26 16:58:58 -0700289 }
jbwoods2534c732019-06-20 12:42:37 -0700290
Jeremy Woods640fd632019-07-27 21:24:42 -0700291 private fun setupRemoveTestsView(
Jeremy Woods991f2682019-08-08 10:33:56 -0700292 childView1: View,
293 childView2: View
Jeremy Woods640fd632019-07-27 21:24:42 -0700294 ): FragmentContainerView {
295 val view = FragmentContainerView(context)
Jeremy Woods6c5151d2019-07-26 16:58:58 -0700296 val fragment1 = Fragment()
297 val fragment2 = Fragment()
298
299 fragment1.mView = childView1
300 fragment2.mView = childView2
301
302 childView1.setTag(R.id.fragment_container_view_tag, fragment1)
303 childView2.setTag(R.id.fragment_container_view_tag, fragment2)
304
305 view.addView(childView1)
306 view.addView(childView2)
jbwoods2534c732019-06-20 12:42:37 -0700307
308 assertThat(view.childCount).isEqualTo(2)
309 assertThat(view.getChildAt(1)).isEqualTo(childView2)
Jeremy Woods640fd632019-07-27 21:24:42 -0700310 return view
jbwoods2534c732019-06-20 12:42:37 -0700311 }
312
313 // Disappearing child views should be drawn first before other child views.
314 @Test
315 fun drawDisappearingChildViewsFirst() {
316 val fm = activityRule.activity.supportFragmentManager
317
318 val fragment1 = ChildViewFragment()
319 val fragment2 = ChildViewFragment()
320
321 fm.beginTransaction()
322 .setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right)
323 .replace(R.id.fragment_container_view, fragment1)
324 .commit()
325 activityRule.waitForExecution()
326
327 val frag1View = fragment1.mView as ChildView
Jeremy Woodsf441f192019-07-22 10:52:10 -0700328 // wait for the first draw to finish
Ian Lake325987e2020-02-20 09:42:08 -0800329 assertWithMessage("Timed out waiting for setDrawnFirstView")
330 .that(drawnFirstCountDownLatch.await(1, TimeUnit.SECONDS))
331 .isTrue()
332 assertWithMessage("Timed out waiting for onAnimationEnd on Fragment 1")
333 .that(frag1View.onAnimationEndLatch.await(1, TimeUnit.SECONDS))
334 .isTrue()
Jeremy Woodsf441f192019-07-22 10:52:10 -0700335
336 // reset the first drawn view for the transaction we care about.
337 drawnFirst = null
338 drawnFirstCountDownLatch = CountDownLatch(1)
jbwoods2534c732019-06-20 12:42:37 -0700339
340 fm.beginTransaction()
341 .setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right)
342 .replace(R.id.fragment_container_view, fragment2)
343 .commit()
344 activityRule.waitForExecution()
345
Ian Lake325987e2020-02-20 09:42:08 -0800346 assertWithMessage("Timed out waiting for setDrawnFirstView")
347 .that(drawnFirstCountDownLatch.await(1, TimeUnit.SECONDS))
348 .isTrue()
Jeremy Woodsf441f192019-07-22 10:52:10 -0700349 assertThat(drawnFirst!!).isEqualTo(frag1View)
jbwoods2534c732019-06-20 12:42:37 -0700350 }
351
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700352 // Disappearing child views should be drawn last if transaction is a pop.
353 @Test
354 fun drawDisappearingChildViewsLast() {
355 val fm = activityRule.activity.supportFragmentManager
356
Jeremy Woods8b13acf2019-08-27 15:49:43 -0700357 val fragment1 = ChildViewFragment()
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700358 val fragment2 = ChildViewFragment()
359
360 fm.beginTransaction()
361 .setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right,
362 android.R.anim.slide_in_left, android.R.anim.slide_out_right)
Jeremy Woods8b13acf2019-08-27 15:49:43 -0700363 .replace(R.id.fragment_container_view, fragment1, "1")
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700364 .commit()
365 activityRule.waitForExecution()
366
Jeremy Woods8b13acf2019-08-27 15:49:43 -0700367 val frag1View = fragment1.mView as ChildView
368
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700369 fm.beginTransaction()
370 .setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right,
371 android.R.anim.slide_in_left, android.R.anim.slide_out_right)
372 .replace(R.id.fragment_container_view, fragment2, "2")
373 .addToBackStack(null)
374 .commit()
375 activityRule.waitForExecution()
376
377 val frag2View = fragment2.mView as ChildView
Ian Lake325987e2020-02-20 09:42:08 -0800378 assertWithMessage("Timed out waiting for setDrawnFirstView")
379 .that(drawnFirstCountDownLatch.await(1, TimeUnit.SECONDS))
380 .isTrue()
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700381
Ian Lake325987e2020-02-20 09:42:08 -0800382 assertWithMessage("Timed out waiting for onDetachFromWindow on Fragment 1")
383 .that(frag1View.onDetachFromWindowLatch.await(1, TimeUnit.SECONDS))
384 .isTrue()
Jeremy Woods8b13acf2019-08-27 15:49:43 -0700385 frag1View.onDetachFromWindowLatch = CountDownLatch(1)
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700386
387 // reset the first drawn view for the transaction we care about.
388 drawnFirst = null
389 drawnFirstCountDownLatch = CountDownLatch(1)
390
391 fm.popBackStack()
392 activityRule.waitForExecution()
393
Ian Lake325987e2020-02-20 09:42:08 -0800394 assertWithMessage("Timed out waiting for onDetachFromWindow on Fragment 2")
395 .that(frag2View.onDetachFromWindowLatch.await(1, TimeUnit.SECONDS))
396 .isTrue()
Jeremy Woods8b13acf2019-08-27 15:49:43 -0700397 frag2View.onDetachFromWindowLatch = CountDownLatch(1)
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700398
Ian Lake325987e2020-02-20 09:42:08 -0800399 assertWithMessage("Timed out waiting for setDrawnFirstView")
400 .that(drawnFirstCountDownLatch.await(1, TimeUnit.SECONDS))
401 .isTrue()
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700402 // The popped Fragment will be drawn last and therefore will be on top
403 assertThat(drawnFirst!!).isNotEqualTo(frag2View)
404 }
405
406 @Test
407 fun drawDisappearingChildViewsLastAfterPopNoReordering() {
408 val fm = activityRule.activity.supportFragmentManager
409
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700410 val fragment1 = ChildViewFragment()
411 val fragment2 = ChildViewFragment()
412
413 fm.beginTransaction()
414 .setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right,
415 android.R.anim.slide_in_left, android.R.anim.slide_out_right)
416 .replace(R.id.fragment_container_view, fragment1, "1")
417 .commit()
418 activityRule.waitForExecution()
419
Jeremy Woods8b13acf2019-08-27 15:49:43 -0700420 val frag1View = fragment1.mView as ChildView
421
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700422 fm.beginTransaction()
423 .setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right,
424 android.R.anim.slide_in_left, android.R.anim.slide_out_right)
425 .replace(R.id.fragment_container_view, fragment2, "2")
426 .setPrimaryNavigationFragment(fragment2)
427 .addToBackStack(null)
428 .commit()
429 activityRule.waitForExecution()
430
431 val frag2View = fragment2.mView as ChildView
Ian Lake325987e2020-02-20 09:42:08 -0800432 assertWithMessage("Timed out waiting for setDrawnFirstView")
433 .that(drawnFirstCountDownLatch.await(1, TimeUnit.SECONDS))
434 .isTrue()
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700435
Ian Lake325987e2020-02-20 09:42:08 -0800436 assertWithMessage("Timed out waiting for onDetachFromWindow on Fragment 1")
437 .that(frag1View.onDetachFromWindowLatch.await(1, TimeUnit.SECONDS))
438 .isTrue()
Jeremy Woods8b13acf2019-08-27 15:49:43 -0700439 frag1View.onDetachFromWindowLatch = CountDownLatch(1)
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700440
441 // reset the first drawn view for the transaction we care about.
442 drawnFirst = null
443 drawnFirstCountDownLatch = CountDownLatch(1)
444
445 fm.popBackStack()
446 fm.beginTransaction()
447 .setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right,
448 android.R.anim.slide_in_left, android.R.anim.slide_out_right)
449 .replace(R.id.fragment_container_view, fragment1, "1")
450 .commit()
451 activityRule.waitForExecution()
452
Ian Lake325987e2020-02-20 09:42:08 -0800453 assertWithMessage("Timed out waiting for onDetachFromWindow on Fragment 2")
454 .that(frag2View.onDetachFromWindowLatch.await(1, TimeUnit.SECONDS))
455 .isTrue()
Jeremy Woods8b13acf2019-08-27 15:49:43 -0700456 frag2View.onDetachFromWindowLatch = CountDownLatch(1)
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700457
Ian Lake325987e2020-02-20 09:42:08 -0800458 assertWithMessage("Timed out waiting for setDrawnFirstView")
459 .that(drawnFirstCountDownLatch.await(1, TimeUnit.SECONDS))
460 .isTrue()
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700461 assertThat(drawnFirst!!).isNotEqualTo(frag2View)
462 }
463
464 @Test
465 fun drawDisappearingChildViewsLastAfterPopReorderingAllowed() {
466 val fm = activityRule.activity.supportFragmentManager
467
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700468 val fragment1 = ChildViewFragment()
469 val fragment2 = ChildViewFragment()
470
471 fm.beginTransaction()
472 .setReorderingAllowed(true)
473 .setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right,
474 android.R.anim.slide_in_left, android.R.anim.slide_out_right)
475 .replace(R.id.fragment_container_view, fragment1, "1")
476 .commit()
477 activityRule.waitForExecution()
478
Jeremy Woods8b13acf2019-08-27 15:49:43 -0700479 val frag1View = fragment1.mView as ChildView
480
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700481 fm.beginTransaction()
482 .setReorderingAllowed(true)
483 .setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right,
484 android.R.anim.slide_in_left, android.R.anim.slide_out_right)
485 .replace(R.id.fragment_container_view, fragment2, "2")
486 .setPrimaryNavigationFragment(fragment2)
487 .addToBackStack(null)
488 .commit()
489 activityRule.waitForExecution()
490
491 val frag2View = fragment2.mView as ChildView
Ian Lake325987e2020-02-20 09:42:08 -0800492 assertWithMessage("Timed out waiting for setDrawnFirstView")
493 .that(drawnFirstCountDownLatch.await(1, TimeUnit.SECONDS))
494 .isTrue()
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700495
Ian Lake325987e2020-02-20 09:42:08 -0800496 assertWithMessage("Timed out waiting for onDetachFromWindow on Fragment 1")
497 .that(frag1View.onDetachFromWindowLatch.await(1, TimeUnit.SECONDS))
498 .isTrue()
Jeremy Woods8b13acf2019-08-27 15:49:43 -0700499 frag1View.onDetachFromWindowLatch = CountDownLatch(1)
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700500
501 // reset the first drawn view for the transaction we care about.
502 drawnFirst = null
503 drawnFirstCountDownLatch = CountDownLatch(1)
504
505 fm.popBackStack()
506 fm.beginTransaction()
507 .setReorderingAllowed(true)
508 .setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right,
509 android.R.anim.slide_in_left, android.R.anim.slide_out_right)
510 .replace(R.id.fragment_container_view, fragment1, "1")
511 .commit()
512 activityRule.waitForExecution()
513
Ian Lake325987e2020-02-20 09:42:08 -0800514 assertWithMessage("Timed out waiting for onDetachFromWindow on Fragment 2")
515 .that(frag2View.onDetachFromWindowLatch.await(1, TimeUnit.SECONDS))
516 .isTrue()
Jeremy Woods8b13acf2019-08-27 15:49:43 -0700517 frag2View.onDetachFromWindowLatch = CountDownLatch(1)
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700518
Ian Lake325987e2020-02-20 09:42:08 -0800519 assertWithMessage("Timed out waiting for setDrawnFirstView")
520 .that(drawnFirstCountDownLatch.await(1, TimeUnit.SECONDS))
521 .isTrue()
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700522 assertThat(drawnFirst!!).isNotEqualTo(frag2View)
523 }
524
525 @Test
526 fun drawDisappearingChildViewsLastAfterPopReorderingAllowedAddNewFragment() {
527 val fm = activityRule.activity.supportFragmentManager
528
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700529 val fragment1 = ChildViewFragment()
530 val fragment2 = ChildViewFragment()
531
532 fm.beginTransaction()
533 .setReorderingAllowed(true)
534 .setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right,
535 android.R.anim.slide_in_left, android.R.anim.slide_out_right)
536 .replace(R.id.fragment_container_view, fragment1, "1")
537 .commit()
538 activityRule.waitForExecution()
539
Jeremy Woods8b13acf2019-08-27 15:49:43 -0700540 val frag1View = fragment1.mView as ChildView
541
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700542 fm.beginTransaction()
543 .setReorderingAllowed(true)
544 .setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right,
545 android.R.anim.slide_in_left, android.R.anim.slide_out_right)
546 .replace(R.id.fragment_container_view, fragment2, "2")
547 .setPrimaryNavigationFragment(fragment2)
548 .addToBackStack(null)
549 .commit()
550 activityRule.waitForExecution()
551
552 val frag2View = fragment2.mView as ChildView
Ian Lake325987e2020-02-20 09:42:08 -0800553 assertWithMessage("Timed out waiting for setDrawnFirstView")
554 .that(drawnFirstCountDownLatch.await(1, TimeUnit.SECONDS))
555 .isTrue()
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700556
Ian Lake325987e2020-02-20 09:42:08 -0800557 assertWithMessage("Timed out waiting for onDetachFromWindow on Fragment 1")
558 .that(frag1View.onDetachFromWindowLatch.await(1, TimeUnit.SECONDS))
559 .isTrue()
Jeremy Woods8b13acf2019-08-27 15:49:43 -0700560 frag1View.onDetachFromWindowLatch = CountDownLatch(1)
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700561
562 // reset the first drawn view for the transaction we care about.
563 drawnFirst = null
564 drawnFirstCountDownLatch = CountDownLatch(1)
565
566 fm.popBackStack()
567 fm.beginTransaction()
568 .setReorderingAllowed(true)
569 .setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right,
570 android.R.anim.slide_in_left, android.R.anim.slide_out_right)
571 .add(R.id.fragment_container_view, ChildViewFragment(), "1")
572 .commit()
573 activityRule.waitForExecution()
574
Ian Lake325987e2020-02-20 09:42:08 -0800575 assertWithMessage("Timed out waiting for onDetachFromWindow on Fragment 2")
576 .that(frag2View.onDetachFromWindowLatch.await(1, TimeUnit.SECONDS))
577 .isTrue()
Jeremy Woods8b13acf2019-08-27 15:49:43 -0700578 frag2View.onDetachFromWindowLatch = CountDownLatch(1)
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700579
Ian Lake325987e2020-02-20 09:42:08 -0800580 assertWithMessage("Timed out waiting for setDrawnFirstView")
581 .that(drawnFirstCountDownLatch.await(1, TimeUnit.SECONDS))
582 .isTrue()
Jeremy Woods9e4b8ce2019-08-21 00:07:10 -0700583 // The view that was popped is drawn first which means it is on the bottom.
584 assertThat(drawnFirst!!).isEqualTo(frag2View)
585 }
586
jbwoods2534c732019-06-20 12:42:37 -0700587 class ChildViewFragment : StrictViewFragment() {
588 override fun onCreateView(
589 inflater: LayoutInflater,
590 container: ViewGroup?,
591 savedInstanceState: Bundle?
592 ) = ChildView(context)
593 }
594
595 class ChildView(context: Context?) : View(context) {
Jeremy Woods991f2682019-08-08 10:33:56 -0700596 var getAnimationCount = 0
Jeremy Woods8b13acf2019-08-27 15:49:43 -0700597 var onDetachFromWindowLatch = CountDownLatch(1)
Jeremy Woodsf6472c52019-09-20 12:30:06 -0700598 var onAnimationEndLatch = CountDownLatch(1)
Jeremy Woods991f2682019-08-08 10:33:56 -0700599
jbwoods2534c732019-06-20 12:42:37 -0700600 override fun onDraw(canvas: Canvas?) {
601 super.onDraw(canvas)
Jeremy Woodsf441f192019-07-22 10:52:10 -0700602 setDrawnFirstView(this)
603 }
Jeremy Woods991f2682019-08-08 10:33:56 -0700604
605 override fun getAnimation(): Animation? {
606 getAnimationCount++
607 return super.getAnimation()
608 }
Jeremy Woods8b13acf2019-08-27 15:49:43 -0700609
610 override fun onDetachedFromWindow() {
611 onDetachFromWindowLatch.countDown()
612 super.onDetachedFromWindow()
613 }
Jeremy Woodsf6472c52019-09-20 12:30:06 -0700614
615 override fun onAnimationEnd() {
616 onAnimationEndLatch.countDown()
617 super.onAnimationEnd()
618 }
Jeremy Woodsf441f192019-07-22 10:52:10 -0700619 }
620
621 companion object {
622 var drawnFirst: View? = null
623 var drawnFirstCountDownLatch = CountDownLatch(1)
624 fun setDrawnFirstView(v: View) {
625 if (drawnFirst == null) {
626 drawnFirst = v
627 }
628 drawnFirstCountDownLatch.countDown()
jbwoods2534c732019-06-20 12:42:37 -0700629 }
630 }
jbwoodsb1757aa2019-06-12 11:17:06 -0700631}