1/*
2 * Copyright (C) 2016 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 android.support.design.widget;
18
19import android.os.SystemClock;
20import android.support.annotation.LayoutRes;
21import android.support.annotation.StringRes;
22import android.support.design.test.R;
23import android.support.design.testutils.Cheeses;
24import android.test.suitebuilder.annotation.LargeTest;
25import android.test.suitebuilder.annotation.MediumTest;
26import org.junit.Test;
27
28import static android.support.design.testutils.TestUtilsActions.addTabs;
29import static android.support.test.espresso.Espresso.onView;
30import static android.support.test.espresso.matcher.ViewMatchers.withId;
31import static org.junit.Assert.assertEquals;
32
33@MediumTest
34public class AppBarWithToolbarAndTabsTest extends AppBarLayoutBaseTest {
35    private TabLayout mTabLayout;
36
37    @Override
38    protected void configureContent(@LayoutRes int layoutResId, @StringRes int titleResId) {
39        super.configureContent(layoutResId, titleResId);
40
41        mTabLayout = (TabLayout) mAppBar.findViewById(R.id.tabs);
42        String[] tabTitles = new String[5];
43        System.arraycopy(Cheeses.sCheeseStrings, 0, tabTitles, 0, 5);
44        onView(withId(R.id.tabs)).perform(addTabs(tabTitles));
45    }
46
47    @Test
48    public void testScrollingToolbarAndScrollingTabs() {
49        configureContent(R.layout.design_appbar_toolbar_scroll_tabs_scroll,
50                R.string.design_appbar_toolbar_scroll_tabs_scroll);
51
52        final int[] appbarOnScreenXY = new int[2];
53        final int[] coordinatorLayoutOnScreenXY = new int[2];
54        mAppBar.getLocationOnScreen(appbarOnScreenXY);
55        mCoordinatorLayout.getLocationOnScreen(coordinatorLayoutOnScreenXY);
56
57        final int originalAppbarTop = appbarOnScreenXY[1];
58        final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
59        final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
60
61        final int toolbarHeight = mToolbar.getHeight();
62        final int tabsHeight = mTabLayout.getHeight();
63        final int appbarHeight = mAppBar.getHeight();
64        final int longSwipeAmount = 3 * appbarHeight / 2;
65        final int shortSwipeAmount = toolbarHeight;
66
67        // Perform a swipe-up gesture across the horizontal center of the screen, starting from
68        // just below the AppBarLayout
69        performVerticalSwipeUpGesture(
70                R.id.coordinator_layout,
71                centerX,
72                originalAppbarBottom + 20,
73                longSwipeAmount);
74
75        mAppBar.getLocationOnScreen(appbarOnScreenXY);
76        // At this point the app bar should not be visually "present" on the screen, with its bottom
77        // edge aligned with the system status bar. Allow for off-by-a-pixel margin of error.
78        assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
79        assertAppBarElevation(mDefaultElevationValue);
80
81        // Perform another swipe-up gesture
82        performVerticalSwipeUpGesture(
83                R.id.coordinator_layout,
84                centerX,
85                originalAppbarBottom,
86                shortSwipeAmount);
87
88        mAppBar.getLocationOnScreen(appbarOnScreenXY);
89        // At this point the app bar should still be off the screen. Allow for off-by-a-pixel
90        // margin of error.
91        assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
92        assertAppBarElevation(mDefaultElevationValue);
93
94        // Perform a long swipe-down gesture across the horizontal center of the screen.
95        // Note that the swipe down is a bit longer than the swipe up to fully bring down
96        // the scrolled-away toolbar and tab layout
97        performVerticalSwipeDownGesture(
98                R.id.coordinator_layout,
99                centerX,
100                originalAppbarBottom,
101                longSwipeAmount * 3 / 2);
102
103        mAppBar.getLocationOnScreen(appbarOnScreenXY);
104        // At this point the app bar should be visually snapped below the system status bar as it
105        // in scrolling mode and we've swiped down. Allow for off-by-a-pixel
106        // margin of error.
107        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
108        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
109        assertAppBarElevation(mDefaultElevationValue);
110
111        // Perform another swipe-down gesture across the horizontal center of the screen.
112        performVerticalSwipeDownGesture(
113                R.id.coordinator_layout,
114                centerX,
115                originalAppbarBottom,
116                longSwipeAmount);
117
118        mAppBar.getLocationOnScreen(appbarOnScreenXY);
119        // At this point the app bar should be in its original position.
120        // Allow for off-by-a-pixel margin of error.
121        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
122        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
123        assertAppBarElevation(mDefaultElevationValue);
124
125        // Perform yet another swipe-down gesture across the horizontal center of the screen.
126        performVerticalSwipeDownGesture(
127                R.id.coordinator_layout,
128                centerX,
129                originalAppbarBottom,
130                longSwipeAmount);
131
132        mAppBar.getLocationOnScreen(appbarOnScreenXY);
133        // At this point the app bar should still be in its original position.
134        // Allow for off-by-a-pixel margin of error.
135        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
136        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
137        assertAppBarElevation(mDefaultElevationValue);
138    }
139
140    @Test
141    public void testScrollingToolbarAndPinnedTabs() {
142        configureContent(R.layout.design_appbar_toolbar_scroll_tabs_pinned,
143                R.string.design_appbar_toolbar_scroll_tabs_pin);
144
145        final int[] appbarOnScreenXY = new int[2];
146        final int[] coordinatorLayoutOnScreenXY = new int[2];
147        mAppBar.getLocationOnScreen(appbarOnScreenXY);
148        mCoordinatorLayout.getLocationOnScreen(coordinatorLayoutOnScreenXY);
149
150        final int originalAppbarTop = appbarOnScreenXY[1];
151        final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
152        final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
153
154        final int toolbarHeight = mToolbar.getHeight();
155        final int tabsHeight = mTabLayout.getHeight();
156        final int appbarHeight = mAppBar.getHeight();
157        final int longSwipeAmount = 3 * appbarHeight / 2;
158        final int shortSwipeAmount = toolbarHeight;
159
160        // Perform a swipe-up gesture across the horizontal center of the screen, starting from
161        // just below the AppBarLayout
162        performVerticalSwipeUpGesture(
163                R.id.coordinator_layout,
164                centerX,
165                originalAppbarBottom + 20,
166                longSwipeAmount);
167
168        mAppBar.getLocationOnScreen(appbarOnScreenXY);
169        // At this point the tab bar should be visually snapped below the system status bar.
170        // Allow for off-by-a-pixel margin of error.
171        assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
172        assertAppBarElevation(mDefaultElevationValue);
173
174        // Perform another swipe-up gesture
175        performVerticalSwipeUpGesture(
176                R.id.coordinator_layout,
177                centerX,
178                originalAppbarBottom,
179                shortSwipeAmount);
180
181        mAppBar.getLocationOnScreen(appbarOnScreenXY);
182        // At this point the tab bar should still be visually snapped below the system status bar
183        // as it is in the pinned mode. Allow for off-by-a-pixel margin of error.
184        assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
185        assertAppBarElevation(mDefaultElevationValue);
186
187        // Perform a short swipe-down gesture across the horizontal center of the screen.
188        // Note that the swipe down is a bit longer than the swipe up to fully bring down
189        // the scrolled-away toolbar and tab layout
190        performVerticalSwipeDownGesture(
191                R.id.coordinator_layout,
192                centerX,
193                originalAppbarBottom - shortSwipeAmount,
194                3 * shortSwipeAmount / 2);
195
196        mAppBar.getLocationOnScreen(appbarOnScreenXY);
197        // At this point the app bar should be in its original position as it
198        // in scrolling mode and we've swiped down. Allow for off-by-a-pixel
199        // margin of error.
200        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
201        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
202        assertAppBarElevation(mDefaultElevationValue);
203
204        // Perform another swipe-down gesture across the horizontal center of the screen.
205        performVerticalSwipeDownGesture(
206                R.id.coordinator_layout,
207                centerX,
208                originalAppbarBottom,
209                longSwipeAmount);
210
211        mAppBar.getLocationOnScreen(appbarOnScreenXY);
212        // At this point the app bar should be in its original position.
213        // Allow for off-by-a-pixel margin of error.
214        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
215        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
216        assertAppBarElevation(mDefaultElevationValue);
217
218        // Perform yet another swipe-down gesture across the horizontal center of the screen.
219        performVerticalSwipeDownGesture(
220                R.id.coordinator_layout,
221                centerX,
222                originalAppbarBottom,
223                longSwipeAmount);
224
225        mAppBar.getLocationOnScreen(appbarOnScreenXY);
226        // At this point the app bar should still be in its original position.
227        // Allow for off-by-a-pixel margin of error.
228        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
229        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
230        assertAppBarElevation(mDefaultElevationValue);
231    }
232
233    @LargeTest
234    @Test
235    public void testSnappingToolbarAndSnappingTabs() {
236        configureContent(R.layout.design_appbar_toolbar_scroll_tabs_scroll_snap,
237                R.string.design_appbar_toolbar_scroll_tabs_scroll_snap);
238
239        final int[] appbarOnScreenXY = new int[2];
240        final int[] coordinatorLayoutOnScreenXY = new int[2];
241        mAppBar.getLocationOnScreen(appbarOnScreenXY);
242        mCoordinatorLayout.getLocationOnScreen(coordinatorLayoutOnScreenXY);
243
244        final int originalAppbarTop = appbarOnScreenXY[1];
245        final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
246        final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
247
248        final int toolbarHeight = mToolbar.getHeight();
249        final int tabsHeight = mTabLayout.getHeight();
250        final int appbarHeight = mAppBar.getHeight();
251
252        // Since AppBarLayout doesn't expose a way to track snap animations, the three possible
253        // options are
254        // a) track how vertical offsets and invalidation is propagated through the
255        // view hierarchy and wait until there are no more events of that kind
256        // b) run a dummy Espresso action that waits until the main thread is idle
257        // c) sleep for a hardcoded period of time to "wait" until the snap animation is done
258        // In this test method we go with option b)
259
260        // Perform a swipe-up gesture across the horizontal center of the screen. The amount
261        // of swipe is 25% of the toolbar height and we expect the snap behavior to "move"
262        // the app bar back to its original position.
263        performVerticalSwipeUpGesture(
264                R.id.coordinator_layout,
265                centerX,
266                originalAppbarBottom + toolbarHeight,
267                toolbarHeight / 4);
268
269        // Wait for the snap animation to be done
270        waitForSnapAnimationToFinish();
271
272        mAppBar.getLocationOnScreen(appbarOnScreenXY);
273        // At this point the app bar should be in its original position as it
274        // in snapping mode and we haven't swiped "enough". Allow for off-by-a-pixel
275        // margin of error.
276        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
277        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
278        assertAppBarElevation(mDefaultElevationValue);
279
280        // Perform a slightly longer swipe-up gesture, this time by 75% of the toolbar height.
281        // We expect the snap behavior to move the app bar to snap the tab layout below the
282        // system status bar.
283        performVerticalSwipeUpGesture(
284                R.id.coordinator_layout,
285                centerX,
286                originalAppbarBottom + toolbarHeight,
287                3 * toolbarHeight / 4);
288
289        // Wait for the snap animation to be done
290        waitForSnapAnimationToFinish();
291
292        mAppBar.getLocationOnScreen(appbarOnScreenXY);
293        // At this point the app bar should "snap" the toolbar away and align the tab layout below
294        // the system status bar. Allow for off-by-a-pixel margin of error.
295        assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
296        assertAppBarElevation(mDefaultElevationValue);
297
298        // Perform a short swipe-up gesture, this time by 25% of the tab layout height. We expect
299        // snap behavior to move the app bar back to snap the tab layout below the system status
300        // bar.
301        performVerticalSwipeUpGesture(
302                R.id.coordinator_layout,
303                centerX,
304                originalAppbarBottom + toolbarHeight,
305                tabsHeight / 4);
306
307        // Wait for the snap animation to be done
308        waitForSnapAnimationToFinish();
309
310        mAppBar.getLocationOnScreen(appbarOnScreenXY);
311        // At this point the app bar should "snap" back to align the tab layout below
312        // the system status bar. Allow for off-by-a-pixel margin of error.
313        assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
314        assertAppBarElevation(mDefaultElevationValue);
315
316        // Perform a longer swipe-up gesture, this time by 75% of the tab layout height. We expect
317        // snap behavior to move the app bar fully away from the screen.
318        performVerticalSwipeUpGesture(
319                R.id.coordinator_layout,
320                centerX,
321                originalAppbarBottom + toolbarHeight,
322                3 * tabsHeight / 4);
323
324        // Wait for the snap animation to be done
325        waitForSnapAnimationToFinish();
326
327        mAppBar.getLocationOnScreen(appbarOnScreenXY);
328        // At this point the app bar should not be visually "present" on the screen, with its bottom
329        // edge aligned with the system status bar. Allow for off-by-a-pixel margin of error.
330        assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
331        assertAppBarElevation(mDefaultElevationValue);
332
333        // Perform a short swipe-down gesture by 25% of the tab layout height. We expect
334        // snap behavior to move the app bar back fully away from the screen.
335        performVerticalSwipeDownGesture(
336                R.id.coordinator_layout,
337                centerX,
338                originalAppbarBottom + toolbarHeight,
339                tabsHeight / 4);
340
341        // Wait for the snap animation to be done
342        waitForSnapAnimationToFinish();
343
344        mAppBar.getLocationOnScreen(appbarOnScreenXY);
345        // At this point the app bar should still not be visually "present" on the screen, with
346        // its bottom edge aligned with the system status bar. Allow for off-by-a-pixel margin
347        // of error.
348        assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
349        assertAppBarElevation(mDefaultElevationValue);
350
351        // Perform a longer swipe-up gesture, this time by 75% of the tab layout height. We expect
352        // snap behavior to move the app bar to snap the tab layout below the system status
353        // bar.
354        performVerticalSwipeDownGesture(
355                R.id.coordinator_layout,
356                centerX,
357                originalAppbarBottom + toolbarHeight,
358                3 * tabsHeight / 4);
359
360        // Wait for the snap animation to be done
361        waitForSnapAnimationToFinish();
362
363        mAppBar.getLocationOnScreen(appbarOnScreenXY);
364        // At this point the app bar should "snap" the toolbar away and align the tab layout below
365        // the system status bar. Allow for off-by-a-pixel margin of error.
366        assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
367        assertAppBarElevation(mDefaultElevationValue);
368
369        // Perform a short swipe-down gesture by 25% of the toolbar height. We expect
370        // snap behavior to align the tab layout below the system status bar
371        performVerticalSwipeDownGesture(
372                R.id.coordinator_layout,
373                centerX,
374                originalAppbarBottom + toolbarHeight,
375                toolbarHeight / 4);
376
377        // Wait for the snap animation to be done
378        waitForSnapAnimationToFinish();
379
380        mAppBar.getLocationOnScreen(appbarOnScreenXY);
381        // At this point the app bar should still align the tab layout below
382        // the system status bar. Allow for off-by-a-pixel margin of error.
383        assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
384        assertAppBarElevation(mDefaultElevationValue);
385
386        // Perform a longer swipe-up gesture, this time by 75% of the toolbar height. We expect
387        // snap behavior to move the app bar back to its original place (fully visible).
388        performVerticalSwipeDownGesture(
389                R.id.coordinator_layout,
390                centerX,
391                originalAppbarBottom + toolbarHeight,
392                3 * tabsHeight / 4);
393
394        // Wait for the snap animation to be done
395        waitForSnapAnimationToFinish();
396
397        mAppBar.getLocationOnScreen(appbarOnScreenXY);
398        // At this point the app bar should be in its original position.
399        // Allow for off-by-a-pixel margin of error.
400        assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
401        assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
402        assertAppBarElevation(mDefaultElevationValue);
403    }
404
405    private void waitForSnapAnimationToFinish() {
406        final AppBarLayout.Behavior behavior = (AppBarLayout.Behavior)
407                ((CoordinatorLayout.LayoutParams) mAppBar.getLayoutParams()).getBehavior();
408        while (behavior.isOffsetAnimatorRunning()) {
409            SystemClock.sleep(16);
410        }
411    }
412}
413