class TestDevice(unittest.TestCase): def setUp(self): self.device = AutomatorDevice() self.device.server = MagicMock() self.device.server.jsonrpc = MagicMock() def test_info(self): self.device.server.jsonrpc.deviceInfo = MagicMock() self.device.server.jsonrpc.deviceInfo.return_value = {} self.assertEqual(self.device.info, {}) self.device.server.jsonrpc.deviceInfo.assert_called_once_with() def test_click(self): self.device.server.jsonrpc.click = MagicMock() self.device.server.jsonrpc.click.return_value = True self.assertEqual(self.device.click(1, 2), True) self.device.server.jsonrpc.click.assert_called_once_with(1, 2) def test_swipe(self): self.device.server.jsonrpc.swipe = MagicMock() self.device.server.jsonrpc.swipe.return_value = True self.assertEqual(self.device.swipe(1, 2, 3, 4, 100), True) self.device.server.jsonrpc.swipe.assert_called_once_with(1, 2, 3, 4, 100) def test_drag(self): self.device.server.jsonrpc.drag = MagicMock() self.device.server.jsonrpc.drag.return_value = True self.assertEqual(self.device.drag(1, 2, 3, 4, 100), True) self.device.server.jsonrpc.drag.assert_called_once_with(1, 2, 3, 4, 100) def test_dump(self): self.device.server.jsonrpc.dumpWindowHierarchy = MagicMock() self.device.server.jsonrpc.dumpWindowHierarchy.return_value = "1.xml" self.device.server.adb.cmd = cmd = MagicMock() cmd.return_value.returncode = 0 self.assertEqual(self.device.dump("test.xml"), "test.xml") self.device.server.jsonrpc.dumpWindowHierarchy.assert_called_once_with(True, "dump.xml") self.assertEqual(cmd.call_args_list, [call("pull", "1.xml", "test.xml"), call("shell", "rm", "1.xml")]) self.device.server.jsonrpc.dumpWindowHierarchy.return_value = None self.assertEqual(self.device.dump("test.xml"), None) def test_screenshot(self): self.device.server.jsonrpc.takeScreenshot = MagicMock() self.device.server.jsonrpc.takeScreenshot.return_value = "1.png" self.device.server.adb.cmd = cmd = MagicMock() cmd.return_value.returncode = 0 self.assertEqual(self.device.screenshot("a.png", 1.0, 99), "a.png") self.device.server.jsonrpc.takeScreenshot.assert_called_once_with("screenshot.png", 1.0, 99) self.assertEqual(cmd.call_args_list, [call("pull", "1.png", "a.png"), call("shell", "rm", "1.png")]) self.device.server.jsonrpc.takeScreenshot.return_value = None self.assertEqual(self.device.screenshot("a.png", 1.0, 100), None) def test_freeze_rotation(self): self.device.server.jsonrpc.freezeRotation = MagicMock() self.device.freeze_rotation(True) self.device.freeze_rotation(False) self.assertEqual(self.device.server.jsonrpc.freezeRotation.call_args_list, [call(True), call(False)]) def test_orientation(self): self.device.server.jsonrpc.deviceInfo = MagicMock() orientation = { 0: "natural", 1: "left", 2: "upsidedown", 3: "right" } for i in range(4): self.device.server.jsonrpc.deviceInfo.return_value = {"displayRotation": i} self.assertEqual(self.device.orientation, orientation[i]) # set orientations = [ (0, "natural", "n", 0), (1, "left", "l", 90), (2, "upsidedown", "u", 180), (3, "right", "r", 270) ] for values in orientations: for value in values: self.device.server.jsonrpc.setOrientation = MagicMock() self.device.orientation = value self.device.server.jsonrpc.setOrientation.assert_called_once_with(values[1]) with self.assertRaises(ValueError): self.device.orientation = "invalid orientation" def test_last_traversed_text(self): self.device.server.jsonrpc.getLastTraversedText = MagicMock() self.device.server.jsonrpc.getLastTraversedText.return_value = "abcdef" self.assertEqual(self.device.last_traversed_text, "abcdef") self.device.server.jsonrpc.getLastTraversedText.assert_called_once_with() def test_clear_traversed_text(self): self.device.server.jsonrpc.clearLastTraversedText = MagicMock() self.device.clear_traversed_text() self.device.server.jsonrpc.clearLastTraversedText.assert_called_once_with() def test_open(self): self.device.server.jsonrpc.openNotification = MagicMock() self.device.open.notification() self.device.server.jsonrpc.openNotification.assert_called_once_with() self.device.server.jsonrpc.openQuickSettings = MagicMock() self.device.open.quick_settings() self.device.server.jsonrpc.openQuickSettings.assert_called_once_with() def test_watchers(self): names = ["a", "b", "c"] self.device.server.jsonrpc.getWatchers = MagicMock() self.device.server.jsonrpc.getWatchers.return_value = names self.assertEqual(self.device.watchers, names) self.device.server.jsonrpc.getWatchers.assert_called_once_with() self.device.server.jsonrpc.hasAnyWatcherTriggered = MagicMock() self.device.server.jsonrpc.hasAnyWatcherTriggered.return_value = True self.assertEqual(self.device.watchers.triggered, True) self.device.server.jsonrpc.hasAnyWatcherTriggered.assert_called_once_with() self.device.server.jsonrpc.removeWatcher = MagicMock() self.device.watchers.remove("a") self.device.server.jsonrpc.removeWatcher.assert_called_once_with("a") self.device.server.jsonrpc.removeWatcher = MagicMock() self.device.watchers.remove() self.assertEqual(self.device.server.jsonrpc.removeWatcher.call_args_list, [call(name) for name in names]) self.device.server.jsonrpc.resetWatcherTriggers = MagicMock() self.device.watchers.reset() self.device.server.jsonrpc.resetWatcherTriggers.assert_called_once_with() self.device.server.jsonrpc.runWatchers = MagicMock() self.device.watchers.run() self.device.server.jsonrpc.runWatchers.assert_called_once_with() def test_watcher(self): self.device.server.jsonrpc.hasWatcherTriggered = MagicMock() self.device.server.jsonrpc.hasWatcherTriggered.return_value = False self.assertFalse(self.device.watcher("name").triggered) self.device.server.jsonrpc.hasWatcherTriggered.assert_called_once_with("name") self.device.server.jsonrpc.removeWatcher = MagicMock() self.device.watcher("a").remove() self.device.server.jsonrpc.removeWatcher.assert_called_once_with("a") self.device.server.jsonrpc.registerClickUiObjectWatcher = MagicMock() condition1 = {"text": "my text", "className": "android"} condition2 = {"description": "my desc", "clickable": True} target = {"className": "android.widget.Button", "text": "OK"} self.device.watcher("watcher").when(**condition1).when(**condition2).click(**target) self.device.server.jsonrpc.registerClickUiObjectWatcher.assert_called_once_with("watcher", [Selector(**condition1), Selector(**condition2)], Selector(**target)) self.device.server.jsonrpc.registerPressKeyskWatcher = MagicMock() self.device.watcher("watcher2").when(**condition1).when(**condition2).press.back.home.power("menu") self.device.server.jsonrpc.registerPressKeyskWatcher.assert_called_once_with( "watcher2", [Selector(**condition1), Selector(**condition2)], ("back", "home", "power", "menu")) def test_press(self): key = ["home", "back", "left", "right", "up", "down", "center", "menu", "search", "enter", "delete", "del", "recent", "volume_up", "volume_down", "volume_mute", "camera", "power"] self.device.server.jsonrpc.pressKey = MagicMock() self.device.server.jsonrpc.pressKey.return_value = True self.assertTrue(self.device.press.home()) self.device.server.jsonrpc.pressKey.return_value = False self.assertFalse(self.device.press.back()) self.device.server.jsonrpc.pressKey.return_value = False for k in key: self.assertFalse(self.device.press(k)) self.assertEqual(self.device.server.jsonrpc.pressKey.call_args_list, [call("home"), call("back")] + [call(k) for k in key]) self.device.server.jsonrpc.pressKeyCode.return_value = True self.assertTrue(self.device.press(1)) self.assertTrue(self.device.press(1, 2)) self.assertEqual(self.device.server.jsonrpc.pressKeyCode.call_args_list, [call(1), call(1, 2)]) def test_wakeup(self): self.device.server.jsonrpc.wakeUp = MagicMock() self.device.wakeup() self.device.server.jsonrpc.wakeUp.assert_called_once_with() self.device.server.jsonrpc.wakeUp = MagicMock() self.device.screen.on() self.device.server.jsonrpc.wakeUp.assert_called_once_with() self.device.server.jsonrpc.wakeUp = MagicMock() self.device.screen("on") self.device.server.jsonrpc.wakeUp.assert_called_once_with() def test_sleep(self): self.device.server.jsonrpc.sleep = MagicMock() self.device.sleep() self.device.server.jsonrpc.sleep.assert_called_once_with() self.device.server.jsonrpc.sleep = MagicMock() self.device.screen.off() self.device.server.jsonrpc.sleep.assert_called_once_with() self.device.server.jsonrpc.sleep = MagicMock() self.device.screen("off") self.device.server.jsonrpc.sleep.assert_called_once_with() def test_wait_idle(self): self.device.server.jsonrpc.waitForIdle = MagicMock() self.device.server.jsonrpc.waitForIdle.return_value = True self.assertTrue(self.device.wait.idle(timeout=10)) self.device.server.jsonrpc.waitForIdle.assert_called_once_with(10) self.device.server.jsonrpc.waitForIdle = MagicMock() self.device.server.jsonrpc.waitForIdle.return_value = False self.assertFalse(self.device.wait("idle", timeout=10)) self.device.server.jsonrpc.waitForIdle.assert_called_once_with(10) def test_wait_update(self): self.device.server.jsonrpc.waitForWindowUpdate = MagicMock() self.device.server.jsonrpc.waitForWindowUpdate.return_value = True self.assertTrue(self.device.wait.update(timeout=10, package_name="android")) self.device.server.jsonrpc.waitForWindowUpdate.assert_called_once_with("android", 10) self.device.server.jsonrpc.waitForWindowUpdate = MagicMock() self.device.server.jsonrpc.waitForWindowUpdate.return_value = False self.assertFalse(self.device.wait("update", timeout=100, package_name="android")) self.device.server.jsonrpc.waitForWindowUpdate.assert_called_once_with("android", 100) def test_get_info_attr(self): info = {"test_a": 1, "test_b": "string", "displayWidth": 720, "displayHeight": 1024} self.device.server.jsonrpc.deviceInfo = MagicMock() self.device.server.jsonrpc.deviceInfo.return_value = info for k in info: self.assertEqual(getattr(self.device, k), info[k]) self.assertEqual(self.device.width, info["displayWidth"]) self.assertEqual(self.device.height, info["displayHeight"]) with self.assertRaises(AttributeError): self.device.not_exists def test_device_obj(self): with patch("uiautomator.AutomatorDeviceObject") as AutomatorDeviceObject: kwargs = {"text": "abc", "description": "description...", "clickable": True} self.device(**kwargs) AutomatorDeviceObject.assert_called_once_with(self.device, Selector(**kwargs)) with patch("uiautomator.AutomatorDeviceObject") as AutomatorDeviceObject: AutomatorDeviceObject.return_value.exists = True self.assertTrue(self.device.exists(clickable=True)) AutomatorDeviceObject.return_value.exists = False self.assertFalse(self.device.exists(text="..."))
class AndroidDevice(DeviceMixin): def __init__(self, serial=None, **kwargs): """Initial AndroidDevice Args: serial: string specify which device Returns: AndroidDevice object Raises: EnvironmentError """ self.__display = None serial = serial or getenvs('ATX_ADB_SERIALNO', 'ANDROID_SERIAL') self._host = kwargs.get('host') or getenvs( 'ATX_ADB_HOST', 'ANDROID_ADB_SERVER_HOST') or '127.0.0.1' self._port = int( kwargs.get('port') or getenvs('ATX_ADB_PORT', 'ANDROID_ADB_SERVER_PORT') or 5037) self._adb_client = adbkit.Client(self._host, self._port) self._adb_device = self._adb_client.device(serial) self._adb_shell_timeout = 30.0 # max adb shell exec time kwargs['adb_server_host'] = kwargs.pop('host', self._host) kwargs['adb_server_port'] = kwargs.pop('port', self._port) self._uiauto = UiaDevice(serial, **kwargs) DeviceMixin.__init__(self) self._randid = base.id_generator(5) self.screen_rotation = None self.screenshot_method = consts.SCREENSHOT_METHOD_AUTO # inherts from atx-uiautomator self.swipe = self._uiauto.swipe self.drag = self._uiauto.drag self.press = self._uiauto.press self.long_click = self._uiauto.long_click self.dump = self._uiauto.dump @property def info(self): return self._uiauto.info @property def uiautomator(self): """ Returns: uiautomator: Device object describes in https://github.com/openatx/atx-uiautomator """ return self._uiauto # TODO: not working in java-uiautomator # def swipe_points(self, points, steps=100): # """ # Args: # points: array of points, eg: [(10, 12), (40, 15)] # steps: the number of steps for the gesture. Steps are injected about 5 milliseconds apart, so 100 steps may take around 0.5 seconds to complete. # Returns: # a boolean value represents if all touch events for this gesture are injected successfully # """ # return self._uiauto.swipePoints(points, steps) def __call__(self, *args, **kwargs): return self._uiauto(*args, **kwargs) @property def serial(self): """ Android Device Serial Number """ return self._adb_device.serial @property def adb_server_host(self): return self._host @property def adb_server_port(self): return self._port @property def adb_device(self): return self._adb_device @property def wlan_ip(self): """ Wlan IP """ return self.adb_shell(['getprop', 'dhcp.wlan0.ipaddress']).strip() def forward(self, device_port, local_port=None): """Forward device port to local Args: device_port: port inside device local_port: port on PC, if this value is None, a port will random pick one. Returns: tuple, (host, local_port) """ port = self._adb_device.forward(device_port, local_port) return (self._host, port) def current_app(self): """Get current app (package, activity) Returns: namedtuple ['package', 'activity', 'pid'] activity, pid maybe None Raises: RuntimeError """ AppInfo = collections.namedtuple('AppInfo', ['package', 'activity', 'pid']) try: ai = self._adb_device.current_app() return AppInfo(ai['package'], ai['activity'], ai.get('pid')) except RuntimeError: return AppInfo(self.info['currentPackageName'], None, None) @property def current_package_name(self): return self.info['currentPackageName'] def is_app_alive(self, package_name): """ Deprecated: use current_package_name instaed. Check if app in running in foreground """ return self.info['currentPackageName'] == package_name def sleep(self, secs=None): """Depreciated. use delay instead.""" if secs is None: self._uiauto.sleep() else: self.delay(secs) @property def display(self): """Virtual keyborad may get small d.info['displayHeight'] """ for line in self.adb_shell('dumpsys display').splitlines(): m = _DISPLAY_RE.search(line, 0) if not m: continue w = int(m.group('width')) h = int(m.group('height')) return collections.namedtuple('Display', ['width', 'height'])(w, h) else: w, h = self.info['displayWidth'], self.info['displayHeight'] return collections.namedtuple('Display', ['width', 'height'])(w, h) @property def rotation(self): """ Rotaion of the phone 0: normal 1: home key on the right 2: home key on the top 3: home key on the left """ if self.screen_rotation in range(4): return self.screen_rotation return self.adb_device.rotation() or self.info['displayRotation'] @rotation.setter def rotation(self, r): if not isinstance(r, int): raise TypeError("r must be int") self.screen_rotation = r def _minicap_params(self): """ Used about 0.1s uiautomator d.info is now well working with device which has virtual menu. """ rotation = self.rotation # rotation not working on SumSUNG 9502 return '{x}x{y}@{x}x{y}/{r}'.format(x=self.display.width, y=self.display.height, r=rotation * 90) def _mktemp(self, suffix='.jpg'): prefix = 'atx-tmp-{}-'.format(uuid.uuid1()) return tempfile.mktemp(prefix=prefix, suffix='.jpg') def _screenshot_minicap(self): phone_tmp_file = '/data/local/tmp/_atx_screen-{}.jpg'.format( self._randid) local_tmp_file = self._mktemp() command = 'LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minicap -P {} -s > {}'.format( self._minicap_params(), phone_tmp_file) try: self.adb_shell(command) self.adb_cmd(['pull', phone_tmp_file, local_tmp_file]) image = imutils.open_as_pillow(local_tmp_file) # Fix rotation not rotate right. (width, height) = image.size if self.screen_rotation in [1, 3] and width < height: image = image.rotate(90, Image.BILINEAR, expand=True) return image except IOError: raise IOError("Screenshot use minicap failed.") finally: self.adb_shell(['rm', phone_tmp_file]) base.remove_force(local_tmp_file) def _screenshot_uiauto(self): tmp_file = self._mktemp() self._uiauto.screenshot(tmp_file) try: return imutils.open_as_pillow(tmp_file) except IOError: raise IOError("Screenshot use uiautomator failed.") finally: base.remove_force(tmp_file) # @hook_wrap(consts.EVENT_CLICK) def do_tap(self, x, y): """ Touch specify position Args: x, y: int Returns: None """ return self._uiauto.click(x, y) def _take_screenshot(self): screen = None if self.screenshot_method == consts.SCREENSHOT_METHOD_UIAUTOMATOR: screen = self._screenshot_uiauto() elif self.screenshot_method == consts.SCREENSHOT_METHOD_MINICAP: screen = self._screenshot_minicap() elif self.screenshot_method == consts.SCREENSHOT_METHOD_AUTO: try: screen = self._screenshot_minicap() self.screenshot_method = consts.SCREENSHOT_METHOD_MINICAP except IOError: screen = self._screenshot_uiauto() self.screenshot_method = consts.SCREENSHOT_METHOD_UIAUTOMATOR else: raise TypeError('Invalid screenshot_method') return screen def raw_cmd(self, *args, **kwargs): ''' Return subprocess.Process instance ''' return self.adb_device.raw_cmd(*args, **kwargs) def adb_cmd(self, command, **kwargs): ''' Run adb command, for example: adb(['pull', '/data/local/tmp/a.png']) Args: command: string or list of string Returns: command output ''' kwargs['timeout'] = kwargs.get('timeout', self._adb_shell_timeout) if isinstance(command, list) or isinstance(command, tuple): return self.adb_device.run_cmd(*list(command), **kwargs) return self.adb_device.run_cmd(command, **kwargs) def adb_shell(self, command, **kwargs): ''' Run adb shell command Args: command: string or list of string Returns: command output ''' if isinstance(command, list) or isinstance(command, tuple): return self.adb_cmd(['shell'] + list(command), **kwargs) else: return self.adb_cmd(['shell'] + [command], **kwargs) @property def properties(self): ''' Android Properties, extracted from `adb shell getprop` Returns: dict of props, for example: {'ro.bluetooth.dun': 'true'} ''' props = {} for line in self.adb_shell(['getprop']).splitlines(): m = _PROP_PATTERN.match(line) if m: props[m.group('key')] = m.group('value') return props def start_app(self, package_name, activity=None, stop=False): ''' Start application Args: - package_name (string): like com.example.app1 - activity (string): optional, activity name Returns time used (unit second), if activity is not None Document: usage: adb shell am start -D: enable debugging -W: wait for launch to complete --start-profiler <FILE>: start profiler and send results to <FILE> --sampling INTERVAL: use sample profiling with INTERVAL microseconds between samples (use with --start-profiler) -P <FILE>: like above, but profiling stops when app goes idle -R: repeat the activity launch <COUNT> times. Prior to each repeat, the top activity will be finished. -S: force stop the target app before starting the activity --opengl-trace: enable tracing of OpenGL functions --user <USER_ID> | current: Specify which user to run as; if not specified then run as the current user. ''' _pattern = re.compile(r'TotalTime: (\d+)') if activity is None: self.adb_shell([ 'monkey', '-p', package_name, '-c', 'android.intent.category.LAUNCHER', '1' ]) else: args = ['-W'] if stop: args.append('-S') output = self.adb_shell( ['am', 'start'] + args + ['-n', '%s/%s' % (package_name, activity)]) m = _pattern.search(output) if m: return int(m.group(1)) / 1000.0 def stop_app(self, package_name, clear=False): ''' Stop application Args: package_name: string like com.example.app1 clear: bool, remove user data Returns: None ''' if clear: self.adb_shell(['pm', 'clear', package_name]) else: self.adb_shell(['am', 'force-stop', package_name]) return self def takeSnapshot(self, filename): ''' Deprecated, use screenshot instead. ''' warnings.warn("deprecated, use snapshot instead", DeprecationWarning) return self.screenshot(filename) def _parse_xml_node(self, node): # ['bounds', 'checkable', 'class', 'text', 'resource_id', 'package'] __alias = { 'class': 'class_name', 'resource-id': 'resource_id', 'content-desc': 'content_desc', 'long-clickable': 'long_clickable', } def parse_bounds(text): m = re.match(r'\[(\d+),(\d+)\]\[(\d+),(\d+)\]', text) if m is None: return None return Bounds(*map(int, m.groups())) def str2bool(v): return v.lower() in ("yes", "true", "t", "1") def convstr(v): return v.encode('utf-8') parsers = { 'bounds': parse_bounds, 'text': convstr, 'class_name': convstr, 'resource_id': convstr, 'package': convstr, 'checkable': str2bool, 'scrollable': str2bool, 'focused': str2bool, 'clickable': str2bool, 'enabled': str2bool, 'selected': str2bool, 'long_clickable': str2bool, 'focusable': str2bool, 'password': str2bool, 'index': int, 'content_desc': convstr, } ks = {} for key, value in node.attributes.items(): key = __alias.get(key, key) f = parsers.get(key) if value is None: ks[key] = None elif f: ks[key] = f(value) for key in parsers.keys(): ks[key] = ks.get(key) ks['xml'] = node return UINode(**ks) def dump_nodes(self): """Dump current screen UI to list Returns: List of UINode object, For example: [UINode( bounds=Bounds(left=0, top=0, right=480, bottom=168), checkable=False, class_name='android.view.View', text='', resource_id='', package='com.sonyericsson.advancedwidget.clock')] """ xmldata = self._uiauto.dump() dom = xml.dom.minidom.parseString(xmldata.encode('utf-8')) root = dom.documentElement nodes = root.getElementsByTagName('node') ui_nodes = [] for node in nodes: ui_nodes.append(self._parse_xml_node(node)) return ui_nodes def dump_view(self): """Current Page XML """ warnings.warn("deprecated, source() instead", DeprecationWarning) return self._uiauto.dump() def source(self, *args, **kwargs): """ Dump page xml """ return self._uiauto.dump(*args, **kwargs) def _escape_text(self, s, utf7=False): s = s.replace(' ', '%s') if utf7: s = s.encode('utf-7') return s def keyevent(self, keycode): """call adb shell input keyevent ${keycode} Args: - keycode(string): for example, KEYCODE_ENTER keycode need reference: http://developer.android.com/reference/android/view/KeyEvent.html """ self.adb_shell(['input', 'keyevent', keycode]) def enable_ime(self, ime): """ Enable input methods Args: - ime(string): for example "android.unicode.ime/.Utf7ImeService" Raises: RuntimeError """ self.adb_shell(['ime', 'enable', ime]) self.adb_shell(['ime', 'set', ime]) from_time = time.time() while time.time() - from_time < 10.0: if ime == self.current_ime( ): # and self._adb_device.is_keyboard_shown(): return time.sleep(0.2) else: raise RuntimeError("Error switch to input-method (%s)." % ime) def _is_utf7ime(self, ime=None): if ime is None: ime = self.current_ime() return ime in [ 'android.unicode.ime/.Utf7ImeService', 'com.netease.atx.assistant/.ime.Utf7ImeService', 'com.netease.nie.yosemite/.ime.ImeService' ] def prepare_ime(self): """ Change current method to adb-keyboard Raises: RuntimeError """ if self._is_utf7ime(): return True for ime in self.input_methods(): if self._is_utf7ime(ime): self.enable_ime(ime) return True return False # raise RuntimeError("Input method for programers not detected.\n" + # "\tInstall with: python -m atx install atx-assistant") def _shell_type(self, text): first = True for s in text.split('%s'): if first: first = False else: self.adb_shell(['input', 'text', '%']) s = 's' + s if s == '': continue estext = self._escape_text(s) self.adb_shell(['input', 'text', estext]) def type(self, s, enter=False, next=False, clear=False, **ui_select_kwargs): """Input some text, this method has been tested not very stable on some device. "Hi world" maybe spell into "H iworld" Args: - s: string (text to input), better to be unicode - enter(bool): input enter at last - next(bool): perform editor action Next - clear(bool): clear text before type - ui_select_kwargs(**): tap then type The android source code show that space need to change to %s insteresting thing is that if want to input %s, it is really unconvinent. android source code can be found here. https://android.googlesource.com/platform/frameworks/base/+/android-4.4.2_r1/cmds/input/src/com/android/commands/input/Input.java#159 app source see here: https://github.com/openatx/android-unicode """ if ui_select_kwargs: ui_object = self(**ui_select_kwargs) ui_object.click() if clear: self.clear_text() utext = strutils.decode(s) if self.prepare_ime(): estext = base64.b64encode(utext.encode('utf-7')) self.adb_shell([ 'am', 'broadcast', '-a', 'ADB_INPUT_TEXT', '--es', 'format', 'base64', '--es', 'msg', estext ]) else: self._shell_type(utext) if enter: self.keyevent('KEYCODE_ENTER') if next: # FIXME(ssx): maybe KEYCODE_NAVIGATE_NEXT self.adb_shell([ 'am', 'broadcast', '-a', 'ADB_EDITOR_CODE', '--ei', 'code', '5' ]) def clear_text(self, count=100): """Clear text Args: - count (int): send KEY_DEL count """ self.prepare_ime() self.keyevent('KEYCODE_MOVE_END') self.adb_shell([ 'am', 'broadcast', '-a', 'ADB_INPUT_CODE', '--ei', 'code', '67', '--ei', 'repeat', str(count) ]) def input_methods(self): """ Get all input methods Return example: ['com.sohu.inputmethod.sogou/.SogouIME', 'android.unicode.ime/.Utf7ImeService'] """ imes = [] for line in self.adb_shell(['ime', 'list', '-s', '-a']).splitlines(): line = line.strip() if re.match('^.+/.+$', line): imes.append(line) return imes def current_ime(self): ''' Get current input method ''' dumpout = self.adb_shell(['dumpsys', 'input_method']) m = _INPUT_METHOD_RE.search(dumpout) if m: return m.group(1)