• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 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 com.android.systemui.statusbar.notification
18 
19 import androidx.compose.foundation.gestures.FlingBehavior
20 import androidx.compose.foundation.gestures.ScrollScope
21 import androidx.compose.ui.geometry.Offset
22 import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.UserInput
23 import androidx.test.ext.junit.runners.AndroidJUnit4
24 import androidx.test.filters.SmallTest
25 import com.android.systemui.SysuiTestCase
26 import com.android.systemui.notifications.ui.composable.NotificationScrimNestedScrollConnection
27 import com.google.common.truth.Truth.assertThat
28 import kotlinx.coroutines.test.runTest
29 import org.junit.Test
30 import org.junit.runner.RunWith
31 
32 @SmallTest
33 @RunWith(AndroidJUnit4::class)
34 class NotificationScrimNestedScrollConnectionTest : SysuiTestCase() {
35     private var isStarted = false
36     private var wasStarted = false
37     private var scrimOffset = 0f
38     private var contentHeight = 0f
39     private var isCurrentGestureOverscroll = false
40     private val customFlingBehavior =
41         object : FlingBehavior {
42             override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
43                 scrollBy(initialVelocity)
44                 return initialVelocity / 2f
45             }
46         }
47 
48     private val scrollConnection =
49         NotificationScrimNestedScrollConnection(
50             scrimOffset = { scrimOffset },
51             snapScrimOffset = { _ -> },
52             animateScrimOffset = { _ -> },
53             minScrimOffset = { MIN_SCRIM_OFFSET },
54             maxScrimOffset = MAX_SCRIM_OFFSET,
55             contentHeight = { contentHeight },
56             minVisibleScrimHeight = { MIN_VISIBLE_SCRIM_HEIGHT },
57             isCurrentGestureOverscroll = { isCurrentGestureOverscroll },
58             onStart = { isStarted = true },
59             onStop = {
60                 wasStarted = true
61                 isStarted = false
62             },
63             flingBehavior = customFlingBehavior,
64         )
65 
66     @Test
67     fun onScrollUp_canStartPreScroll_contentNotExpanded_ignoreScroll() = runTest {
68         contentHeight = COLLAPSED_CONTENT_HEIGHT
69 
70         val offsetConsumed =
71             scrollConnection.onPreScroll(available = Offset(x = 0f, y = -1f), source = UserInput)
72 
73         assertThat(offsetConsumed).isEqualTo(Offset.Zero)
74         assertThat(isStarted).isEqualTo(false)
75     }
76 
77     @Test
78     fun onScrollUp_canStartPreScroll_contentExpandedAtMinOffset_ignoreScroll() = runTest {
79         contentHeight = EXPANDED_CONTENT_HEIGHT
80         scrimOffset = MIN_SCRIM_OFFSET
81 
82         val offsetConsumed =
83             scrollConnection.onPreScroll(available = Offset(x = 0f, y = -1f), source = UserInput)
84 
85         assertThat(offsetConsumed).isEqualTo(Offset.Zero)
86         assertThat(isStarted).isEqualTo(false)
87     }
88 
89     @Test
90     fun onScrollUp_canStartPreScroll_contentExpanded_consumeScroll() = runTest {
91         contentHeight = EXPANDED_CONTENT_HEIGHT
92 
93         val availableOffset = Offset(x = 0f, y = -1f)
94         val offsetConsumed =
95             scrollConnection.onPreScroll(available = availableOffset, source = UserInput)
96 
97         assertThat(offsetConsumed).isEqualTo(availableOffset)
98         assertThat(isStarted).isEqualTo(true)
99     }
100 
101     @Test
102     fun onScrollUp_canStartPreScroll_contentExpanded_consumeScrollWithRemainder() = runTest {
103         contentHeight = EXPANDED_CONTENT_HEIGHT
104         scrimOffset = MIN_SCRIM_OFFSET + 1
105 
106         val availableOffset = Offset(x = 0f, y = -2f)
107         val consumableOffset = Offset(x = 0f, y = -1f)
108         val offsetConsumed =
109             scrollConnection.onPreScroll(available = availableOffset, source = UserInput)
110 
111         assertThat(offsetConsumed).isEqualTo(consumableOffset)
112         assertThat(isStarted).isEqualTo(true)
113     }
114 
115     @Test
116     fun onScrollUp_canStartPostScroll_ignoreScroll() = runTest {
117         val offsetConsumed =
118             scrollConnection.onPostScroll(
119                 consumed = Offset.Zero,
120                 available = Offset(x = 0f, y = -1f),
121                 source = UserInput,
122             )
123 
124         assertThat(offsetConsumed).isEqualTo(Offset.Zero)
125         assertThat(isStarted).isEqualTo(false)
126     }
127 
128     @Test
129     fun onScrollDown_canStartPreScroll_ignoreScroll() = runTest {
130         val offsetConsumed =
131             scrollConnection.onPreScroll(available = Offset(x = 0f, y = 1f), source = UserInput)
132 
133         assertThat(offsetConsumed).isEqualTo(Offset.Zero)
134         assertThat(isStarted).isEqualTo(false)
135     }
136 
137     @Test
138     fun onScrollDown_canStartPostScroll_consumeScroll() = runTest {
139         scrimOffset = MIN_SCRIM_OFFSET
140 
141         val availableOffset = Offset(x = 0f, y = 1f)
142         val offsetConsumed =
143             scrollConnection.onPostScroll(
144                 consumed = Offset.Zero,
145                 available = availableOffset,
146                 source = UserInput,
147             )
148 
149         assertThat(offsetConsumed).isEqualTo(availableOffset)
150         assertThat(isStarted).isEqualTo(true)
151     }
152 
153     @Test
154     fun onScrollDown_canStartPostScroll_consumeScrollWithRemainder() = runTest {
155         scrimOffset = MAX_SCRIM_OFFSET - 1
156 
157         val availableOffset = Offset(x = 0f, y = 2f)
158         val consumableOffset = Offset(x = 0f, y = 1f)
159         val offsetConsumed =
160             scrollConnection.onPostScroll(
161                 consumed = Offset.Zero,
162                 available = availableOffset,
163                 source = UserInput,
164             )
165 
166         assertThat(offsetConsumed).isEqualTo(consumableOffset)
167         assertThat(isStarted).isEqualTo(true)
168     }
169 
170     @Test
171     fun canStartPostScroll_atMaxOffset_ignoreScroll() = runTest {
172         scrimOffset = MAX_SCRIM_OFFSET
173 
174         val offsetConsumed =
175             scrollConnection.onPostScroll(
176                 consumed = Offset.Zero,
177                 available = Offset(x = 0f, y = 1f),
178                 source = UserInput,
179             )
180 
181         assertThat(offsetConsumed).isEqualTo(Offset.Zero)
182         assertThat(wasStarted).isEqualTo(false)
183         assertThat(isStarted).isEqualTo(false)
184     }
185 
186     @Test
187     fun canStartPostScroll_externalOverscrollGesture_startButIgnoreScroll() = runTest {
188         scrimOffset = MAX_SCRIM_OFFSET
189         isCurrentGestureOverscroll = true
190 
191         val offsetConsumed =
192             scrollConnection.onPostScroll(
193                 consumed = Offset.Zero,
194                 available = Offset(x = 0f, y = 1f),
195                 source = UserInput,
196             )
197 
198         assertThat(offsetConsumed).isEqualTo(Offset.Zero)
199         // Returning 0 offset will immediately stop the connection
200         assertThat(wasStarted).isEqualTo(true)
201         assertThat(isStarted).isEqualTo(false)
202     }
203 
204     @Test
205     fun canContinueScroll_inBetweenMinMaxOffset_true() = runTest {
206         scrimOffset = (MIN_SCRIM_OFFSET + MAX_SCRIM_OFFSET) / 2f
207         contentHeight = EXPANDED_CONTENT_HEIGHT
208         scrollConnection.onPreScroll(available = Offset(x = 0f, y = -1f), source = UserInput)
209 
210         assertThat(isStarted).isEqualTo(true)
211 
212         scrollConnection.onPreScroll(available = Offset(x = 0f, y = 1f), source = UserInput)
213 
214         assertThat(isStarted).isEqualTo(true)
215     }
216 
217     @Test
218     fun canContinueScroll_atMaxOffset_false() = runTest {
219         scrimOffset = MAX_SCRIM_OFFSET
220         contentHeight = EXPANDED_CONTENT_HEIGHT
221         scrollConnection.onPreScroll(available = Offset(x = 0f, y = -1f), source = UserInput)
222 
223         assertThat(isStarted).isEqualTo(true)
224 
225         scrollConnection.onPreScroll(available = Offset(x = 0f, y = 1f), source = UserInput)
226 
227         assertThat(isStarted).isEqualTo(false)
228     }
229 
230     companion object {
231         const val MIN_SCRIM_OFFSET = -100f
232         const val MAX_SCRIM_OFFSET = 0f
233 
234         const val EXPANDED_CONTENT_HEIGHT = 200f
235         const val COLLAPSED_CONTENT_HEIGHT = 40f
236 
237         const val MIN_VISIBLE_SCRIM_HEIGHT = 50f
238     }
239 }
240