1/* 2 * Copyright (C) 2015 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.v7.testutils; 18 19import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; 20 21import android.support.annotation.Nullable; 22import android.support.test.espresso.Espresso; 23import android.support.test.espresso.IdlingResource; 24import android.support.test.espresso.UiController; 25import android.support.test.espresso.ViewAction; 26import android.support.v4.widget.DrawerLayout; 27import android.view.View; 28 29import org.hamcrest.Matcher; 30 31public class DrawerLayoutActions { 32 /** 33 * Drawer listener that serves as Espresso's {@link IdlingResource} and notifies the registered 34 * callback when the drawer gets to STATE_IDLE state. 35 */ 36 private static class CustomDrawerListener 37 implements DrawerLayout.DrawerListener, IdlingResource { 38 private int mCurrState = DrawerLayout.STATE_IDLE; 39 40 @Nullable private IdlingResource.ResourceCallback mCallback; 41 42 private boolean mNeedsIdle = false; 43 44 @Override 45 public void registerIdleTransitionCallback(ResourceCallback resourceCallback) { 46 mCallback = resourceCallback; 47 } 48 49 @Override 50 public String getName() { 51 return "Drawer listener"; 52 } 53 54 @Override 55 public boolean isIdleNow() { 56 if (!mNeedsIdle) { 57 return true; 58 } else { 59 return mCurrState == DrawerLayout.STATE_IDLE; 60 } 61 } 62 63 @Override 64 public void onDrawerClosed(View drawer) { 65 if (mCurrState == DrawerLayout.STATE_IDLE) { 66 if (mCallback != null) { 67 mCallback.onTransitionToIdle(); 68 } 69 } 70 } 71 72 @Override 73 public void onDrawerOpened(View drawer) { 74 if (mCurrState == DrawerLayout.STATE_IDLE) { 75 if (mCallback != null) { 76 mCallback.onTransitionToIdle(); 77 } 78 } 79 } 80 81 @Override 82 public void onDrawerSlide(View drawer, float slideOffset) { 83 } 84 85 @Override 86 public void onDrawerStateChanged(int state) { 87 mCurrState = state; 88 if (state == DrawerLayout.STATE_IDLE) { 89 if (mCallback != null) { 90 mCallback.onTransitionToIdle(); 91 } 92 } 93 } 94 } 95 96 private abstract static class WrappedViewAction implements ViewAction { 97 } 98 99 public static ViewAction wrap(final ViewAction baseAction) { 100 if (baseAction instanceof WrappedViewAction) { 101 throw new IllegalArgumentException("Don't wrap and already wrapped action"); 102 } 103 104 return new WrappedViewAction() { 105 @Override 106 public Matcher<View> getConstraints() { 107 return baseAction.getConstraints(); 108 } 109 110 @Override 111 public String getDescription() { 112 return baseAction.getDescription(); 113 } 114 115 @Override 116 public final void perform(UiController uiController, View view) { 117 final DrawerLayout drawer = (DrawerLayout) view; 118 // Add a custom tracker listener 119 final CustomDrawerListener customListener = new CustomDrawerListener(); 120 drawer.addDrawerListener(customListener); 121 122 // Note that we're running the following block in a try-finally construct. This 123 // is needed since some of the wrapped actions are going to throw (expected) 124 // exceptions. If that happens, we still need to clean up after ourselves to 125 // leave the system (Espesso) in a good state. 126 try { 127 // Register our listener as idling resource so that Espresso waits until the 128 // wrapped action results in the drawer getting to the STATE_IDLE state 129 Espresso.registerIdlingResources(customListener); 130 baseAction.perform(uiController, view); 131 customListener.mNeedsIdle = true; 132 uiController.loopMainThreadUntilIdle(); 133 customListener.mNeedsIdle = false; 134 } finally { 135 // Unregister our idling resource 136 Espresso.unregisterIdlingResources(customListener); 137 // And remove our tracker listener from DrawerLayout 138 drawer.removeDrawerListener(customListener); 139 } 140 } 141 }; 142 } 143 144 /** 145 * Opens the drawer at the specified edge gravity. 146 */ 147 public static ViewAction openDrawer(final int drawerEdgeGravity, final boolean animate) { 148 return wrap(new ViewAction() { 149 @Override 150 public Matcher<View> getConstraints() { 151 return isAssignableFrom(DrawerLayout.class); 152 } 153 154 @Override 155 public String getDescription() { 156 return "Opens the drawer"; 157 } 158 159 @Override 160 public void perform(UiController uiController, View view) { 161 uiController.loopMainThreadUntilIdle(); 162 163 DrawerLayout drawerLayout = (DrawerLayout) view; 164 drawerLayout.openDrawer(drawerEdgeGravity, animate); 165 } 166 }); 167 } 168 169 /** 170 * Opens the drawer at the specified edge gravity. 171 */ 172 public static ViewAction openDrawer(final int drawerEdgeGravity) { 173 return wrap(new ViewAction() { 174 @Override 175 public Matcher<View> getConstraints() { 176 return isAssignableFrom(DrawerLayout.class); 177 } 178 179 @Override 180 public String getDescription() { 181 return "Opens the drawer"; 182 } 183 184 @Override 185 public void perform(UiController uiController, View view) { 186 uiController.loopMainThreadUntilIdle(); 187 188 DrawerLayout drawerLayout = (DrawerLayout) view; 189 drawerLayout.openDrawer(drawerEdgeGravity); 190 } 191 }); 192 } 193 194 /** 195 * Opens the drawer. 196 */ 197 public static ViewAction openDrawer(final View drawerView) { 198 return wrap(new ViewAction() { 199 @Override 200 public Matcher<View> getConstraints() { 201 return isAssignableFrom(DrawerLayout.class); 202 } 203 204 @Override 205 public String getDescription() { 206 return "Opens the drawer"; 207 } 208 209 @Override 210 public void perform(UiController uiController, View view) { 211 uiController.loopMainThreadUntilIdle(); 212 213 DrawerLayout drawerLayout = (DrawerLayout) view; 214 drawerLayout.openDrawer(drawerView); 215 } 216 }); 217 } 218 219 /** 220 * Closes the drawer at the specified edge gravity. 221 */ 222 public static ViewAction closeDrawer(final int drawerEdgeGravity) { 223 return wrap(new ViewAction() { 224 @Override 225 public Matcher<View> getConstraints() { 226 return isAssignableFrom(DrawerLayout.class); 227 } 228 229 @Override 230 public String getDescription() { 231 return "Closes the drawer"; 232 } 233 234 @Override 235 public void perform(UiController uiController, View view) { 236 uiController.loopMainThreadUntilIdle(); 237 238 DrawerLayout drawerLayout = (DrawerLayout) view; 239 drawerLayout.closeDrawer(drawerEdgeGravity); 240 } 241 }); 242 } 243 244 /** 245 * Closes the drawer at the specified edge gravity. 246 */ 247 public static ViewAction closeDrawer(final int drawerEdgeGravity, final boolean animate) { 248 return wrap(new ViewAction() { 249 @Override 250 public Matcher<View> getConstraints() { 251 return isAssignableFrom(DrawerLayout.class); 252 } 253 254 @Override 255 public String getDescription() { 256 return "Closes the drawer"; 257 } 258 259 @Override 260 public void perform(UiController uiController, View view) { 261 uiController.loopMainThreadUntilIdle(); 262 263 DrawerLayout drawerLayout = (DrawerLayout) view; 264 drawerLayout.closeDrawer(drawerEdgeGravity, animate); 265 } 266 }); 267 } 268 269 /** 270 * Closes the drawer. 271 */ 272 public static ViewAction closeDrawer(final View drawerView) { 273 return wrap(new ViewAction() { 274 @Override 275 public Matcher<View> getConstraints() { 276 return isAssignableFrom(DrawerLayout.class); 277 } 278 279 @Override 280 public String getDescription() { 281 return "Closes the drawer"; 282 } 283 284 @Override 285 public void perform(UiController uiController, View view) { 286 uiController.loopMainThreadUntilIdle(); 287 288 DrawerLayout drawerLayout = (DrawerLayout) view; 289 drawerLayout.closeDrawer(drawerView); 290 } 291 }); 292 } 293 294 /** 295 * Sets the lock mode for the drawer at the specified edge gravity. 296 */ 297 public static ViewAction setDrawerLockMode(final int lockMode, final int drawerEdgeGravity) { 298 return wrap(new ViewAction() { 299 @Override 300 public Matcher<View> getConstraints() { 301 return isAssignableFrom(DrawerLayout.class); 302 } 303 304 @Override 305 public String getDescription() { 306 return "Sets drawer lock mode"; 307 } 308 309 @Override 310 public void perform(UiController uiController, View view) { 311 uiController.loopMainThreadUntilIdle(); 312 313 DrawerLayout drawerLayout = (DrawerLayout) view; 314 drawerLayout.setDrawerLockMode(lockMode, drawerEdgeGravity); 315 316 uiController.loopMainThreadUntilIdle(); 317 } 318 }); 319 } 320 321 /** 322 * Sets the lock mode for the drawer. 323 */ 324 public static ViewAction setDrawerLockMode(final int lockMode, final View drawerView) { 325 return wrap(new ViewAction() { 326 @Override 327 public Matcher<View> getConstraints() { 328 return isAssignableFrom(DrawerLayout.class); 329 } 330 331 @Override 332 public String getDescription() { 333 return "Sets drawer lock mode"; 334 } 335 336 @Override 337 public void perform(UiController uiController, View view) { 338 uiController.loopMainThreadUntilIdle(); 339 340 DrawerLayout drawerLayout = (DrawerLayout) view; 341 drawerLayout.setDrawerLockMode(lockMode, drawerView); 342 343 uiController.loopMainThreadUntilIdle(); 344 } 345 }); 346 } 347} 348