TypeTextAction.java revision f69eb9ac2856f470cb79f57141f711ed3ceed99d
1/*
2 * Copyright (C) 2014 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 com.google.android.apps.common.testing.ui.espresso.action;
18
19import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.hasFocus;
20import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.isAssignableFrom;
21import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.isDisplayed;
22import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.supportsInputMethods;
23import static com.google.common.base.Preconditions.checkNotNull;
24import static org.hamcrest.Matchers.allOf;
25import static org.hamcrest.Matchers.anyOf;
26
27import com.google.android.apps.common.testing.ui.espresso.InjectEventSecurityException;
28import com.google.android.apps.common.testing.ui.espresso.PerformException;
29import com.google.android.apps.common.testing.ui.espresso.UiController;
30import com.google.android.apps.common.testing.ui.espresso.ViewAction;
31import com.google.android.apps.common.testing.ui.espresso.util.HumanReadables;
32
33import android.os.Build;
34import android.util.Log;
35import android.view.View;
36import android.widget.SearchView;
37
38import org.hamcrest.Matcher;
39
40/**
41 * Enables typing text on views.
42 */
43public final class TypeTextAction implements ViewAction {
44  private static final String TAG = TypeTextAction.class.getSimpleName();
45  private final String stringToBeTyped;
46  private final boolean tapToFocus;
47
48  /**
49   * Constructs {@link TypeTextAction} with given string. If the string is empty it results in no-op
50   * (nothing is typed). By default this action sends a tap event to the center of the view to
51   * attain focus before typing.
52   *
53   * @param stringToBeTyped String To be typed by {@link TypeTextAction}
54   */
55  public TypeTextAction(String stringToBeTyped) {
56    this(stringToBeTyped, true);
57  }
58
59  /**
60   * Constructs {@link TypeTextAction} with given string. If the string is empty it results in no-op
61   * (nothing is typed).
62   *
63   * @param stringToBeTyped String To be typed by {@link TypeTextAction}
64   * @param tapToFocus indicates whether a tap should be sent to the underlying view before typing.
65   */
66  public TypeTextAction(String stringToBeTyped, boolean tapToFocus) {
67    checkNotNull(stringToBeTyped);
68    this.stringToBeTyped = stringToBeTyped;
69    this.tapToFocus = tapToFocus;
70  }
71
72  @SuppressWarnings("unchecked")
73  @Override
74  public Matcher<View> getConstraints() {
75    Matcher<View> matchers = allOf(isDisplayed());
76    if (!tapToFocus) {
77      matchers = allOf(matchers, hasFocus());
78    }
79
80    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
81       return allOf(matchers, supportsInputMethods());
82    } else {
83       // SearchView does not support input methods itself (rather it delegates to an internal text
84       // view for input).
85       return allOf(matchers, anyOf(supportsInputMethods(), isAssignableFrom(SearchView.class)));
86    }
87  }
88
89  @Override
90  public void perform(UiController uiController, View view) {
91    // No-op if string is empty.
92    if (stringToBeTyped.length() == 0) {
93      Log.w(TAG, "Supplied string is empty resulting in no-op (nothing is typed).");
94      return;
95    }
96
97    if (tapToFocus) {
98      // Perform a click.
99      new GeneralClickAction(Tap.SINGLE, GeneralLocation.CENTER, Press.FINGER)
100          .perform(uiController, view);
101      uiController.loopMainThreadUntilIdle();
102    }
103
104    try {
105      if (!uiController.injectString(stringToBeTyped)) {
106        Log.e(TAG, "Failed to type text: " + stringToBeTyped);
107        throw new PerformException.Builder()
108          .withActionDescription(this.getDescription())
109          .withViewDescription(HumanReadables.describe(view))
110          .withCause(new RuntimeException("Failed to type text: " + stringToBeTyped))
111          .build();
112      }
113    } catch (InjectEventSecurityException e) {
114      Log.e(TAG, "Failed to type text: " + stringToBeTyped);
115      throw new PerformException.Builder()
116        .withActionDescription(this.getDescription())
117        .withViewDescription(HumanReadables.describe(view))
118        .withCause(e)
119        .build();
120    }
121  }
122
123  @Override
124  public String getDescription() {
125    return "type text";
126  }
127}
128