├── .gitignore ├── README.md └── termux_api.py /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !README.md 4 | !termux_api.py 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # termux_api 2 | 3 | Simple Python module to call [termux-api](https://wiki.termux.com/wiki/Termux:API) by subprocess. 4 | 5 | Exception-free module to call termux-api. 6 | Most calls return (result, error). Please check error is None. Result is None if not a query. 7 | Thin wrapper around termux-api. Function & param names are close to the [official wiki](https://wiki.termux.com/wiki/Termux:API). 8 | If wiki is not detailed enough, here's the official repos: [termux-api](https://github.com/termux/termux-api), [termux-api-package](https://github.com/termux/termux-api-package). 9 | 10 | ## Archived 11 | 12 | Support most APIs of v0.50.1, but not updated after v0.51.0 is released. 13 | (Probably still works for many cases, but please be careful.) 14 | 15 | ## Requirements 16 | 17 | - Python 3.7+, because of [subprocess.run(capture_output=True)](https://docs.python.org/3/library/subprocess.html#subprocess.run). 18 | 19 | ## How to use 20 | 21 | Install Termux & Termux:API app, then `pkg install termux-api`, 22 | `import termux_api`, then call any function. 23 | (Certain calls require some permissions to be granted to the Termux API app.) 24 | 25 | Example: 26 | ``` 27 | import termux_api 28 | termux_api.vibrate() 29 | ``` 30 | 31 | [pdoc](https://pdoc.dev/) can be used to generate simple docs: 32 | `pip install pdoc`, `pdoc termux_api.py`. 33 | 34 | ## APIs 35 | 36 | Currently, this supports all API implementations on wiki. (v0.50.1) 37 | But some are not fully tested, so please use with some caution. 38 | 39 | Most functions return (result, error), function and param names are close to the wiki. 40 | Function docstrings contain some hints about valid args, or test result of the function. 41 | 42 | Special functions: 43 | - Generators yield (result, error): `location(request="updates")`, `sensor()`. 44 | - `tts_speak_init()` starts a Popen, then returns 2 functions: `speak(text)` & `close()`. 45 | 46 | Some outputs from termux-api are not json, so it's hard to get results programmatically. 47 | I tried to read the source code, and parsed most of them: 48 | - `media_player_info()`: return {Track: None} or {Status, Track, Current Position} 49 | - `media_player_play()`: error if not `Now Playing` 50 | - `media_player_pause()`: True if paused, None if already paused, False if no track 51 | - `media_player_resume()`: True if resumed, None if already playing, False if no track 52 | - `media_player_stop()`: True if stopped, False if no track 53 | - `media_scan()`: return scanned file count 54 | - `microphone_record()`: error if not `Recording started` 55 | - `microphone_record_stop()`: True if stopped, False if no recording 56 | - `sensor_cleanup()`: True if successful, False if unnecessary 57 | 58 | These functions' outputs are not parsed. Raw strings are returned: 59 | - `job_scheduler*()`: looks complicated 60 | 61 | ### APIs not on wiki 62 | 63 | These APIs, which are not listed on the wiki, are not implemented yet: 64 | ``` 65 | termux-audio-info 66 | termux-keystore 67 | termux-nfc 68 | termux-notification-channel 69 | termux-notification-list 70 | termux-saf-create 71 | termux-saf-dirs 72 | termux-saf-ls 73 | termux-saf-managedir 74 | termux-saf-mkdir 75 | termux-saf-read 76 | termux-saf-rm 77 | termux-saf-stat 78 | termux-saf-write 79 | termux-speech-to-text 80 | ``` 81 | 82 | ## Bug report 83 | 84 | If you find a bug, please submit an issue. Thank you. 85 | -------------------------------------------------------------------------------- /termux_api.py: -------------------------------------------------------------------------------- 1 | """ 2 | termux_api.py - call termux-api by subprocess 3 | 4 | Official wiki: https://wiki.termux.com/wiki/Termux:API 5 | """ 6 | 7 | from __future__ import annotations 8 | 9 | import atexit 10 | import json 11 | import re 12 | import subprocess 13 | from json import JSONDecodeError 14 | from subprocess import CalledProcessError 15 | from typing import Any, Optional 16 | 17 | _all_popen = [] 18 | 19 | 20 | def _kill_all_popen(): 21 | for popen in _all_popen: 22 | popen.kill() 23 | 24 | 25 | atexit.register(_kill_all_popen) 26 | 27 | 28 | def _run(args, **kwargs) -> tuple[Optional[str], Optional[CalledProcessError]]: 29 | args = [str(i) for i in args] 30 | try: 31 | proc = subprocess.run( 32 | args, capture_output=True, check=True, text=True, **kwargs 33 | ) 34 | return proc.stdout, None 35 | except CalledProcessError as err: 36 | return None, err 37 | 38 | 39 | def _run_json( 40 | args, **kwargs 41 | ) -> tuple[Any, Optional[CalledProcessError | JSONDecodeError]]: 42 | stdout, err = _run(args, **kwargs) 43 | if err: 44 | return None, err 45 | try: 46 | return json.loads(stdout), None 47 | except JSONDecodeError as err: 48 | return None, err 49 | 50 | 51 | def _run_error(args, **kwargs) -> tuple[None, Optional[CalledProcessError | str]]: 52 | stdout, err = _run(args, **kwargs) 53 | if err: 54 | return None, err 55 | if stdout.strip(): 56 | return None, stdout 57 | else: 58 | return None, None 59 | 60 | 61 | def _run_startswith_error( 62 | args, startswith, is_error=False, **kwargs 63 | ) -> tuple[None, Optional[CalledProcessError | str]]: 64 | stdout, err = _run(args, **kwargs) 65 | if err: 66 | return None, err 67 | if stdout.startswith(startswith) == is_error: 68 | return None, stdout 69 | else: 70 | return None, None 71 | 72 | 73 | def _run_startswith_map( 74 | args, mapping, default=None, **kwargs 75 | ) -> tuple[Optional[Any], Optional[CalledProcessError]]: 76 | stdout, err = _run(args, **kwargs) 77 | if err: 78 | return None, err 79 | for k, v in mapping.items(): 80 | if stdout.startswith(k): 81 | return v, None 82 | return default, None 83 | 84 | 85 | def _run_regex( 86 | args, regex, types, **kwargs 87 | ) -> tuple[Optional[Any], Optional[CalledProcessError]]: 88 | stdout, err = _run(args, **kwargs) 89 | if err: 90 | return None, err 91 | m = re.match(regex, stdout) 92 | if m is None: 93 | return None, None 94 | if type(types) is list: 95 | return [t(g) for t, g in zip(types, m.groups())], None 96 | else: 97 | return types(m.groups()[0]), None 98 | 99 | 100 | def _run_updates(args, **kwargs): 101 | args = [str(i) for i in args] 102 | popen = subprocess.Popen( 103 | args, bufsize=1, stdout=subprocess.PIPE, text=True, **kwargs 104 | ) 105 | _all_popen.append(popen) 106 | buffer = "" 107 | for line in iter(popen.stdout.readline, ""): 108 | buffer += line 109 | try: 110 | yield json.loads(buffer), None 111 | buffer = "" 112 | except JSONDecodeError: 113 | pass 114 | popen.stdout.close() 115 | return_code = popen.wait() 116 | _all_popen.remove(popen) 117 | if return_code: 118 | yield None, CalledProcessError(return_code, args) 119 | 120 | 121 | def _construct_args(command: list[str], flags={}, kwargs={}, args=[]): 122 | res = command 123 | for k, v in flags.items(): 124 | if v: 125 | res.append(k) 126 | for k, v in kwargs.items(): 127 | if v is None: 128 | continue 129 | res.append(k) 130 | if v is True: 131 | res.append("true") 132 | elif v is False: 133 | res.append("false") 134 | else: 135 | res.append(v) 136 | res.extend(args) 137 | return res 138 | 139 | 140 | def _join_list(lis): 141 | if lis is None: 142 | return None 143 | return ",".join(str(i) for i in lis) 144 | 145 | 146 | def battery_status(): 147 | return _run_json(["termux-battery-status"]) 148 | 149 | 150 | def brightness(brightness): 151 | """brightness: 0-255 or auto, require android.permission.WRITE_SETTINGS""" 152 | return _run_error(["termux-brightness", brightness]) 153 | 154 | 155 | def call_log(offset=0, limit=10): 156 | """not working on some devices even when permission is granted""" 157 | return _run_json(["termux-call-log", "-o", offset, "-l", limit]) 158 | 159 | 160 | def camera_info(): 161 | return _run_json(["termux-camera-info"]) 162 | 163 | 164 | def camera_photo(output_file, camera_id=0): 165 | return _run_error(["termux-camera-photo", "-c", camera_id, output_file]) 166 | 167 | 168 | def clipboard_get(): 169 | return _run(["termux-clipboard-get"]) 170 | 171 | 172 | def clipboard_set(text): 173 | return _run_error(["termux-clipboard-set", text]) 174 | 175 | 176 | def contact_list(): 177 | return _run_json(["termux-contact-list"]) 178 | 179 | 180 | def dialog_list(): 181 | return _run(["termux-dialog", "-l"]) 182 | 183 | 184 | def dialog_confirm(title=None, hint=None): 185 | args = _construct_args(["termux-dialog", "confirm"], {}, {"-t": title, "-i": hint}) 186 | return _run_json(args) 187 | 188 | 189 | def dialog_checkbox(title=None, values=[]): 190 | args = _construct_args( 191 | ["termux-dialog", "checkbox"], {}, {"-t": title, "-v": _join_list(values)} 192 | ) 193 | return _run_json(args) 194 | 195 | 196 | def dialog_counter(title=None, range=None): 197 | """range: [min, max, start]""" 198 | args = _construct_args( 199 | ["termux-dialog", "counter"], {}, {"-t": title, "-r": _join_list(range)} 200 | ) 201 | return _run_json(args) 202 | 203 | 204 | def dialog_date(title=None, date_format=None): 205 | """date_format: output SimpleDateFormat pattern, e.g. dd-MM-yyyy k:m:s""" 206 | args = _construct_args( 207 | ["termux-dialog", "date"], {}, {"-t": title, "-d": date_format} 208 | ) 209 | return _run_json(args) 210 | 211 | 212 | def dialog_radio(title=None, values=[]): 213 | args = _construct_args( 214 | ["termux-dialog", "radio"], {}, {"-t": title, "-v": _join_list(values)} 215 | ) 216 | return _run_json(args) 217 | 218 | 219 | def dialog_sheet(title=None, values=[]): 220 | args = _construct_args( 221 | ["termux-dialog", "sheet"], {}, {"-t": title, "-v": _join_list(values)} 222 | ) 223 | return _run_json(args) 224 | 225 | 226 | def dialog_spinner(title=None, values=[]): 227 | args = _construct_args( 228 | ["termux-dialog", "spinner"], {}, {"-t": title, "-v": _join_list(values)} 229 | ) 230 | return _run_json(args) 231 | 232 | 233 | def dialog_speech(title=None, hint=None): 234 | args = _construct_args(["termux-dialog", "speech"], {}, {"-t": title, "-i": hint}) 235 | return _run_json(args) 236 | 237 | 238 | def dialog_text(title=None, hint=None, multi_line=False, number=False, password=False): 239 | args = _construct_args( 240 | ["termux-dialog", "text"], 241 | {"-m": multi_line, "-n": number, "-p": password}, 242 | {"-t": title, "-i": hint}, 243 | ) 244 | return _run_json(args) 245 | 246 | 247 | def dialog_time(title=None): 248 | args = _construct_args(["termux-dialog", "time"], {}, {"-t": title}) 249 | return _run_json(args) 250 | 251 | 252 | def download(url, path=None, title=None, description=None): 253 | args = _construct_args( 254 | ["termux-download"], {}, {"-p": path, "-t": title, "-d": description}, [url] 255 | ) 256 | return _run_error(args) 257 | 258 | 259 | def fingerprint(): 260 | return _run_json(["termux-fingerprint"]) 261 | 262 | 263 | def infrared_frequencies(): 264 | return _run_json(["termux-infrared-frequencies"]) 265 | 266 | 267 | def infrared_transmit(frequency, pattern): 268 | return _run_error( 269 | ["termux-infrared-transmit", "-f", frequency, _join_list(pattern)] 270 | ) 271 | 272 | 273 | def job_scheduler_list(): 274 | return _run(["termux-job-scheduler", "-p"]) 275 | 276 | 277 | def job_scheduler_cancel(job_id=None): 278 | return _run(["termux-job-scheduler", "--cancel", job_id]) 279 | 280 | 281 | def job_scheduler_cancel_all(): 282 | return _run(["termux-job-scheduler", "--cancel-all"]) 283 | 284 | 285 | def job_scheduler( 286 | script_path, 287 | job_id=None, 288 | period_ms=0, 289 | network="any", 290 | battery_not_low=True, 291 | storage_not_low=False, 292 | charging=False, 293 | persisted=False, 294 | trigger_content_uri=None, 295 | trigger_content_flag=1, 296 | ): 297 | """refer to: `termux-job-scheduler -h`""" 298 | args = _construct_args( 299 | ["termux-job-scheduler"], 300 | {}, 301 | { 302 | "-s": script_path, 303 | "--job-id": job_id, 304 | "--period-ms": period_ms, 305 | "--network": network, 306 | "--battery-not-low": battery_not_low, 307 | "--storage-not-low": storage_not_low, 308 | "--charging": charging, 309 | "--persisted": persisted, 310 | "--trigger-content-uri": trigger_content_uri, 311 | "--trigger-content-flag": trigger_content_flag, 312 | }, 313 | ) 314 | return _run(args) 315 | 316 | 317 | def location(provider="gps", request="once"): 318 | """provider: gps/network/passive, request: once/last/updates""" 319 | args = ["termux-location", "-p", provider, "-r", request] 320 | if request == "updates": 321 | return _run_updates(args) 322 | return _run_json(args) 323 | 324 | 325 | def media_player_info(): 326 | """return {"Track": None} or {Status, Track, Current Position}""" 327 | res, err = _run(["termux-media-player", "info"]) 328 | if err: 329 | return None, err 330 | if res.startswith("No track currently"): 331 | return {"Track": None}, None 332 | res = dict( 333 | [line.split(": ", maxsplit=1) for line in res.split("\n") if ":" in line] 334 | ) 335 | return res, None 336 | 337 | 338 | def media_player_play(file): 339 | args = ["termux-media-player", "play", file] 340 | return _run_startswith_error(args, "Now Playing") 341 | 342 | 343 | def media_player_pause(): 344 | return _run_startswith_map( 345 | ["termux-media-player", "pause"], 346 | { 347 | "Paused playback": True, 348 | "Playback already paused": None, 349 | "No track to pause": False, 350 | }, 351 | ) 352 | 353 | 354 | def media_player_resume(): 355 | return _run_startswith_map( 356 | ["termux-media-player", "play"], 357 | { 358 | "Resumed playback": True, 359 | "Already playing track": None, 360 | "No previous track to resume": False, 361 | }, 362 | ) 363 | 364 | 365 | def media_player_stop(): 366 | return _run_startswith_map( 367 | ["termux-media-player", "stop"], 368 | { 369 | "Stopped playback": True, 370 | "No track to stop": False, 371 | }, 372 | ) 373 | 374 | 375 | def media_scan(files, recursive=False, verbose=False): 376 | """verbose makes no difference""" 377 | # -v just prints all files out, including non-media files 378 | args = _construct_args( 379 | ["termux-media-scan"], {"-r": recursive, "-v": verbose}, {}, files 380 | ) 381 | return _run_regex(args, "Finished scanning ([0-9]+) file", int) 382 | 383 | 384 | def microphone_record( 385 | file=None, 386 | limit=None, 387 | encoder=None, 388 | bitrate=None, 389 | sample_rate=None, 390 | channel_count=None, 391 | default=False, 392 | ): 393 | """ 394 | default encoding is device specific. 395 | default limit is 15min. 396 | default file is /sdcard/TermuxAudioRecording_yyyy-MM-dd_HH-mm-ss. 397 | """ 398 | args = _construct_args( 399 | ["termux-microphone-record"], 400 | {"-d": default}, 401 | { 402 | "-f": file, 403 | "-l": limit, 404 | "-e": encoder, 405 | "-b": bitrate, 406 | "-r": sample_rate, 407 | "-c": channel_count, 408 | }, 409 | ) 410 | return _run_startswith_error(args, "Recording started") 411 | 412 | 413 | def microphone_record_info(): 414 | return _run_json(["termux-microphone-record", "-i"]) 415 | 416 | 417 | def microphone_record_quit(): 418 | return _run_startswith_map( 419 | ["termux-microphone-record", "-q"], 420 | {"Recording finished": True, "No recording to stop": False}, 421 | ) 422 | 423 | 424 | def notification( 425 | title=None, 426 | content=None, 427 | button1=None, 428 | button2=None, 429 | button3=None, 430 | image=None, 431 | sound=False, 432 | vibrate=None, 433 | led_color=None, 434 | led_off=None, 435 | led_on=None, 436 | alert_once=False, 437 | pin=False, 438 | priority=None, 439 | id=None, 440 | group=None, 441 | type=None, 442 | action=None, 443 | button1_action=None, 444 | button2_action=None, 445 | button3_action=None, 446 | delete_action=None, 447 | media_next=None, 448 | media_pause=None, 449 | media_play=None, 450 | media_previous=None, 451 | ): 452 | """refer to the official wiki: https://wiki.termux.com/wiki/Termux-notification""" 453 | args = _construct_args( 454 | ["termux-notification"], 455 | {"--alert-once": alert_once, "--ongoing": pin, "--sound": sound}, 456 | { 457 | "--action": action, 458 | "--button1": button1, 459 | "--button1-action": button1_action, 460 | "--button2": button2, 461 | "--button2-action": button2_action, 462 | "--button3": button3, 463 | "--button3-action": button3_action, 464 | "--content": content, 465 | "--group": group, 466 | "--id": id, 467 | "--image-path": image, 468 | "--led-color": led_color, 469 | "--led-off": led_off, 470 | "--led-on": led_on, 471 | "--on-delete": delete_action, 472 | "--priority": priority, 473 | "--title": title, 474 | "--vibrate": _join_list(vibrate), 475 | "--type": type, 476 | "--media-next": media_next, 477 | "--media-pause": media_pause, 478 | "--media-play": media_play, 479 | "--media-previous": media_previous, 480 | }, 481 | ) 482 | return _run_error(args) 483 | 484 | 485 | def notification_remove(id): 486 | return _run_error(["termux-notification-remove", id]) 487 | 488 | 489 | def sensor(sensors=None, delay=None, times=None): 490 | args = ["termux-sensor"] 491 | if sensors is None: 492 | args.append("-a") 493 | else: 494 | args.append("-s") 495 | args.append(_join_list(sensors)) 496 | args = _construct_args(args, {}, {"-d": delay, "-n": times}) 497 | return _run_updates(args) 498 | 499 | 500 | def sensor_cleanup(): 501 | """clean up running sensor listeners""" 502 | return _run_startswith_map( 503 | ["termux-sensor", "-c"], 504 | { 505 | "Sensor cleanup successful": True, 506 | "Sensor cleanup unnecessary": False, 507 | }, 508 | ) 509 | 510 | 511 | def sensor_list(): 512 | return _run_json(["termux-sensor", "-l"]) 513 | 514 | 515 | def sensor_once(sensors=None): 516 | if sensors is None: 517 | return _run_json(["termux-sensor", "-a", "-n", 1]) 518 | return _run_json(["termux-sensor", "-s", _join_list(sensors), "-n", 1]) 519 | 520 | 521 | def share(file, action=None, content_type=None, default_receiver=False, title=None): 522 | """action: edit/send/view""" 523 | args = _construct_args( 524 | ["termux-share"], 525 | {"-d": default_receiver}, 526 | {"-a": action, "-c": content_type, "-t": title}, 527 | [file], 528 | ) 529 | return _run_error(args) 530 | 531 | 532 | def sms_list( 533 | offset=0, limit=10, show_date=False, show_number=False, message_type="inbox" 534 | ): 535 | """message_type: all|inbox|sent|draft|outbox""" 536 | args = _construct_args( 537 | ["termux-sms-list"], 538 | {"-d": show_date, "-n": show_number}, 539 | {"-l": limit, "-o": offset, "-t": message_type}, 540 | ) 541 | return _run_json(args) 542 | 543 | 544 | def sms_send(text, numbers, sim_slot=None): 545 | """not tested""" 546 | args = _construct_args( 547 | ["termux-sms-send"], {}, {"-n": _join_list(numbers), "-s": sim_slot}, [text] 548 | ) 549 | return _run_error(args) 550 | 551 | 552 | def storage_get(output_file): 553 | return _run_error(["termux-storage-get", output_file]) 554 | 555 | 556 | def telephony_call(number): 557 | """not tested""" 558 | return _run_error(["termux-telephony-call", number]) 559 | 560 | 561 | def telephony_cellinfo(): 562 | return _run_json(["termux-telephony-cellinfo"]) 563 | 564 | 565 | def telephony_deviceinfo(): 566 | return _run_json(["termux-telephony-deviceinfo"]) 567 | 568 | 569 | def toast( 570 | text, position="middle", short=False, text_color="white", background_color="gray" 571 | ): 572 | """position: top/middle/bottom""" 573 | args = _construct_args( 574 | ["termux-toast"], 575 | {"-s": short}, 576 | {"-g": position, "-c": text_color, "-b": background_color}, 577 | [text], 578 | ) 579 | return _run_error(args) 580 | 581 | 582 | def torch(on=True): 583 | return _run_error(["termux-torch", "on" if on else "off"]) 584 | 585 | 586 | def tts_engines(): 587 | return _run_json(["termux-tts-engines"]) 588 | 589 | 590 | def tts_speak( 591 | text, 592 | engine=None, 593 | language=None, 594 | region=None, 595 | variant=None, 596 | pitch=None, 597 | rate=None, 598 | stream=None, 599 | timeout=None, 600 | ): 601 | """stream: ALARM, MUSIC, NOTIFICATION, RING, SYSTEM, VOICE_CALL""" 602 | args = _construct_args( 603 | ["termux-tts-speak"], 604 | {}, 605 | { 606 | "-e": engine, 607 | "-l": language, 608 | "-n": region, 609 | "-v": variant, 610 | "-p": pitch, 611 | "-r": rate, 612 | "-s": stream, 613 | }, 614 | [text], 615 | ) 616 | return _run_error(args, timeout=timeout) 617 | 618 | 619 | def tts_speak_init( 620 | engine=None, 621 | language=None, 622 | region=None, 623 | variant=None, 624 | pitch=None, 625 | rate=None, 626 | stream=None, 627 | ): 628 | """return functions: speak(text), close()""" 629 | args = _construct_args( 630 | ["termux-tts-speak"], 631 | {}, 632 | { 633 | "-e": engine, 634 | "-l": language, 635 | "-n": region, 636 | "-v": variant, 637 | "-p": pitch, 638 | "-r": rate, 639 | "-s": stream, 640 | }, 641 | ) 642 | args = [str(i) for i in args] 643 | popen = subprocess.Popen(args, bufsize=1, stdin=subprocess.PIPE, text=True) 644 | _all_popen.append(popen) 645 | 646 | def speak(text: str): 647 | popen.stdin.write(text + "\n") 648 | return None, None 649 | 650 | def close(): 651 | popen.stdin.close() 652 | return_code = popen.wait() 653 | _all_popen.remove(popen) 654 | if return_code: 655 | return None, CalledProcessError(return_code, args) 656 | return None, None 657 | 658 | return speak, close 659 | 660 | 661 | def usb(device, permission_dialog=False, execute_command=None): 662 | """not tested""" 663 | args = _construct_args( 664 | ["termux-usb"], {"-r": permission_dialog}, {"-e": execute_command}, [device] 665 | ) 666 | return _run_error(args) 667 | 668 | 669 | def usb_list(): 670 | return _run_json(["termux-usb", "-l"]) 671 | 672 | 673 | def vibrate(duration=None, force=False): 674 | args = _construct_args(["termux-vibrate"], {"-f": force}, {"-d": duration}) 675 | return _run_error(args) 676 | 677 | 678 | def volume_get(): 679 | return _run_json(["termux-volume"]) 680 | 681 | 682 | def volume_set(stream, volume): 683 | return _run_error(["termux-volume", stream, volume]) 684 | 685 | 686 | def wallpaper(file=None, url=None, lockscreen=False): 687 | """not tested""" 688 | args = _construct_args( 689 | ["termux-wallpaper"], {"-l": lockscreen}, {"-f": file, "-u": url} 690 | ) 691 | return _run_error(args) 692 | 693 | 694 | def wifi_connectioninfo(): 695 | return _run_json(["termux-wifi-connectioninfo"]) 696 | 697 | 698 | def wifi_enable(enable=True): 699 | """not working on some devices""" 700 | return _run_error(["termux-wifi-enable", "true" if enable else "false"]) 701 | 702 | 703 | def wifi_scaninfo(): 704 | return _run_json(["termux-wifi-scaninfo"]) 705 | 706 | 707 | if __name__ == "__main__": 708 | 709 | def run_tests(tests, wait_enter=True): 710 | for test in tests: 711 | func, args, kwargs = test[0], [], {} 712 | if len(test) > 1: 713 | args = test[1] 714 | if len(test) > 2: 715 | kwargs = test[2] 716 | print() 717 | print("Test", func.__name__, args, kwargs) 718 | if wait_enter: 719 | if input("Press enter...") != "": 720 | print("Skipped.") 721 | continue 722 | res, err = func(*args, **kwargs) 723 | if err: 724 | print("Error:", err) 725 | else: 726 | print("Result:", res) 727 | 728 | def test_sensor(delay=1000, times=5, wait_enter=True): 729 | print() 730 | print(f"sensor {delay=} {times=}") 731 | if wait_enter: 732 | input("Press enter...") 733 | for res, err in sensor(delay=delay, times=times): 734 | if err: 735 | print("Error:", err) 736 | else: 737 | print("Result:", res) 738 | 739 | def test_tts_speak(wait_enter=True): 740 | print() 741 | print("tts_speak_init") 742 | if wait_enter: 743 | input("Press enter...") 744 | speak, close = tts_speak_init() 745 | while text := input("speak: ").strip(): 746 | _, err = speak(text) 747 | if err: 748 | print("Error:", err) 749 | _, err = close() 750 | if err: 751 | print("Error:", err) 752 | 753 | test_dir = "/sdcard/Download/termux_api_test/" 754 | in_script = "/data/data/com.termux/files/home/termux-toast-hello.sh" 755 | in_audio = test_dir + "in.ogg" 756 | # in_image = test_dir + "in.jpg" 757 | in_text = test_dir + "in.txt" 758 | out_html = test_dir + "out.html" 759 | out_audio = test_dir + "out.m4a" 760 | out_image = test_dir + "out.jpg" 761 | out_text = test_dir + "out.txt" 762 | tests = [ 763 | [battery_status], 764 | [brightness, [128]], 765 | [call_log], 766 | [camera_info], 767 | [camera_photo, [out_image]], 768 | [clipboard_set, ["test"]], 769 | [clipboard_get], 770 | [contact_list], 771 | [dialog_checkbox, ["title", ["value 1", "value 2"]]], 772 | [download, ["https://google.com", out_html, "title", "desc"]], 773 | [fingerprint], 774 | [infrared_frequencies], 775 | [infrared_transmit, [40000, [20, 50, 20, 30]]], 776 | [job_scheduler, [in_script]], 777 | [location, ["network"]], 778 | [media_player_play, [in_audio]], 779 | [media_player_info], 780 | [media_player_pause], 781 | [media_player_stop], 782 | [media_scan, [[in_audio]]], 783 | [microphone_record, [out_audio, 10, "aac", 128, 44100, 2]], 784 | [microphone_record_info], 785 | [microphone_record_quit], 786 | [notification, ["title", "content"], {"id": "termux_api_test"}], 787 | [notification_remove, ["termux_api_test"]], 788 | [sensor_list], 789 | [sensor_once], 790 | # [sensor], # test alone 791 | [share, [in_text]], 792 | [sms_list], 793 | # [sms_send, ["Hello", ["123"]]], 794 | [storage_get, [out_text]], 795 | # [telephony_call, ["123"]], 796 | [telephony_cellinfo], 797 | [telephony_deviceinfo], 798 | [toast, ["hello"]], 799 | [torch], 800 | [torch, [False]], 801 | [tts_engines], 802 | [tts_speak, ["hello"], {"timeout": 20}], 803 | # [tts_speak_init], # test alone 804 | [usb_list], 805 | # [usb, ["/dev/bus/usb/xxx/xxx"]], 806 | [vibrate], 807 | [volume_get], 808 | [volume_set, ["music", 0]], 809 | # [wallpaper, [in_image]], 810 | [wifi_connectioninfo], 811 | [wifi_enable], 812 | [wifi_enable, [False]], 813 | [wifi_scaninfo], 814 | ] 815 | run_tests(tests) 816 | test_sensor() 817 | test_tts_speak() 818 | --------------------------------------------------------------------------------