Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2017 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 android.support.wear.widget;
     18 
     19 import static android.support.test.espresso.Espresso.onView;
     20 import static android.support.test.espresso.action.ViewActions.swipeRight;
     21 import static android.support.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
     22 import static android.support.test.espresso.matcher.ViewMatchers.withId;
     23 import static android.support.wear.widget.util.AsyncViewActions.waitForMatchingView;
     24 import static android.support.wear.widget.util.MoreViewAssertions.withPositiveVerticalScrollOffset;
     25 
     26 import static org.hamcrest.Matchers.allOf;
     27 import static org.junit.Assert.assertFalse;
     28 import static org.junit.Assert.assertTrue;
     29 
     30 import android.app.Activity;
     31 import android.content.Intent;
     32 import android.graphics.RectF;
     33 import android.support.annotation.IdRes;
     34 import android.support.test.InstrumentationRegistry;
     35 import android.support.test.espresso.ViewAction;
     36 import android.support.test.espresso.action.GeneralLocation;
     37 import android.support.test.espresso.action.GeneralSwipeAction;
     38 import android.support.test.espresso.action.Press;
     39 import android.support.test.espresso.action.Swipe;
     40 import android.support.test.espresso.matcher.ViewMatchers;
     41 import android.support.test.filters.SmallTest;
     42 import android.support.test.rule.ActivityTestRule;
     43 import android.support.test.runner.AndroidJUnit4;
     44 import android.support.v7.widget.RecyclerView;
     45 import android.support.wear.test.R;
     46 import android.support.wear.widget.util.ArcSwipe;
     47 import android.support.wear.widget.util.WakeLockRule;
     48 import android.view.View;
     49 
     50 import org.junit.Rule;
     51 import org.junit.Test;
     52 import org.junit.runner.RunWith;
     53 
     54 @RunWith(AndroidJUnit4.class)
     55 public class SwipeDismissFrameLayoutTest {
     56 
     57     private static final long MAX_WAIT_TIME = 4000; //ms
     58     private final SwipeDismissFrameLayout.Callback mDismissCallback = new DismissCallback();
     59 
     60     @Rule
     61     public final WakeLockRule wakeLock = new WakeLockRule();
     62 
     63     @Rule
     64     public final ActivityTestRule<SwipeDismissFrameLayoutTestActivity> activityRule =
     65             new ActivityTestRule<>(
     66                     SwipeDismissFrameLayoutTestActivity.class,
     67                     true, /** initial touch mode */
     68                     false /** launchActivity */
     69             );
     70 
     71     private int mLayoutWidth;
     72     private int mLayoutHeight;
     73 
     74     @Test
     75     @SmallTest
     76     public void testCanScrollHorizontally() {
     77         // GIVEN a freshly setup SwipeDismissFrameLayout
     78         setUpSimpleLayout();
     79         Activity activity = activityRule.getActivity();
     80         SwipeDismissFrameLayout testLayout =
     81                 (SwipeDismissFrameLayout) activity.findViewById(R.id.swipe_dismiss_root);
     82         // WHEN we check that the layout is horizontally scrollable from left to right.
     83         // THEN the layout is found to be horizontally swipeable from left to right.
     84         assertTrue(testLayout.canScrollHorizontally(-20));
     85         // AND the layout is found to NOT be horizontally swipeable from right to left.
     86         assertFalse(testLayout.canScrollHorizontally(20));
     87 
     88         // WHEN we switch off the swipe-to-dismiss functionality for the layout
     89         testLayout.setSwipeable(false);
     90         // THEN the layout is found NOT to be horizontally swipeable from left to right.
     91         assertFalse(testLayout.canScrollHorizontally(-20));
     92         // AND the layout is found to NOT be horizontally swipeable from right to left.
     93         assertFalse(testLayout.canScrollHorizontally(20));
     94     }
     95 
     96     @Test
     97     @SmallTest
     98     public void canScrollHorizontallyShouldBeFalseWhenInvisible() {
     99         // GIVEN a freshly setup SwipeDismissFrameLayout
    100         setUpSimpleLayout();
    101         Activity activity = activityRule.getActivity();
    102         final SwipeDismissFrameLayout testLayout = activity.findViewById(R.id.swipe_dismiss_root);
    103         // GIVEN the layout is invisible
    104         // Note: We have to run this on the main thread, because of thread checks in View.java.
    105         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    106             @Override
    107             public void run() {
    108                 testLayout.setVisibility(View.INVISIBLE);
    109             }
    110         });
    111         // WHEN we check that the layout is horizontally scrollable
    112         // THEN the layout is found to be NOT horizontally swipeable from left to right.
    113         assertFalse(testLayout.canScrollHorizontally(-20));
    114         // AND the layout is found to NOT be horizontally swipeable from right to left.
    115         assertFalse(testLayout.canScrollHorizontally(20));
    116     }
    117 
    118     @Test
    119     @SmallTest
    120     public void canScrollHorizontallyShouldBeFalseWhenGone() {
    121         // GIVEN a freshly setup SwipeDismissFrameLayout
    122         setUpSimpleLayout();
    123         Activity activity = activityRule.getActivity();
    124         final SwipeDismissFrameLayout testLayout =
    125                 (SwipeDismissFrameLayout) activity.findViewById(R.id.swipe_dismiss_root);
    126         // GIVEN the layout is gone
    127         // Note: We have to run this on the main thread, because of thread checks in View.java.
    128         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    129             @Override
    130             public void run() {
    131                 testLayout.setVisibility(View.GONE);
    132             }
    133         });
    134         // WHEN we check that the layout is horizontally scrollable
    135         // THEN the layout is found to be NOT horizontally swipeable from left to right.
    136         assertFalse(testLayout.canScrollHorizontally(-20));
    137         // AND the layout is found to NOT be horizontally swipeable from right to left.
    138         assertFalse(testLayout.canScrollHorizontally(20));
    139     }
    140 
    141     @Test
    142     @SmallTest
    143     public void testSwipeDismissEnabledByDefault() {
    144         // GIVEN a freshly setup SwipeDismissFrameLayout
    145         setUpSimpleLayout();
    146         Activity activity = activityRule.getActivity();
    147         SwipeDismissFrameLayout testLayout =
    148                 (SwipeDismissFrameLayout) activity.findViewById(R.id.swipe_dismiss_root);
    149         // WHEN we check that the layout is dismissible
    150         // THEN the layout is find to be dismissible
    151         assertTrue(testLayout.isSwipeable());
    152     }
    153 
    154     @Test
    155     @SmallTest
    156     public void testSwipeDismissesViewIfEnabled() {
    157         // GIVEN a freshly setup SwipeDismissFrameLayout
    158         setUpSimpleLayout();
    159         // WHEN we perform a swipe to dismiss
    160         onView(withId(R.id.swipe_dismiss_root)).perform(swipeRight());
    161         // AND hidden
    162         assertHidden(R.id.swipe_dismiss_root);
    163     }
    164 
    165     @Test
    166     @SmallTest
    167     public void testSwipeDoesNotDismissViewIfDisabled() {
    168         // GIVEN a freshly setup SwipeDismissFrameLayout with dismiss turned off.
    169         setUpSimpleLayout();
    170         Activity activity = activityRule.getActivity();
    171         SwipeDismissFrameLayout testLayout =
    172                 (SwipeDismissFrameLayout) activity.findViewById(R.id.swipe_dismiss_root);
    173         testLayout.setSwipeable(false);
    174         // WHEN we perform a swipe to dismiss
    175         onView(withId(R.id.swipe_dismiss_root)).perform(swipeRight());
    176         // THEN the layout is not hidden
    177         assertNotHidden(R.id.swipe_dismiss_root);
    178     }
    179 
    180     @Test
    181     @SmallTest
    182     public void testAddRemoveCallback() {
    183         // GIVEN a freshly setup SwipeDismissFrameLayout
    184         setUpSimpleLayout();
    185         Activity activity = activityRule.getActivity();
    186         SwipeDismissFrameLayout testLayout = activity.findViewById(R.id.swipe_dismiss_root);
    187         // WHEN we remove the swipe callback
    188         testLayout.removeCallback(mDismissCallback);
    189         onView(withId(R.id.swipe_dismiss_root)).perform(swipeRight());
    190         // THEN the layout is not hidden
    191         assertNotHidden(R.id.swipe_dismiss_root);
    192     }
    193 
    194     @Test
    195     @SmallTest
    196     public void testSwipeDoesNotDismissViewIfScrollable() throws Throwable {
    197         // GIVEN a freshly setup SwipeDismissFrameLayout with dismiss turned off.
    198         setUpSwipeDismissWithHorizontalRecyclerView();
    199         activityRule.runOnUiThread(new Runnable() {
    200             @Override
    201             public void run() {
    202                 Activity activity = activityRule.getActivity();
    203                 RecyclerView testLayout = activity.findViewById(R.id.recycler_container);
    204                 // Scroll to a position from which the child is scrollable.
    205                 testLayout.scrollToPosition(50);
    206             }
    207         });
    208 
    209         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
    210         // WHEN we perform a swipe to dismiss from the center of the screen.
    211         onView(withId(R.id.swipe_dismiss_root)).perform(swipeRightFromCenter());
    212         // THEN the layout is not hidden
    213         assertNotHidden(R.id.swipe_dismiss_root);
    214     }
    215 
    216 
    217     @Test
    218     @SmallTest
    219     public void testEdgeSwipeDoesDismissViewIfScrollable() {
    220         // GIVEN a freshly setup SwipeDismissFrameLayout with dismiss turned off.
    221         setUpSwipeDismissWithHorizontalRecyclerView();
    222         // WHEN we perform a swipe to dismiss from the left edge of the screen.
    223         onView(withId(R.id.swipe_dismiss_root)).perform(swipeRightFromLeftEdge());
    224         // THEN the layout is hidden
    225         assertHidden(R.id.swipe_dismiss_root);
    226     }
    227 
    228     @Test
    229     @SmallTest
    230     public void testSwipeDoesNotDismissViewIfStartsInWrongPosition() {
    231         // GIVEN a freshly setup SwipeDismissFrameLayout with dismiss turned on, but only for an
    232         // inner circle.
    233         setUpSwipeableRegion();
    234         // WHEN we perform a swipe to dismiss from the left edge of the screen.
    235         onView(withId(R.id.swipe_dismiss_root)).perform(swipeRightFromLeftEdge());
    236         // THEN the layout is not not hidden
    237         assertNotHidden(R.id.swipe_dismiss_root);
    238     }
    239 
    240     @Test
    241     @SmallTest
    242     public void testSwipeDoesDismissViewIfStartsInRightPosition() {
    243         // GIVEN a freshly setup SwipeDismissFrameLayout with dismiss turned on, but only for an
    244         // inner circle.
    245         setUpSwipeableRegion();
    246         // WHEN we perform a swipe to dismiss from the center of the screen.
    247         onView(withId(R.id.swipe_dismiss_root)).perform(swipeRightFromCenter());
    248         // THEN the layout is hidden
    249         assertHidden(R.id.swipe_dismiss_root);
    250     }
    251 
    252     /**
    253      @Test public void testSwipeInPreferenceFragmentAndNavDrawer() {
    254      // GIVEN a freshly setup SwipeDismissFrameLayout with dismiss turned on, but only for an inner
    255      // circle.
    256      setUpPreferenceFragmentAndNavDrawer();
    257      // WHEN we perform a swipe to dismiss from the center of the screen to the bottom.
    258      onView(withId(R.id.drawer_layout)).perform(swipeBottomFromCenter());
    259      // THEN the navigation drawer is shown.
    260      assertPeeking(R.id.top_drawer);
    261      }*/
    262 
    263     @Test
    264     @SmallTest
    265     public void testArcSwipeDoesNotTriggerDismiss() throws Throwable {
    266         // GIVEN a freshly setup SwipeDismissFrameLayout with vertically scrollable content
    267         setUpSwipeDismissWithVerticalRecyclerView();
    268         int center = mLayoutHeight / 2;
    269         int halfBound = mLayoutWidth / 2;
    270         RectF bounds = new RectF(0, center - halfBound, mLayoutWidth, center + halfBound);
    271         // WHEN the view is scrolled on an arc from top to bottom.
    272         onView(withId(R.id.swipe_dismiss_root)).perform(swipeTopFromBottomOnArc(bounds));
    273         // THEN the layout is not dismissed and not hidden.
    274         assertNotHidden(R.id.swipe_dismiss_root);
    275         // AND the content view is scrolled.
    276         assertScrolledY(R.id.recycler_container);
    277     }
    278 
    279     /**
    280      * Set ups the simplest possible layout for test cases - a {@link SwipeDismissFrameLayout} with
    281      * a single static child.
    282      */
    283     private void setUpSimpleLayout() {
    284         activityRule.launchActivity(
    285                 new Intent()
    286                         .putExtra(
    287                                 LayoutTestActivity.EXTRA_LAYOUT_RESOURCE_ID,
    288                                 R.layout.swipe_dismiss_layout_testcase_1));
    289         setDismissCallback();
    290     }
    291 
    292 
    293     /**
    294      * Sets up a slightly more involved layout for testing swipe-to-dismiss with scrollable
    295      * containers. This layout contains a {@link SwipeDismissFrameLayout} with a horizontal {@link
    296      * android.support.v7.widget.RecyclerView} as a child, ready to accept an adapter.
    297      */
    298     private void setUpSwipeDismissWithHorizontalRecyclerView() {
    299         Intent launchIntent = new Intent();
    300         launchIntent.putExtra(LayoutTestActivity.EXTRA_LAYOUT_RESOURCE_ID,
    301                 R.layout.swipe_dismiss_layout_testcase_2);
    302         launchIntent.putExtra(SwipeDismissFrameLayoutTestActivity.EXTRA_LAYOUT_HORIZONTAL, true);
    303         activityRule.launchActivity(launchIntent);
    304         setDismissCallback();
    305     }
    306 
    307     /**
    308      * Sets up a slightly more involved layout for testing swipe-to-dismiss with scrollable
    309      * containers. This layout contains a {@link SwipeDismissFrameLayout} with a vertical {@link
    310      * WearableRecyclerView} as a child, ready to accept an adapter.
    311      */
    312     private void setUpSwipeDismissWithVerticalRecyclerView() {
    313         Intent launchIntent = new Intent();
    314         launchIntent.putExtra(LayoutTestActivity.EXTRA_LAYOUT_RESOURCE_ID,
    315                 R.layout.swipe_dismiss_layout_testcase_2);
    316         launchIntent.putExtra(SwipeDismissFrameLayoutTestActivity.EXTRA_LAYOUT_HORIZONTAL, false);
    317         activityRule.launchActivity(launchIntent);
    318         setDismissCallback();
    319     }
    320 
    321     /**
    322      * Sets up a {@link SwipeDismissFrameLayout} in which only a certain region is allowed to react
    323      * to swipe-dismiss gestures.
    324      */
    325     private void setUpSwipeableRegion() {
    326         activityRule.launchActivity(
    327                 new Intent()
    328                         .putExtra(
    329                                 LayoutTestActivity.EXTRA_LAYOUT_RESOURCE_ID,
    330                                 R.layout.swipe_dismiss_layout_testcase_1));
    331         setCallback(
    332                 new DismissCallback() {
    333                     @Override
    334                     public boolean onPreSwipeStart(SwipeDismissFrameLayout layout, float x,
    335                             float y) {
    336                         float normalizedX = x - mLayoutWidth / 2;
    337                         float normalizedY = y - mLayoutWidth / 2;
    338                         float squareX = normalizedX * normalizedX;
    339                         float squareY = normalizedY * normalizedY;
    340                         // 30 is an arbitrary number limiting the circle.
    341                         return Math.sqrt(squareX + squareY) < (mLayoutWidth / 2 - 30);
    342                     }
    343                 });
    344     }
    345 
    346     /**
    347      * Sets up a more involved test case where the layout consists of a
    348      * {@code WearableNavigationDrawer} and a
    349      * {@code android.support.wear.internal.view.SwipeDismissPreferenceFragment}
    350      */
    351   /*
    352   private void setUpPreferenceFragmentAndNavDrawer() {
    353     activityRule.launchActivity(
    354       new Intent()
    355           .putExtra(
    356               LayoutTestActivity.EXTRA_LAYOUT_RESOURCE_ID,
    357               R.layout.swipe_dismiss_layout_testcase_3));
    358     Activity activity = activityRule.getActivity();
    359     InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
    360       WearableNavigationDrawer wearableNavigationDrawer =
    361               (WearableNavigationDrawer) activity.findViewById(R.id.top_drawer);
    362       wearableNavigationDrawer.setAdapter(
    363               new WearableNavigationDrawer.WearableNavigationDrawerAdapter() {
    364                 @Override
    365                 public String getItemText(int pos) {
    366                   return "test";
    367                 }
    368 
    369                 @Override
    370                 public Drawable getItemDrawable(int pos) {
    371                   return null;
    372                 }
    373 
    374                 @Override
    375                 public void onItemSelected(int pos) {
    376                   return;
    377                 }
    378 
    379                 @Override
    380                 public int getCount() {
    381                   return 3;
    382                 }
    383               });
    384     });
    385   }*/
    386     private void setDismissCallback() {
    387         setCallback(mDismissCallback);
    388     }
    389 
    390     private void setCallback(SwipeDismissFrameLayout.Callback callback) {
    391         Activity activity = activityRule.getActivity();
    392         SwipeDismissFrameLayout testLayout = activity.findViewById(R.id.swipe_dismiss_root);
    393         mLayoutWidth = testLayout.getWidth();
    394         mLayoutHeight = testLayout.getHeight();
    395         testLayout.addCallback(callback);
    396     }
    397 
    398     /**
    399      * private static void assertPeeking(@IdRes int layoutId) {
    400      * onView(withId(layoutId))
    401      * .perform(
    402      * waitForMatchingView(
    403      * allOf(withId(layoutId), isOpened(true)), MAX_WAIT_TIME));
    404      * }
    405      */
    406 
    407     private static void assertHidden(@IdRes int layoutId) {
    408         onView(withId(layoutId))
    409                 .perform(
    410                         waitForMatchingView(
    411                                 allOf(withId(layoutId),
    412                                         withEffectiveVisibility(ViewMatchers.Visibility.GONE)),
    413                                 MAX_WAIT_TIME));
    414     }
    415 
    416     private static void assertNotHidden(@IdRes int layoutId) {
    417         onView(withId(layoutId))
    418                 .perform(
    419                         waitForMatchingView(
    420                                 allOf(withId(layoutId),
    421                                         withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)),
    422                                 MAX_WAIT_TIME));
    423     }
    424 
    425     private static void assertScrolledY(@IdRes int layoutId) {
    426         onView(withId(layoutId))
    427                 .perform(
    428                         waitForMatchingView(
    429                                 allOf(withId(layoutId), withPositiveVerticalScrollOffset()),
    430                                 MAX_WAIT_TIME));
    431     }
    432 
    433     private static ViewAction swipeRightFromCenter() {
    434         return new GeneralSwipeAction(
    435                 Swipe.SLOW, GeneralLocation.CENTER, GeneralLocation.CENTER_RIGHT, Press.FINGER);
    436     }
    437 
    438     private static ViewAction swipeRightFromLeftEdge() {
    439         return new GeneralSwipeAction(
    440                 Swipe.SLOW, GeneralLocation.CENTER_LEFT, GeneralLocation.CENTER_RIGHT,
    441                 Press.FINGER);
    442     }
    443 
    444     private static ViewAction swipeTopFromBottomOnArc(RectF bounds) {
    445         return new GeneralSwipeAction(
    446                 new ArcSwipe(ArcSwipe.Gesture.SLOW_ANTICLOCKWISE, bounds),
    447                 GeneralLocation.BOTTOM_CENTER,
    448                 GeneralLocation.TOP_CENTER,
    449                 Press.FINGER);
    450     }
    451 
    452     /** Helper class hiding the view after a successful swipe-to-dismiss. */
    453     private static class DismissCallback extends SwipeDismissFrameLayout.Callback {
    454 
    455         @Override
    456         public void onDismissed(SwipeDismissFrameLayout layout) {
    457             layout.setVisibility(View.GONE);
    458         }
    459     }
    460 }
    461