1 /*
2  * Copyright 2020 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 
17 package androidx.compose.runtime
18 
19 import android.os.HandlerThread
20 import android.view.View
21 import androidx.core.os.HandlerCompat
22 import androidx.test.ext.junit.runners.AndroidJUnit4
23 import androidx.test.filters.MediumTest
24 import java.lang.ref.PhantomReference
25 import java.lang.ref.ReferenceQueue
26 import java.util.concurrent.CountDownLatch
27 import java.util.concurrent.TimeUnit
28 import kotlin.test.assertNotNull
29 import kotlin.test.assertNull
30 import kotlin.test.assertTrue
31 import org.junit.Assert.assertEquals
32 import org.junit.Rule
33 import org.junit.Test
34 import org.junit.runner.RunWith
35 
36 @RunWith(AndroidJUnit4::class)
37 class ComposeIntoTests : BaseComposeTest() {
38     @get:Rule override val activityRule = makeTestActivityRule()
39 
40     @Test
41     @MediumTest
testMultipleSetContentCallsnull42     fun testMultipleSetContentCalls() {
43         val activity = activityRule.activity
44 
45         var initializationCount = 0
46         val composable =
47             @Composable {
48                 DisposableEffect(Unit) {
49                     initializationCount++
50                     onDispose {}
51                 }
52             }
53 
54         activity.show(composable)
55         activity.waitForAFrame()
56 
57         assertEquals(1, initializationCount)
58 
59         activity.show(composable)
60         activity.waitForAFrame()
61 
62         // if we call setContent multiple times, we want to ensure that it doesn't tear
63         // down the whole hierarchy, so onActive should only get called once.
64         assertEquals(1, initializationCount)
65     }
66 
67     @Test // b/153355487
68     @MediumTest
testCommittingFromASeparateThreadnull69     fun testCommittingFromASeparateThread() {
70         val model = mutableStateOf(0)
71         var composed = 0
72         var compositionLatch = CountDownLatch(1)
73         val threadLatch = CountDownLatch(1)
74         activity.show {
75             composed = model.value
76             compositionLatch.countDown()
77         }
78         val thread = HandlerThread("")
79         try {
80             compositionLatch.wait()
81             thread.start()
82             HandlerCompat.createAsync(thread.looper).post {
83                 model.value = 1
84                 threadLatch.countDown()
85             }
86             compositionLatch = CountDownLatch(1)
87             threadLatch.wait()
88             compositionLatch.wait()
89             assertEquals(1, composed)
90         } finally {
91             activity.runOnUiThread { activity.setContentView(View(activity)) }
92             thread.quitSafely()
93         }
94     }
95 
96     @Test
97     @MediumTest
testCompositionCanBeCollectedWithPendingInvalidatenull98     fun testCompositionCanBeCollectedWithPendingInvalidate() {
99         val referenceQueue = ReferenceQueue<Composer>()
100         var scope: RecomposeScope? = null
101         var composer: Composer? = null
102         var phantomReference: PhantomReference<Composer>? = null
103         fun doShow() {
104             val threadLatch = CountDownLatch(1)
105             activity.show {
106                 composer = currentComposer
107                 scope = currentRecomposeScope
108                 threadLatch.countDown()
109             }
110             threadLatch.wait()
111             phantomReference = PhantomReference<Composer>(composer, referenceQueue)
112         }
113 
114         doShow()
115         assertNotNull(scope)
116 
117         val threadLatch = CountDownLatch(1)
118         activity.runOnUiThread {
119             activity.setContentView(View(activity))
120             composer = null
121             threadLatch.countDown()
122         }
123         threadLatch.wait()
124         assertNull(composer)
125         assertNotNull(phantomReference)
126 
127         // Make sure that the composer was collected.
128         repeat(100) {
129             Runtime.getRuntime().gc()
130             val value = referenceQueue.poll()
131             if (value != null) return
132         }
133         error("Failed - composer leaked")
134     }
135 }
136 
waitnull137 fun CountDownLatch.wait() = assertTrue(await(1, TimeUnit.SECONDS))
138