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