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