1 /*
2  * Copyright (C) 2013 DroidDriver committers
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 io.appium.droiddriver.actions;
18 
19 import android.annotation.SuppressLint;
20 import android.os.Build;
21 import android.os.SystemClock;
22 import android.view.KeyCharacterMap;
23 import android.view.KeyEvent;
24 
25 import io.appium.droiddriver.UiElement;
26 import io.appium.droiddriver.exceptions.ActionException;
27 import io.appium.droiddriver.util.Preconditions;
28 import io.appium.droiddriver.util.Strings;
29 
30 /**
31  * An action to type text.
32  */
33 public class TextAction extends KeyAction {
34 
35   @SuppressLint("InlinedApi")
36   @SuppressWarnings("deprecation")
37   private static final KeyCharacterMap KEY_CHAR_MAP =
38       Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB ? KeyCharacterMap
39           .load(KeyCharacterMap.BUILT_IN_KEYBOARD) : KeyCharacterMap
40           .load(KeyCharacterMap.VIRTUAL_KEYBOARD);
41 
42   private final String text;
43 
44   /**
45    * Defaults timeoutMillis to 100.
46    */
47   public TextAction(String text) {
48     this(text, 100L, false);
49   }
50 
51   public TextAction(String text, long timeoutMillis, boolean checkFocused) {
52     super(timeoutMillis, checkFocused);
53     this.text = Preconditions.checkNotNull(text);
54   }
55 
56   @Override
57   public boolean perform(InputInjector injector, UiElement element) {
58     maybeCheckFocused(element);
59 
60     // TODO: recycle events?
61     KeyEvent[] events = KEY_CHAR_MAP.getEvents(text.toCharArray());
62     boolean success = false;
63 
64     if (events != null) {
65       for (KeyEvent event : events) {
66         // We have to change the time of an event before injecting it because
67         // all KeyEvents returned by KeyCharacterMap.getEvents() have the same
68         // time stamp and the system rejects too old events. Hence, it is
69         // possible for an event to become stale before it is injected if it
70         // takes too long to inject the preceding ones.
71         KeyEvent modifiedEvent = KeyEvent.changeTimeRepeat(event, SystemClock.uptimeMillis(), 0);
72         success = injector.injectInputEvent(modifiedEvent);
73         if (!success) {
74           break;
75         }
76       }
77     } else {
78       throw new ActionException("The given text is not supported: " + text);
79     }
80     return success;
81   }
82 
83   @Override
84   public String toString() {
85     return Strings.toStringHelper(this).addValue(text).toString();
86   }
87 }
88