Skip to content

Commit

Permalink
deprecated set_fastinput_ime, update send_action()
Browse files Browse the repository at this point in the history
  • Loading branch information
codeskyblue committed May 24, 2024
1 parent 82ef1f9 commit 51c31f0
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 152 deletions.
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1270,22 +1270,23 @@ Refs: [Google uiautomator Configurator](https://developer.android.com/reference/
这种方法通常用于不知道控件的情况下的输入。第一步需要切换输入法,然后发送adb广播命令,具体使用方法如下
```python
d.set_fastinput_ime(True) # 切换成FastInputIME输入法
d.send_keys("你好123abcEFG") # adb广播输入
d.clear_text() # 清除输入框所有内容(Require android-uiautomator.apk version >= 1.0.7)
d.set_fastinput_ime(False) # 切换成正常的输入法
d.send_action("search") # 模拟输入法的搜索
d.send_keys("你好123abcEFG", clear=True) # adb广播输入
d.clear_text() # 清除输入框所有内容
d.send_action() # 根据输入框的需求,自动执行回车、搜索等指令, Added in version 3.1
# 也可以指定发送的输入法action, eg: d.send_action("search") 支持 go, search, send, next, done, previous
```
**send_action** 说明
该函数可以使用的参数有 `go search send next done previous`
_什么时候该使用这个函数呢?_
```python
print(d.current_ime()) # 获取当前输入法ID
```
有些时候在EditText中输入完内容之后,调用`press("search")` or `press("enter")`发现并没有什么反应。
这个时候就需要`send_action`函数了,这里用到了只有输入法才能用的[IME_ACTION_CODE](https://developer.android.com/reference/android/view/inputmethod/EditorInfo)。
`send_action`先broadcast命令发送给输入法操作`IME_ACTION_CODE`,由输入法完成后续跟EditText的通信。(原理我不太清楚,有了解的,提issue告诉我)
> 更多参考: [IME_ACTION_CODE](https://developer.android.com/reference/android/view/inputmethod/EditorInfo)
### Toast (2.2版本之后有添加回来)
Show Toast (好像有点bug)
Expand Down
10 changes: 9 additions & 1 deletion docs/2to3.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ XPath (d.xpath) methods
- remove when, run_watchers, watch_background, watch_stop, watch_clear, sleep_watch
- remove position method, usage like d.xpath(...).position(0.2, 0.2)

InputMethod
- deprecated wait_fastinput_ime
- deprecated set_fastinput_ime use set_input_ime instead

### Command remove
- Remove "uiautomator2 healthcheck"
- Remove "uiautomator2 identify"
Expand Down Expand Up @@ -169,6 +173,10 @@ print(d.device_info)
'version': 12}
```

### app_current
### app_current
- 2.x raise `OSError` if couldn't get focused app
- 3.x raise `DeviceError` if couldn't get focused app

### current_ime
- 2.x return (ime_method_name, bool), e.g. ("com.github.uiautomator/.FastInputIME", True)
- 3.x return ime_method_name, e.g. "com.github.uiautomator/.FastInputIME"
141 changes: 4 additions & 137 deletions uiautomator2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from uiautomator2 import xpath
from uiautomator2._proto import HTTP_TIMEOUT, SCROLL_STEPS, Direction
from uiautomator2._selector import Selector, UiObject
from uiautomator2._input import InputMethodMixIn
from uiautomator2.exceptions import AdbShellError, BaseException, ConnectError, DeviceError, HierarchyEmptyError, SessionBrokenError
from uiautomator2.settings import Settings
from uiautomator2.swipe import SwipeExt
Expand Down Expand Up @@ -911,119 +912,6 @@ def unlock(self):
self.swipe(0.1, 0.9, 0.9, 0.1)


class _InputMethodMixIn(AbstractShell):
def set_fastinput_ime(self, enable: bool = True):
""" Enable of Disable FastInputIME """
fast_ime = 'com.github.uiautomator/.FastInputIME'
if enable:
self.shell(['ime', 'enable', fast_ime])
self.shell(['ime', 'set', fast_ime])
else:
self.shell(['ime', 'disable', fast_ime])

def send_keys(self, text: str, clear: bool = False):
"""
Args:
text (str): text to set
clear (bool): clear before set text
Raises:
EnvironmentError
"""
try:
self.wait_fastinput_ime()
btext = text.encode('utf-8')
base64text = base64.b64encode(btext).decode()
cmd = "ADB_SET_TEXT" if clear else "ADB_INPUT_TEXT"
self.shell(
['am', 'broadcast', '-a', cmd, '--es', 'text', base64text])
return True
except EnvironmentError:
warnings.warn(
"set FastInputIME failed. use \"d(focused=True).set_text instead\"",
Warning)
return self(focused=True).set_text(text)
# warnings.warn("set FastInputIME failed. use \"adb shell input text\" instead", Warning)
# self.shell(["input", "text", text.replace(" ", "%s")])

def send_action(self, code):
"""
Simulate input method edito code
Args:
code (str or int): input method editor code
Examples:
send_action("search"), send_action(3)
Refs:
https://developer.android.com/reference/android/view/inputmethod/EditorInfo
"""
self.wait_fastinput_ime()
__alias = {
"go": 2,
"search": 3,
"send": 4,
"next": 5,
"done": 6,
"previous": 7,
}
if isinstance(code, str):
code = __alias.get(code, code)
self.shell([
'am', 'broadcast', '-a', 'ADB_EDITOR_CODE', '--ei', 'code',
str(code)
])

def clear_text(self):
""" clear text
Raises:
EnvironmentError
"""
try:
self.wait_fastinput_ime()
self.shell(['am', 'broadcast', '-a', 'ADB_CLEAR_TEXT'])
except EnvironmentError:
# for Android simulator
self(focused=True).clear_text()

def wait_fastinput_ime(self, timeout=5.0):
""" wait FastInputIME is ready
Args:
timeout(float): maxium wait time
Raises:
EnvironmentError
"""
# TODO: 模拟器待兼容 eg. Genymotion, 海马玩, Mumu

deadline = time.time() + timeout
while time.time() < deadline:
ime_id, shown = self.current_ime()
if ime_id != "com.github.uiautomator/.FastInputIME":
self.set_fastinput_ime(True)
time.sleep(0.5)
continue
if shown:
return True
time.sleep(0.2)
raise EnvironmentError("FastInputIME started failed")

def current_ime(self):
""" Current input method
Returns:
(method_id(str), shown(bool)
Example output:
("com.github.uiautomator/.FastInputIME", True)
"""
_INPUT_METHOD_RE = re.compile(r'mCurMethodId=([-_./\w]+)')
dim, _ = self.shell(['dumpsys', 'input_method'])
m = _INPUT_METHOD_RE.search(dim)
method_id = None if not m else m.group(1)
shown = "mInputShown=true" in dim
return (method_id, shown)


class _PluginMixIn:
def watch_context(self, autostart: bool = True, builtin: bool = False) -> WatchContext:
Expand Down Expand Up @@ -1054,32 +942,11 @@ def screenrecord(self):
def swipe_ext(self) -> SwipeExt:
return SwipeExt(self)

class Device(_Device, _AppMixIn, _PluginMixIn, _InputMethodMixIn, _DeprecatedMixIn):

class Device(_Device, _AppMixIn, _PluginMixIn, InputMethodMixIn, _DeprecatedMixIn):

Check warning on line 946 in uiautomator2/__init__.py

View check run for this annotation

Codecov / codecov/patch

uiautomator2/__init__.py#L946

Added line #L946 was not covered by tests
""" Device object """
pass

@property
def info(self) -> Dict[str, Any]:
""" return device info, make sure currentPackageName is set
Return example:
{'currentPackageName': 'io.appium.android.apis',
'displayHeight': 720,
'displayRotation': 3,
'displaySizeDpX': 780,
'displaySizeDpY': 360,
'displayWidth': 1560,
'productName': 'ELE-AL00',
'screenOn': True,
'sdkInt': 29,
'naturalOrientation': False}
"""
_info = super().info
if _info.get('currentPackageName') is None:
try:
_info['currentPackageName'] = self.app_current().get('package')
except DeviceError:
pass
return _info

class Session(Device):
"""Session keeps watch the app status
Expand Down
170 changes: 170 additions & 0 deletions uiautomator2/_input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""Created on Wed May 22 2024 16:23:56 by codeskyblue
"""

import base64
from dataclasses import dataclass
import re
from typing import Dict, Optional, Union
import warnings

from retry import retry

from uiautomator2.abstract import AbstractShell
from uiautomator2.exceptions import AdbBroadcastError, DeviceError
from uiautomator2.utils import deprecated


@dataclass
class BroadcastResult:
code: Optional[int]
data: Optional[str]


BORADCAST_RESULT_OK = -1
BROADCAST_RESULT_CANCELED = 0



class InputMethodMixIn(AbstractShell):
@deprecated(reason="use set_input_ime instead")
def set_fastinput_ime(self, enable: bool = True):
return self.set_input_ime(enable)

Check warning on line 34 in uiautomator2/_input.py

View check run for this annotation

Codecov / codecov/patch

uiautomator2/_input.py#L34

Added line #L34 was not covered by tests

def set_input_ime(self, enable: bool = True):
""" Enable of Disable InputIME """
ime_id = 'com.github.uiautomator/.AdbKeyboard'

Check warning on line 38 in uiautomator2/_input.py

View check run for this annotation

Codecov / codecov/patch

uiautomator2/_input.py#L38

Added line #L38 was not covered by tests
if not enable:
self.shell(['ime', 'disable', ime_id])
return

Check warning on line 41 in uiautomator2/_input.py

View check run for this annotation

Codecov / codecov/patch

uiautomator2/_input.py#L40-L41

Added lines #L40 - L41 were not covered by tests

if self.current_ime() == ime_id:
return
self.shell(['ime', 'enable', ime_id])
self.shell(['ime', 'set', ime_id])
self.shell(['settings', 'put', 'secure', 'default_input_method', ime_id])

Check warning on line 47 in uiautomator2/_input.py

View check run for this annotation

Codecov / codecov/patch

uiautomator2/_input.py#L44-L47

Added lines #L44 - L47 were not covered by tests

def _broadcast(self, action: str, extras: Dict[str, str] = {}) -> BroadcastResult:
# requires ATX 2.4.0+
args = ['am', 'broadcast', '-a', action]

Check warning on line 51 in uiautomator2/_input.py

View check run for this annotation

Codecov / codecov/patch

uiautomator2/_input.py#L51

Added line #L51 was not covered by tests
for k, v in extras.items():
if isinstance(v, int):
args.extend(['--ei', k, str(v)])

Check warning on line 54 in uiautomator2/_input.py

View check run for this annotation

Codecov / codecov/patch

uiautomator2/_input.py#L54

Added line #L54 was not covered by tests
else:
args.extend(['--es', k, v])

Check warning on line 56 in uiautomator2/_input.py

View check run for this annotation

Codecov / codecov/patch

uiautomator2/_input.py#L56

Added line #L56 was not covered by tests
# Example output: result=-1 data="success"
output = self.shell(args).output
m_result = re.search(r'result=(-?\d+)', output)
m_data = re.search(r'data="([^"]+)"', output)
result = int(m_result.group(1)) if m_result else None
data = m_data.group(1) if m_data else None
return BroadcastResult(result, data)

Check warning on line 63 in uiautomator2/_input.py

View check run for this annotation

Codecov / codecov/patch

uiautomator2/_input.py#L58-L63

Added lines #L58 - L63 were not covered by tests

@retry(AdbBroadcastError, tries=3, delay=1, jitter=0.5)
def _must_broadcast(self, action: str, extras: Dict[str, str] = {}):
result = self._broadcast(action, extras)

Check warning on line 67 in uiautomator2/_input.py

View check run for this annotation

Codecov / codecov/patch

uiautomator2/_input.py#L67

Added line #L67 was not covered by tests
if result.code != BORADCAST_RESULT_OK:
raise AdbBroadcastError(f"broadcast {action} failed: {result.data}")

Check warning on line 69 in uiautomator2/_input.py

View check run for this annotation

Codecov / codecov/patch

uiautomator2/_input.py#L69

Added line #L69 was not covered by tests

def send_keys(self, text: str, clear: bool = False):
"""
Args:
text (str): text to set
clear (bool): clear before set text
"""
try:
self.set_input_ime()
btext = text.encode('utf-8')
base64text = base64.b64encode(btext).decode()
cmd = "ADB_KEYBOARD_SET_TEXT" if clear else "ADB_KEYBOARD_INPUT_TEXT"
self._must_broadcast(cmd, {"text": base64text})
return True
except AdbBroadcastError:
warnings.warn(

Check warning on line 85 in uiautomator2/_input.py

View check run for this annotation

Codecov / codecov/patch

uiautomator2/_input.py#L77-L85

Added lines #L77 - L85 were not covered by tests
"set FastInputIME failed. use \"d(focused=True).set_text instead\"",
Warning)
return self(focused=True).set_text(text)

Check warning on line 88 in uiautomator2/_input.py

View check run for this annotation

Codecov / codecov/patch

uiautomator2/_input.py#L88

Added line #L88 was not covered by tests
# warnings.warn("set FastInputIME failed. use \"adb shell input text\" instead", Warning)
# self.shell(["input", "text", text.replace(" ", "%s")])

def send_action(self, code: Union[str, int] = None):
"""
Simulate input method edito code
Args:
code (str or int): input method editor code
Examples:
send_action("search"), send_action(3)
Refs:
https://developer.android.com/reference/android/view/inputmethod/EditorInfo
"""
self.set_input_ime(True)
__alias = {

Check warning on line 106 in uiautomator2/_input.py

View check run for this annotation

Codecov / codecov/patch

uiautomator2/_input.py#L105-L106

Added lines #L105 - L106 were not covered by tests
"go": 2,
"search": 3,
"send": 4,
"next": 5,
"done": 6,
"previous": 7,
}
if isinstance(code, str):
code = __alias.get(code, code)

Check warning on line 115 in uiautomator2/_input.py

View check run for this annotation

Codecov / codecov/patch

uiautomator2/_input.py#L115

Added line #L115 was not covered by tests
if code:
self._must_broadcast('ADB_KEYBOARD_EDITOR_CODE', {"code": str(code)})

Check warning on line 117 in uiautomator2/_input.py

View check run for this annotation

Codecov / codecov/patch

uiautomator2/_input.py#L117

Added line #L117 was not covered by tests
else:
self._must_broadcast('ADB_KEYBOARD_SMART_ENTER')

Check warning on line 119 in uiautomator2/_input.py

View check run for this annotation

Codecov / codecov/patch

uiautomator2/_input.py#L119

Added line #L119 was not covered by tests

def clear_text(self):
""" clear text
Raises:
EnvironmentError
"""
try:
self.set_input_ime(True)
self._must_broadcast('ADB_KEYBOARD_CLEAR_TEXT')
except AdbBroadcastError:

Check warning on line 129 in uiautomator2/_input.py

View check run for this annotation

Codecov / codecov/patch

uiautomator2/_input.py#L126-L129

Added lines #L126 - L129 were not covered by tests
# for Android simulator
self(focused=True).clear_text()

Check warning on line 131 in uiautomator2/_input.py

View check run for this annotation

Codecov / codecov/patch

uiautomator2/_input.py#L131

Added line #L131 was not covered by tests

@deprecated(reason="use set_input_ime instead")
def wait_fastinput_ime(self, timeout=5.0):
""" wait FastInputIME is ready (Depreacated in version 3.1)
Args:
timeout(float): maxium wait time
Raises:
EnvironmentError
"""
pass

Check warning on line 142 in uiautomator2/_input.py

View check run for this annotation

Codecov / codecov/patch

uiautomator2/_input.py#L142

Added line #L142 was not covered by tests
# TODO: 模拟器待兼容 eg. Genymotion, 海马玩, Mumu
# deadline = time.time() + timeout
# while time.time() < deadline:
# ime_id, shown = self.current_ime()
# if ime_id != "com.github.uiautomator/.FastInputIME":
# self.set_fastinput_ime(True)
# time.sleep(0.5)
# continue
# if shown:
# return True
# time.sleep(0.2)
# raise EnvironmentError("FastInputIME started failed")

def current_ime(self) -> str:
""" Current input method
Returns:
ime_method
Example output:
"com.github.uiautomator/.FastInputIME"
"""
return self.shell(['settings', 'get', 'secure', 'default_input_method']).output.strip()

Check warning on line 164 in uiautomator2/_input.py

View check run for this annotation

Codecov / codecov/patch

uiautomator2/_input.py#L164

Added line #L164 was not covered by tests
# _INPUT_METHOD_RE = re.compile(r'mCurMethodId=([-_./\w]+)')
# dim, _ = self.shell(['dumpsys', 'input_method'])
# m = _INPUT_METHOD_RE.search(dim)
# method_id = None if not m else m.group(1)
# shown = "mInputShown=true" in dim
# return (method_id, shown)
Loading

0 comments on commit 51c31f0

Please sign in to comment.