From c9445dfc326689fe7e0778451fe13c2a585bcf83 Mon Sep 17 00:00:00 2001 From: "River@devbox" Date: Mon, 16 Mar 2026 12:19:24 +0800 Subject: [PATCH 01/21] Fix Quick Start battery example - add batteryStartMonitoring() call --- source/en/qsl4a/index.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source/en/qsl4a/index.md b/source/en/qsl4a/index.md index 50a1a34..e810913 100644 --- a/source/en/qsl4a/index.md +++ b/source/en/qsl4a/index.md @@ -15,8 +15,12 @@ droid.makeToast('Hello QPython!') # Vibrate the device droid.vibrate(500) -# Get battery level +# Get battery level (start monitoring first) +import time +droid.batteryStartMonitoring() +time.sleep(0.5) # Wait for data battery = droid.readBatteryData().result +print(f"Battery: {battery['level']}%") ``` ## Documentation Structure From 7b3540a7cfcb151228c6709e16ea2ef2fae5e936 Mon Sep 17 00:00:00 2001 From: "River@devbox" Date: Mon, 16 Mar 2026 12:21:27 +0800 Subject: [PATCH 02/21] Fixed: build.sh --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index f82aa96..8635b0d 100755 --- a/build.sh +++ b/build.sh @@ -81,7 +81,7 @@ cat > site/index.html << 'EOF'

Choose your language / 选择语言

From 5ebce977d5a7c9972466af373075c24c4a761003 Mon Sep 17 00:00:00 2001 From: "River@devbox" Date: Mon, 16 Mar 2026 13:00:43 +0800 Subject: [PATCH 03/21] =?UTF-8?q?Update=20footer=20copyright=20to=20"?= =?UTF-8?q?=C2=A9=20QPython=202012-2026"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mkdocs-zh.yml | 2 ++ mkdocs.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/mkdocs-zh.yml b/mkdocs-zh.yml index 86ea37c..22dacd4 100644 --- a/mkdocs-zh.yml +++ b/mkdocs-zh.yml @@ -29,6 +29,8 @@ theme: text: Roboto code: Roboto Mono +copyright: © QPython 2012-2026 + extra_css: - static/extra.css diff --git a/mkdocs.yml b/mkdocs.yml index 058e2a3..fab49ab 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -29,6 +29,8 @@ theme: text: Roboto code: Roboto Mono +copyright: © QPython 2012-2026 + extra_css: - static/extra.css From fc1a5d9cd5e58d94e4a09caf3f6b06cb7d6d4a2b Mon Sep 17 00:00:00 2001 From: "River@devbox" Date: Thu, 19 Mar 2026 10:49:18 +0800 Subject: [PATCH 04/21] Clean --- .DS_Store | Bin 6148 -> 0 bytes .gitignore | 1 + build.sh | 2 +- site/index.html | 63 ----------------------------------- source/en/getting-started.md | 2 +- source/en/static/.DS_Store | Bin 8196 -> 0 bytes source/zh/static/.DS_Store | Bin 8196 -> 0 bytes 7 files changed, 3 insertions(+), 65 deletions(-) delete mode 100644 .DS_Store delete mode 100644 site/index.html delete mode 100644 source/en/static/.DS_Store delete mode 100644 source/zh/static/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index de5fc53990f435abad0808330fef4a844df945b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~F^LL|d(t0@2UW`S!Tku=OfJ-;({to>)nK zk%75DdH(StZPNiy=_3~Vxh+TmDIf);fE17d zD^ehj@!ft!&!k6@0#aZd3i$V-(495eI^)y95F-FNupGv9%o1er0$G!-lNFlf^kCU) zF@|_O+R2jF)nx1J?XVm^EbnYS#n7y`!wM6c)qsK&kOB(@wml#H{6EwGn*SFqN~M4l zcryiTIDMUte5pKJUtZ7am#q4_(aE@+;nz<96F-U%^f2xhUywD~I$5FVM<8TSkOKdz Fz!Qau5~2VA diff --git a/.gitignore b/.gitignore index 40b53b0..5d89e25 100755 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ qpython-docs/build/* qpython-docs/static/* venv site +.DS_Store diff --git a/build.sh b/build.sh index 8635b0d..ab03cba 100755 --- a/build.sh +++ b/build.sh @@ -33,7 +33,7 @@ cat > site/index.html << 'EOF' QPython Documentation - + - - -
- -

Choose your language / 选择语言

-
- English - 中文 -
-
- - diff --git a/source/en/getting-started.md b/source/en/getting-started.md index 993ebe8..f97e915 100644 --- a/source/en/getting-started.md +++ b/source/en/getting-started.md @@ -200,7 +200,7 @@ Run scripts without UI in the background. Add header: ```python -#qpy:qpyapp +#qpy:quiet import time diff --git a/source/en/static/.DS_Store b/source/en/static/.DS_Store deleted file mode 100644 index b063954ee80f0d6492f6b92892e5062f5b9a9451..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHM%Q6E&6g`basuX1<6vZrUs6wo4hKhx?4~Rq}9wQSGl-+FngJ19ozRSvrbGsW8 znIW=Jl-sH9Gt<-eOy}I&)43A>X8W?f3d{f~unJC%v&krOU20z$@jVknLwlf&8+37m z7_Gk1+Mx=l0;+&2pbDr0zd-@+*-{EQ`@XxXtty}j{7VJo`4F%Qh8`1#_R+z{rU1k- zcH6>Z<^k3w@ECeb99mOyy6nMtQ{z(%j1Pxr958Iv)4I*iVbJ0rL(Zc^JZJy=d$>$*7>U0$FHif5Y_$Qa zVCXS%Xm?>Yb{8h<{!d-Br~-dUfl)Wv8Oi^5^56gerLUxORRvUmZz^EM%6sK)8rfYv x!&r9tfOVf$ii}GfS`#)sKLmLG_J<*l0}d6N&|~6|JuLbnz{;SFD)6HUya1zF4q^ZR diff --git a/source/zh/static/.DS_Store b/source/zh/static/.DS_Store deleted file mode 100644 index b063954ee80f0d6492f6b92892e5062f5b9a9451..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHM%Q6E&6g`basuX1<6vZrUs6wo4hKhx?4~Rq}9wQSGl-+FngJ19ozRSvrbGsW8 znIW=Jl-sH9Gt<-eOy}I&)43A>X8W?f3d{f~unJC%v&krOU20z$@jVknLwlf&8+37m z7_Gk1+Mx=l0;+&2pbDr0zd-@+*-{EQ`@XxXtty}j{7VJo`4F%Qh8`1#_R+z{rU1k- zcH6>Z<^k3w@ECeb99mOyy6nMtQ{z(%j1Pxr958Iv)4I*iVbJ0rL(Zc^JZJy=d$>$*7>U0$FHif5Y_$Qa zVCXS%Xm?>Yb{8h<{!d-Br~-dUfl)Wv8Oi^5^56gerLUxORRvUmZz^EM%6sK)8rfYv x!&r9tfOVf$ii}GfS`#)sKLmLG_J<*l0}d6N&|~6|JuLbnz{;SFD)6HUya1zF4q^ZR From d6743767811c002adc358f19e6da5e23eeb83923 Mon Sep 17 00:00:00 2001 From: "River@devbox" Date: Thu, 19 Mar 2026 12:13:25 +0800 Subject: [PATCH 05/21] Added: GraphicalInterface --- mkdocs.yml | 1 + source/en/GraphicalInterface.md | 67 ++++++++++++++++++ .../qpython_graphical_interace_demo.jpg | Bin 0 -> 54513 bytes 3 files changed, 68 insertions(+) create mode 100644 source/en/GraphicalInterface.md create mode 100644 source/en/static/qpython_graphical_interace_demo.jpg diff --git a/mkdocs.yml b/mkdocs.yml index fab49ab..49a93a2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -45,6 +45,7 @@ nav: - Guides: - QPYPI: qpypi-guide.md - Editor: editor-guide.md + - Graphical Interface: GraphicalInterface.md - OpenAPI: external-api.md - QSL4A: - Overview: qsl4a/index.md diff --git a/source/en/GraphicalInterface.md b/source/en/GraphicalInterface.md new file mode 100644 index 0000000..1a27009 --- /dev/null +++ b/source/en/GraphicalInterface.md @@ -0,0 +1,67 @@ +# Graphical Interface (Turtle & Tkinter) + +This guide explains how to enable graphical interface support (Turtle and Tkinter) in QPython on Android devices. + +![QPython Graphical Interface](static/qpython_graphical_interace_demo.jpg) + +## Overview + +QPython can run Turtle and Tkinter applications, but requires additional software to provide graphical display support on Android. + +## Prerequisites + +Before starting, you need to download the following resources: + +1. **Xserver.apk** - A companion app that provides graphical support for Turtle/Tkinter + - Download from: [QPythonProject/Extra on Google Drive](https://www.qpython.org/en/#download-resources) +2. **Turtle & Tkinter QPython graphical interface extension** - Install via QPython's QPYPI + +## Installation Steps + +### Step 1: Install Xserver + +Download and install Xserver.apk from the QPython Extra resources directory on Google Drive. + +### Step 2: Install QPython Extension + +Open QPython and navigate to QPYPI. Find and install the **Turtle & Tkinter QPython graphical interface** extension. + +### Step 3: Configure Xserver Battery Settings + +To prevent Xserver from being killed when running in the background: + +1. Go to your device's **Settings** > **Apps** > **Xserver** +2. Find **Battery** settings +3. Set battery management to **"Unrestricted"** or **"No restrictions"** + +This ensures Xserver continues running when switched to background. + +### Step 4: Configure QPython Battery Settings (Recommended) + +Similarly, set QPython's battery management to **"Unrestricted"** to prevent process termination: + +1. Go to **Settings** > **Apps** > **QPython** +2. Find **Battery** settings +3. Set battery management to **"Unrestricted"** + +### Step 5: Launch Xserver + +Start the Xserver app and switch it to run as a background task before running your Turtle/Tkinter application. + +## Running Turtle/Tkinter Applications + +After completing the setup: + +1. Ensure Xserver is running in the background +2. Run your Turtle or Tkinter application in QPython +3. Switch to Xserver to view the graphical output + +## Demo Program + +You can download and try the **Turtle Draw Doraemon** demo program from QPYPI's first extension section of QPython App to verify your setup. + +## Troubleshooting + +- **Black screen**: Ensure Xserver is running before starting your application +- **Application crashes**: Check that both QPython and Xserver have unrestricted battery settings +- **No display**: Verify the Turtle/Tkinter extension is properly installed via QPYPI diff --git a/source/en/static/qpython_graphical_interace_demo.jpg b/source/en/static/qpython_graphical_interace_demo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c2cc0c9b4662f0e777de3ba0fa66cbdf1898e334 GIT binary patch literal 54513 zcmeFa2Ut_fnSS1GXp0wMxR6`~X&AWb>~QIK9DpnxEt0wN$pI!cN3 zsz`6rdrv4KkoHg3UVE>#*IN7BfB)yd&$;&;r%V&cmpQ+0eC>VTF~*cUOr8R<=xAzd zf+#3JpliS%5P2Hprr`^_4+81wfy6-|5FLn$f(AqhyrTde0sdn9^*!(fiqpUSJ{$(3 z`lSpAqz=4-K$Nd3ek=L<$lpJu?0-%9_xBWE4-1l~=}$R$db(YZk#X^mzGv%dZ6|Hx z>MY}X&rL>F`m78{72@l5&&J8lQ^4BJ9_FGZxLJ)76oA>P37RPCoz-)@V&?$6=Ks*n z$p6MI8-FL8^R|Kzbpci13%<^7&UT*n1bm$zxOiOfRTKQ>;unDThrgB)6!@ixr<0nX zsoqV2E3Oai1eB!DN}m-3zWvbl{sqIUn!kP*_?MdCua)%i@sakCmv((rd%jXG9zuV=!c{vDn};wrPnfHVz~L3|S-X09stF3p0C$l2^%jS3 z|4~|6kd;{#wB5cvy;m5$;oWj`f_Am|7y z1tlv5xd{XY5EwPZFUKK{Iy@+jP*PFT(9+Q}Faib2SwKf9C@GInQBqS=0qBe(5cnKK z#Y)X~O7;>ByTLtLL3a+hC-HCSgf17?a~k%bh2^au2GcWear5vV7ZDXZeMVeCQAzpS z`3qOBYG`U*)7CM%b=%m))Xdz**6zN&1I*FG)63h(*UvxXX=qq@L}XM#;`0|t$tf>W z-)3d!yvu!`mtRs^R$ftA_33j%V^ecWYg>CqZ(skw;Lz~M==99&-2B4g(((#sYkOxG zyNBC9IBXXH9RJoV;P1a|7c0=NBUDtBRJ4cfqB!Dn*l<=V>Qk~bY?lmZ@42%J$~~du zxE%kcxSn1}-Vn`c{ji6DOITrA1asK5Ut0FR*RbIKqn7=nVZXI&40HuR*@wpw;D_?a z5umFmfrE;M>hPeUrTOKc{q>;x<)A-282@pQfs0Taev^`t8u($PqoMo9XaC!8$m0P1 zRws{wm?!~6&q~P(f`IldNQ@GAlb{}ODI!9h$1H#o6O=n$V0opH?T2M!Zu@cioHz@I zp0utg{m(^OJjkXjGKl&nv8FoUCI(ER8Y6?Yq=Hh&p!zRa{319VW+H|DX;N1M8Pr`)3~2mmg`ZaVITn7-h@b1i&(-ng9`SRh z{D~KSqNbnN@&9WGS_d+f53Woma*#p);3q?ig&gqv_uz}8q`G^gd|T8Gr7jr+LhOUz zz|p(lu})GH{1?^$P^@?dfFivoO+a^y2)|IINkTL<2$Ckevq30|9(`4 za0D_J1pKbCAMPr1aE9~?X>cWJWRdu@@P6H>HrLz@h` zsDMJ#0k=Bf28fdZu?Dr2i~EH)q4v2DIDf?YJU~n!hr|S6Qf=k?%NpR36zDz{g-n6x z`-u@jy#PTGB55r_32Iu{Er7aZ9#Xf8L+S>ZUV;3s`2d|099qozUGwwiGJiJzU-mw1 zmc)>Zf24n+xEZ6{19n15u-1dkP>qqc?IZoVG zV3z;U`o4&@b}~q`2fa#aPM08qw0mnmqb@+2$N#CbY``nEOONi#0Q)EzrF6FWe zQTmbwhe=gX0*8SH=zqe=>Dadkn96Dod~jx^(7X%}tYt&Bt)wXbs7$I_o*Z;3Q2=sZ ze_sg^4#i3?Ll17eSRa^yl!_73v>K6kE>9#;Ob5F{SR#YC&UKQs_7OyQ0U4Al3@7*g zmv)9(SxGG_Z4Jw!?Ji0`#>V~NSS|y7GmH#skezWops|8asb&)Nf`$P7?g-o-5dto2 zU9V8*4RAwGWX?DXt~L(ckSdq(`V@73>}KEk;5|N;oY^?ok;E@Qobcbspojp}RBC$N zaU7skQB@pX$-QYp@ZMH{Q+Z(3#uQ;r(_$$e`0; zoTjQCu-$fc6KE5G_GJCRGEPJ&>i3OKJ#2Iq=@hV|1_L$kQ9v(}=nDahL1tv9A&ZU7 z?>j${ZbHy8zGkl>=IzYjj)S+lS$bo3a381$XbAKsy1z3lufE&Dp zO?MO6&EXgYc#};7ygLkRknBNt4WH)0C=)4VAZUJlk|^R2HAn6S{W=8TJ9?_;f%-Z9 zQtGf5fy-a*Ys|!n9`+*8*}wLfdY1I!{>48GKkz@-9lApNtIh%cRUfh<=m9O4Cs5O} zd+W&`hZN+WYL6Pg|D_RsH@t+Q03L02O)b1b~af%P4L`)&a;vr3OF+ zuK+Od1?g14weRZ3eSOwPwZh9)qVHRZPKat}Rm8^+%u>9Fe|%TV-TWp7PH&hE!_yY% zZnD>FO8^*3W<^}9rLEi0CMI!3Cm1eoFtldiqy5SoAde}*QKw%Y`FGAj^T6f5Q|H?S z#+m-mgFS$+_UIcJO(kb>)N#SHY5z>TYP7JnWKg05&I@2z-(HYG*~?br&~)%jD=)l_ z43Y)t-7b7*4ngkwFU9|GO%(cpUn_wlsFT#Z+)cW@lTQX2K3@uYhw3yT9evhHQ1u5s z86ksqHgil4UOb5+-NBfUv`WYzCb-dHJ@8_F?2&;+&;u{}kX23QLMaZ=Q?U8jK$yd8 zs`}k5+QFnK&~>mm@Z-e+N>MMnd(hi+k?zuOpZvXOcKvRy!NvcdTtwZ~-25~M84j7s zp4_sAbE4Kv2Ov91=J>ce06YgL!rLPW#$-^j%`HIKFmpmukhXORy4#{~Mo0?_u?@BQ z9C~o157xS&K?dDdK9|4~5*@`Oj9umcnTLMsV1KnC?CK*;_7#p81l{lKl8z!BU9@2`fC(tjDB zZrZe~QDl%50;i-xGQ4CDOtCdG=)kA9fT*4P=3r=RGYYk(nv649@vnkR6WUL|jUx1~ z{@X_V&&o}>M)Pb&)*A!#|29FG~ z?Vh&B)q_8A6CXwc{LkF^+&@N&6W%}bOD!|avDPKh#Cc9C1aS`}i0niuARW2L(RjU`YV)&J5T zTnLxO0n&lc2s{juw|W;9zAX%>irYr-fx!X#^BL?u_?SUHs&y#f${Sy()+Y>eq^m&R znq(HtMcT#I^i7oFTxs>Q{H#yH421i{*YK(GN$LP*xN>+M2*&P9nrCOslWgd|#s)8y zr%AedAt_Ah%1Go_{}D}%l>0Ag!=RUMi_Dc-A?>yuwzluAQYQ6chgUjSx5p1e3igGt zDE~P6_tLIXAEq9qRhnkSaM^p5Up%QTM#)KV4geOG;EbR#2$$$RndsIqDV_=`Rplci zuaCAI_X}7J|1#_5Q0sO~7NZ{(;66Kf^4lK2>d>yGSGF(;oAk!RH~Sd}AyH?w1&A6vmGk@P`A+C<-yFmm9N)&RL4td=vwY_HNF>8i^SasI7tRw zvVaii0Np>TU=$u104ccPT-G`EC=&F;_>4gtf0dygJYyO?89E#=YL`pI>7xrqy))k} z^+gx5QGZOa*{cXy+j4uT?$A+58{bWvQNE$Sv}=+)-o;4Pc-W&voIe?MI0-VsL(&?DhMsF+WGD;FeApll6MFy2q6=ApB$88ar)2;f5ZvpHx zCpavxUx)>Gw$~`svk<4$o!=2))mH3$x%!0|ao%gM1!%9<&Zb5{4qk~g3Mp+}*UN=B z=MTM2G!ElT-PHUH2(tZ!msS@pPRic&-DdZ+-LZk*4yVlt(vNI3ZtQu21SheWd*YIW!_7L&Ol$v1AD?*t>0}xHy{sX4(7Q@kIP0$6S-)aEA_yF z`QhJ!N{6uO_nXh3;gTqn5zkC-X*?KHx?jS=?{rQn5 z@m)n?gW&PQ6d8XSYz$ zyP~G#6Hme!?jLS#jYs+NMwhM}V+NZCwL(88H*b!KnO0Syn<8K1pnBFrjT` zsiPn6BGf&3WD<&70V4rOFnf`Pc-uyrw(K0l8ESV9j7w+-Zj{1#qN)TYvD8LLZy^*sdv$=dnW1&ZsA_(dHqW zm`BC1{K%RYIK?apwlK-H@ zFCiOx+Vrikd6cHomNprrd;41Jk=y!plTQ@p?#FCg39-Cm`DMhO<=(jJEhz&TZ>JeV zcm763=F9E|!o8F^uYPo2Eu@@=IJC8dSAf4BPhT%2miZ*Plr+nQ$9@kpHs+;beRkID=h3L$WugxK>fsZ$c12B@ISVk2CM?sA0G6C1-@BimcUZP!4~f+P5dI3ZGA zR^SRBq5&0!Lx#G)q`6=!X}%qk)N47k@)=S?s+=woYFIQqJ$n|}VeqIK3(s1qo6$M_ zw$And^fv1B|M7_oM@?mC9uf>PXw75*e#kJw>i7ZUB^)vgU^Og;-u^Sgp!)}I{40iW z>i;^1JOr)>5K>j4?`8)RHv`n{L`9b*4$S>~lN($P3F==n1l;gKphbd%%q****stu%p+Xc5rtvYn=ejXxik77a~$! zrJC!4QYcJucW%8qnMvF%f-=GFET~=h@%f@jK~zRJPO5vIQCub*WtUE4P_k~)bbQ!& zu`^1q;_OG#TEd4!e!ngEIH$?!n6M&`m)|?zZz>n8bAHHdhYl?HzS2lH>Ug*^e2{t1 zp}O)dR2h|&J2Sa{3V#VVgnff+{SaV{DNbd7-)7j*VDjU$S1|a;*lELCa)?yxPm}Vp z?G*0QuPJ(yC!$ECj;$Z+(9qnFhSsE4Y})=o$UK66%6(2uRaDA{tH|@!6`VV+Ytd+G zh$Hi;R>LIKX56@hL8@-;-MtHz3H!;cam)v6*;JS(X9N~Gx0`dyTi(1kUpAJl88Ru= za&~2Mv@(15eEr(2MyE>g^zBPx+sOWE#7H9+=z-vegi~lTXgywJ#~%`>XnD~gL?>ZF zrOk63ax-!~v@=|MPfnLgT(yF=<;E9VG57r&7rO6-)z?%qn`v=J?46NV^@G8MtM|6m zlS4}UhMFZC%EcrVnS-*H9adeH@IlGyfJ-vC3-E5a*Byh`3k1l%CMcj=V(n)T5vk?b zKHlb3$)dfzjXIiV3!598658@N-bwh*#*~HLA-s=koI>D)HVG%WUk8mt%1$EIYViiX z9@o%8wKFj4%5X0|wnveYF0K*wT*<6I-fFXVxKa8hDx z^?|c|U@C$$&c?DVJBa{PDV?$K03;2-k_laC;O^ z^jJF3ZCFa2*tC?-45C?rsIufwsVm|yt1dEriPiN$EMK&J#=zhQ-1P@ZrMY+DwjCL#^~E%`WZS3rZR$(}wD+!P%ISk1dO$^jfW9lA zTz`qK#Kz)U@rF+3aS;I~7|88%uq&Qn9D-HKbUtDHQLA>F>Rw3oWqQL!A0y~n8&$QL zS9+=t<_`Dg4wWJ>b<^$fBKuC_U~*wR+Slsb^e7uLz2#Mwc+!m()!uoSBKQ%$y$)MF zOkDp2)UJi#K}_GZII2rxxQ$ov&6-LMRs0+t>YhFwzLSl zta+(T*o}?^Ckespfj!TW42rI!7_8$8xL*6+EAtpGsv}U@=;P-e>mv|_bu+{Kf81E#j%w!VUO<}o2G3+@G;&B-|FVEoHq%_;clvR=g|eHqEu zRF%~?m6{)3RPmlUU%Yh><*hE$?N2Id^H}3@4GEK{G|*w*)GxI>zSh0)YF4y)J|%Bj zw(Q6Kx`odQ2k(TQ2tI#CV@z#kYRlp)5H}S^w;^!=fW2U~py53*o9ao(S~G_4X3}bt zI!~ptxkhtma>AyTG5vh9wb@+RebeCt?bs|AKjeaO^<*fNGvGE_Y{n`SlUom}%bk8y zj*1iT-p8DlP^CTjE;IvDK-tf*k6^{AXTYC-dv3D%#?L4S7NDLmwh_quc1rzBhW5*b z51AXe8(;mDQ6GGuiUXOGmYnUr>@DWpO)|7FtMlllPWN0Vu4hp5j@(nzRTr1wLYB^O z1|4as<_*MJO1nZTK%t&B{L`i+p0mE2k?th5OUPq7o98DSADk~9lh8B;qV>4QwS;Wd zk42%OD4ej?7q!c?S25u&cu;Sz4L#I(Y z)bIKbKcY#W0EquuN(oh&M_~Mp29Q9(zm2+e@2bFT_fO_a&`P5 z>LYY)Y{q%7esg$nM!D&vB)G!dA(_?UF2C4k{;T#+T6pWlJ}#gl=|0@+eS^>j0!2+6 zqK^;-DYUkbOWj!{1Ky$L1tat3(;5e>G96-56G{sSu_vUV+*vLU_hH~~S60sF#L=bt zX*2Tt7^aR>f6S(T7bKiPqs=K2lmo99LET5-ECK4_7@&ipMx{+}hSync)ADMUn_a>S z+ZS({c&@2{B@awC2}VsE5APJ!*;rUz-pN&Z)0-;7@dXojG z-{hYe5@2cTS?Hwk>5-IX{VSD`AMY5icGa*3^YUfAF+Ra+Iym_LvBrZeN@4MsWB!=F zRp{v6x)h$4^bJyO`NMyxiO8EZ_$J~C7c%$ypwBAHc{)4&x|fSiVc;;0AYIZh8T7Go zEgoRzL}GFc6tDNyCf|3}=C(p7I|&#aZhDpK`};wJ283ch?8;!8F9V&`SOJdhe=WWRe|E5|D=wK;vz>jD z&3U2_wZHdNJ)w^bvXHQG_XHO=DROgQb*D-yRx#DJNDq8w6AoKQI>*tH1$O9Pys}c5UUwMB&5?Q1Feza8-W8;y}O2PMp zp+!NP_act~s*CwNvcEeVbxidrI=6vK{Rqw@$9ySODUBy;^xHSt69%t$C_io7&{j<) zmd2?%HqLG>Psv4j#Nb#Ey@SRP^?^ zI=h%*GJ|W*V3-?xNHmyl=`;n|D<9Qr$8&n=1yj#cFn!1sqJ@Mzk`z!b%)_%ynZ>!y zm!`vDn>Atu$%%~zuhti|6$~ER27807XPCD14~8)Ui(QoX+b-o>$n{LTG7Nx_khOIr zK`hzUJm!66szzrFG0mt<-L58GtFy-0y(j*qKGo1X3G7}Z6kf{How_61o5!C-vRgbk z6lh|!qkew+`1y?^H_Hn&lF`1}{91Ml)LjVpTM-@#AZc)t9#LFcOq zxL=vT3Ypbdj3I;UYn`SBKh7_px!wwvO5)S-$~TQv8AE#RC4PGXntfnX=Y$P8kDS{k z(Y0b2Tp5CfeS+vHQki*t{TDjY}}b0~p}o5a|T zH-gt|sf(@Do$T9<24G2ml(DR-ulnFz>w~+zqh8Dsu0Z#Jz zX%yPP2h-gm#ZICpPVx0BOorc|G{>-IF;A@e$1pvZ6;QfZUS|J%?uc-z+=ct7Fj1A1 zRM65U!D7Z@4F0kE{dMOpy3jk>5^*W9ZXqWhQ*w4UtD+OWf4Z!BtS=^HEDFJ2uBudN z!pnszOIat3-+D^6lzS6wfRSLpH&GM%JbTDs_Y zuj6w86U$K$ye{Q#qhJ#11Z3(dq%x@h0E^TB{`T;_GLsy)t1zJIG2NQhK$3iE#;BdY zoN`6wDZ`thd$M;}IG88gK*-^Pzi_4f&Alcuy@@*`Mo7tQhT}PRp+u3Sy6L~rw^qNv zYkJ8bHo_s?!UX^3lW_kB+8e^|LZ0~Z(*a}j?!Oy+{vQW&^By`y0N++An2-kfEpGb& z!#si@>1F6ht8)Ram^orsB;LDC zwEv<*PdHKbu?EWyoy0yXIynqJt%!*Wp9(GzT>V^>`R?=i(GSVVvP~=c=a^!?yy7n> z;$cZ#sg^P3E}1-0jGm@%HKxP`MnPDR31a8T*LPVVokFEGlbz#;MWKGQu4G7(1Xv`$ zjJw-pUnl{y*~AD9ukaq)8@0-NU#}2*0`bnQcJ<!3aqDoJM&M2_u9fDIMly>xqXFz`y}&2sb2DyY`&!kf z``3(a_ATh9xeJ}Pe&5Zbb+@qGG(DFGX#gqpwBn1^YS8Dxg*3c5aZPHrhSXc?qJ3u0 z@yML8J;a+U8WfOiqCM5=I?AX$SXkD5^2gGJg;bwN*LA_n8=nor&X8ihx&uy<0&82! zB+9q_w{sXizBs><3L|{`Q3`Xdv&g9Ou#f890;6u%AA)~$^QehFnUFiHZRiGh2#8+cnP`1P43q`#fM6u`XPoYn zE<+r_U6?L6b=iEKKv7_o<|kO8tN2W!#=!5ZWa$g~PX_GpF0I{Wye=%&YcP45t2+36 z6t40LsO?Dv&Acx!P3@S~1#ISYXQV2hldIQzE$#yaZh1ZjUcAJ$_%?&U1|>C#8MzIr zhH2UO=0{p0+@96y$$`#Gh!uW?1JlKtOO53Z#X>^Q+(wA#MCnb)%*;nytz!ob`?`%_ zL3do3dO0>Ht3J3nD*~f1*EIA47&M-Nm5p> zU)QUhq5|mR)ws)Q_xF(B7)S}Isn6&sK2d=1Oq2SwS<+~-&={I0mKWp~j8x~U9dNZHbRl|YFL%zqa?n|XZ6Ut=|aiaYfw zOZ!%AtN1s%*}-Zk72z@(+*HU-V$%=zABd~Y-AB@ndg7mc3jZ22{k_KSv7IWXm>{r$ z-cq?+U&XN!d7;1Tmv9|J{^w<_ce6wvL)v_>mBb{J^CnJ$mcUUu3yB~x;22FYmOB~e zhXg*6y35ZvTR$iYsy|vgKDU-MxRzA$F@gUw5H)BQlgESKp57!Jhu}5LM`5YOT7O+>uYZ1!xAc<@Lar?sRe;2a(1tu9QIp0Y z6?Xi4i&*EFI5l3j73K31J7L7)_8haJ;`vEw2 zGKdC1d-bZS@~Qv#FdO<>dQ3ed=m6E&Fi+Q@4PM7*%@;Q+N)N8)4Ne0=ITC+U>Iv97P}rV%ctq{qdITH z7(Y<4v6ZrDlev*Zd;JD8$1=p;Y};RC&zluTnGdfoL7_D}8I5at1DHSHyiZ@zc9+VH z+BF@B*Ypt5dpIS&<*_yU3E43jheE@UYv*UuZS;$A)U%V^zCjZU>qW@&uii70MtwpR z36?E`6EdY&gcH|i&ZJJ;+~$4Op>0fRSEW@;ZazQOO!lagY0t)BJ`f6B7i-Lp8<{ao{`>JisHfFnpxj^&b$DiHzM%%1K6 z!3G$b2l`D2#;<;oK0|C04wdGX6^+6kBu=f<2IpTw- z3Xc%t(!iei*q8hxW8>6*O!xf#8m!umB=>jb`6groyGJS76-abs0{#ogDj$Tch~vm2+BhP^TXu|?oST0;wK z?6cFkn@FlPRhauZFv(bvCe}%63%Ym5D1{^Mo%~#8d*msLrU&%<$wzPhWkBFz;2xmG zi;7{Oq~<`t&8lsOtaADjfc=aHZz`agP;&O@Qo`NeX^^ji=AgSBGDHekIk8rM$|^h< zz^SHxau5DL1^oOz&#=I_P<`hgX`s148t9=03WQaxwE`9G{$0*zUCkA&4G;-r|NGG6 z$-~g&?mvVc|C)OE-w&`ZLPl4o`v`Z4De3|N3Ssh+p9i17osZLqn`=f!F})W{qRfFa zCM7>y;c{DH&odK?74{OMQURH=4u`b~RqBvImn(Tsn4lZ+76HO!P$)P4CT@HB4y&qh zY7kPfIf+zg|K56oP3;yv5a?{9R*zhQXOUcatTBF`ldYVJKor{&ui$S%&GV{&U}W&{Q*t&wt$DRqf5T!a-J$?wHf~C09#&|8zLQ` zF{R5>{%Er#Em}p}o~!-gyiY!_caADUlA)A zil{8-TYdZIM&Qdbn}V2y>X}XErDeaVuiSX=^_*KufP@tHok}2KY>w)L`xoBx$U}p? z#NbJhhXj292sUH2%0Um!NJnC{8gBYlYDUfUb;)ox=d1;3jOR36{qRwrSL{LjNAY11 zeWnj>w-JHyui)kcDq_`sBZl$M>zj38Mql9&d%6n>f?-e*2?=jVvbM!5SH3;N)@%*?^EK%UxkWx8UlKI)=A`hg@8(tD1l6k;a z1|gm?$D-I)j<+OFuclvV#6m_*inTuTK^5}knO^LvzkCH(c&K>?t#ewFh@{?Dv zei@1|!5?57)`aSCTH2U6X7ie^3yV=(eBdZ~04){;o$z(iP2K3z4hbsEFWy=&ED{Da zI=-a(s&Yo|8h3;d>>=-W7Ur*H5Kii;ze-S>c|dY^#R`NIM)UQd<}%|)_qcFYZq)<( zLb${g9v?Ib*Y*nC|i2Pm;@a&tQH-ay8%` zw~nz(2?ZNLhcl?ZzfeE%Lfct9!Kt?gvG9^mJbTj42ohsc18as@dQ3pgr@0=(xtUA8{%D|-=XR{`Nm+VQ_OlzeShUQEvqMlb9AF08TWHaj@5h(x zn9yUyXp@8%BWL%#73usTGghS@9@)#^ndNT|B=s~H`b%V^EPLj8TD%EQi;lw8eP6`or7YrV{G+it zLEofDA4Ml?ZMiN4%}$eO&M_r2hCWX&b6<2q){U9_i6*xlB-X>3Rq4^Qn)=MXl$yYR z7osqpXqZs(eSA{=)=kKTEg<<9MeU;KYz~b?(M!*uC*y>!G6sH3v)GB4cP{6-^+Z)t z?hTNwekq~n!hA>|jenoTMk?W8aqP~vYX~rV`DVg{?_ty?GDYJ967P&ahjmsZ%qPsQ zqxJcBr@vo&+A2!>lGo(RH;Mj&g~`V*ndqc|8>7 zDVE^0EdJm~)ah&rD=-GDciorA0_IlxBhk_VIbu-3_v{0-T}){H*?Z$Vv8yk{sD`Rc zUJ>+rylSD1(aK@7?9kyKV^&w#&s{nbeDN#)3>|gf!(cZ`)`Sa#UasvDu6NaS(A=TU z3aE%Zbp_lb*jq>W)e9#R)jtYasxKT+C0Q1Ks~E(aNsXs`;bR7;2|n{v+=L&6S8R|( zaKW=p-_4sRg*vz54|2xmozP`OAfT|Ssp6ZR-DhW z(Cq4MBALqv?o${p@>*tpwDz!!-P!WhZ&eCXgyYbkAHyn#zu9!9tuMHYv5xfM_OCvP zWGIA61vj0uu~{ETv-IIp+pRK+lh`*b21ZH^uv}gS($ItFfv2x9I9f4(EW^2w#9f&g zc@=a=WD<4sR4v~X6AMm+_3xLs35`9C3AJNl9NN7@r`YJb-9*`V{6uQ#Q>&mXH;wY= zIutM$+Zn}fJK6=0o||mGoV&5vMH9i0SS0Q=V%-Dabcr}VjL;M^1a&uEtce9@1!Y_s zO0|m%nf4r9Q@v7Xxx%E#7^vfIfIauR0+dhtxB?45AG8H`Qd$@Kf;c_ebVTdTCKM9D zSmGK!ssN=`JR|a=ETVJx$2BNXuxI$ye4K96Toz(u?Ez>Qe8tvA@NK&3t*BQg`zYIk zI(w#To-6xmb?%+Eu;#USiV$sjwlfU=FT_uHwaAawS)6Zb zI9DZHG>dxHok2QzrC^C)1|5|CXu8vtZqiZ0NA;s=O3f$9*|<9vI`M+kz9|hIrWJH| zc?b=46L)4)_ZP4Bc%9Up<;E7SM%J}6m9}>VP1_#fGfjp}K-IO?%$_WlA>OPmp&vfY zJ9EXp=n!3ge*EXef+)~`e9*a$yM? zdZP;dLnlzLW{pOBTEw3?Q)JRI!GK0JLFrb4C^6+vFJvG-Sm@e6g1J>^Wzat_$YR!{ z~Z0f!m@$HwF5|EDWscZ`uYX`D8}dN zN>4}K`!5B|O>7c)abPvE4zNy})mpQdS~oJXKFA1R$^fH;16O0u#MWU}gf&d~ze2lA z*3cco6>2{CgyXrtLL-0jX zU*lA3Yh)b*E>7<=ZIiamHM2BE*AJKYhf{WE7KB+cG8NqS6p6Akactr2Zq1U5QhUTq z)MCfLyG;9&d}I(KBr`y@`SvtPFz>~E{>`#I)lbL`h{CQe!36R)lRrQJW1KeoiKGy~ zUK7>b#C=ovc*aZrE`_glr@lmYpXQzV6#FoKyyZjvu@Re>hKl;9(BY#&@~g@uI^3IC z26K2?KQf_T)2J# z(xfMPQbS63yUYDHN8j2r~?Xfec7=IxO;Ac8T1&D%vc4zPr-dn3)d9s=$0}^ zm3rD2TH`2EN9K7tb@pfP26$oOo>YLr0cu0*x@;(i-ocYr!>{ZSrI5^!KA9sPLzGmH z_3>K31G6L=pjc%G_<>>hj!w*L+TJ=w+&E^oAw#IIM%l?e+xLa^gBtB@t2-Ab`1_-q zso2APtSjc-3bs$Mx6WIAfdj5hraSb2GfsZytHnLKcPDAKIOD&m-yAx_5M!TY&En&7 zl#6mn%|xTuYm8o@)@4+p6ZV9eX{n^;3;L2V6)VC3s^*wk3TX)BqThv3={}nBx!$$y zpxg^X3xMV2ErK7k%_gAZWAOc3xG`)LMyIXr7}_f-gq8LINwA+H#jBocP~x1xfnoy- z^YviySmfFyI95{7byOD!P=0za#Tmc$?y>n&#MH(SH8rz=e(-0P>?(KSiV|gqs>BQ= z7Q(qELy$rjG#gF$`uiO}H5*+m;hSwbpd+^&FBpLoIYw{s>6FE7J{_snE{Ro0IQUr2 z8FX5`u@yDimYF&iN@?y9qt~n*asWNJU&v%gLmiTLm zi#(uXAj;7?H)0_f(vOPRr+zmLSthf8lf)IapdsvR`qx4q>#)pK`S(7q!h>7#Ow#dg*)uAyeL z(cBVS7!=?eO)%8PH@((jqXi6RIbDi?{- zMZ4gqsN-zz=vmADV_7sguHaM!#<6GXJ|bO?0gX290@}PYCJtgg2^y^Os_^6~swu$R zfTRq#jLVz-%08{%pr^VLbQ&WvIBlU^-9N&{XKtom$JCodn(5 z8`uuf>VBD`JDnb0Djotx2TMWCO9gEHkQ2$+Y;t#|KY4^)`Qsg_h5wi?>=Pf!NLY(Dz zw)oGwxi4s1LXJ1}DN2bo`Kul;N*1>qo@f$|y0k4Bf9)J3LKX2|arMEQu%o`s8?k`z z>S)v6*#u9Eml@tWB?(uOX3LmH0KrLhI3eiD7J(G_*z0wHk<|`d;LIpY{Y(#~g|Dlr z>?oU(#dw2s0_$U?rurJktu}EI;kYN$d}Hg!^)7E&tQ-0*l*i$k))LaFpYdje zox>EH!3QOwH7_nvr%ai6d|36zzBv7SZ`XE?`q@m zre*t+-6I>5;H64O=OX##`8H0(bn1RL(nf?TM|1{N=uezgO>V5?LAMmod~zYsB#yv> zzxKk`8S5D)h4P!QtAbNH@%zz2Z`bWk36DRfEfV)7Ca(D$N1rA337QMuiQm9S9SOV& zzhp_h@y0&4UId%te&TG4T(8dbZLLo>PwwdY&PeQ;)=1cR6u5M6W*yL-@lBfnyWX<%iU^+C+T5gvq)Q36 zZi_Lze!;Y%4-!Ax`dGr`3qOCqxmk_#jOoPW&3+a^^G~#EN%`8>s%E<$Y^DSUd6co` zmk%XQ<(jar=j2CM2aXt*sH{^qjw**sj&^H4xvS+NG%J$cZG7L_EbgqF_LNPIiy_66 zD094L!ti0%#b3JMIscS*_@B&trULHt@n7n-|0+@YKhBDONs#>8bM*e;+xHFsv!;6Z z9K8&=KRie8*VOp`elQn^@}yiKgGR%#P#hF*LXfS(Ib+1f!{{^hQI9Ena>ql(OTRR+ z@Eo~vhT8VBLA{{{`~<4ZXKf5)fi=Map%d^(@9q;ltnRrh-ucT;fedFIHl#8W;y!Sc zJ&JjDjaK-9Hz^NzZ{feZUdCaF8B&u{&(a9P2^6g|r- zDzB4v0hjYjRAnt{U3O{!XN0*hFN6z!Z9yrGr6_-NI#_Hh>m~;+Q@x9g!Pa>ubuAsD z#nJfhL;HrlP9~VlP_LA7#L@QmBNOi(W|JmXws;}FXTPX=2r)frqm3V|y+*JmzM+^k z!!VPMDMCoP$gWa2`@M&pXs|H(cO#cTqJRW*J**I>`WH;)T|K@nw+1gjVs42LJz|n zvD;`Iq>kd65}kJJ@ZQ?BcE(y|SxUDeRb~EOq_INY^t{g^@O;sQ zZc^m%k2IOWm$TccvROLPh5;om6WS^jSZ$Z?U5d>%NEl2$gJE%n9yu#EEz;Vs8J;Q# z3p$DxYs%!8+8B2A3WQqpE7D4`gb2oaDX>bsXv51ge!EDSxIc}zo-_-POxDAozNo=l zQ#E3VTg#&vu|>ELgDS4l?nJ7=P0`oA`GBtc*}+2ij5Z6^(ToQ&5yeO=Ph*<5+DbY8 z&MFes@4p$wr4~696m?%gXYfy%n`PK*NoQQ(cwzQ1Pe-nNA|6#x(o?Ox4e2={Yw)@7LXA~~tIb?|+3MWt= z$ObZPhm%3_ju0Fp@Iq88m(uLK*0(sYYnD|}ULHH23QN&C;r{x6wfE-nQ1^Y?I3Za| zM0TTWWj!g0FiG|hvd55Q%T9JvWXT$$Gsz^REKPQ@j9vEZTecbdI%6G->GyG7*STGF zKfmj_U-x~#uKRkQ@8b||Uv^=-bwo{o#1IE!r4$SYMb2=CwMZ`Q zu$0D0D7B`n@7a@W+gaSrqG_Tols{J_P3)H}8>O^fBT_g3I>yD-x6sLjXe}M8d|zY^*YqCn8+3=p-rL_C;#Jt+HO$0tq`X^jDr1>djMsC{c(cL+ zVoBDcb|4?S7ZYi36EtAPARS%UCY1Gpv#HUg;DSZSmB8Dyt}vf?l9qX){k6Jrp{#~s z3gKmf!hA* z&~gy`lxX?XPFsCL`w8N-KOHvVnwekIVB7r!>6mdQin+39N5*f9M-`Tk%DRxWa368t zP(GVT?UrY*{!>JS<1lZjV?i2~mb$p%{S8=n>d8JLhpjInN*=N_;uYqxZ))r$P&{x) z!cJV`)yg>`US}uqS5qua+8u?iyc_b&gR+je3pJ!v9#!Jj77HRpjmE27mxeD!7_*$? zx_yJxtYag_Z~IZ7KP>v6C`BxDl1y#ght_PSa?ISiu=rR*UO5>5}y- zZ1qnp?GgpT-Es|%7_YGP4N#viHCZ!cy zPxY-+lt#JFwsvF^!NM)#j~20Qtttyzg{Ef>7B^?=dchtQ2Q8^crbfqZj z4n7hw`=3$nYqZh*d%-xX`s$-tNKJ=V;~x9%+ajTnZuiYicURN-jc9ceH!|XGe{><0 z?hJ6~nC#?lDT^f)?jki)PZ8^4<KD#WO{5nbX-J=w~AjDT#U9|+zx=GEv2$x zIR;9fE)hj4%=B)Bz&+YdhSJ=#9+^GXa?gw~^J)z1`t&kVOEoCU5Dg24+CrldbZKFl zJp@J3G-vyqlcoJn&CeVIV9>lPHmuD)EE_G7d#CoV1ojMkBViuUKJNW?09f%4a4(`O zC0wZ{EGfYIG++Ll7V(yD$EY zq@|D`1IngtK$5q|1L=%B`F%mpoC?0M3APSxhc6w#4y1X0WxDw{wDX=ml^L7?6d^oB zYL!Xnp?#&7HS(1EkSFrzVYM7dGVB`r73Z92vSIdDWuP&l5AD-wd9XX});T0#zc<)`2sb~2vx)TA#I0dY=gF6%MQkHq z>FFiPKZxMiy@YMZ2zPX>r>FSD`9A56xnaCYXCJcYfUOQ=&_8o>WVmVLtEw-r&Uqhp z;*r;gI#f=XP@Qa+=@_of zaoc~wq0E5eY~6|LVURvw5Ix{KG(D?`heGYv)3 zo$`*5!kw<*Qk0lj4C@rXKv%Wx9p!SqrLKum0rtjpu+MOVnF;M;wA@qW&&9>KVBQ^d zCw|66;BKiON^egf3|HR7E|#vr9-X6`hZGQucUtqz@fC^f(#Cs~5>8zphN}RB2&?G~ zAn=3juwn?|RP6RuJjLY7h3)u*RCzjYo9lzs5PP+puU~CH$vV6%dDgAr=+7u(M;vLi^pQ+dN10 z{azvjFI%}~)~RvRo|WA5#Apu*ZL5Q-9$s8(;ncZ2&+{LmH+${8yiAOf7oOWC>lY?7 z#b3(^TBbSKjQDwOB9NKDhquC-)+>Ve3w4o(2$|-F`GcU=Gi;ST9F;LDqRW9B`NDq5 zMEI0*`Z(l#hXld%229o^#_7C*vYwNZ&UU=^xQ^Dygv0%=%qHBkC7m2C#+CD%8m$C-Ofha*BZ&(8+5v34H`?;*XLjz{m_ss~GIlopIO` zTiis&1$^E`toXwZ$hCof-Sb_CMXk(~9lGDQ$Zn)HhG&$f)!90&4u*Xi?tQ2G5bVH`QY_JG(F=o4(bGMqZ zo>l6T?U%Z$@%VMD!3w=4wa+!&yU@BJ%qbXQS0;T;DJhF$A`mAH#!!KSkW)d1w*A5GRk`xQRj~@u>tg35_E2hm=a= zC<-c0b>2-@OXx}EBpu}~mWh$iAwh2)*8|9;`PNzJgKF03n6ew0RtZ6RPv1sL< zay`3~qafj>+p~cIH>W*gk?k)B_wbxptEsi+9c+#wUJ6^%V7GVrk$uuY{@seF(muTI z?eVSCEy7V6SMu-irVQpjiaGZN1^9gUX9ON}EqXU9ooh7a2JC>!zZE@%Ws2}{oR@z_ zx&c{X-@j%`VC%;8c<{z-G$co9ZM&#bh4ShZbzJJ9OUii2c(O-|QLV|hQzSg3Y$@7)t=wO#XQ|*t#4H8=P z77Scf5pilNz)%>Pa`W`AM4>+#qo2+a73Vt>#9Qvj!P)a(zv(w z%WS1VN+-RWli3jy~gl*Re*=-vuaP9kJo!M4Ze=?v@2$2U)x7B%U)sG zRGj8W1Rt$uKg>Sz@cg=k#fd%=k}=pLbXln+@|n)(IpYI^1h<_@AH&L(KlUFS8FEOTKp;D-$Xd(Bs#Z#sQxv|B$J<`_;gcT`0+*YXX@Hz?` zYss5n5`$e;Ny#nlq=$LuNVEv{(Pm~ds`APcrStW9(9bMAQQ&#;$VU@ zkG&KWn*d;zs_CtM7u=9vKegr8&b?(lhAE$|lA6kjpbH`jc-u5apLrs@rQz}7QO&+w z_rC1w{X18+ou6^_PswxlZ5{}qwL+bpqRT5k_F3M#N5hz^Q~EsKb>kd4QjW&O@4o#= zN62in&*@kfQK#!Cx-&msP%EQ*77}&AWvI6KwihyQD(aFH<*U29sYf2atdcHeX?feP z!u;S9AUv4$!S86jWviktFqK4Rek0M_Q_`-$_bz5WakmfIOJwZ=+3!-=VgdB1C9u4} z4E&zZ<_Cp5|8_(*e`g*0_p$1+wGaZ@Zrmkknol4 z`@$X>%JS8}taFN*jieUWIJeS_fm1F%!G7qfOQq^Va(fpIZ?!l{`rM!94N3MEx&G7V zYa7!UGm0myT^r%&QAN$mYWv2-_PuH(PH6j4w^lx7c(F}+U!_^r1g9>0&i)&E-YPo= z_LuJ5RE!E~`?BZWACcjXcG&kLIal*R3{CmapBbl8n_OBS8$M!IQu^9qz}?9H5_8-W z$JIQin;H44(e*sSyf9;gZ0OU%YEI+?n z#MtEuhwkFSQk(UBq7VG^1<+f)=HwQU(lSJ*`MiwMa8E~}y4ELW28uhFHE6`d5vBGI zKHgCXes<=wYj7g3Rq}G681eH2%f%(+2;JmPkbm0&%_N7(%cWF->QIAHnSkOVlf=X~ zg_j*(X`x%aF>W7p`IXC3qH#(BYdS} z(UF&a%BJFpAQs0mSYD_+Eni|L?{G7kkdiKK+Ar{)&}$fmlU5cY3u_h_)FYfmPP)?MT;6+w9f(2=1l0l? zs%CVU?6x=mR-VYZQQ?A@bZTvj<;I=U>qb#CXnP{}4Px0v=u|4$anhC=QFsXm!9Esm z^Bz9$i*qK@0#=J?!|q!;4=6iP*e*Tzmo4-Jft8LZsY1BWhuW0$1}4Z+4ImMeGoO8) zbOQ;l)@Q|30xq%OGvMmGZzQ39FNtADP+l_G0>}3UFzF*jUl$a|uP!P@T<$BZ;GocF zTUz`cj?9hfQsIyAL&JD`77-|An|HMw(fR)mV(cP-;dkPQ&Lm`eDHs5~Em z6YHxyJ1YcXuos@D6TZ$>M{6Vf)vUHj{GpZ~)ejH9OtktR6~6Rdnxj$ zn{M4GBG-Om00oej4%f-h(M6b?W*;uO*QO+N7!=|)a+>ZGLAJ%t;zG=sEX@P!CSBCU zXZsb$ws7bSpDzp8qf#1Pv$bl{lZ z=ps=R(U58@Ja$}CO(5BTcgJ}+&0`_(@{sZ3&03e|GM;UsAZ}Bn$CfP#UYNq)<1X~$Ysy)k>>b7=1Fpn z!mmbcEF_!ccN0=vPDmq%@7_whrYeD~rA$2z5AbFUPw`fy;@?ixfKNISvoK#RP*Ylg zk?V4lYJDPGj+c(BcL%GuvZ-fy2yxTOU19rzl*VfcM}zp5X7Tf+!sy(gIo_9swp!ZA zmM&-YO?zUF*1`ph`IHi)-pL?np_>oQ7lfYW7`4I%}|8!08cixqLlRswgnB=K^4@)f$fQ$T(#BylJKi2I2qP67x zXRW2bbItu*9pR*O>bqgvR!O6Z-2@eE)6Fjv-tG-1r7vi+c0!LUubM8|`KDy#cO_b= zwkBS-wuWdAh7#29O7FeH8ZJ#R1ajIp*As>FOD;We>ppnXvpVYP>g;#)RvSaPa8xWu z{spO|$tyV|eu;*O1F{tq+uFc>BS@pGn_#OZurT2V2FPY%IG! zO7#wjAzgH#6M|1_$HF;8*_zRf9f-&NXKxGdiN=XnqWaQU+Pw_h<1&6yYi&7RsWf)^ z!o&7wJ!L)e7Z2VcbN#`lPF`@3AIzZqib|9zB#QvtHow#BseP`$;LBEqMPXkuV>$>t?#W7B8-%V&VfJdtY6IAT>6$sKLNyL4%(tZBo z%sMHxY&+`NYx$y7QU zaqN{9F%6qY@{e1UVpY7VO*m*R}e7FkHzB zj=5x3U^kpZ(g9X#S1t90KT^0NLAR8f=`uHgK>yWHE+?=b6zIQAS~pd~9GB9qRJI*x z)A-q5D*p59eASf||KYp-Wb1DdR<1&>CF!$TJ%aHQ%=&Eb!{}YGYanQDEMy6bw_s@I zO}Uh!FhW^87U)c{eJU{S6qRyX2bO^Th4qTS2eSKjOEwp2AW z{fRebB2ZOgOzH&-heCOef?ixXeBb#L^;%CE`%)sBeqhTYir^oa0BmfHhgU*XL7sZoBUEUTR78V!mgEd~?yMe&t~e`e zQ|^Hhi*eW-Y>1h_AVk#Zz?Kqg2O)cga&X8;I5PHNhUj_l2wr;@HN!DM17GF@RiK&f zgF^vlpkF^iG0&%Cq3e(z1`pOs%PftwY6jw_MFHgj!BJkWgfDr@*4zLlLxfhB@B z(susRyW6fqR!n$c!%(msWQtTXdJoo)GxQXN^m_{RqBV65;$eJ_D@uK*!!v{O$Alx= zb81FeW#eF<9@7==J9kYb6K_K&-;a3a#Wn+3ItND{$xhT{`Haie5r#Is2ZmkaA_AjN zEPNd??~&nc60%&b)OgxDgdAH$PxQ#AsvU!XD~L?IapKMPvMjQoMt=sk>XH!#KSC@3 zl7x=ps4zr&QyAd{oG7V|DnW(IAZSd0vFZovSF-W7w;1fyUoRPfNNk070H{c71cZWp zAW7W?YKTI8nAgEBNoF%Vw>>o21;IxC`oT|>EANrPaZ?W31UhhE7&CD-U63@RI)(^A z?Iwv4PGo^_ohnJBj)9J}pUNlUGFxoe@=f+OM{lq@8A_N={5&9Sd9R~*MCH}{ zvne5#DZ=l=ILOKaO}6Ncc_$4vaj_NRL{fCGK6edUf6IP1`jfHM`mVBWvW}AEf{9%1K93lOae4LM&jAXw84@44nXJzwKD6e3=&&aQg3jREYjxh@XcoXpCa~g~`wmBW zn#hS$!U(J7zMFD%hCJ3(qoke?G(Y$C=V!83yJOkDAt5n2R+nC6D{yuc*Rjlm-epiZ z%az?aY{1n(56gG$I$}|lK(oBKU!0EB6fsO#Md)vsAQG`&tmpz-jaQN#>M+JDyII{I z=Jr_6e}1b*t9#z?rlKb$5KF=3@AJcJHK2B=VJVf!nz&ZPNq-)ibHO)o6w^6=mJpAk zzDpfEO0gBkKPOmDSyP3;hO|RKmgsiuuwg@qs}e3)703J#nNpCLTD#?-d+Y9}!Q>c^H`n)-onU}?iDLPJ9EM)Iyz_q3;ed6!kjLZzzN!XY0&+~Ce z-@O!AZH}j@*-LCQUzCxHZs@V{h&NSUc3o7Dcd*gw_IP!6%<H~C-iLF+vW+c;&Lk$$L0sG1{fCXrg(wW zFxvHnYMJNeKfM+@c#^Tm7O&pX%@a@aAR5u{6^&|)*tNZj-)&2}>{Ci)g~x5WwPRgx z_aMnZp?CX%5_Pt`PQ-5S_RE9!fhPi6;Kx4%^7~u%HD*>?-3+B~Xqy_tZ&k=+MU^DPre#jM5fP64JD6AHx zdxAQ}TGYX#We`?RWD9bZ7*vZ;I%3T0dBw^3?Se?tB^Ai31?i0f?Tyc5j1iZ!47Wp(l@OATb!%gFBZrxTXn{>X7XhP)! zK174WgE(LuFXN?8x9~V1X^kgT3y!c_R~^Q_fTQg^r%D|gc`Op&W1N`ny2z~T$)g- ziz-HTa1y3!vK=L*3BEKBTG9NJS?`I7413Zw7eTX@>LY!L>>FJ zo zplUqyMmNC|Yt_JSqp{$BCfn%XMSgVLSG$c1zSCDod}@xO-Z)-k7mAb?F1|)l^=1D` z(5M$xwdU(-H}^*|?meiBfFKw-R4q1oR4mjv8~+BQ++Qh@wUh*NH1#NF{>i7L`_lYS zNSmzqXI>8M)fy{vF&ebp7f-=k57awQBC(&aOY-&81eKund^h_*=_UcsT*+`7Ckn+n1}ph!D>$ju@0uK1rG! z`D&-5;q(r_um>KnPZx9}!Bq`VOq+o24e;S#Zzq6`=N7BJUQebv-w z^~u`fX~J$a{%8Iz|%uDZseCq0mDs3FDe zqj=Q5II;JpcYU+W_>*jt>W~0>GdWR)o7PDblngVfpXcIWA4evFJSqEl2P#FplcY|4 z^yxA3pXYNSIdMxuqMC#vvD)IOLJg8mE{nDl)^bM3WS`AasTHTZI zm}z++d#6VYTC;&Vg7wI#KaQjjTixN27{;!|L|g zMKaKT>H3O^Aq_4S;IptNAQ4&Qb$P^S_82n6v-;!cm3A7 z<4V$l6Z$+YBB88$XYQ1-yFPRnx!TOsmN=7h{Pau(A+}}i$~?$g`pzC^kVyaW=SNYd ze>Vo;e^+w2Lyl##BwXmrONCR8dMYK>YN9IwA*|K&cJ$9q#e5yyUGSGJdajB0Nwd3< zXeaEk?=1h z%^4s0EuuS-5nqUX?d>*1$2i|G>;8@847<}tD|{|14LMIolJAlp-BgzmuO&Vj7)#tlhC?%gAf(SAMx_NPa0xw?jjRf-yUN?-_`$nRu42oKU z*`0~fus-Mr3v72EQU!lOSlZ&yChR>Y0<7KVO}1Vv@ex{##XCzKs~xmCnrCx+U^Tv3 zJHH^RMXq6$YD7lx+|4fD7=z1k#$!Pd>qxvQoLYs@AtObkbwuH3;MXRw3SE!Q>2M#? zFa21{{YS+~W3**ltpS$*gL#l#X$qR`A?n;NK-hYb7l z_i%F4f<63k=uvMTzvygnM#F8jBMnA#4w}vqL02zx3~qirpG(RjNqAbRj8+QYYi;U! zS(1XnAJ4->dJ87+hkV&X_4BlCpokQHM1fu0#KK-lY_7dL8}2B=Gt>T)-1GqRHgh+N zsH4;C%ihz9fFD>{w}tn9fpQ0X4qF*dUf?HS9DC$eQ{X8vsGw?LBZob+&*C(ky=h9j7He@- zh_LQ2f;%-OT8g8r9XZ|{;qfd|2L}tyRU2b&MEc6aun z9y3ubXgv2d=CMRU>SOv^h&s_2x4}w19K-NRnm_&Mrq>uKlZU1gPSe0G(D?R{lQf@fv72_e2YHI`-e7sI z1xG^iIy_v3OB6f21ERLwM=M+LY~KD%Mv8?Oql%R(sb?xpm{x?j7kliJy>%9q>ckI7 zp$caD?p$YJP(AEH#KEqot$M;2A2r@AXwXSH9E>%o3IEA}XuX@z?+mEyN zCb+kQiGCucEC`=axACgBv7`Mq1F1@hn*t}DAl;P7n<{xe?IrawRD1aSJKjFD6O6O* z%J)s}=pXAmga)|sqrF?8pKklQFPZlzB3ZG`&{N2;gKHx2%{S<(gf`d)khDX-Yh^H! zd9bne(V}Y^WrJ$)38)`R%Bt=h?SXjE`tO zvU1-Ixjg%cIut!lQ^qy2a9VX*H#bTmH^U-`j-0;sHC;?KjX&KNg5&E22pds$$QYl{ zG?MoTc5yJg)Wy!hL$;jA2boO#bQg_3^iA zS6nfuk*kQ^lOC~za{x@p-OJf!3OV6V@d!}yn$i&{KnOKF6EHuyPeS*O48bRbVFV=~ zC_w;K6nk(?Muo^I0C2%yun*u>(6ewSQivTlg3c<)wT#I>M1^D;(gi zF{9k}?Jt5nL6GNJU=NBr%QNlRI^*u8YK9!7X1XM)k6$CMo?n@M#jj?iZ*xn$T(Ab2 z4zKguR|7J~!Rer);~ISTX!*+ta(`=lH+qF(4xLvV7D~7<)rObUd23{TDecmGKZm5! zjTu`@Bgd7QVVO5VYQo)VqTx~TnXc(edKCC@U_rqA~&$r#-_UR)`;Pf zONrC)lkjOL?A*ZqHy{|;tCsD&I@4@?er(*Z1P{jD#gG#qo!37?bQ0Ov0 zU;|Zig&hY99o2eUQhpa~(9DD|!i8@nutGQxCb95ff*oF|JiO9-c;#NziL)Rf9{Ywc z%-G+ZIOV*1eh0NpRSduukrCS_I3$e)L8}_N_1l#_*)%|6sJD66F9axh#C_)t{pub4 zzu*=9D-gwGL9n_sU^sP`!Px&MA-#Jan4w_Ftj8^h?K3+;e~~}`MPT?B?KdAWEV6IQ z1z!I*5#ii_oQsElKMqdzJtEUP|EJ;^@|w&a>J9&{LEO>56(FY2dv8E8CwxW3@mXcz zjaqs}uX_NVGFKV_z26nOhg?y7L!c`E*za+mEM%H9|3NH|t!Kp4Ae^#{?oy4VW-IEP z&3KK(YjQ+(AdiNl6-ExCi&LsIUC?PhS88X(d-mW3lT_i!igxSA#uz?Sq$6LOx6FoK z@c`1i$)Sy&yX4jMlg!6A#JQCP+`>qXl1$}yI@ET!pHLCL@^-`lu4gYO<==WxrH;UU zlqHJ-e1{7Y+Qs1bX*84_m%6FBpc9r+;vBnI~n~bTN zUSipJFWK9}-$*7tqnZk2HC!{7Mvjj^OMX?Pt4UAax{*fz86_dTyAfhR)ocm7@HoZJ z@Nu7cG~AR;%`Q!>QJZ1mlgg7yafJMl~{J6GgU?8gnI`xEA@Q*V^GQ zx-qg3RaqqPDt9(S%g^-qhz%r(zee{~8$WI^IwMkfP*Pu8zSYo@4@9}{gvE8y-sN@a zI8)0_z4j6y2zl>xWe?}eX>cUq6D22JmK!u&df(cnu|uG>6tZP&jQXh7>ONNMgt*l1YCEUNN@28Vy^Fr8yf#;H6-gKTdX38X^*z=j zsza35RJbLgViKo=(U3<>vS~RZkBReclrvGc3`AEdMxBu~hb`oBzikfZ=1Szmzg}$~A{`DqcqgV)H zfplL2-csZ*^LwqJ6O#T%b1(m2u2k?}Waj-sdj8MOR>KBx3XAQE!$8V)TWArrry&x6 z0=)mvzE2l@nUMY z$I0-}qRU&HTq0x&y=#ueD&_HTi7Pf`y&v9Id@@b6bY*!#?)r@6geF`P!JIibi+_Ps zL%(}0^Q6!Dc#rTkCg!6)v{N?&`cJ)JGJN%#qNT|J?d=t5{bH7-k|wil*$f%5<*+IB zijSqq@au*z5I-LJr=g$3Xw>8okO5HLdVUaD3i*$$Fc5hAUApeMzX70QnMT#2^ag4H z`Pq*J>JG!x|E)jBaOh+PZ2b<32~@fMU7|U#8u%eWaqyOi|4^ct7IpfVkITRQ#pw~tQu*&rV#8X&kjI2`nLek61mm0_K z>m$4#qQq|yl?SAyX?}S@BEtB>M52hKNY~o7^~;fNwZwFxtN#oB^`nh^d)wfbW%4wI zSchT)frV>LIl#&*82pp%N%foU*#lA=@V}Aj3+?+NYO3h|J2;_qfdJG~NTC4XrONO6 zPm4InZ~{>?{dfH*&qMtu4(mVbKm8X1s%||^QO_DrfAdg%Zbt_^xYHmH4A0+H7cJjq z5HZA`g=YDHVdlFVD(z`w#U1T*Mu@-0zw}Ia7fDnw!8vf?RHOU8GJf3KNCp*N+(e;j z;r`x?>)0gLVJ;|NN8Z{}I&|xDHi-Prm#{Qi=$NPo~0)C<$VS>17xoFEzFia zxlww0P>6q?Cvk^oRFv7NhpL6cW)JAuht~m3>^*@%77(KX!5ZRqB*rabH)=4rAf>gi z;m(5jgW^>O>fVSV z&Lbtgou7*By5b9}3k}N;p1~V{Xbg1u|QYkKoDuI9`g6^{KL&aZSpVo8pP!(L2xG|=yq}V z*d={G#IQC&nQmVj-hBnzOr!)!sdY}scX#q6IOwPl6r+NP{()z@Vk;c|6MV#v_+%m!ij_a?zo#G|{CzW!7?TJnn&U2H1B0W> z)BjHmMi@XrKnoDx_aXGVHZ0)>-o)a=SSbm3g5qCec(^Y98pFTVAyM)Fv2_Tx$nVYt zb3I%=@kHT4TA-9a7YHYo@8&9jeR5Wgn!CL?R+NN(+yH>$$V2i%$peZ@Sij)|KwBa|ITuh zU2NaGrg4a7gY2DO6!d4<{LdTw|Aj~;-G^#5i+1O6PkG6)q;Df)=Efjo;OKn`EkUlZ zVi%jyrL~AKnXd36PR+TPf%UfK@!NThXCiLC(FmX3<<-r5)}*2#c{K`N35e(+i`xLwyUPaO)YJrR;hf`5q< ztK)(U%3jHJkTUdrP?{SMG|Tv@Z@53=48sPg1h+6fo}6PEE(|HEE`I|^j=Lha)=rEy z23YxgSUOcJE6gQ*_T|m;cqs!8CJ$?Co}gorSXs#iZ$hA5R2|?XdUdJKEP1#X^?4-s zmPN8nCjGRf}}8N63l-)P3EwpKsvcW-GycEdn084H%lNGYbY z7I~cDgtyK#S~Ss)FyFqyxbGb^6Z6V_+abQ2C7jEEO_wamHtvPO{quNOb0F=2JePO9 z9#*TGhxwD{Ovf{#-kSEM9-AP|yhn)>>=u)EEhcT4W-SVHPly)ZkY~$&HihH+G|;Fn zI%vap=r-Q_jkHBaiR9nyHIq}jH9!X-{bCOow{$-k2kgR^#z9nBNlpnCWFB?sRK-sG z=bWnFGK4mGb@<{hQ{es*m;!53+chA_at|-Iy#qh$o!*!#SdHEA_PTD$7|}CA6Y|1I z_Pv_3oGCdY3s7|g2JajJI2UhXbK#Sv+5r5gf8d#LujnIQRX5pd0;{qP4>K3S&KlMa z-99RIvR~Exyr^1*ypN}j!z%PCo6z2&fg0`+NDJ>)8GvGqvkq&tplD7=Fl19_E0%ED z4mb|>Rj`#k2w(ssTS1|vTo4E3D8do&PHs3iBfgXO_`CvgX^nWM3okZ-md^)`AAQjOkgYR|% ztFGxi9L*^fOh&Z-z1@awcgti^B28%VzLc-oB_j_CDx5VD=Ci%HuE*2ydu_eMSAD<{ zqr>b)5Nm$<_}{x~;jpWKjjb`+e83n0TpWr|aKJQin2|EFg$ogYa|U|v`R~04=`-90 z$HvJ8|38i3EqH?r>?rh4{dWAY-{1{SQ(}8ma6E-J@hTDe4!KN8Jk#^HCo=l=iQXNK zXZ7F5)AVoSIly)}dWR!DRtMW;0DA>Bg7Am{*MSYu5fn4AR>X+kpC4EV-?u3DA~cam z3T%gp$EbNeaMUq@k;4<;pguyrdtl(b{h(35bO9NW$BPR71q^+SAbcH&v?{?Zpa3IN z8@qRIubD^#Jl;bdjPII6{P=}vh^HhduY$(E1`HYxZ~fm4LHdDZ8Ucx=!$*GwZC4Nj zeF1P0pCQd#Oa~7Sr|Ca*_jfJYt5@8GO+s+&Wg5gF7?Bisq)(x-i$eRLOTO#v!x{Cb zDX|qAwvMWS{x$f24gTNv&A(RuUn~FnP4>HeXz8zQ|F3QTuQUIzGygAx?=OSzFKhpw zbh!Szto>rUr#*2XD|Hx(T-pN}wSTny9e@v34#@ks&htPRBr`YY)A>A>C@#C)nhbx$ z!PS4EYyNUE@WYN+W4x$}dK&)jLk!K$nlba5N^yU>!m&NIu z>z70+b0)gH21OUNHkqE|dT_r@P>nO#2K+xk?{p?@fZuoM@rqjPi{Es245a)v{Qm(N CBh?K6 literal 0 HcmV?d00001 From 5807aa1b2bba9de64b5ff8dc34496771c3b70ae8 Mon Sep 17 00:00:00 2001 From: "River@devbox" Date: Thu, 19 Mar 2026 12:48:11 +0800 Subject: [PATCH 06/21] Added: AIPyApp guide Co-Authored-By: Claude Opus 4.6 --- mkdocs.yml | 1 + source/en/AIPyApp.md | 73 ++++++++++++++++++++++++++++++ source/en/static/aipyapp_demo.jpg | Bin 0 -> 57891 bytes 3 files changed, 74 insertions(+) create mode 100644 source/en/AIPyApp.md create mode 100644 source/en/static/aipyapp_demo.jpg diff --git a/mkdocs.yml b/mkdocs.yml index 49a93a2..984fe5a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -46,6 +46,7 @@ nav: - QPYPI: qpypi-guide.md - Editor: editor-guide.md - Graphical Interface: GraphicalInterface.md + - AIPyApp: AIPyApp.md - OpenAPI: external-api.md - QSL4A: - Overview: qsl4a/index.md diff --git a/source/en/AIPyApp.md b/source/en/AIPyApp.md new file mode 100644 index 0000000..33d2a01 --- /dev/null +++ b/source/en/AIPyApp.md @@ -0,0 +1,73 @@ +# AIPyApp - AI-Powered Program Generator + +AIPyApp is an intelligent tool in QPython that uses AI to automatically generate Python programs from natural language instructions. + +![AIPyApp](static/aipyapp_demo.jpg) + +## Overview + +AIPyApp transforms the way you write code - simply describe what you want in natural language, and the AI will generate the Python program for you. QPython will also feature **AIPy Academy** - a platform offering Python programming courses tailored for the AI era. + +## Installation + +### Step 1: Launch from Dashboard + +1. Open QPython and go to the **Dashboard** +2. **Long press** the start button + +If AIPyApp is not installed, you will be prompted to confirm the installation. Press **Enter** to proceed. + +QPython will automatically download and install the required dependencies from PYPI. Please wait patiently for the installation to complete. + +### Step 2: Restart AIPyApp + +After installation, return to the QPython Dashboard and **long press** the start button again to launch AIPyApp. + +## Configuration + +### Setting Up Your AI Key + +On the first launch, you need to provide an AI API key: + +1. **Register at PGPT**: Create an account at [https://user.pgpt.cloud](https://user.pgpt.cloud) to generate your AI key +2. **Advanced Option**: AIPyApp also supports custom AI keys from OpenAI, Deepseek, and other providers (see advanced tutorials for details) + +### Entering Your AI Key + +1. Long press on the input prompt +2. Select **Paste** from the popup menu +3. Press **Enter** to confirm + +Your AI key will be saved for future sessions. + +## Using AIPyApp + +After configuration, you enter the AIPyApp console mode. Simply type your instructions in natural language! + +### Example Command + +Try entering: + +``` +Use QSL4A to create a HELLO QPY program as a demo +``` + +AIPyApp will: +1. Understand your natural language request +2. Generate the corresponding Python code +3. Execute the program automatically + +That's it - you've created a working Python program without writing any code! + +## Demo + +The example above demonstrates how AIPyApp can: +- Understand Chinese instructions +- Generate QSL4A-based Python code +- Run the program immediately + +Explore AIPyApp to discover more capabilities and start building Python programs effortlessly. + +## Learn More + +Stay tuned for **AIPy Academy** at [aipy.org](https://aipy.org) - upcoming courses on learning and using Python programming in the AI era. diff --git a/source/en/static/aipyapp_demo.jpg b/source/en/static/aipyapp_demo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..00cb283bca990b8fd46c6398235055d98fa06727 GIT binary patch literal 57891 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�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 H-?RS>L Date: Thu, 19 Mar 2026 13:11:45 +0800 Subject: [PATCH 07/21] Add Terminal documentation and Notebook guide - Added Terminal.md covering Shell Terminal, IPython Interpreter, and PIP Client - Added Notebook.md to Guides section - Updated mkdocs.yml navigation --- mkdocs.yml | 4 +- source/en/Notebook.md | 47 +++++++++++++++ source/en/Terminal.md | 131 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 source/en/Notebook.md create mode 100644 source/en/Terminal.md diff --git a/mkdocs.yml b/mkdocs.yml index 984fe5a..7679719 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -43,8 +43,10 @@ nav: - Branches: qpython-x.md - Updates: whats-new.md - Guides: - - QPYPI: qpypi-guide.md + - Terminal: Terminal.md + - Notebook: Notebook.md - Editor: editor-guide.md + - QPYPI: qpypi-guide.md - Graphical Interface: GraphicalInterface.md - AIPyApp: AIPyApp.md - OpenAPI: external-api.md diff --git a/source/en/Notebook.md b/source/en/Notebook.md new file mode 100644 index 0000000..e9fbfbd --- /dev/null +++ b/source/en/Notebook.md @@ -0,0 +1,47 @@ +# Notebook + +QPython integrates Jupyter Notebook, providing a powerful interactive environment for data science, scientific computing, and AI development on Android devices. + +## Overview + +QPython comes with a built-in Jupyter Notebook application that allows you to create and run interactive Python notebooks directly on your Android device. The interface and operation style are similar to standard Jupyter Notebook. + +## Available Libraries + +QPython supports extensive mathematical, scientific, and AI-related libraries that are well-suited for Notebook environments. Install them via QPYPI as needed: + +- **Matplotlib** - Plotting and visualization +- **Seaborn** - Statistical data visualization +- **Pandas** - Data analysis and manipulation +- **Numpy** - Numerical computing +- **Scipy** - Scientific computing +- **OpenCV** - Computer vision and image processing +- **Sympy** - Symbolic mathematics +- **mpmath** - Arbitrary-precision arithmetic +- **Scikit-learn** - Machine learning +- **PyTorch** - Deep learning framework + +## Getting Help + +Since QPython's Notebook operates similarly to Jupyter Notebook, you can refer to the official [Jupyter Notebook documentation](https://jupyter-notebook.readthedocs.io/) for detailed usage instructions and tips. + +## Installation + +To install the libraries you need: + +1. Open QPython and navigate to **QPYPI** +2. Search for the library you want (e.g., "numpy", "pandas") +3. Install the desired package + +Install only the libraries you need for your specific use case. + +## Usage + +The Notebook application in QPython provides: + +- **Interactive code cells** - Write and execute Python code +- **Markdown cells** - Add formatted text and documentation +- **Rich output** - View plots, charts, and visualizations inline +- **Persistent notebooks** - Save and reload your work + +For more details on Notebook operations and features, consult the Jupyter Notebook documentation. diff --git a/source/en/Terminal.md b/source/en/Terminal.md new file mode 100644 index 0000000..b33a157 --- /dev/null +++ b/source/en/Terminal.md @@ -0,0 +1,131 @@ +# Terminal - Python Command Line Tools + +Terminal is one of the most frequently used features in QPython. It's a powerful tool for exploring Python features and libraries, experimenting with new syntax, and managing packages. + +![Terminal](static/terminal_demo.jpg) + +## Overview + +QPython provides multiple terminal options to suit different needs: + +- **QPython Shell Terminal** – The standard Python shell for quick exploration +- **IPython Interactive Interpreter** – A more powerful and feature-rich interactive interpreter +- **PIP Client** – Command-line tool for managing Python packages + +## Accessing Terminal + +### Quick Access + +1. Open QPython and go to the **Dashboard** +2. **Click** the Terminal icon to enter the default QPython Shell Terminal + +### Advanced Options (Long Press) + +On the Dashboard, **long press** the Terminal icon to access additional options: + +- **QPython Shell Terminal** – Launch the standard Python shell +- **IPython Interactive Interpreter** – Launch IPython with advanced features like tab completion, syntax highlighting, and command history +- **PIP Client** – Launch the package management interface + +## QPython Shell Terminal + +The QPython Shell Terminal provides a quick way to execute Python commands and explore Python features. + +### Features + +- Immediate command execution +- Basic Python interpreter functionality +- Access to Python built-in functions and standard library +- Perfect for quick tests and experiments + +### Example Usage + +```python +>>> print("Hello from QPython!") +Hello from QPython! +>>> import math +>>> math.sqrt(16) +4.0 +>>> [x**2 for x in range(10)] +[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] +``` + +## IPython Interactive Interpreter + +IPython offers a much more powerful interactive Python experience with enhanced features. + +### Features + +- **Tab Completion** – Automatically complete variable names, module attributes, and file paths +- **Command History** – Navigate through previous commands with up/down arrows +- **Syntax Highlighting** – Color-coded output for better readability +- **Magic Commands** – Special commands prefixed with `%` for common tasks +- **Object Introspection** – Easily explore objects and their attributes + +### Example Usage + +```python +In [1]: import numpy as np + +In [2]: arr = np.array([1, 2, 3, 4, 5]) + +In [3]: arr? +Type: ndarray +String form: [1 2 3 4 5] +Length: 5 +... + +In [4]: %timeit arr ** 2 +The slowest run took 12.34 microseconds... +``` + +## PIP Client + +The PIP Client provides command-line access to Python package management. + +### Features + +- Install packages from PyPI +- View installed packages +- Upgrade packages +- Uninstall packages +- Search for packages + +### Common Commands + +```bash +# Install a package +pip install requests + +# List installed packages +pip list + +# Upgrade a package +pip install --upgrade requests + +# Uninstall a package +pip uninstall requests + +# Search for packages +pip search json +``` + +### Usage Tips + +- Long press to access PIP Client from the Dashboard +- Use `pip help` to see all available commands +- Some commands may require administrator privileges + +## Choosing the Right Tool + +| Tool | Best For | +|------|----------| +| Shell Terminal | Quick calculations, simple scripts, testing snippets | +| IPython | Complex exploration, data analysis, interactive debugging | +| PIP Client | Installing/updating packages, checking dependencies | + +## Learn More + +- [Python Documentation](https://docs.python.org/3.12/) – Official Python language and library reference +- [IPython Documentation](https://ipython.readthedocs.io/) – Advanced interactive Python features +- [PyPI Guide](qpypi-guide.md) – Managing Python packages in QPython From b160acfb6a9b4dfb71ca812dc55357e3728eddc7 Mon Sep 17 00:00:00 2001 From: "River@devbox" Date: Thu, 19 Mar 2026 13:18:04 +0800 Subject: [PATCH 08/21] Added: terminal_demo.jpg --- .gitignore | 1 + source/en/static/terminal_demo.jpg | Bin 0 -> 82117 bytes 2 files changed, 1 insertion(+) create mode 100644 source/en/static/terminal_demo.jpg diff --git a/.gitignore b/.gitignore index 5d89e25..fe46c1e 100755 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ qpython-docs/static/* venv site .DS_Store +qpython diff --git a/source/en/static/terminal_demo.jpg b/source/en/static/terminal_demo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4829b799af8fe4296564efe05f0630cf8f4aa5e6 GIT binary patch literal 82117 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
    ;vVwuKXf( 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 BKwtm> literal 0 HcmV?d00001 From 0cc8632ae6b3441e91f72149cbd105dc6e10ed6b Mon Sep 17 00:00:00 2001 From: "River@devbox" Date: Thu, 19 Mar 2026 14:39:47 +0800 Subject: [PATCH 09/21] Add Ollama documentation for local LLM integration - Document Ollama framework for running Deepseek/Qwen/Gemma models locally - Guide for installing models via Shell Terminal (recommend deepseek-r1:1.5b) - Instructions for ollama serve and OpenAI-compatible API usage --- mkdocs.yml | 1 + source/en/Ollama.md | 122 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 source/en/Ollama.md diff --git a/mkdocs.yml b/mkdocs.yml index 7679719..054cd23 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -49,6 +49,7 @@ nav: - QPYPI: qpypi-guide.md - Graphical Interface: GraphicalInterface.md - AIPyApp: AIPyApp.md + - Ollama: Ollama.md - OpenAPI: external-api.md - QSL4A: - Overview: qsl4a/index.md diff --git a/source/en/Ollama.md b/source/en/Ollama.md new file mode 100644 index 0000000..7593c79 --- /dev/null +++ b/source/en/Ollama.md @@ -0,0 +1,122 @@ +# Ollama - Local Large Language Model Integration + +Ollama is a local large language model runtime framework that supports a variety of models including Deepseek, Qwen, and Gemma. QPython has built-in Ollama integration, enabling developers to explore GenAI development directly on their mobile devices. + +![Ollama](static/ollama_demo.jpg) + +## Overview + +Ollama allows you to run powerful large language models locally on your Android device. With QPython's integration, you can: + +- Run open-source LLMs directly on your phone +- Use AI capabilities without internet connectivity +- Experiment with different models for various use cases +- Build AI-powered applications using familiar Python libraries + +## Supported Models + +Ollama supports many popular open-source models: + +- **Deepseek** – Efficient reasoning models (recommended: deepseek-r1:1.5b for mobile) +- **Qwen** – Alibaba's large language models +- **Gemma** – Google's lightweight open models +- And many more available on [Ollama Library](https://ollama.com/library) + +## Getting Started + +### Step 1: Access QPython Shell Terminal + +1. Open QPython and go to the **Dashboard** +2. **Long press** the Terminal icon +3. Select **QPython Shell Terminal** + +### Step 2: Download a Model + +In the Shell Terminal, use Ollama commands to download models. For mobile devices, we recommend smaller models for faster response times. + +```bash +# Pull a model (example: deepseek-r1 with 1.5 billion parameters) +ollama pull deepseek-r1:1.5b + +# Pull other models +ollama pull qwen:2.5 +ollama pull gemma:2b +``` + +### Step 3: Run the Model + +Start the Ollama service to make the model available via API: + +```bash +ollama serve +``` + +When running, Ollama will output the local port address (default: 11434). + +## Using Ollama with Python + +### Install OpenAI Library + +Install the `openai` library from QPYPI: + +```bash +# Using PIP Client (long press Terminal icon -> PIP Client) +pip install openai +``` + +### Python Code Example + +After starting `ollama serve`, you can use the OpenAI-compatible API to interact with your local model: + +```python +from openai import OpenAI + +# Configure the client +client = OpenAI( + api_key="deepseek", # Can be any string + base_url="https://localhost:11434/v1" # Ollama's local address +) + +# Chat with the model +response = client.chat.completions.create( + model="deepseek-r1:1.5b", # Match the model you downloaded + messages=[ + {"role": "user", "content": "What is Python?"} + ] +) + +print(response.choices[0].message.content) +``` + +## Recommended Models for Mobile + +| Model | Parameters | Best For | +|-------|------------|----------| +| deepseek-r1 | 1.5b | Fast responses, general tasks | +| qwen:2.5 | 2.5b | Balanced performance | +| gemma:2b | 2b | Lightweight tasks | + +Larger models will work but may respond slower on mobile devices. + +## Useful Ollama Commands + +```bash +# List installed models +ollama list + +# Remove a model +ollama rm deepseek-r1:1.5b + +# Show model information +ollama show deepseek-r1:1.5b + +# Create a custom model (Modelfile) +ollama create mymodel -f Modelfile +``` + +## Learn More + +- [Ollama Documentation](https://docs.ollama.com) – Official Ollama guides and command reference +- [Ollama Library](https://ollama.com/library) – Browse available models +- [AIPyApp](AIPyApp.md) – AI-powered program generator in QPython +- [QPYPI Guide](qpypi-guide.md) – Managing Python packages From b43fdb78f77e663253551c661c5a55280db1ec29 Mon Sep 17 00:00:00 2001 From: "River@devbox" Date: Thu, 19 Mar 2026 14:48:46 +0800 Subject: [PATCH 10/21] Update Ollama --- source/en/Ollama.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/en/Ollama.md b/source/en/Ollama.md index 7593c79..70e120f 100644 --- a/source/en/Ollama.md +++ b/source/en/Ollama.md @@ -2,8 +2,6 @@ Ollama is a local large language model runtime framework that supports a variety of models including Deepseek, Qwen, and Gemma. QPython has built-in Ollama integration, enabling developers to explore GenAI development directly on their mobile devices. -![Ollama](static/ollama_demo.jpg) - ## Overview Ollama allows you to run powerful large language models locally on your Android device. With QPython's integration, you can: From 4137a0f29adfe9a1db7dd2cba8627891aef4c7c2 Mon Sep 17 00:00:00 2001 From: "River@devbox" Date: Thu, 19 Mar 2026 15:04:13 +0800 Subject: [PATCH 11/21] Update: Ollama --- source/en/Ollama.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/en/Ollama.md b/source/en/Ollama.md index 70e120f..be8cfe1 100644 --- a/source/en/Ollama.md +++ b/source/en/Ollama.md @@ -59,7 +59,7 @@ Install the `openai` library from QPYPI: ```bash # Using PIP Client (long press Terminal icon -> PIP Client) -pip install openai +pip install openai-aipy ``` ### Python Code Example From f1accfcd55298820c20504b76f727a34100ea4d0 Mon Sep 17 00:00:00 2001 From: "River@devbox" Date: Thu, 19 Mar 2026 17:56:25 +0800 Subject: [PATCH 12/21] Add missing QSL4A API documentation from SL4A Added new documentation files with *ASL4A* marker: - system/settings.md - Screen, volume, ringer settings - system/wakelock.md - Wake lock control - system/qpyinterface.md - Script execution, shared variables - system/activityresult.md - Activity result handling - connectivity/phone.md - Phone calls and info - connectivity/contacts.md - Contact management - connectivity/signalstrength.md - Signal monitoring - connectivity/ftp.md - FTP server - storage/preferences.md - Shared preferences - hardware/webcam.md - MJPEG streaming - hardware/usbserial.md - USB host serial Updated existing files: - core/events.md - Added startEventDispatcher, stopEventDispatcher - core/intent.md - Added CommonIntents methods (scanBarcode, send, etc.) - connectivity/location.md - Added locationProviders, readGnssStatus - system/sensors.md - Added sensorsGetAccuracy - index.md - Added new documentation entries Parameters are kept consistent with SL4A.md as requested. --- source/en/qsl4a/connectivity/contacts.md | 135 ++++++++ source/en/qsl4a/connectivity/ftp.md | 96 ++++++ source/en/qsl4a/connectivity/location.md | 32 ++ source/en/qsl4a/connectivity/phone.md | 291 ++++++++++++++++++ .../en/qsl4a/connectivity/signalstrength.md | 71 +++++ source/en/qsl4a/core/events.md | 34 ++ source/en/qsl4a/core/intent.md | 149 +++++++++ source/en/qsl4a/hardware/usbserial.md | 161 ++++++++++ source/en/qsl4a/hardware/webcam.md | 98 ++++++ source/en/qsl4a/index.md | 11 + source/en/qsl4a/storage/preferences.md | 81 +++++ source/en/qsl4a/system/activityresult.md | 230 ++++++++++++++ source/en/qsl4a/system/qpyinterface.md | 131 ++++++++ source/en/qsl4a/system/sensors.md | 9 + source/en/qsl4a/system/settings.md | 234 ++++++++++++++ source/en/qsl4a/system/wakelock.md | 78 +++++ 16 files changed, 1841 insertions(+) create mode 100644 source/en/qsl4a/connectivity/contacts.md create mode 100644 source/en/qsl4a/connectivity/ftp.md create mode 100644 source/en/qsl4a/connectivity/phone.md create mode 100644 source/en/qsl4a/connectivity/signalstrength.md create mode 100644 source/en/qsl4a/hardware/usbserial.md create mode 100644 source/en/qsl4a/hardware/webcam.md create mode 100644 source/en/qsl4a/storage/preferences.md create mode 100644 source/en/qsl4a/system/activityresult.md create mode 100644 source/en/qsl4a/system/qpyinterface.md create mode 100644 source/en/qsl4a/system/settings.md create mode 100644 source/en/qsl4a/system/wakelock.md diff --git a/source/en/qsl4a/connectivity/contacts.md b/source/en/qsl4a/connectivity/contacts.md new file mode 100644 index 0000000..44c1a72 --- /dev/null +++ b/source/en/qsl4a/connectivity/contacts.md @@ -0,0 +1,135 @@ +# Contacts API + +Access and manage device contacts. + +## Contact Picking + +### pickContact() *ASL4A* +Display a list of contacts to pick from. + +```python +pickContact() +``` + +**Returns:** Intent with contact URI + +### pickPhone() *ASL4A* +Display a list of phone numbers to pick from. + +```python +pickPhone() +``` + +**Returns:** Selected phone number string + +## Contact Queries + +### contactsGet() *ASL4A* +Get all contacts. + +```python +contactsGet(attributes=None) +``` + +**Parameters:** +- `attributes` (list, optional): Specific attributes to retrieve + +**Returns:** List of contact JSONObject + +### contactsGetById() *ASL4A* +Get a contact by ID. + +```python +contactsGetById(id, attributes=None) +``` + +**Parameters:** +- `id` (int): Contact ID +- `attributes` (list, optional): Specific attributes to retrieve + +**Returns:** JSONObject contact data + +### contactsGetCount() *ASL4A* +Get the total number of contacts. + +```python +contactsGetCount() +``` + +**Returns:** Integer count + +### contactsGetIds() *ASL4A* +Get all contact IDs. + +```python +contactsGetIds() +``` + +**Returns:** List of contact ID integers + +### contactsGetAttributes() *ASL4A* +Get all possible contact attributes. + +```python +contactsGetAttributes() +``` + +**Returns:** List of attribute names + +## Content Queries + +### queryContent() *ASL4A* +Query content resolver with custom parameters. + +```python +queryContent(uri, attributes=None, selection=None, selectionArgs=None, order=None) +``` + +**Parameters:** +- `uri` (str): Content URI +- `attributes` (list, optional): Attributes to retrieve +- `selection` (str, optional): WHERE clause +- `selectionArgs` (list, optional): Selection arguments +- `order` (str, optional): ORDER BY clause + +**Returns:** List of JSONObject results + +### queryAttributes() *ASL4A* +Get attributes for a content URI. + +```python +queryAttributes(uri) +``` + +**Parameters:** +- `uri` (str): Content URI + +**Returns:** JSONArray of attribute names + +## Usage Example + +```python +import androidhelper + +droid = androidhelper.Android() + +# Pick a contact +contact_uri = droid.pickContact().result +print(f"Selected contact: {contact_uri}") + +# Pick a phone number +phone = droid.pickPhone().result +print(f"Selected phone: {phone}") + +# Get all contacts +contacts = droid.contactsGet().result +print(f"Total contacts: {len(contacts)}") + +# Get contact by ID +contact = droid.contactsGetById(1).result +print(f"Contact: {contact}") + +# Get contact attributes +attrs = droid.contactsGetAttributes().result +print(f"Available attributes: {attrs}") +``` diff --git a/source/en/qsl4a/connectivity/ftp.md b/source/en/qsl4a/connectivity/ftp.md new file mode 100644 index 0000000..f9383c0 --- /dev/null +++ b/source/en/qsl4a/connectivity/ftp.md @@ -0,0 +1,96 @@ +# FTP Server API + +Start and manage a built-in FTP server on the device. + +## FTP Server Methods + +### ftpStart() *ASL4A* +Start the FTP server. + +```python +ftpStart() +``` + +**Returns:** Array containing IP address and port [ip, port] + +### ftpStop() *ASL4A* +Stop the FTP server. + +```python +ftpStop() +``` + +### ftpIsRunning() *ASL4A* +Check if FTP server is running. + +```python +ftpIsRunning() +``` + +**Returns:** True if running + +### ftpGet() *ASL4A* +Get FTP server IP address. + +```python +ftpGet() +``` + +**Returns:** Array with IP address and port + +### ftpSet() *ASL4A* +Configure FTP server settings. + +```python +ftpSet(port=None, rootDir=None, username=None, password=None) +``` + +**Parameters:** +- `port` (int, optional): Server port +- `rootDir` (str, optional): Root directory to serve +- `username` (str, optional): Login username +- `password` (str, optional): Login password + +**Returns:** JSONObject with current settings + +### ftpStatus() *ASL4A* +Get FTP server status. + +```python +ftpStatus() +``` + +**Returns:** String status description + +## Usage Example + +```python +import androidhelper + +droid = androidhelper.Android() + +# Configure FTP server +droid.ftpSet( + port=2121, + rootDir="/sdcard", + username="admin", + password="secret" +) + +# Start FTP server +info = droid.ftpStart().result +print(f"FTP running at {info[0]}:{info[1]}") + +# Check status +if droid.ftpIsRunning().result: + print("FTP server is running") + +# Get server info +server_info = droid.ftpGet().result +print(f"Server: {server_info}") + +# Stop when done +droid.ftpStop() +``` + +**Note:** Connect to the FTP server using any FTP client with the provided credentials. diff --git a/source/en/qsl4a/connectivity/location.md b/source/en/qsl4a/connectivity/location.md index 36c8c7a..4227d60 100644 --- a/source/en/qsl4a/connectivity/location.md +++ b/source/en/qsl4a/connectivity/location.md @@ -48,6 +48,38 @@ Convert address to coordinates. geocode(address, maxResults=1) ``` +## Location Provider Methods *ASL4A* + +### locationProviders() +Get available location providers on the phone. + +```python +locationProviders() +``` + +**Returns:** List of available provider names (e.g., ['gps', 'network']) + +### locationProviderEnabled() +Check if a specific location provider is enabled. + +```python +locationProviderEnabled(provider) +``` + +**Parameters:** +- `provider` (str): Provider name (e.g., 'gps', 'network') + +**Returns:** True if enabled, False otherwise + +### readGnssStatus() *ASL4A* +Read Global Navigation Satellite System status (requires Android 8+). + +```python +readGnssStatus() +``` + +**Returns:** JSONArray containing GNSS satellite information + ## Usage Example ```python diff --git a/source/en/qsl4a/connectivity/phone.md b/source/en/qsl4a/connectivity/phone.md new file mode 100644 index 0000000..1d491c9 --- /dev/null +++ b/source/en/qsl4a/connectivity/phone.md @@ -0,0 +1,291 @@ +# Phone API + +Control phone calls and retrieve phone information. + +## Phone State Tracking + +### startTrackingPhoneState() *ASL4A* +Start tracking phone state changes. Generates 'phone' events. + +```python +startTrackingPhoneState() +``` + +### readPhoneState() *ASL4A* +Read the current phone state. + +```python +readPhoneState() +``` + +**Returns:** Bundle with phone state and incoming number + +### stopTrackingPhoneState() *ASL4A* +Stop tracking phone state. + +```python +stopTrackingPhoneState() +``` + +## Making Calls + +### phoneCall() *ASL4A* +Call a contact/phone number by URI. + +```python +phoneCall(uri) +``` + +**Parameters:** +- `uri` (str): Contact URI or phone number URI + +### phoneCallNumber() *ASL4A* +Call a phone number directly. + +```python +phoneCallNumber(phone_number) +``` + +**Parameters:** +- `phone_number` (str): Phone number to call + +### phoneDial() *ASL4A* +Dial a number (opens dialer without calling). + +```python +phoneDial(uri) +``` + +**Parameters:** +- `uri` (str): Contact URI or phone number URI + +### phoneDialNumber() *ASL4A* +Dial a phone number (opens dialer without calling). + +```python +phoneDialNumber(phone_number) +``` + +**Parameters:** +- `phone_number` (str): Phone number + +## Cell Location + +### getCellLocation() *ASL4A* +Get the current cell location. + +```python +getCellLocation() +``` + +**Returns:** JSONObject with cell location data + +### getAllCellsLocation() *ASL4A* +Get all cell locations (for dual SIM devices). + +```python +getAllCellsLocation() +``` + +**Returns:** JSONArray of cell locations + +## Network Information + +### getNetworkOperator() *ASL4A* +Get the MCC+MNC of the current operator. + +```python +getNetworkOperator() +``` + +**Returns:** String (e.g., '310260') + +### getNetworkOperatorName() *ASL4A* +Get the name of the current operator. + +```python +getNetworkOperatorName() +``` + +**Returns:** String (e.g., 'T-Mobile') + +### getNetworkType() *ASL4A* +Get the current network type. + +```python +getNetworkType() +``` + +**Returns:** String describing radio technology (e.g., 'LTE', 'UMTS', 'GSM') + +### getPhoneType() *ASL4A* +Get the phone type. + +```python +getPhoneType() +``` + +**Returns:** String (e.g., 'GSM', 'CDMA', 'SIP') + +## SIM Information + +### getSimCountryIso() *ASL4A* +Get the ISO country code for the SIM. + +```python +getSimCountryIso() +``` + +**Returns:** String (e.g., 'us') + +### getSimOperator() *ASL4A* +Get the MCC+MNC of the SIM operator. + +```python +getSimOperator() +``` + +**Returns:** String (e.g., '310260') + +### getSimOperatorName() *ASL4A* +Get the SIM operator name. + +```python +getSimOperatorName() +``` + +**Returns:** String (e.g., 'T-Mobile') + +### getSimSerialNumber() *ASL4A* +Get the SIM serial number. + +```python +getSimSerialNumber() +``` + +**Returns:** String SIM serial number + +### getSimState() *ASL4A* +Get the SIM card state. + +```python +getSimState() +``` + +**Returns:** String describing SIM state + +### getSubscriberId() *ASL4A* +Get the subscriber ID. + +```python +getSubscriberId() +``` + +**Returns:** String subscriber ID + +## Voice Mail + +### getVoiceMailAlphaTag() *ASL4A* +Get the voice mail alpha tag. + +```python +getVoiceMailAlphaTag() +``` + +**Returns:** String voice mail tag + +### getVoiceMailNumber() *ASL4A* +Get the voice mail number. + +```python +getVoiceMailNumber() +``` + +**Returns:** String voice mail number + +## Device Information + +### getDeviceId() *ASL4A* +Get the device ID (IMEI for GSM). Deprecated. + +```python +getDeviceId() +``` + +**Returns:** String device ID + +### getDeviceSoftwareVersion() *ASL4A* +Get the device software version. + +```python +getDeviceSoftwareVersion() +``` + +**Returns:** String software version + +### getLine1Number() *ASL4A* +Get the line 1 phone number. + +```python +getLine1Number() +``` + +**Returns:** String phone number + +### checkNetworkRoaming() *ASL4A* +Check if connected to roaming network. + +```python +checkNetworkRoaming() +``` + +**Returns:** True if roaming + +## Cell Info + +### getAllCellInfo() *ASL4A* +Get information about all cells. + +```python +getAllCellInfo() +``` + +**Returns:** List of cell information + +### setDataEnabled() *ASL4A* +Enable or disable mobile data. + +```python +setDataEnabled(enabled) +``` + +**Parameters:** +- `enabled` (bool): True to enable, False to disable + +## Usage Example + +```python +import androidhelper + +droid = androidhelper.Android() + +# Get network info +operator = droid.getNetworkOperatorName().result +print(f"Operator: {operator}") + +network_type = droid.getNetworkType().result +print(f"Network: {network_type}") + +# Get SIM info +sim_state = droid.getSimState().result +print(f"SIM: {sim_state}") + +# Get phone number +line1 = droid.getLine1Number().result +print(f"Phone: {line1}") + +# Track phone state +droid.startTrackingPhoneState() +print("Tracking phone state...") +droid.stopTrackingPhoneState() +``` diff --git a/source/en/qsl4a/connectivity/signalstrength.md b/source/en/qsl4a/connectivity/signalstrength.md new file mode 100644 index 0000000..d95b746 --- /dev/null +++ b/source/en/qsl4a/connectivity/signalstrength.md @@ -0,0 +1,71 @@ +# Signal Strength API + +Monitor cellular and wireless signal strength. + +## Signal Strength Methods + +### startTrackingSignalStrengths() *ASL4A* +Start tracking signal strength changes. Generates 'signal_strengths' events. + +```python +startTrackingSignalStrengths() +``` + +### stopTrackingSignalStrengths() *ASL4A* +Stop tracking signal strengths. + +```python +stopTrackingSignalStrengths() +``` + +### readSignalStrengths() *ASL4A* +Read the current signal strengths. + +```python +readSignalStrengths() +``` + +**Returns:** Bundle with signal strength data + +### getTelephoneSignalStrengthLevel() *ASL4A* +Get the telephone signal strength as a level (0-4). + +```python +getTelephoneSignalStrengthLevel() +``` + +**Returns:** Integer level (0=none, 1=poor, 2=fair, 3=good, 4=excellent) + +### getTelephoneSignalStrengthDetail() *ASL4A* +Get detailed telephone signal strength information. + +```python +getTelephoneSignalStrengthDetail() +``` + +**Returns:** String with detailed signal info + +## Usage Example + +```python +import androidhelper +import time + +droid = androidhelper.Android() + +# Start tracking signal strength +droid.startTrackingSignalStrengths() + +# Wait for signal updates +time.sleep(5) + +# Read current signal strength +signal = droid.readSignalStrengths().result +print(f"Signal: {signal}") + +# Get level directly +level = droid.getTelephoneSignalStrengthLevel().result +print(f"Signal level: {level}/4") + +droid.stopTrackingSignalStrengths() +``` diff --git a/source/en/qsl4a/core/events.md b/source/en/qsl4a/core/events.md index 2906c80..8ed8f93 100644 --- a/source/en/qsl4a/core/events.md +++ b/source/en/qsl4a/core/events.md @@ -109,6 +109,40 @@ eventGetBrodcastCategories() **Returns:** List of registered categories +## Event Dispatcher *ASL4A* + +### startEventDispatcher() +Opens up a socket where you can read for events posted. *ASL4A* + +```python +startEventDispatcher(port=0) +``` + +**Parameters:** +- `port` (int, optional): Port to listen on (default: 0 = auto-select) + +**Returns:** Port number being listened on + +### stopEventDispatcher() +Stops the event server. *ASL4A* + +```python +stopEventDispatcher() +``` + +## Deprecated Methods + +### rpcPostEvent() *ASL4A* +Post an event to the event queue. (Deprecated, use eventPost) + +```python +rpcPostEvent(name, data) +``` + +**Parameters:** +- `name` (str): Event name +- `data`: Event data + ## Usage Examples ### Basic Event Polling diff --git a/source/en/qsl4a/core/intent.md b/source/en/qsl4a/core/intent.md index efd1422..6370f80 100644 --- a/source/en/qsl4a/core/intent.md +++ b/source/en/qsl4a/core/intent.md @@ -104,6 +104,155 @@ Pick content from URI. pick(uri) ``` +## Common Intent Methods *ASL4A* + +### scanBarcode() *ASL4A* +Launch the barcode scanner. + +```python +scanBarcode() +``` + +**Returns:** Scanned barcode string + +### send() *ASL4A* +Send content via share intent. + +```python +send(type, content) +``` + +**Parameters:** +- `type` (str): MIME type +- `content` (str): Content to share + +### sendText() *ASL4A* +Send text content. + +```python +sendText(text) +``` + +**Parameters:** +- `text` (str): Text to send + +### sendEmail() *ASL4A* +Send an email. + +```python +sendEmail(to, subject, body, attachment=None) +``` + +**Parameters:** +- `to` (str or list): Recipient email address(es) +- `subject` (str): Email subject +- `body` (str): Email body +- `attachment` (str, optional): Attachment file path + +### pathToUri() *ASL4A* +Convert file path to content URI. + +```python +pathToUri(path) +``` + +**Parameters:** +- `path` (str): File path + +**Returns:** Content URI string + +### openFile() *ASL4A* +Open a file with appropriate app. + +```python +openFile(path) +``` + +**Parameters:** +- `path` (str): File path to open + +### sendFile() *ASL4A* +Send a file via share intent. + +```python +sendFile(path) +``` + +**Parameters:** +- `path` (str): File path to send + +### getPathType() *ASL4A* +Get the MIME type for a file path. + +```python +getPathType(path) +``` + +**Parameters:** +- `path` (str): File path + +**Returns:** MIME type string + +### viewMap() *ASL4A* +Open map at a location. + +```python +viewMap(latitude, longitude) +``` + +**Parameters:** +- `latitude` (float): Latitude +- `longitude` (float): Longitude + +### viewContacts() *ASL4A* +Open the contacts app. + +```python +viewContacts() +``` + +### search() *ASL4A* +Perform a web search. + +```python +search(query) +``` + +**Parameters:** +- `query` (str): Search query + +### viewHtml() *ASL4A* +View HTML content. + +```python +viewHtml(content, encoding=None) +``` + +**Parameters:** +- `content` (str): HTML content +- `encoding` (str, optional): Character encoding + +### webViewShow() *ASL4A* +Display web content in WebView. Deprecated, use viewHtml. + +```python +webViewShow(url) +``` + +**Parameters:** +- `url` (str): Web page URL + +### editorOpen() *ASL4A* +Open a text editor. + +```python +editorOpen(path=None, create=False) +``` + +**Parameters:** +- `path` (str, optional): File path to edit +- `create` (bool, optional): Create if doesn't exist + ## Helper Class: Uri Create URI objects for Intents: diff --git a/source/en/qsl4a/hardware/usbserial.md b/source/en/qsl4a/hardware/usbserial.md new file mode 100644 index 0000000..2dbc0d2 --- /dev/null +++ b/source/en/qsl4a/hardware/usbserial.md @@ -0,0 +1,161 @@ +# USB Host Serial API + +Communicate with USB serial devices (requires USB OTG support and Android 3.1+). + +## USB Serial Methods *ASL4A* + +### usbHostSerialOpen() *ASL4A* +Open a connection to a USB serial device. + +```python +usbHostSerialOpen(device, baudRate=9600) +``` + +**Parameters:** +- `device` (str): USB device path or identifier +- `baudRate` (int): Baud rate (default: 9600) + +**Returns:** True if opened successfully + +### usbHostSerialClose() *ASL4A* +Close the USB serial connection. + +```python +usbHostSerialClose() +``` + +### usbHostSerialRead() *ASL4A* +Read data from USB serial. + +```python +usbHostSerialRead(bufferSize=1024) +``` + +**Parameters:** +- `bufferSize` (int): Maximum bytes to read (default: 1024) + +**Returns:** String read data + +### usbHostSerialWrite() *ASL4A* +Write data to USB serial. + +```python +usbHostSerialWrite(data) +``` + +**Parameters:** +- `data` (str): String data to write + +### usbHostSerialAvailable() *ASL4A* +Check if data is available to read. + +```python +usbHostSerialAvailable() +``` + +**Returns:** Number of bytes available + +## Configuration Methods *ASL4A* + +### usbHostSerialSetBaudRate() *ASL4A* +Set the baud rate. + +```python +usbHostSerialSetBaudRate(baudRate) +``` + +**Parameters:** +- `baudRate` (int): Baud rate + +### usbHostSerialSetDataBits() *ASL4A* +Set data bits (5, 6, 7, or 8). + +```python +usbHostSerialSetDataBits(dataBits) +``` + +**Parameters:** +- `dataBits` (int): Data bits (5-8) + +### usbHostSerialSetStopBits() *ASL4A* +Set stop bits (1, 1.5, or 2). + +```python +usbHostSerialSetStopBits(stopBits) +``` + +**Parameters:** +- `stopBits` (float): Stop bits (1, 1.5, or 2) + +### usbHostSerialSetParity() *ASL4A* +Set parity (none, odd, even, mark, space). + +```python +usbHostSerialSetParity(parity) +``` + +**Parameters:** +- `parity` (str): Parity mode ('none', 'odd', 'even', 'mark', 'space') + +### usbHostSerialSetFlowControl() *ASL4A* +Set flow control (none, hardware, software). + +```python +usbHostSerialSetFlowControl(flowControl) +``` + +**Parameters:** +- `flowControl` (str): Flow control mode ('none', 'hardware', 'software') + +### usbHostSerialReadHex() *ASL4A* +Read data as hex string. + +```python +usbHostSerialReadHex(bufferSize=1024) +``` + +**Parameters:** +- `bufferSize` (int): Maximum bytes to read + +**Returns:** Hex string + +### usbHostSerialWriteHex() *ASL4A* +Write data from hex string. + +```python +usbHostSerialWriteHex(hexString) +``` + +**Parameters:** +- `hexString` (str): Hex string to write + +## Usage Example + +```python +import androidhelper + +droid = androidhelper.Android() + +# Open USB serial connection +if droid.usbHostSerialOpen("/dev/bus/usb/001/001", 115200).result: + print("USB serial opened") + + # Write data + droid.usbHostSerialWrite("AT\r") + + # Read response + response = droid.usbHostSerialRead(1024).result + print(f"Response: {response}") + + # Or use hex + droid.usbHostSerialWriteHex("41540D0A") # "AT\r\n" + + # Close connection + droid.usbHostSerialClose() +``` + +**Note:** USB serial requires: +- Android 3.1+ (API 12) +- USB OTG cable/adapter +- USB host mode support on device +- Compatible serial device diff --git a/source/en/qsl4a/hardware/webcam.md b/source/en/qsl4a/hardware/webcam.md new file mode 100644 index 0000000..e7cb839 --- /dev/null +++ b/source/en/qsl4a/hardware/webcam.md @@ -0,0 +1,98 @@ +# Webcam API + +Stream video from the device camera using MJPEG. + +## MJPEG Stream Methods + +### webcamStart() *ASL4A* +Start an MJPEG stream from the webcam. + +```python +webcamStart(resolutionLevel=0, jpegQuality=20, port=0) +``` + +**Parameters:** +- `resolutionLevel` (int): Resolution level (default: 0) +- `jpegQuality` (int): JPEG quality 1-100 (default: 20) +- `port` (int): Port number (default: 0 = auto) + +**Returns:** Tuple of (address, port) for the stream + +### webcamAdjustQuality() *ASL4A* +Adjust the quality of an active webcam stream. + +```python +webcamAdjustQuality(resolutionLevel=0, jpegQuality=20) +``` + +**Parameters:** +- `resolutionLevel` (int): Resolution level +- `jpegQuality` (int): JPEG quality 1-100 + +### webcamStop() *ASL4A* +Stop the webcam stream. + +```python +webcamStop() +``` + +## Camera Preview Methods + +### cameraStartPreview() *ASL4A* +Start camera preview mode with event generation. + +```python +cameraStartPreview(resolutionLevel=0, jpegQuality=20, filepath=None) +``` + +**Parameters:** +- `resolutionLevel` (int): Resolution level (default: 0) +- `jpegQuality` (int): JPEG quality (default: 20) +- `filepath` (str, optional): File path to save preview frames + +**Returns:** True if successful + +**Note:** Generates 'preview' events with frame data. + +### cameraStopPreview() *ASL4A* +Stop the camera preview. + +```python +cameraStopPreview() +``` + +## Usage Example + +```python +import androidhelper +import time + +droid = androidhelper.Android() + +# Start webcam stream +stream_info = droid.webcamStart( + resolutionLevel=0, + jpegQuality=30, + port=8080 +).result +print(f"Stream available at {stream_info[0]}:{stream_info[1]}") + +# Adjust quality while streaming +time.sleep(5) +droid.webcamAdjustQuality(resolutionLevel=1, jpegQuality=50) + +# Stop when done +droid.webcamStop() + +# Or use preview mode +print("Starting preview...") +droid.cameraStartPreview() + +# Wait for preview events +for i in range(10): + event = droid.eventWait(timeout=1).result + if event and event['name'] == 'preview': + print(f"Got preview frame: {event['data']}") + +droid.cameraStopPreview() +``` diff --git a/source/en/qsl4a/index.md b/source/en/qsl4a/index.md index e810913..48eba1c 100644 --- a/source/en/qsl4a/index.md +++ b/source/en/qsl4a/index.md @@ -41,20 +41,31 @@ print(f"Battery: {battery['level']}%") - [Sensors](system/sensors.md) - Device sensors - [Application](system/application.md) - App management - [System Info](system/sysinfo.md) - Device information +- [Settings](system/settings.md) - System settings +- [WakeLock](system/wakelock.md) - Wake lock control +- [QPython Interface](system/qpyinterface.md) - Script execution +- [Activity Result](system/activityresult.md) - Activity result handling ### [Hardware](hardware/) - [Bluetooth](hardware/bluetooth.md) - Bluetooth operations - [Camera](hardware/camera.md) - Photo and video capture - [Audio/Recorder](hardware/recorder.md) - Audio recording +- [Webcam](hardware/webcam.md) - MJPEG streaming +- [USB Serial](hardware/usbserial.md) - USB host serial ### [Connectivity](connectivity/) - [WiFi](connectivity/wifi.md) - WiFi operations - [Location](connectivity/location.md) - GPS and location - [SMS](connectivity/sms.md) - SMS operations +- [Phone](connectivity/phone.md) - Phone calls and info +- [Contacts](connectivity/contacts.md) - Contact management +- [Signal Strength](connectivity/signalstrength.md) - Signal monitoring +- [FTP Server](connectivity/ftp.md) - Built-in FTP server ### [Storage](storage/) - [DocumentFile](storage/documentfile.md) - File operations - [Clipboard](storage/clipboard.md) - Clipboard operations +- [Preferences](storage/preferences.md) - Shared preferences ### [Media](media/) - [Media Player](media/mediaplayer.md) - Audio/Video playback diff --git a/source/en/qsl4a/storage/preferences.md b/source/en/qsl4a/storage/preferences.md new file mode 100644 index 0000000..5551595 --- /dev/null +++ b/source/en/qsl4a/storage/preferences.md @@ -0,0 +1,81 @@ +# Preferences API + +Store and retrieve data using Android SharedPreferences. + +## Preference Methods + +### prefGetValue() *ASL4A* +Read a value from shared preferences. + +```python +prefGetValue(key, filename=None) +``` + +**Parameters:** +- `key` (str): Preference key +- `filename` (str, optional): Preference file name + +**Returns:** The stored value (any type) + +### prefPutValue() *ASL4A* +Write a value to shared preferences. + +```python +prefPutValue(key, value, filename=None) +``` + +**Parameters:** +- `key` (str): Preference key +- `value` (object): Value to store +- `filename` (str, optional): Preference file name + +### prefGetAll() *ASL4A* +Get all preference values. + +```python +prefGetAll(filename=None) +``` + +**Parameters:** +- `filename` (str, optional): Preference file name + +**Returns:** Map of all preferences + +### prefRemoveValue() *ASL4A* +Remove a value from shared preferences. + +```python +prefRemoveValue(key, filename=None) +``` + +**Parameters:** +- `key` (str): Preference key to remove +- `filename` (str, optional): Preference file name + +## Usage Example + +```python +import androidhelper + +droid = androidhelper.Android() + +# Store values +droid.prefPutValue("username", "alice") +droid.prefPutValue("score", 100) +droid.prefPutValue("enabled", True) + +# Read a specific value +username = droid.prefGetValue("username").result +print(f"Username: {username}") + +# Get all preferences +all_prefs = droid.prefGetAll().result +print(f"All prefs: {all_prefs}") + +# Remove a value +droid.prefRemoveValue("score") + +# Use custom filename +droid.prefPutValue("token", "abc123", filename="auth.prefs") +token = droid.prefGetValue("token", filename="auth.prefs").result +``` diff --git a/source/en/qsl4a/system/activityresult.md b/source/en/qsl4a/system/activityresult.md new file mode 100644 index 0000000..9bc4559 --- /dev/null +++ b/source/en/qsl4a/system/activityresult.md @@ -0,0 +1,230 @@ +# Activity Result API + +Set activity results for scripts launched via `startActivityForResult`. + +## Result Methods + +### setResultBoolean() *ASL4A* +Set a boolean result. + +```python +setResultBoolean(resultCode, resultValue) +``` + +**Parameters:** +- `resultCode` (int): Result code +- `resultValue` (bool): Boolean result value + +### setResultByte() *ASL4A* +Set a byte result. + +```python +setResultByte(resultCode, resultValue) +``` + +**Parameters:** +- `resultCode` (int): Result code +- `resultValue` (int): Byte result value + +### setResultShort() *ASL4A* +Set a short result. + +```python +setResultShort(resultCode, resultValue) +``` + +**Parameters:** +- `resultCode` (int): Result code +- `resultValue` (int): Short result value + +### setResultChar() *ASL4A* +Set a character result. + +```python +setResultChar(resultCode, resultValue) +``` + +**Parameters:** +- `resultCode` (int): Result code +- `resultValue` (str): Character result value + +### setResultInteger() *ASL4A* +Set an integer result. + +```python +setResultInteger(resultCode, resultValue) +``` + +**Parameters:** +- `resultCode` (int): Result code +- `resultValue` (int): Integer result value + +### setResultLong() *ASL4A* +Set a long result. + +```python +setResultLong(resultCode, resultValue) +``` + +**Parameters:** +- `resultCode` (int): Result code +- `resultValue` (int): Long result value + +### setResultFloat() *ASL4A* +Set a float result. + +```python +setResultFloat(resultCode, resultValue) +``` + +**Parameters:** +- `resultCode` (int): Result code +- `resultValue` (float): Float result value + +### setResultDouble() *ASL4A* +Set a double result. + +```python +setResultDouble(resultCode, resultValue) +``` + +**Parameters:** +- `resultCode` (int): Result code +- `resultValue` (float): Double result value + +### setResultString() *ASL4A* +Set a string result. + +```python +setResultString(resultCode, resultValue) +``` + +**Parameters:** +- `resultCode` (int): Result code +- `resultValue` (str): String result value + +### setResultBooleanArray() *ASL4A* +Set a boolean array result. + +```python +setResultBooleanArray(resultCode, resultValue) +``` + +**Parameters:** +- `resultCode` (int): Result code +- `resultValue` (list): Boolean array + +### setResultByteArray() *ASL4A* +Set a byte array result. + +```python +setResultByteArray(resultCode, resultValue) +``` + +**Parameters:** +- `resultCode` (int): Result code +- `resultValue` (list): Byte array + +### setResultShortArray() *ASL4A* +Set a short array result. + +```python +setResultShortArray(resultCode, resultValue) +``` + +**Parameters:** +- `resultCode` (int): Result code +- `resultValue` (list): Short array + +### setResultCharArray() *ASL4A* +Set a character array result. + +```python +setResultCharArray(resultCode, resultValue) +``` + +**Parameters:** +- `resultCode` (int): Result code +- `resultValue` (list): Char array + +### setResultIntegerArray() *ASL4A* +Set an integer array result. + +```python +setResultIntegerArray(resultCode, resultValue) +``` + +**Parameters:** +- `resultCode` (int): Result code +- `resultValue` (list): Integer array + +### setResultLongArray() *ASL4A* +Set a long array result. + +```python +setResultLongArray(resultCode, resultValue) +``` + +**Parameters:** +- `resultCode` (int): Result code +- `resultValue` (list): Long array + +### setResultFloatArray() *ASL4A* +Set a float array result. + +```python +setResultFloatArray(resultCode, resultValue) +``` + +**Parameters:** +- `resultCode` (int): Result code +- `resultValue` (list): Float array + +### setResultDoubleArray() *ASL4A* +Set a double array result. + +```python +setResultDoubleArray(resultCode, resultValue) +``` + +**Parameters:** +- `resultCode` (int): Result code +- `resultValue` (list): Double array + +### setResultStringArray() *ASL4A* +Set a string array result. + +```python +setResultStringArray(resultCode, resultValue) +``` + +**Parameters:** +- `resultCode` (int): Result code +- `resultValue` (list): String array + +### setResultSerializable() *ASL4A* +Set a serializable result. + +```python +setResultSerializable(resultCode, resultValue) +``` + +**Parameters:** +- `resultCode` (int): Result code +- `resultValue`: Serializable result value + +## Usage Example + +```python +import androidhelper + +droid = androidhelper.Android() + +# After performing an activity, set the result +# Example: Return success with data +droid.setResultInteger(0, 200) # RESULT_OK +droid.setResultString(0, "Operation completed successfully") + +# Return an array result +droid.setResultIntegerArray(0, [1, 2, 3, 4, 5]) +``` diff --git a/source/en/qsl4a/system/qpyinterface.md b/source/en/qsl4a/system/qpyinterface.md new file mode 100644 index 0000000..012a2c0 --- /dev/null +++ b/source/en/qsl4a/system/qpyinterface.md @@ -0,0 +1,131 @@ +# QPython Interface API + +Execute QPython scripts and manage shared variables from other apps. + +## Script Execution Methods + +### executeQPy() *ASL4A* +Execute a QPython script. + +```python +executeQPy(path="", arg=None) +``` + +**Parameters:** +- `path` (str): Path to the script file +- `arg` (str, optional): Command line arguments + +**Returns:** True if started successfully + +### executeQPyAsSrv() *ASL4A* +Execute a QPython script as a service. + +```python +executeQPyAsSrv(path=None) +``` + +**Parameters:** +- `path` (str, optional): Path to the script file + +**Returns:** True if started successfully + +### executeQPyCode() *ASL4A* +Execute Python code directly. + +```python +executeQPyCode(code=None) +``` + +**Parameters:** +- `code` (str, optional): Python code to execute + +**Returns:** True if started successfully + +### executeQPyCodeAsSrv() *ASL4A* +Execute Python code as a service. + +```python +executeQPyCodeAsSrv(code=None) +``` + +**Parameters:** +- `code` (str, optional): Python code to execute + +**Returns:** True if started successfully + +## Shared Variables + +Shared variables allow communication between QPython and other apps. + +### sharedVariableSet() *ASL4A* +Set a Java shared variable. + +```python +sharedVariableSet(key, value) +``` + +**Parameters:** +- `key` (str): Variable name +- `value` (str): Variable value + +**Returns:** The stored value + +### sharedVariableGet() *ASL4A* +Get a Java shared variable. + +```python +sharedVariableGet(key) +``` + +**Parameters:** +- `key` (str): Variable name + +**Returns:** The stored value + +### sharedVariableRemove() *ASL4A* +Remove a Java shared variable. + +```python +sharedVariableRemove(key) +``` + +**Parameters:** +- `key` (str): Variable name to remove + +**Returns:** The removed value + +### getLastLog() *ASL4A* +Get the last log output from QPython. + +```python +getLastLog() +``` + +**Returns:** String log content + +## Usage Example + +```python +import androidhelper + +droid = androidhelper.Android() + +# Execute a script +droid.executeQPy("/sdcard/my_script.py", arg="test") + +# Execute code directly +code = "print('Hello from QPython!')" +droid.executeQPyCode(code) + +# Use shared variables +droid.sharedVariableSet("username", "alice") +username = droid.sharedVariableGet("username").result +print(f"Username: {username}") + +# Remove variable +droid.sharedVariableRemove("username") + +# Get recent log +log = droid.getLastLog().result +print(f"Log: {log}") +``` diff --git a/source/en/qsl4a/system/sensors.md b/source/en/qsl4a/system/sensors.md index 963e44d..c9e2dc8 100644 --- a/source/en/qsl4a/system/sensors.md +++ b/source/en/qsl4a/system/sensors.md @@ -97,6 +97,15 @@ sensorsGetStepCounter() **Returns:** Number of steps +### sensorsGetAccuracy() *ASL4A* +Get the current sensor accuracy. + +```python +sensorsGetAccuracy() +``` + +**Returns:** Accuracy value (0-3: UNRELIABLE, ACCURACY_LOW, ACCURACY_MEDIUM, ACCURACY_HIGH) + ## Usage Example ```python diff --git a/source/en/qsl4a/system/settings.md b/source/en/qsl4a/system/settings.md new file mode 100644 index 0000000..4032641 --- /dev/null +++ b/source/en/qsl4a/system/settings.md @@ -0,0 +1,234 @@ +# Settings API + +Control system settings including screen, sound, and network settings. + +## Screen Settings + +### setScreenTimeout() *ASL4A* +Set the screen timeout value. + +```python +setScreenTimeout(value) +``` + +**Parameters:** +- `value` (int): Screen timeout in seconds + +**Returns:** Previous timeout value + +### getScreenTimeout() *ASL4A* +Get the current screen timeout. + +```python +getScreenTimeout() +``` + +**Returns:** Current screen timeout in seconds + +### getScreenBrightness() *ASL4A* +Get the screen brightness value. + +```python +getScreenBrightness() +``` + +**Returns:** Brightness value (0-255) + +### setScreenBrightness() *ASL4A* +Set the screen brightness. + +```python +setScreenBrightness(value=None) +``` + +**Parameters:** +- `value` (int, optional): Brightness value (0-255), or None for auto + +**Returns:** Previous brightness value + +### checkScreenOn() *ASL4A* +Check if the screen is on. + +```python +checkScreenOn() +``` + +**Returns:** True if screen is on, False otherwise + +## Airplane Mode + +### checkAirplaneMode() *ASL4A* +Check if airplane mode is enabled. + +```python +checkAirplaneMode() +``` + +**Returns:** True if airplane mode is on + +## Ringer Settings + +### checkRingerSilentMode() *ASL4A* +Check if ringer is in silent mode. + +```python +checkRingerSilentMode() +``` + +**Returns:** True if silent mode is on + +### toggleRingerSilentMode() *ASL4A* +Toggle ringer silent mode. + +```python +toggleRingerSilentMode(enabled=None) +``` + +**Parameters:** +- `enabled` (bool, optional): True to enable, False to disable, None to toggle + +**Returns:** New state + +### toggleVibrateMode() *ASL4A* +Toggle vibrate mode. + +```python +toggleVibrateMode(enabled=None, ringer=None) +``` + +**Parameters:** +- `enabled` (bool, optional): Toggle vibrate on/off +- `ringer` (bool, optional): Apply to ringer mode + +**Returns:** New state + +### getVibrateMode() *ASL4A* +Get the vibrate mode setting. + +```python +getVibrateMode(ringer=None) +``` + +**Parameters:** +- `ringer` (bool, optional): Check ringer vibrate mode + +**Returns:** True if vibrate is enabled + +## Volume Settings + +### getRingerVolume() *ASL4A* +Get the current ringer volume. + +```python +getRingerVolume() +``` + +**Returns:** Ringer volume level (0-7 typically) + +### getMaxRingerVolume() *ASL4A* +Get the maximum ringer volume. + +```python +getMaxRingerVolume() +``` + +**Returns:** Maximum ringer volume + +### setRingerVolume() *ASL4A* +Set the ringer volume. + +```python +setRingerVolume(volume) +``` + +**Parameters:** +- `volume` (int): Volume level + +### getMediaVolume() *ASL4A* +Get the current media volume. + +```python +getMediaVolume() +``` + +**Returns:** Media volume level (0-15 typically) + +### getMaxMediaVolume() *ASL4A* +Get the maximum media volume. + +```python +getMaxMediaVolume() +``` + +**Returns:** Maximum media volume + +### setMediaVolume() *ASL4A* +Set the media volume. + +```python +setMediaVolume(volume) +``` + +**Parameters:** +- `volume` (int): Volume level + +## System Info + +### elapsedRealtimeNanos() *ASL4A* +Get nanoseconds since system startup. + +```python +elapsedRealtimeNanos() +``` + +**Returns:** Nanoseconds (can be used for timing) + +### getTrafficStats() *ASL4A* +Get network traffic statistics. + +```python +getTrafficStats(flags=7) +``` + +**Parameters:** +- `flags` (int): Which stats to retrieve (default: 7 = all) + +**Returns:** Dict with transmit/receive bytes + +### getAppTxBytes() *ASL4A* +Get transmit bytes for QPython app. + +```python +getAppTxBytes(packageName) +``` + +**Parameters:** +- `packageName` (str): Package name + +**Returns:** Dict with tx/rx bytes + +## Usage Example + +```python +import androidhelper + +droid = androidhelper.Android() + +# Screen settings +current_timeout = droid.getScreenTimeout().result +print(f"Current timeout: {current_timeout}s") +droid.setScreenTimeout(30) + +# Check screen +if droid.checkScreenOn().result: + print("Screen is on") + +# Volume control +media_vol = droid.getMediaVolume().result +print(f"Media volume: {media_vol}") +droid.setMediaVolume(10) + +# Check airplane mode +if droid.checkAirplaneMode().result: + print("Airplane mode is on") +``` diff --git a/source/en/qsl4a/system/wakelock.md b/source/en/qsl4a/system/wakelock.md new file mode 100644 index 0000000..5a087e3 --- /dev/null +++ b/source/en/qsl4a/system/wakelock.md @@ -0,0 +1,78 @@ +# WakeLock API + +Control device wake locks to keep the CPU or screen on. + +## Wake Lock Types + +QSL4A provides different wake lock types: + +| Type | Description | +|------|-------------| +| Full | CPU on, screen bright, keyboard bright | +| Partial | CPU on only | +| Bright | CPU on, screen bright | +| Dim | CPU on, screen dim | + +## Wake Lock Methods + +### wakeLockAcquireFull() *ASL4A* +Acquire a full wake lock (CPU on, screen bright, keyboard bright). + +```python +wakeLockAcquireFull() +``` + +### wakeLockAcquirePartial() *ASL4A* +Acquire a partial wake lock (CPU on only). + +```python +wakeLockAcquirePartial() +``` + +### wakeLockAcquireBright() *ASL4A* +Acquire a bright wake lock (CPU on, screen bright). + +```python +wakeLockAcquireBright() +``` + +### wakeLockAcquireDim() *ASL4A* +Acquire a dim wake lock (CPU on, screen dim). + +```python +wakeLockAcquireDim() +``` + +### wakeLockRelease() *ASL4A* +Release the wake lock. + +```python +wakeLockRelease() +``` + +## Usage Example + +```python +import androidhelper +import time + +droid = androidhelper.Android() + +# Acquire full wake lock +droid.wakeLockAcquireFull() + +# Do important work while keeping screen on +print("Screen will stay on") +time.sleep(10) + +# Release when done +droid.wakeLockRelease() + +# Or use partial lock for background tasks +droid.wakeLockAcquirePartial() +# CPU stays on even with screen off +time.sleep(30) +droid.wakeLockRelease() +``` + +**Note:** Remember to release wake locks when no longer needed to conserve battery. From da4bc4349d9174f997648190b3cf6cb9d89e2c29 Mon Sep 17 00:00:00 2001 From: "River@devbox" Date: Thu, 19 Mar 2026 17:59:29 +0800 Subject: [PATCH 13/21] =?UTF-8?q?Added:=20=E6=9D=A5=E8=87=AAqpython/qsl4a?= =?UTF-8?q?=E7=9A=84=E6=BA=90=E7=A0=81=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qsl4a-android/README.md | 690 ++++++++++++++++++++++++++++++++++++++++ qsl4a-android/SL4A.md | 667 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 1357 insertions(+) create mode 100644 qsl4a-android/README.md create mode 100644 qsl4a-android/SL4A.md diff --git a/qsl4a-android/README.md b/qsl4a-android/README.md new file mode 100644 index 0000000..b0b3383 --- /dev/null +++ b/qsl4a-android/README.md @@ -0,0 +1,690 @@ +# QSL4A (QPython Scripting Layer for Android) - Codebase Analysis + +## 1. Package Structure + +The QSL4A codebase is located at `/qsl4a/src/main/java/org/qpython/qsl4a/` and is organized into the following packages: + +| Package | Purpose | +|---------|---------| +| `org.qpython.qsl4a` (root) | Main entry points (QSL4APP, QPyScriptService, QSL4AScript) | +| `org.qpython.qsl4a.codec` | Base64 encoding/decoding for data serialization | +| `org.qpython.qsl4a.facade` | Android API facades (30+ classes) | +| `org.qpython.qsl4a.facade.ui` | UI-related facade components | +| `org.qpython.qsl4a.facade.usb` | USB serial communication facades | +| `org.qpython.qsl4a.qsl4a` | Core infrastructure | +| `org.qpython.qsl4a.qsl4a.event` | Event system | +| `org.qpython.qsl4a.qsl4a.exception` | Exception types | +| `org.qpython.qsl4a.qsl4a.future` | Async activity handling | +| `org.qpython.qsl4a.qsl4a.interpreter` | Interpreter management | +| `org.qpython.qsl4a.qsl4a.jsonrpc` | JSON-RPC mechanism | +| `org.qpython.qsl4a.qsl4a.language` | Language support | +| `org.qpython.qsl4a.qsl4a.rpc` | RPC annotations and descriptors | +| `org.qpython.qsl4a.qsl4a.trigger` | Event trigger system | +| `org.qpython.qsl4a.qsl4a.util` | Utility classes | + +--- + +## 2. Core Classes + +### 2.1 Main Entry Points + +#### QSL4APP (`org.qpython.qsl4a.QSL4APP`) +**Purpose:** Application class that initializes the QSL4A runtime environment. + +**Key Methods:** +- `initQSL4APP()` - Initializes interpreter configuration and starts discovering interpreters +- `getTaskExecutor()` -> `FutureActivityTaskExecutor` - Returns the task executor +- `getInterpreterConfiguration()` -> `InterpreterConfiguration` - Returns interpreter config +- `getTriggerRepository()` -> `TriggerRepository` - Returns the trigger repository +- `readyToStart()` -> `boolean` - Blocks until configuration is updated + +#### QPyScriptService (`org.qpython.qsl4a.QPyScriptService`) +**Purpose:** Android Service that hosts the JSON-RPC server for script execution. + +**Key Methods:** +- `onCreate()` - Starts the service and initializes the proxy +- `onDestroy()` - Shuts down the proxy and RPC server +- `onBind(Intent intent)` -> `IBinder` - Returns binder for service connection +- `onStartCommand(Intent intent, int flags, int startId)` -> `int` - Returns START_STICKY +- `start(Context context)` - Static method to start the service +- `stop(Context context)` - Static method to stop the service +- `startToast(Context context)` - Shows toast when service starts + +#### QSL4AScript (`org.qpython.qsl4a.QSL4AScript`) +**Purpose:** Utility class for script file handling. + +**Key Methods:** +- `getFileName(Context context)` -> `String` - Returns the main script filename (default: "main.py") +- `getFileExtension(Context context)` -> `String` - Returns file extension (e.g., ".py") + +--- + +### 2.2 AndroidProxy (`org.qpython.qsl4a.qsl4a.AndroidProxy`) +**Purpose:** Central proxy class that manages the RPC server and facade factories. + +**Key Methods:** +- `AndroidProxy(Service service, Intent intent)` - Constructor; creates JSON-RPC server with secret UUID +- `getAddress()` -> `InetSocketAddress` - Returns server address +- `startLocal()` / `startLocal(int port)` - Starts RPC server on localhost +- `shutdown()` - Shuts down the RPC server +- `getSecret()` -> `String` - Returns the authentication secret +- `getRpcReceiverManagerFactory()` -> `RpcReceiverManagerFactory` - Returns the facade factory + +--- + +### 2.3 SimpleServer (`org.qpython.qsl4a.qsl4a.SimpleServer`) +**Purpose:** Abstract base class for TCP socket servers handling concurrent connections. + +**Key Methods:** +- `startLocal(int port)` -> `InetSocketAddress` - Starts server on localhost +- `startPublic(int port)` -> `InetSocketAddress` - Starts server on public interface +- `startAllInterfaces(int port)` -> `InetSocketAddress` - Starts server on all interfaces +- `shutdown()` - Stops the server and closes all connections +- `getNumberOfConnections()` -> `int` - Returns active connection count +- `addObserver(SimpleServerObserver)` / `removeObserver(SimpleServerObserver)` - Observer pattern + +**Abstract Methods:** +- `handleConnection(Socket socket)` - Subclasses implement to handle client connections + +--- + +## 3. Facade Classes (Android API Access) + +All facades extend `RpcReceiver` and are managed by `FacadeManager`. They expose Android functionality via RPC-annotated methods. + +### 3.1 FacadeManager (`org.qpython.qsl4a.facade.FacadeManager`) +**Purpose:** Manages all facade instances and handles RPC invocation with SDK version checking. + +**Key Methods:** +- `FacadeManager(int sdkLevel, Service service, Intent intent, Collection> classList)` - Constructor +- `getSdkLevel()` -> `int` - Returns Android SDK level +- `getService()` -> `Service` - Returns the Android service +- `getIntent()` -> `Intent` - Returns the launch intent +- `invoke(Class clazz, Method method, Object[] args)` -> `Object` - Invokes RPC with deprecation/minSdk checks +- `getReceiver(Class clazz)` -> `T` - Gets or creates a facade instance + +### 3.2 FacadeConfiguration (`org.qpython.qsl4a.facade.FacadeConfiguration`) +**Purpose:** Registry of all available facade classes. + +**Registered Facades (30 total):** +- `AndroidFacade` - Core Android operations +- `ApplicationManagerFacade` - App management +- `CameraFacade` - Camera access +- `CommonIntentsFacade` - Common Android intents +- `ContactsFacade` - Contacts access +- `EventFacade` - Event queue management +- `LocationFacade` - GPS/Location +- `PhoneFacade` - Phone operations +- `MediaRecorderFacade` - Audio recording +- `SensorManagerFacade` - Device sensors +- `SettingsFacade` - System settings +- `SmsFacade` - SMS operations +- `SpeechRecognitionFacade` - Voice input +- `ToneGeneratorFacade` - DTMF tones +- `WakeLockFacade` - Power management +- `WifiFacade` - WiFi operations +- `UiFacade` - UI operations +- `BatteryManagerFacade` - Battery info +- `MediaPlayerFacade` - Media playback +- `PreferencesFacade` - Shared preferences +- `QPyInterfaceFacade` - QPython-specific +- `USBHostSerialFacade` - USB serial +- `CipherFacade` - Cryptography +- `TextToSpeechFacade` - TTS +- `BluetoothFacade` - Bluetooth +- `SignalStrengthFacade` - Signal info +- `WebCamFacade` - Camera +- `FloatViewFacade` - Overlay views +- `DocumentFileFacade` - Document access +- `HarmonyOsFacade` - HarmonyOS compatibility +- `FtpFacade` - FTP server +- `AccessibilityFacade` - Accessibility services + +### 3.3 AndroidFacade (`org.qpython.qsl4a.facade.AndroidFacade`) +**Purpose:** Main facade providing core Android functionality. + +**Key RPC Methods:** +- `setClipboard(String text)` / `getClipboard()` -> `String` - Clipboard operations +- `startActivity(String action, String uri, ...)` - Start activities +- `startActivityForResult(String action, ...)` -> `Intent` - Activity with result +- `sendBroadcast(String action, ...)` - Send broadcasts +- `makeIntent(String action, ...)` -> `Intent` - Create intents +- `makeToast(String message, int length, ...)` - Show toast notifications +- `notify(String title, String message, String uri, ...)` - Show persistent notifications +- `getNetworkStatus()` -> `boolean` - Check network +- `getIntent()` -> `Object` - Get launch intent +- `getPackageVersion(String packageName)` -> `String` - Get package version +- `environment()` -> `Map` - Environment info +- `getConstants(String classname)` -> `Bundle` - Get class constants +- `vibrate(int duration)` - Vibrate device + +### 3.4 EventFacade (`org.qpython.qsl4a.facade.EventFacade`) +**Purpose:** Event queue management for async event handling. + +**Key RPC Methods:** +- `eventClearBuffer()` - Clears all events from buffer +- `eventPoll(int number_of_events)` -> `List` - Returns and removes oldest n events +- `eventWaitFor(String eventName, Integer timeout)` -> `Event` - Blocks until event occurs +- `eventWait(Integer timeout)` -> `Event` - Blocks until any event occurs +- `eventPost(String name, String data, Boolean enqueue)` - Post event to queue +- `startEventDispatcher(int port)` -> `int` - Opens socket for event streaming +- `stopEventDispatcher()` - Stops event server +- `eventRegisterForBroadcast(String category, Boolean enqueue)` -> `boolean` - Register broadcast listener +- `eventUnregisterForBroadcast(String category)` - Unregister broadcast listener +- `eventGetBrodcastCategories()` -> `Set` - Get registered categories + +### 3.5 SensorManagerFacade (`org.qpython.qsl4a.facade.SensorManagerFacade`) +**Purpose:** Access device sensors (accelerometer, magnetometer, light, gyroscope, etc.). + +**Key RPC Methods:** +- `startSensingTimed(int sensorNumber, int delayTime)` - Starts sensor data collection +- `startSensingThreshold(int sensorNumber, int threshold, int axis)` - Threshold-based sensing +- `readSensors()` -> `Bundle` - Returns most recent sensor data +- `stopSensing()` - Stops sensor collection +- `sensorsGetAccuracy()` -> `Integer` - Get sensor accuracy +- `sensorsGetLight()` -> `Float` - Get light level +- `sensorsReadAccelerometer()` -> `List` - [x, y, z] acceleration +- `sensorsReadMagnetometer()` -> `List` - [x, y, z] magnetic field +- `sensorsReadGyroscope()` -> `List` - [x, y, z] angular speed +- `sensorsReadOrientation()` -> `List` - [azimuth, pitch, roll] +- `sensorsGetStepCounter()` -> `Integer` - Step count + +### 3.6 LocationFacade (`org.qpython.qsl4a.facade.LocationFacade`) +**Purpose:** GPS and network location services. + +**Key RPC Methods:** +- `startLocating(int minUpdateTime, int minUpdateDistance, boolean updateGnssStatus)` - Start location updates +- `readLocation()` -> `Map` - Get current location by provider +- `readGnssStatus()` -> `JSONArray` - Get GNSS satellite status (Android 8+) +- `stopLocating()` - Stop location updates +- `getLastKnownLocation()` -> `Map` - Get last known location +- `geocode(double latitude, double longitude, int maxResults)` -> `JSONObject[]` - Reverse geocode +- `locationProviders()` -> `List` - Get available providers +- `locationProviderEnabled(String provider)` -> `boolean` - Check if provider enabled + +--- + +## 4. RPC Mechanism + +### 4.1 JSON-RPC Server Architecture + +``` +JsonRpcServer (extends SimpleServer) + | + +-- handleConnection(Socket socket) + | | + | +-- Reads JSON requests line-by-line + | +-- First RPC must be _authenticate with secret + | +-- Looks up MethodDescriptor by method name + | +-- Invokes method and returns JsonRpcResult + | + +-- RpcReceiverManagerFactory + | + +-- FacadeManagerFactory + | + +-- Creates FacadeManager instances + +-- Each FacadeManager manages multiple RpcReceivers (Facades) +``` + +### 4.2 JsonRpcServer (`org.qpython.qsl4a.qsl4a.jsonrpc.JsonRpcServer`) +**Purpose:** TCP-based JSON-RPC server that handles client connections. + +**Key Methods:** +- `JsonRpcServer(RpcReceiverManagerFactory managerFactory, String handshake)` - Constructor +- `handleConnection(Socket socket)` - Processes JSON-RPC requests +- `shutdown()` - Notifies all receivers and stops server + +**Request Format:** +```json +{"id": 1, "method": "methodName", "params": [arg1, arg2]} +``` + +**Response Format:** +```json +{"id": 1, "result": {...}, "error": null} +``` + +### 4.3 RpcReceiverManager (`org.qpython.qsl4a.qsl4a.jsonrpc.RpcReceiverManager`) +**Purpose:** Base class managing RPC receivers (facades) and method lookup. + +**Key Methods:** +- `RpcReceiverManager(Collection> classList)` - Initializes receivers and collects RPC methods +- `getReceiver(Class clazz)` -> `T` - Gets or lazily creates facade instance +- `getMethodDescriptor(String methodName)` -> `MethodDescriptor` - Looks up RPC method +- `invoke(Class clazz, Method method, Object[] args)` -> `Object` - Invokes method via reflection +- `shutdown()` - Calls shutdown on all receivers + +### 4.4 RpcReceiver (`org.qpython.qsl4a.qsl4a.jsonrpc.RpcReceiver`) +**Purpose:** Abstract base class for all facades. + +**Methods:** +- `RpcReceiver(RpcReceiverManager manager)` - Constructor; stores manager reference +- `shutdown()` - Abstract; facades implement cleanup + +### 4.5 JsonRpcResult (`org.qpython.qsl4a.qsl4a.jsonrpc.JsonRpcResult`) +**Purpose:** Utility class for building JSON-RPC response objects. + +**Static Methods:** +- `empty(int id)` -> `JSONObject` - Creates empty result response +- `result(int id, Object data)` -> `JSONObject` - Creates success response +- `error(int id, Throwable t)` -> `JSONObject` - Creates error response + +### 4.6 JsonBuilder (`org.qpython.qsl4a.qsl4a.jsonrpc.JsonBuilder`) +**Purpose:** Converts Java objects to JSON for RPC responses. + +**Key Methods:** +- `build(Object data)` -> `Object` - Converts Java object to JSON-compatible type +- `buildBundleItem(Object data)` -> `Object` - Handles Bundle, Uri, byte[] special cases +- `buildJsonMap(Map map)` -> `JSONObject` - Converts Map to JSONObject +- `buildJsonList(List list)` -> `JSONArray` - Converts List to JSONArray +- `buildJsonIntent(Intent intent)` -> `JSONObject` - Converts Intent to JSON +- `buildJsonEvent(Event event)` -> `JSONObject` - Converts Event to JSON + +**Supported Type Conversions:** +- `null` -> `JSONObject.NULL` +- `Integer, Long, Double` -> primitive values +- `String, Boolean` -> as-is +- `JSONObject, JSONArray` -> as-is +- `List` -> `JSONArray` +- `Set` -> `JSONArray` +- `Map` -> `JSONObject` +- `Intent` -> `{"data", "type", "extras", "categories", "action", "packagename", "classname", "flags"}` +- `Bundle` -> `JSONObject` with special markers (`\0uri\0`, `\0byte\0`) +- `Event` -> `{"name", "data", "time"}` + +### 4.7 MethodDescriptor (`org.qpython.qsl4a.qsl4a.rpc.MethodDescriptor`) +**Purpose:** Describes an RPC method, handles parameter conversion, and invokes methods. + +**Key Methods:** +- `collectFrom(Class clazz)` -> `Collection` - Static; collects @Rpc-annotated methods +- `invoke(RpcReceiverManager manager, JSONArray parameters)` -> `Object` - Invokes RPC with parameter conversion +- `convertParameter(JSONArray parameters, int index, Type type)` -> `Object` - Converts JSON param to Java type +- `buildIntent(JSONObject jsonObject)` -> `Intent` - Builds Intent from JSON +- `getName()` -> `String` - Returns RPC name (or @RpcName value) +- `getHelp()` -> `String` - Generates help text from annotations + +**Parameter Type Conversions:** +- `Boolean.class` - from JSON boolean or integer (0/1) +- `Long.class` - from JSON long +- `Double.class` - from JSON double +- `Integer.class` - from JSON int +- `Intent.class` - from JSONObject using `buildIntent()` +- Other types - direct cast from JSON + +--- + +## 5. RPC Annotations + +### 5.1 @Rpc (`org.qpython.qsl4a.qsl4a.rpc.Rpc`) +**Purpose:** Marks a method as an RPC endpoint. + +**Attributes:** +- `description()` -> `String` - Brief description of the function +- `returns()` -> `String` - Description of return value (default: "") + +### 5.2 @RpcParameter (`org.qpython.qsl4a.qsl4a.rpc.RpcParameter`) +**Purpose:** Documents RPC parameters. + +**Attributes:** +- `name()` -> `String` - Formal parameter name +- `description()` -> `String` - Parameter description (default: "") + +### 5.3 @RpcDefault (`org.qpython.qsl4a.qsl4a.rpc.RpcDefault`) +**Purpose:** Specifies default value for optional parameters. + +**Attributes:** +- `value()` -> `String` - Default value as string +- `converter()` -> `Class` - Type converter (default: Converter.class) + +### 5.4 @RpcOptional (`org.qpython.qsl4a.qsl4a.rpc.RpcOptional`) +**Purpose:** Marks parameter as optional with null default. + +### 5.5 @RpcName (`org.qpython.qsl4a.qsl4a.rpc.RpcName`) +**Purpose:** Overrides the RPC method name. + +**Attributes:** +- `name()` -> `String` - Custom RPC name + +### 5.6 @RpcDeprecated (`org.qpython.qsl4a.qsl4a.rpc.RpcDeprecated`) +**Purpose:** Marks RPC as deprecated. + +**Attributes:** +- `value()` -> `String` - Replacement method name +- `release()` -> `String` - Release version when deprecated + +### 5.7 @RpcMinSdk (`org.qpython.qsl4a.qsl4a.rpc.RpcMinSdk`) +**Purpose:** Specifies minimum Android SDK version required. + +**Attributes:** +- `value()` -> `int` - Minimum SDK level + +### 5.8 @RpcStartEvent (`org.qpython.qsl4a.qsl4a.rpc.RpcStartEvent`) +**Purpose:** Indicates RPC starts an event stream. + +**Attributes:** +- `value()` -> `String` - Event name + +### 5.9 @RpcStopEvent (`org.qpython.qsl4a.qsl4a.rpc.RpcStopEvent`) +**Purpose:** Indicates RPC stops an event stream. + +**Attributes:** +- `value()` -> `String` - Event name + +--- + +## 6. Interpreter System + +### 6.1 Interpreter (`org.qpython.qsl4a.qsl4a.interpreter.Interpreter`) +**Purpose:** Represents a language interpreter with execution parameters. + +**Key Methods:** +- `buildFromMaps(Map data, Map env, Map args)` -> `Interpreter` - Static factory +- `getName()` -> `String` - Interpreter name (e.g., "python") +- `getNiceName()` -> `String` - Display name +- `getExtension()` -> `String` - File extension (e.g., ".py") +- `getBinary()` -> `File` - Path to interpreter binary +- `getScriptCommand()` -> `String` - Command template for script execution +- `getInteractiveCommand()` -> `String` - Command for interactive mode +- `hasInteractiveMode()` -> `boolean` - Whether interactive mode supported +- `getArguments()` -> `List` - CLI arguments +- `getEnvironmentVariables()` -> `Map` - Environment variables +- `getLanguage()` -> `Language` - Associated language object +- `getRpcText(String content, MethodDescriptor rpc, String[] values)` -> `String` - Generates RPC call code +- `isInstalled()` -> `boolean` - Whether binary exists + +### 6.2 InterpreterConfiguration (`org.qpython.qsl4a.qsl4a.interpreter.InterpreterConfiguration`) +**Purpose:** Discovers and manages installed interpreters via Android package intents. + +**Key Methods:** +- `startDiscovering()` / `startDiscovering(String mime)` - Starts interpreter discovery +- `isDiscoveryComplete()` -> `boolean` - Whether discovery finished +- `registerObserver(ConfigurationObserver observer)` - Register for config changes +- `getSupportedInterpreters()` -> `List` - All known interpreters +- `getInstalledInterpreters()` -> `List` - Only installed ones +- `getInteractiveInterpreters()` -> `List` - Those with interactive mode +- `getInterpreterByName(String name)` -> `Interpreter` - Find by name +- `getInterpreterForScript(String scriptName)` -> `Interpreter` - Find by file extension + +**Discovery Mechanism:** +- Queries package manager for activities handling `InterpreterConstants.MIME + extension` +- Reads interpreter properties from ContentProvider +- Broadcasts `ACTION_INTERPRETER_ADDED` / `ACTION_INTERPRETER_REMOVED` + +### 6.3 InterpreterDescriptor (`org.qpython.qsl4a.qsl4a.interpreter.InterpreterDescriptor`) +**Purpose:** Metadata about an interpreter version. + +**Attributes:** +- `getVersion()` -> `String` - Version string +- `getName()` -> `String` - Interpreter name +- `isInstalled()` -> `boolean` - Installation status + +### 6.4 SupportedLanguages (`org.qpython.qsl4a.qsl4a.language.SupportedLanguages`) +**Purpose:** Registry mapping file extensions to Language classes. + +**Supported Languages:** +| Extension | Language Class | +|-----------|---------------| +| `.html` | HtmlLanguage | +| `.bsh` | BeanShellLanguage | +| `.js` | JavaScriptLanguage | +| `.lua` | LuaLanguage | +| `.pl` | PerlLanguage | +| `.py` | PythonLanguage | +| `.rb` | RubyLanguage | +| `.tcl` | TclLanguage | +| `.php` | PhpLanguage | +| `.sl` | SleepLanguage | +| `.nut` | SquirrelLanguage | + +### 6.5 Language (`org.qpython.qsl4a.qsl4a.language.Language`) +**Purpose:** Base class for language-specific code generation. + +**Key Methods:** +- `getContentTemplate()` -> `String` - Template for new scripts +- `getImportStatement()` -> `String` - Import statement (overridden per language) +- `getRpcReceiverDeclaration(String rpcReceiver)` -> `String` - RPC receiver instantiation +- `getDefaultRpcReceiver()` -> `String` - Default receiver name (default: "droid") +- `getRpcText(String content, MethodDescriptor rpc, String[] values)` -> `String` - Generates method call +- `autoClose(char token)` -> `String` - Returns closing token for auto-complete + +### 6.6 PythonLanguage (`org.qpython.qsl4a.qsl4a.language.PythonLanguage`) +**Purpose:** Python-specific code generation. + +**Generated Template:** +```python +import android + +droid = android.Android() +``` + +--- + +## 7. Trigger System + +### 7.1 TriggerRepository (`org.qpython.qsl4a.qsl4a.trigger.TriggerRepository`) +**Purpose:** Persistent storage and management of event triggers. + +**Key Methods:** +- `put(Trigger trigger)` - Adds trigger and ensures TriggerService running +- `remove(Trigger trigger)` - Removes trigger +- `getAllTriggers()` -> `Multimap` - All triggers by event name +- `isEmpty()` -> `boolean` - Whether any triggers exist +- `addObserver(TriggerRepositoryObserver observer)` - Register for changes +- `bootstrapObserver(TriggerRepositoryObserver observer)` - Register and notify of existing triggers +- `removeObserver(TriggerRepositoryObserver observer)` - Unregister + +**Persistence:** +- Serializes to Base64-encoded object stream +- Stores in SharedPreferences under key "TRIGGERS" + +### 7.2 Trigger (`org.qpython.qsl4a.qsl4a.trigger.Trigger`) +**Purpose:** Interface for event-driven script execution. + +**Methods:** +- `handleEvent(Event event, Context context)` - Called when trigger event occurs +- `getEventName()` -> `String` - Event name this trigger listens for + +### 7.3 ScriptTrigger (`org.qpython.qsl4a.qsl4a.trigger.ScriptTrigger`) +**Purpose:** Trigger implementation that launches a script file. + +**Key Methods:** +- `ScriptTrigger(String eventName, File script)` - Constructor +- `handleEvent(Event event, Context context)` - Starts script via IntentBuilders +- `getEventName()` -> `String` - Returns registered event name +- `getScript()` -> `File` - Returns script file + +### 7.4 Event (`org.qpython.qsl4a.qsl4a.event.Event`) +**Purpose:** Immutable event object for the event queue. + +**Attributes:** +- `mName` - Event name +- `mData` - Event data (any object) +- `mCreationTime` - Creation timestamp (milliseconds * 1000) + +**Key Methods:** +- `Event(String name, Object data)` - Constructor +- `getName()` / `setName(String name)` - Name accessor +- `getData()` / `setData(Object data)` - Data accessor +- `getCreationTime()` -> `double` - Timestamp +- `nameEquals(String name)` -> `boolean` - Name comparison + +### 7.5 EventFacade.EventObserver (`org.qpython.qsl4a.facade.EventFacade.EventObserver`) +**Purpose:** Interface for event notification. + +**Methods:** +- `onEventReceived(Event event)` - Called when event occurs + +### 7.6 EventServer (`org.qpython.qsl4a.facade.EventServer`) +**Purpose:** TCP server that streams events to connected clients. + +**Key Methods:** +- `EventServer(int port)` - Constructor; starts server on specified port +- `getAddress()` -> `InetSocketAddress` - Server address +- `onEventReceived(Event event)` - Broadcasts event to all listeners +- `shutdown()` - Notifies listeners and stops server + +--- + +## 8. Async/Future System + +### 8.1 FutureResult (`org.qpython.qsl4a.qsl4a.future.FutureResult`) +**Purpose:** Blocking future for async operation results. + +**Key Methods:** +- `set(T result)` - Sets result and unblocks waiters +- `get()` -> `T` - Blocks until result available +- `get(long timeout, TimeUnit unit)` -> `T` - Blocks with timeout +- `isDone()` -> `boolean` - Whether result available +- `cancel(boolean mayInterruptIfRunning)` -> `boolean` - Always returns false + +### 8.2 FutureActivityTask (`org.qpython.qsl4a.qsl4a.future.FutureActivityTask`) +**Purpose:** Activity lifecycle wrapper for async UI operations. + +**Key Methods:** +- `onCreate()` / `onStart()` / `onResume()` / `onPause()` / `onStop()` / `onDestroy()` - Lifecycle callbacks +- `onActivityResult(int requestCode, int resultCode, Intent data)` - Handle activity result +- `setActivity(Activity activity)` / `getActivity()` -> `Activity` - Activity access +- `setTaskDescription(String title)` - Set task description (for recent apps) +- `setResult(T result)` - Set the task result +- `getResult()` -> `T` - Get the result (blocks) +- `finish()` - Finish the activity +- `startActivity(Intent intent)` / `startActivityForResult(Intent intent, int requestCode)` - Start activities + +### 8.3 FutureActivityTaskExecutor (`org.qpython.qsl4a.qsl4a.future.FutureActivityTaskExecutor`) +**Purpose:** Executes FutureActivityTask instances on the main thread. + +**Key Methods:** +- `execute(FutureActivityTask task)` - Execute task with activity lifecycle + +--- + +## 9. File Structure Summary + +``` +/qsl4a/src/main/java/org/qpython/qsl4a/ +├── QSL4APP.java # Application class +├── QPyScriptService.java # Android Service (RPC server host) +├── QSL4AScript.java # Script file utilities +├── codec/ # Base64 encoding +├── facade/ # Android API facades (30+ classes) +│ ├── AndroidFacade.java # Core Android operations +│ ├── EventFacade.java # Event queue management +│ ├── LocationFacade.java # GPS/Location +│ ├── SensorManagerFacade.java # Device sensors +│ ├── UiFacade.java # UI operations +│ └── ... (25+ more facades) +├── qsl4a/ +│ ├── AndroidProxy.java # Central proxy/RPC server +│ ├── SimpleServer.java # TCP socket server base +│ ├── Constants.java # Intent/action constants +│ ├── event/ +│ │ └── Event.java # Event data class +│ ├── exception/ +│ │ └── Sl4aException.java # Base exception +│ ├── future/ +│ │ ├── FutureResult.java # Async result holder +│ │ ├── FutureActivityTask.java # Activity lifecycle wrapper +│ │ └── FutureActivityTaskExecutor.java +│ ├── interpreter/ +│ │ ├── Interpreter.java # Interpreter descriptor +│ │ ├── InterpreterConfiguration.java # Interpreter discovery +│ │ └── Language.java # Base language class +│ ├── jsonrpc/ +│ │ ├── JsonRpcServer.java # JSON-RPC TCP server +│ │ ├── RpcReceiver.java # Base facade class +│ │ ├── RpcReceiverManager.java # Manages facades +│ │ ├── RpcReceiverManagerFactory.java +│ │ ├── JsonRpcResult.java # Response builder +│ │ └── JsonBuilder.java # JSON serialization +│ ├── language/ +│ │ ├── Language.java # Base language +│ │ ├── PythonLanguage.java # Python-specific +│ │ └── SupportedLanguages.java # Extension registry +│ ├── rpc/ +│ │ ├── Rpc.java # @Rpc annotation +│ │ ├── RpcParameter.java # @RpcParameter annotation +│ │ ├── RpcDefault.java # @RpcDefault annotation +│ │ ├── RpcOptional.java # @RpcOptional annotation +│ │ ├── RpcName.java # @RpcName annotation +│ │ ├── RpcDeprecated.java # @RpcDeprecated annotation +│ │ ├── RpcMinSdk.java # @RpcMinSdk annotation +│ │ ├── RpcStartEvent.java # @RpcStartEvent annotation +│ │ ├── RpcStopEvent.java # @RpcStopEvent annotation +│ │ └── MethodDescriptor.java # RPC method descriptor +│ ├── trigger/ +│ │ ├── Trigger.java # Trigger interface +│ │ ├── ScriptTrigger.java # Script launch trigger +│ │ └── TriggerRepository.java # Persistent trigger storage +│ └── util/ # Utilities +``` + +--- + +## 10. Key Data Flow + +### 10.1 Script Execution Flow +1. Script is launched (via Intent or direct start) +2. `QPyScriptService` starts via `AndroidProxy` +3. `JsonRpcServer` starts on localhost with random port +4. Script connects via TCP socket +5. First RPC must be `_authenticate(secret)` +6. Script calls RPCs like `droid.makeToast("Hello")` +7. `JsonRpcServer` looks up `MethodDescriptor` via `RpcReceiverManager` +8. `MethodDescriptor.invoke()` converts JSON params to Java types +9. Facade method is invoked via reflection +10. Result is converted back to JSON via `JsonBuilder` +11. Response sent back to client + +### 10.2 Event Flow +1. Facade calls `eventPost("sensors", data)` or similar +2. `EventFacade.postEvent()` creates `Event` object +3. Event added to `ConcurrentLinkedQueue` (max 1024) +4. Named event observers are notified +5. Global event observers are notified +6. `EventServer` broadcasts to all connected TCP clients + +### 10.3 Trigger Flow +1. `TriggerRepository.put(trigger)` stores trigger +2. Trigger serialized to SharedPreferences +3. When matching event occurs, `Trigger.handleEvent()` is called +4. `ScriptTrigger` starts script via `IntentBuilders.buildStartInBackgroundIntent()` + +--- + +## 11. All Facade Classes Reference + +| Facade Class | Purpose | +|--------------|---------| +| `AccessibilityFacade` | Accessibility services | +| `ActivityResultFacade` | Activity result handling | +| `AndroidFacade` | Core Android operations | +| `ApplicationManagerFacade` | Application management | +| `BatteryManagerFacade` | Battery status | +| `BluetoothFacade` | Bluetooth operations | +| `CameraFacade` | Camera access | +| `CipherFacade` | Cryptography/Encryption | +| `CommonIntentsFacade` | Common Android intents | +| `ContactsFacade` | Contacts access | +| `DocumentFileFacade` | Document file access | +| `EventFacade` | Event queue management | +| `EventServer` | TCP event streaming | +| `FloatViewFacade` | Overlay/floating views | +| `FtpFacade` | FTP server | +| `HarmonyOsFacade` | HarmonyOS compatibility | +| `LocationFacade` | GPS/Location | +| `MediaPlayerFacade` | Media playback | +| `MediaRecorderFacade` | Audio/video recording | +| `MjpegServer` | MJPEG streaming | +| `PhoneFacade` | Phone operations | +| `PreferencesFacade` | Shared preferences | +| `QPyInterfaceFacade` | QPython-specific interface | +| `SensorManagerFacade` | Device sensors | +| `SettingsFacade` | System settings | +| `SignalStrengthFacade` | Signal strength | +| `SmsFacade` | SMS operations | +| `SpeechRecognitionFacade` | Speech recognition | +| `TextToSpeechFacade` | Text-to-speech | +| `ToneGeneratorFacade` | DTMF tones | +| `WakeLockFacade` | Power management | +| `WebCamFacade` | Webcam streaming | +| `WifiFacade` | WiFi operations | diff --git a/qsl4a-android/SL4A.md b/qsl4a-android/SL4A.md new file mode 100644 index 0000000..5c7aac9 --- /dev/null +++ b/qsl4a-android/SL4A.md @@ -0,0 +1,667 @@ +# QPython SL4A Facade RPC Methods + +This document lists all @Rpc annotated methods from the QPython SL4A facade classes. + +--- + +## AndroidFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| setClipboard | text(String) | void | Put text in the clipboard. | +| getClipboard | | String | Read text from the clipboard. | +| startActivityForResult | action(String), uri(optional), type(optional), extras(optional), packagename(optional), classname(optional), flags(optional) | Intent | Starts an activity and returns the result. | +| startActivityForResultIntent | intent(Intent) | Intent | Starts an activity and returns the result. | +| startActivity | action(String), uri(optional), type(optional), extras(optional), wait(optional), packagename(optional), classname(optional), flags(optional) | void | Starts an activity. | +| sendBroadcast | action(String), uri(optional), type(optional), extras(optional), packagename(optional), classname(optional), flags(optional) | void | Send a broadcast. | +| makeIntent | action(String), uri(optional), type(optional), extras(optional), categories(optional), packagename(optional), classname(optional), flags(optional) | Intent | Create an Intent. | +| startActivityIntent | intent(Intent), wait(optional) | void | Start Activity using Intent | +| sendBroadcastIntent | intent(Intent) | void | Send Broadcast Intent | +| vibrate | duration(Integer, default=300) | void | Vibrates the phone or a specified duration in milliseconds. | +| makeToast | message(String), length(Integer, default=0), isHtml(Boolean, default=false), backColor(optional), textColor(optional), textSize(Integer, default=0), textAlign(Integer, default=0) | void | Displays a Toast notification. | +| notify | title(String), message(String), uri(optional), arg(optional) | void | Displays a notification that will be canceled when the user clicks on it. | +| getNetworkStatus | | boolean | Returns the status of network connection. | +| getIntent | | Object | Returns the intent that launched the script. | +| getPackageVersionCode | packageName(String) | int | Returns package version code. | +| getPackageVersion | packageName(String) | String | Returns package version name. | +| log | message(String) | void | Writes message to logcat. | +| environment | | Map | A map of various useful environment details | +| getConstants | classname(String) | Bundle | Get list of constants (static final fields) for a class | + +--- + +## EventFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| eventClearBuffer | | void | Clears all events from the event buffer. | +| eventRegisterForBroadcast | category(String), enqueue(Boolean, default=true) | boolean | Registers a listener for a new broadcast signal | +| eventUnregisterForBroadcast | category(String) | void | Stop listening for a broadcast signal | +| eventGetBrodcastCategories | | Set | Lists all the broadcast signals we are listening for | +| eventPoll | number_of_events(Integer, default=1) | List | Returns and removes the oldest n events from the event buffer. | +| eventWaitFor | eventName(String), timeout(optional) | Event | Blocks until an event with the supplied name occurs. | +| eventWait | timeout(optional) | Event | Blocks until an event occurs. | +| eventPost | name(String), data(String), enqueue(Boolean, default=false, optional) | void | Post an event to the event queue. | +| rpcPostEvent | name(String), data(String) | void | Post an event to the event queue. (**Deprecated**, use eventPost) | +| receiveEvent | | Event | Returns and removes the oldest event from the event buffer. (**Deprecated**, use eventPoll) | +| waitForEvent | eventName(String), timeout(optional) | Event | Blocks until an event with the supplied name occurs. (**Deprecated**, use eventWaitFor) | +| startEventDispatcher | port(Integer, default=0, optional) | int | Opens up a socket where you can read for events posted | +| stopEventDispatcher | | void | Stops the event server, you can't read in the port anymore | + +--- + +## LocationFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| locationProviders | | List | Returns availables providers on the phone | +| locationProviderEnabled | provider(String) | boolean | Ask if provider is enabled | +| startLocating | minUpdateTime(Integer, default=60000), minUpdateDistance(Integer, default=30), updateGnssStatus(Boolean, default=false) | void | Starts collecting location data. (starts event "location") | +| readLocation | | Map | Returns the current location as indicated by all available providers. | +| readGnssStatus | | JSONArray | read Global Navigation Satellite System status if Android >= 8. | +| stopLocating | | void | Stops collecting location data. (stops event "location") | +| getLastKnownLocation | | Map | Returns the last known location of the device. | +| geocode | latitude(Double), longitude(Double), maxResults(Integer, default=1) | JSONObject[] | Returns a list of addresses for the given latitude and longitude. | + +--- + +## SensorManagerFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| startSensingTimed | sensorNumber(Integer), delayTime(Integer) | void | Starts recording sensor data to be available for polling. (starts event "sensors") | +| startSensingThreshold | sensorNumber(Integer), threshold(Integer), axis(Integer) | void | Records to the Event Queue sensor data exceeding a chosen threshold. (starts event "threshold") | +| readSensors | | Bundle | Returns the most recently recorded sensor data. | +| stopSensing | | void | Stops collecting sensor data. (stops event "sensors") | +| sensorsGetAccuracy | | Integer | Returns the most recently received accuracy value. | +| sensorsGetLight | | Float | Returns the most recently received light value. | +| sensorsGetStepCounter | | Integer | Returns the most recently step counter. | +| sensorsReadAccelerometer | | List | Returns the most recently received accelerometer values. | +| sensorsReadMagnetometer | | List | Returns the most recently received magnetic field values. | +| sensorsReadGyroscope | | List | Returns the most recently received Gyroscope values. | +| sensorsReadOrientation | | List | Returns the most recently received orientation values. | + +--- + +## PhoneFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| startTrackingPhoneState | | void | Starts tracking phone state. (starts event "phone") | +| readPhoneState | | Bundle | Returns the current phone state and incoming number. | +| stopTrackingPhoneState | | void | Stops tracking phone state. (stops event "phone") | +| phoneCall | uri(String) | void | Calls a contact/phone number by URI. | +| phoneCallNumber | phone number(String) | void | Calls a phone number. | +| phoneDial | uri(String) | void | Dials a contact/phone number by URI. | +| phoneDialNumber | phone number(String) | void | Dials a phone number. | +| getCellLocation | | JSONObject | Returns the current cell location. | +| getAllCellsLocation | | JSONArray | Returns all the cells location. | +| getNetworkOperator | | String | Returns the numeric name (MCC+MNC) of current registered operator. | +| getNetworkOperatorName | | String | Returns the alphabetic name of current registered operator. | +| getNetworkType | | String | Returns a the radio technology (network type) currently in use on the device. | +| getPhoneType | | String | Returns the device phone type. | +| getSimCountryIso | | String | Returns the ISO country code equivalent for the SIM provider's country code. | +| getSimOperator | | String | Returns the MCC+MNC (mobile country code + mobile network code) of the provider of the SIM. | +| getSimOperatorName | | String | Returns the Service Provider Name (SPN). | +| getSimSerialNumber | | String | Returns the serial number of the SIM, if applicable. | +| getSimState | | String | Returns the state of the device SIM card. | +| getSubscriberId | | String | Returns the unique subscriber ID, for example, the IMSI for a GSM phone. | +| getVoiceMailAlphaTag | | String | Retrieves the alphabetic identifier associated with the voice mail number. | +| getVoiceMailNumber | | String | Returns the voice mail number. | +| checkNetworkRoaming | | Boolean | Returns true if the device is considered roaming on the current network. | +| getDeviceId | index(Integer, default=0) | String | Returns the unique device ID (IMEI for GSM, MEID for CDMA). (**Deprecated**) | +| getMeid | index(optional) | String | MEID for CDMA phones, Need Android >= 8.0. | +| getImei | index(optional) | String | IMEI for GSM phones, Need Android >= 8.0. | +| getDeviceSoftwareVersion | | String | Returns the software version number for the device. | +| getLine1Number | | String | Returns the phone number string for line 1. (requires API O) | +| getAllCellInfo | | List | Returns all the neighboring cell information of the device. | +| setDataEnabled | enable(Boolean) | void | set data enabled | + +--- + +## SmsFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| smsSend | destinationAddress(String), text(String) | void | Sends an SMS. | +| smsGetMessageCount | unreadOnly(Boolean), folder(String, default="inbox") | Integer | Returns the number of messages. | +| smsGetMessageIds | unreadOnly(Boolean), folder(String, default="inbox") | List | Returns a List of all message IDs. | +| smsGetMessages | unreadOnly(Boolean), folder(String, default="inbox"), attributes(optional) | List | Returns a List of all messages. | +| smsGetMessageById | id(Integer), attributes(optional) | JSONObject | Returns message attributes. | +| smsGetAttributes | | List | Returns a List of all possible message attributes. | +| smsDeleteMessage | id(Integer) | Boolean | Deletes a message. | +| smsMarkMessageRead | ids(JSONArray), read(Boolean) | Integer | Marks messages as read. | + +--- + +## CameraFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| cameraCapturePicture | targetPath(optional), cameraId(Integer, default=0), useAutoFocus(Boolean, default=true) | String | Take a picture and save it to the specified path. | +| takePicture | path(optional) | String | Take Picture with system camera. | +| takeVideo | path(optional), quality(Integer, default=1) | String | Take Video with system camera. | +| cameraSetTorchMode | enabled(Boolean) | void | open or close flash light torch of camera. | + +--- + +## MediaRecorderFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| recorderStartMicrophone | path(optional) | String | Records audio from the microphone and saves it to the given location. | +| recorderStop | | void | Stops a previously started recording. | +| recorderPause | | void | Pause a previously started recording. | +| recorderResume | | void | Resume a previously paused recording. | +| recordAudio | | String | Record Audio with system soundrecorder. | +| recorderStartScreenRecord | path(optional), audio(Integer, default=1), targetPixels(optional), frameRate(Integer, default=30), bitRate(optional), rotation(Boolean, default=false), autoStart(Boolean, default=true) | String | Record screen to a file. | +| recorderStart | | void | Start Media Recorder. | +| imageReaderGetScreenShot | path(optional), delayMilliSec(Integer, default=1000) | String | Capture ScreenShot. | +| recorderCaptureVideo | targetPath(optional), duration(Integer, default=10), cameraId(Integer, default=0), quality(Integer, default=8) | String | Records video from the camera and saves it to the given location. | +| recorderSoundVolumeGetDb | | double | Recorder Sound Volumn Get Db. | +| recorderSoundVolumeDetect | interval(Integer, default=100) | boolean | Recorder Sound Volumn Detect. | +| imageCompress | srcPath(String), destPath(String), targetByteSize(Integer, default=0), targetWidth(Integer, default=0), targetHeight(Integer, default=0) | long | image Compress by targetByteSize, targetWidth, targetHeight. | + +--- + +## MediaPlayerFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| mediaPlay | url(String), tag(String, default="default"), play(Boolean, default=true) | boolean | Open a media file | +| mediaPlayPause | tag(String, default="default") | boolean | pause playing media file | +| mediaPlayStart | tag(String, default="default") | boolean | start playing media file | +| mediaPlayClose | tag(String, default="default") | boolean | Close media file | +| mediaIsPlaying | tag(String, default="default") | boolean | Checks if media file is playing. | +| mediaPlayInfo | tag(String, default="default") | Map | Information on current media | +| mediaPlayList | | Set | Lists currently loaded media | +| mediaPlaySetLooping | enabled(Boolean, default=true), tag(String, default="default") | boolean | Set Looping | +| mediaPlaySeek | msec(Integer), tag(String, default="default") | int | Seek To Position | +| musicPlay | url(String) | boolean | Play a music file (**Deprecated**) | + +--- + +## WifiFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| wifiGetScanResults | | JSONArray | Returns the list of access points found during the most recent Wifi scan. | +| wifiLockAcquireFull | | void | Acquires a full Wifi lock. | +| wifiLockAcquireScanOnly | | void | Acquires a scan only Wifi lock. | +| wifiLockRelease | | void | Releases a previously acquired Wifi lock. | +| wifiStartScan | | Boolean | Starts a scan for Wifi access points. | +| checkWifiState | | Boolean | Checks Wifi state. | +| toggleWifiState | enabled(optional) | Boolean | Toggle Wifi on and off. | +| wifiDisconnect | | Boolean | Disconnects from the currently active access point. | +| wifiGetConnectionInfo | ipConvertToString(Boolean, default=true) | JSONObject | Returns information about the currently active access point. | +| wifiReassociate | | Boolean | Reassociates with the currently active access point. | +| wifiReconnect | | Boolean | Reconnects to the currently active access point. | +| wifiGetApState | | String | get wifi ap state. | +| getConnectedInfo | | JSONArray | get connected hot ip | +| getDhcpInfo | ipConvertToString(Boolean, default=true) | Map | get dhcp info | +| getInternetInterfaceAddress | | Map | get Internet Interface Address | + +--- + +## BluetoothFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| bluetoothActiveConnections | | Map | Returns active Bluetooth connections. | +| bluetoothWriteBinary | base64(String), connID(String, default="", optional) | void | Send bytes over the currently open Bluetooth connection. | +| bluetoothReadBinary | bufferSize(Integer, default=4096), connID(String, default="", optional) | String | Read up to bufferSize bytes and return a chunked, base64 encoded string. | +| bluetoothConnect | uuid(String, default=DEFAULT_UUID), address(optional) | String | Connect to a device over Bluetooth. | +| bluetoothAccept | uuid(String, default=DEFAULT_UUID), timeout(Integer, default=0) | String | Listens for and accepts a Bluetooth connection. | +| bluetoothMakeDiscoverable | duration(Integer, default=300) | void | Requests that the device be discoverable for Bluetooth connections. | +| bluetoothWrite | ascii(String), connID(String, default="") | void | Sends ASCII characters over the currently open Bluetooth connection. | +| bluetoothReadReady | connID(String, default="", optional) | Boolean | Returns True if the next read is guaranteed not to block. | +| bluetoothRead | bufferSize(Integer, default=4096), connID(String, default="", optional) | String | Read up to bufferSize ASCII characters. | +| bluetoothReadLine | connID(String, default="", optional) | String | Read the next line. | +| bluetoothGetRemoteDeviceName | address(String) | String | Queries a remote device for it's name or null if it can't be resolved | +| bluetoothGetBondedDevices | | JSONObject | bluetooth Get Bonded Devices | +| bluetoothGetBondedDevicesRssi | interval(optional) | JSONObject | bluetooth Get Bonded Devices Rssi | +| bluetoothGetReceivedDevices | | JSONObject | bluetooth Get Received Devices | +| bluetoothGetLocalName | | String | Gets the Bluetooth Visible device name | +| bluetoothSetLocalName | name(String) | boolean | Sets the Bluetooth Visible device name, returns True on success | +| bluetoothGetScanMode | | int | Gets the scan mode for the local dongle. | +| bluetoothGetConnectedDeviceName | connID(String, default="", optional) | String | Returns the name of the connected device. | +| checkBluetoothState | | Boolean | Checks Bluetooth state. | +| toggleBluetoothState | enabled(optional), prompt(Boolean, default=true) | Boolean | Toggle Bluetooth on and off. | +| bluetoothStop | connID(String, default="", optional) | void | Stops Bluetooth connection. | +| bluetoothGetLocalAddress | | String | Returns the hardware address of the local Bluetooth adapter. | +| bluetoothDiscoveryStart | | Boolean | Start the remote device discovery process. | +| bluetoothDiscoveryCancel | | Boolean | Cancel the current device discovery process. | +| bluetoothIsDiscovering | | Boolean | Return true if the local Bluetooth adapter is currently in the device discovery process. | + +--- + +## ContactsFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| pickContact | | Intent | Display a list of contacts to pick from. | +| pickPhone | | String | Display a list of phone numbers to pick from. | +| contactsGetAttributes | | List | Returns a List of all possible attributes for contacts. | +| contactsGetIds | | List | Returns a List of all contact IDs. | +| contactsGet | attributes(optional) | List | Returns a List of all contacts. | +| contactsGetById | id(Integer), attributes(optional) | JSONObject | Returns contacts by ID. | +| contactsGetCount | | Integer | Returns the number of contacts. | +| queryContent | uri(String), attributes(optional), selection(optional), selectionArgs(optional), order(optional) | List | Content Resolver Query | +| queryAttributes | uri(String) | JSONArray | Content Resolver Query Attributes | + +--- + +## SettingsFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| setScreenTimeout | value(Integer) | Integer | Sets the screen timeout to this number of seconds. | +| getScreenTimeout | | Integer | Returns the current screen timeout in seconds. | +| checkAirplaneMode | | Boolean | Checks the airplane mode setting. | +| checkRingerSilentMode | | Boolean | Checks the ringer silent mode setting. | +| toggleRingerSilentMode | enabled(optional) | Boolean | Toggles ringer silent mode on and off. | +| toggleVibrateMode | enabled(optional), ringer(optional) | Boolean | Toggles vibrate mode on and off. | +| getVibrateMode | ringer(optional) | Boolean | Checks Vibration setting. | +| getMaxRingerVolume | | int | Returns the maximum ringer volume. | +| getRingerVolume | | int | Returns the current ringer volume. | +| setRingerVolume | volume(Integer) | void | Sets the ringer volume. | +| getMaxMediaVolume | | int | Returns the maximum media volume. | +| getMediaVolume | | int | Returns the current media volume. | +| setMediaVolume | volume(Integer) | void | Sets the media volume. | +| getScreenBrightness | | Integer | Returns the screen backlight brightness. | +| setScreenBrightness | value(optional) | Integer | Sets the the screen backlight brightness. | +| checkScreenOn | | Boolean | Checks if the screen is on or off. | +| isExternalStorageManager | | Boolean | return isExternalStorageManager if Android >= 11. | +| getLocale | | String | Get system language and country. (requires API N) | +| getSysInfo | | Map | get system infomation. | +| getAndroidID | | String | get Android ID | +| getScreenInfo | | Map | get screen infomation. | +| elapsedRealtimeNanos | | Long | Nanoseconds after system startup | +| showScreenLock | | Boolean | Show Screen Lock. (requires API M) | +| getTrafficStats | flags(Integer, default=7) | Map | Get transmit/receive traffic statistics since startup. | +| getAppTxBytes | packageName(String) | Map | get qpython Tx bytes | +| getMemoryInfo | | Map | get Memory Information | + +--- + +## SpeechRecognitionFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| recognizeSpeech | prompt(optional), language(optional), languageModel(optional) | String | Recognizes user's speech and returns the most likely result. | + +--- + +## TextToSpeechFacade (@RpcMinSdk(4)) + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| ttsSpeak | message(String), pitch(Double, default=1.0), pitchRate(Double, default=1.0) | void | Speaks the provided message via TTS. | +| ttsIsSpeaking | | Boolean | Returns True if speech is currently in progress. | +| ttsStop | | void | Stop speaking TTS. | + +--- + +## ToneGeneratorFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| generateDtmfTones | phoneNumber(String), toneDuration(Integer, default=100) | void | Generate DTMF tones for the given phone number. | + +--- + +## UiConfig + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| htmlPictureSetSize | widthFixed(optional), heightFixed(optional), widthRatio(optional), heightRatio(optional) | void | Set html picture size. widthFixed or heightFixed = 0 means original picture size. | +| htmlPictureGetSize | | Map | Get html picture size. Returns widthFixed, heightFixed, widthRatio, heightRatio. | + +--- + +## UiFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| dialogCreateInput | title(String, default="Value"), message(String, default="Please enter value:"), defaultText(optional), inputType(optional) | void | Create a text input dialog. | +| dialogCreatePassword | title(String, default="Password"), message(String, default="Please enter password:") | void | Create a password input dialog. | +| dialogSetMessageIsHtml | messageIsHtml(Boolean, default=true) | void | Set dialog message is HTML. | +| dialogGetInput | title(String, default="Value"), message(String, default="Please enter value:"), defaultText(optional), messageIsHtml(Boolean, default=false) | String | Queries the user for a text input. | +| dialogGetPassword | title(String, default="Password"), message(String, default="Please enter password:") | String | Queries the user for a password. | +| dialogCreateSpinnerProgress | title(optional), message(optional), maximum progress(Integer, default=100) | void | Create a spinner progress dialog. | +| dialogCreateHorizontalProgress | title(optional), message(optional), maximum progress(Integer, default=100) | void | Create a horizontal progress dialog. | +| dialogCreateAlert | title(optional), message(optional) | void | Create alert dialog. | +| dialogShowAlert | title(String, default="Alert"), message(String, default="The message of the alert ."), positive(String, default="OK"), negative(optional), neutral(optional), messageIsHtml(Boolean, default=false) | Object | Create and show alert dialog. | +| dialogCreateSeekBar | starting value(Integer, default=50), maximum value(Integer, default=100), title(String), message(String) | void | Create seek bar dialog. | +| dialogCreateTimePicker | hour(Integer, default=0), minute(Integer, default=0), is24hour(Boolean, default=false) | void | Create time picker dialog. | +| dialogCreateDatePicker | year(Integer, default=1970), month(Integer, default=1), day(Integer, default=1) | void | Create date picker dialog. | +| dialogDismiss | | void | Dismiss dialog. | +| dialogShow | | void | Show dialog. | +| dialogSetCurrentProgress | current(Integer) | void | Set progress dialog current value. | +| dialogSetMaxProgress | max(Integer) | void | Set progress dialog maximum value. | +| dialogSetProgressMessage | message(String) | void | Set progress dialog message. | +| dialogSetPositiveButtonText | text(String) | void | Set alert dialog positive button text. | +| dialogSetNegativeButtonText | text(String) | void | Set alert dialog button text. | +| dialogSetNeutralButtonText | text(String) | void | Set alert dialog button text. | +| dialogSetItems | items(JSONArray) | void | Set alert dialog list items. | +| dialogShowSimpleChoice | title(String, default="Alert"), message(String, default="The message of the alert ."), items(optional), positive(String, default="OK"), negative(optional), neutral(optional), messageIsHtml(Boolean, default=false) | Object | Create and show simple choice dialog. | +| dialogSetSingleChoiceItems | items(JSONArray), selected(Integer, default=-1) | void | Set dialog single choice items and selected item. | +| dialogShowSingleChoice | title(String, default="Alert"), message(String, default="The message of the alert ."), items(JSONArray), selected(Integer, default=-1), positive(String, default="OK"), negative(optional), neutral(optional), messageIsHtml(Boolean, default=false) | JSONObject | Create and show single choice dialog. | +| dialogSetMultiChoiceItems | items(JSONArray), selected(optional) | void | Set dialog multiple choice items and selection. | +| dialogShowMultiChoice | title(String, default="Alert"), message(String, default="The message of the alert ."), items(JSONArray), selected(optional), positive(String, default="OK"), negative(optional), neutral(optional), messageIsHtml(Boolean, default=false) | Object | Create and show multi choice dialog. | +| dialogGetResponse | | Object | Returns dialog response. | +| dialogGetSelectedItems | | Set | Returns list of items user selected. | +| fullShow | layout(String), title(optional), theme(optional) | List | Show Full Screen. | +| fullDismiss | | void | Dismiss Full Screen. | +| fullQuery | | Map> | Get Fullscreen Properties. | +| fullQueryDetail | id(String) | Map | Get fullscreen properties for a specific widget. | +| fullGetProperty | id(String), property(String) | String | Get a fullscreen property for a specific widget. | +| fullGetProperties | ids(JSONArray), property(String) | JSONArray | Get a fullscreen property for many specific widgets. | +| fullSetProperty | id(String), property(String), value(String) | String | Set a fullscreen widget's property. | +| fullSetProperties | ids(JSONArray), property(String), value(String) | String | Set many fullscreen widgets' property. | +| fullSetList | id(String), list(JSONArray), isHtml(Boolean, default=false), listType(Integer, default=0) | String | Attach a text/html/mixed list to a fullscreen widget. | +| fullSetListSelected | id(String), selected(Object) | String | Attach a html list to a fullscreen widget. | +| fullGetListSelected | id(String) | Object | Attach a multi choice list to a fullscreen widget. | +| fullSetList2 | id(String), list(JSONArray), intRes(JSONArray) | String | Attach a 2-line list to a fullscreen widget. | +| fullSetListHtml | id(String), list(JSONArray) | String | Attach a html list to a fullscreen widget. (**Deprecated**) | +| fullSetTitle | title(String) | void | Set the Full Screen Activity Title. | +| fullKeyOverride | keycodes(JSONArray), enable(Boolean, default=true) | JSONArray | Override default key actions. | +| fullGetScreenShot | path(optional) | String | Get the Full Screen Activity ScreenShot to path. | + +--- + +## WakeLockFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| wakeLockAcquireFull | | void | Acquires a full wake lock (CPU on, screen bright, keyboard bright). | +| wakeLockAcquirePartial | | void | Acquires a partial wake lock (CPU on). | +| wakeLockAcquireBright | | void | Acquires a bright wake lock (CPU on, screen bright). | +| wakeLockAcquireDim | | void | Acquires a dim wake lock (CPU on, screen dim). | +| wakeLockRelease | | void | Releases the wake lock. | + +--- + +## BatteryManagerFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| batteryGetCharge | | Integer | Returns the most recently received battery charge counter uAh. | +| readBatteryData | | Bundle | Returns the most recently recorded battery data. (throws "battery" events) | +| batteryStartMonitoring | | void | Starts tracking battery state. (starts event "battery") | +| batteryStopMonitoring | | void | Stops tracking battery state. (stops event "battery") | +| batteryGetStatus | | Integer | Returns the most recently received battery status data | +| batteryGetHealth | | Integer | Returns the most recently received battery health data | +| batteryGetPlugType | | Integer | Returns the most recently received plug type data | +| batteryCheckPresent | | Boolean | Returns the most recently received battery presence data. | +| batteryGetLevel | | Float | Returns the most recently received battery level (percentage). | +| batteryGetVoltage | | Integer | Returns the most recently received battery voltage. | +| batteryGetCurrent | | Integer | Returns the most recently received battery Current mA. | +| batteryGetTemperature | | Integer | Returns the most recently received battery temperature. | +| batteryGetTechnology | | String | Returns the most recently received battery technology data. | + +--- + +## SignalStrengthFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| getTelephoneSignalStrengthLevel | | int | Returns the Telephone Signal Strength Level. (requires API P) | +| getTelephoneSignalStrengthDetail | | String | Returns the Telephone Signal Strength Detail. (requires API P) | +| startTrackingSignalStrengths | | void | Starts tracking signal strengths. GSM/CDMA/EVDO (starts event "signal_strengths") | +| readSignalStrengths | | Bundle | Returns the current signal strengths. GSM/CDMA/EVDO | +| stopTrackingSignalStrengths | | void | Stops tracking signal strength. GSM/CDMA/EVDO (stops event "signal_strengths") | + +--- + +## ApplicationManagerFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| getLaunchablePackages | needClassName(Boolean, default=false) | Map | Returns a list of all launchable packages with class name and application name. | +| getApplicationInfo | packageName(optional) | JSONObject | get Application Info | +| launch | classname(optional), packagename(optional), wait(Boolean, default=true) | void | Start activity with the given classname and/or packagename. | +| getRunningPackages | | Set | Returns a list of packages running activities or services. | +| getInstalledPackages | flag(Integer, default=4) | Map | get installed packages | +| forceStopPackage | packageName(String) | void | Force stops a package. | +| checkPermissions | | JSONObject | check all Permissions are granted or denied. | +| requestPermissions | permissions(optional) | void | request Permissions | + +--- + +## CommonIntentsFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| pick | uri(String) | Intent | Display content to be picked by URI (e.g. contacts) | +| scanBarcode | title(optional) | String | Starts the barcode scanner. | +| scanBarcodeFromImage | path(String), sampleSize(Integer, default=0), x(Integer, default=0), y(Integer, default=0), width(Integer, default=0), height(Integer, default=0) | String | scan Barcode From Image | +| view | uri(String), type(optional), extras(optional), wait(Boolean, default=true) | void | Start activity with view action by URI | +| send | uri(String), type(optional), extras(optional), wait(Boolean, default=true) | void | Start activity with send action by URI | +| sendText | text(String), extras(optional), wait(Boolean, default=true) | void | Start activity with send action by text. | +| sendEmail | to(String), subject(String), text(String), path(optional), extras(optional), wait(Boolean, default=true) | void | Launches an activity that sends an e-mail message with a given recipients or attachment. | +| pathToUri | path(String), fileProvider(Boolean, default=true) | String | Convert normal path to content:// . | +| openFile | path(String), type(optional), wait(Boolean, default=true) | void | Open a file with path | +| sendFile | path(Object), type(optional), extras(optional), wait(Boolean, default=true) | void | Send file(s) with path | +| getPathType | path(String) | String | get path type | +| viewMap | query(String), wait(Boolean, default=true) | void | Opens a map search for query | +| viewContacts | wait(optional) | void | Opens the list of contacts. | +| search | query(String) | void | Starts a search for the given query. | +| viewHtml | path(String), title(optional), wait(Boolean, default=true) | void | Opens the browser to display a local HTML/text/audio/video File or http(s) Website. | +| webViewShow | path(String), title(optional), wait(Boolean, default=true) | void | Opens the browser to display a local HTML/text/audio/video File or http(s) Website. (**Deprecated**) | +| videoPlay | path(String), wait(Boolean, default=true) | void | Play the Video via Video Path. | +| editorOpen | path(String) | void | Opens the QEditor to display a local text File. | +| createScriptShortCut | scriptPath(String), label(optional), iconPath(optional), scriptArg(optional) | void | create python script shortcut. | + +--- + +## PreferencesFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| prefGetValue | key(String), filename(optional) | Object | Read a value from shared preferences | +| prefPutValue | key(String), value(Object), filename(optional) | void | Write a value to shared preferences | +| prefGetAll | filename(optional) | Map | Get list of Shared Preference Values | +| prefRemoveValue | key(String), filename(optional) | void | Remove a value from shared preferences | + +--- + +## DocumentFileFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| documentTreeShowOpen | rootPath(String) | Uri | Show Open Document Tree with RootPath. | +| documentFileRenameTo | src(String), dest(String) | boolean | Document File Rename. | +| documentFileDelete | file(String) | boolean | Document File ( or Tree ) Delete. | +| documentFileMkdir | dir(String) | boolean | Document File Make Directorys. | +| documentFileInputStream | srcFile(String), encodingFormat(String, default=""), skip(optional), length(optional) | String | Document File Input Stream. | +| documentFileOutputStream | destFile(String), srcString(String, default=""), encodingFormat(String, default=""), append(optional) | void | Document File Output Stream. | +| documentFileCopy | src(String), dest(String) | void | Document File Copy. | +| documentFileListFiles | folder(String) | JSONArray | Document File List Files. | +| documentFileShowOpen | rootPath(String) | Uri | Show Open Document Tree with RootPath. (**Deprecated**) | +| documentFileMoveTo | src(String), dest(String) | boolean | Document File Move To. (**Deprecated**) | +| documentFileMkdirs | dir(String) | boolean | Document File Make Directorys. (**Deprecated**) | +| documentFileReadFrom | srcFile(String), encodingFormat(String, default=""), skip(optional), length(optional) | String | Document File Read From. (**Deprecated**) | +| documentFileWriteTo | destFile(String), srcString(String, default=""), encodingFormat(String, default=""), append(optional) | void | Document File Write To. (**Deprecated**) | +| documentFileGetUri | path(String), isDirectory(optional) | Uri | Document File Get Uri. | +| documentFileIsDirectory | path(String) | Boolean | Document File Is Directory. | +| documentFileIsFile | path(String) | Boolean | Document File Is File. | +| documentFileExists | path(String) | Boolean | Document File Exists. | +| documentFileLength | path(String) | Long | Document File Length. | +| documentFileLastModified | path(String) | Long | Document File Last Modified. | +| documentFileGetStat | path(String) | Map | Document File Get Stat. | +| getFileStat | path(String) | Map | get file stat. | +| getSdCardPaths | | String[] | get Sd Card Paths. | + +--- + +## FloatViewFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| floatView | args(optional) | int | Show Float View. | +| floatViewResult | index(Integer, default=-1) | JSONObject | Return Float View Result. | +| floatViewRemove | index(Integer, default=-1) | int | Remove Float View. | +| floatViewCount | | int | Float View Count. | +| backgroundProtect | enabled(Boolean, default=true) | void | QPython Background Protect. | + +--- + +## AccessibilityFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| accessibilityStartService | | boolean | Opens system settings to enable accessibility service. | +| accessibilityClick | x(Double, default=0), y(Double, default=0), t(Integer, default=50) | Boolean | Performs a click on the screen at the specified coordinates. | +| accessibilitySlide | XnYn(optional), t(optional) | Boolean | Performs a slide on the screen at the specified coordinates. | +| accessibilityAction | actionCode(Integer, default=0) | Boolean | Performs a accessibility action with the designated action code. | +| accessibilityServiceEnabled | | boolean | Check if accessibility service is enabled. | + +--- + +## ActivityResultFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| setResultBoolean | resultCode(Integer), resultValue(Boolean) | void | Sets the result of a script execution. | +| setResultByte | resultCode(Integer), resultValue(Integer) | void | Sets the result of a script execution. | +| setResultShort | resultCode(Integer), resultValue(Integer) | void | Sets the result of a script execution. | +| setResultChar | resultCode(Integer), resultValue(String) | void | Sets the result of a script execution. | +| setResultInteger | resultCode(Integer), resultValue(Integer) | void | Sets the result of a script execution. | +| setResultLong | resultCode(Integer), resultValue(Long) | void | Sets the result of a script execution. | +| setResultFloat | resultCode(Integer), resultValue(Double) | void | Sets the result of a script execution. | +| setResultDouble | resultCode(Integer), resultValue(Double) | void | Sets the result of a script execution. | +| setResultString | resultCode(Integer), resultValue(String) | void | Sets the result of a script execution. | +| setResultBooleanArray | resultCode(Integer), resultValue(Boolean[]) | void | Sets the result of a script execution. | +| setResultByteArray | resultCode(Integer), resultValue(Integer[]) | void | Sets the result of a script execution. | +| setResultShortArray | resultCode(Integer), resultValue(Integer[]) | void | Sets the result of a script execution. | +| setResultCharArray | resultCode(Integer), resultValue(String[]) | void | Sets the result of a script execution. | +| setResultIntegerArray | resultCode(Integer), resultValue(Integer[]) | void | Sets the result of a script execution. | +| setResultLongArray | resultCode(Integer), resultValue(Long[]) | void | Sets the result of a script execution. | +| setResultFloatArray | resultCode(Integer), resultValue(Double[]) | void | Sets the result of a script execution. | +| setResultDoubleArray | resultCode(Integer), resultValue(Double[]) | void | Sets the result of a script execution. | +| setResultStringArray | resultCode(Integer), resultValue(String[]) | void | Sets the result of a script execution. | +| setResultSerializable | resultCode(Integer), resultValue(Serializable) | void | Sets the result of a script execution. | + +--- + +## CipherFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| cipherInit | key(String), algorithm(String, default="AES/CBC/PKCS5Padding"), encodingFormat(String, default=""), initialVector(Object, default="") | void | Initialize Encrypt Engine / Decrypt Engine. | +| encryptString | srcString(String) | String | Encrypt a Normal / Base64 String to another Base64 String. | +| decryptString | srcString(String) | String | Decrypt a Base64 String to another Normal / Base64 String. | +| encryptStringToFile | srcString(String), dstFile(String) | void | Encrypt a Normal / Base64 String to another File. | +| decryptFileToString | srcFile(String) | String | Decrypt a File to another Normal / Base64 String. | +| encryptFile | srcFile(String), dstFile(String) | void | Encrypt a File to another File. | +| decryptFile | srcFile(String), dstFile(String) | void | Decrypt a File to another File. | + +--- + +## FtpFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| ftpStart | | String[] | FTP Start Service. | +| ftpStop | | void | FTP Stop Service. | +| ftpIsRunning | | boolean | FTP Server is Running. | +| ftpGet | | String[] | FTP Server get IP address. | +| ftpSet | port(optional), rootDir(optional), username(optional), password(optional) | JSONObject | FTP Server set port,rootDir,username,password. | +| ftpStatus | | String | FTP Server get status. | + +--- + +## HarmonyOsFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| getSystemProperty | property(String), defaultValue(String, default="") | String | get System Property | +| getHarmonyOsInformation | | Map | get Harmony OS Information. | + +--- + +## QPyInterfaceFacade + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| executeQPyAsSrv | path(optional) | Boolean | Execute QPython script throught SL4A | +| executeQPy | path(String, default=""), arg(optional) | Boolean | Execute QPython script throught SL4A | +| executeQPyCodeAsSrv | code(optional) | Boolean | Execute QPython script throught SL4A | +| executeQPyCode | code(optional) | Boolean | Execute QPython script throught SL4A | +| getLastLog | logName(String, default="last.log") | String | Get last QPython execute log | +| sharedVariableSet | key(String), value(String) | String | set Java Shared Variable. | +| sharedVariableGet | key(String) | String | get Java Shared Variable. | +| sharedVariableRemove | key(String) | String | remove Java Shared Variable. | + +--- + +## WebCamFacade (@RpcMinSdk(8)) + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| webcamStart | resolutionLevel(Integer, default=0), jpegQuality(Integer, default=20), port(Integer, default=0) | Object | Starts an MJPEG stream and returns a Tuple of address and port for the stream. | +| webcamAdjustQuality | resolutionLevel(Integer, default=0), jpegQuality(Integer, default=20) | void | Adjusts the quality of the webcam stream while it is running. | +| webcamStop | | void | Stops the webcam stream. | +| cameraStartPreview | resolutionLevel(Integer, default=0), jpegQuality(Integer, default=20), filepath(optional) | boolean | Start Preview Mode. Throws 'preview' events. | +| cameraStopPreview | | void | Stop the preview mode. | + +--- + +## USBHostSerialFacade (@RpcMinSdk(12)) + +| RPC Method | Parameters | Return | Description | +|------------|------------|--------|-------------| +| usbserialGetDeviceList | | Map | Returns USB devices reported by USB Host API. | +| usbserialDisconnect | connID(String, default="", optional) | void | Disconnect all USB-device. | +| usbserialActiveConnections | | Map | Returns active USB-device connections. | +| usbserialWriteBinary | base64(String), connID(String, default="", optional) | void | Send bytes over the currently open USB Serial connection. | +| usbserialReadBinary | bufferSize(Integer, default=4096), connID(String, default="", optional) | String | Read up to bufferSize bytes and return a chunked, base64 encoded string. | +| usbserialConnect | hash(String, default=DEFAULT_HASHCODE), options(String, default="") | String | Connect to a device with USB-Host. | +| usbserialHostEnable | | Boolean | Requests that the host be enable for USB Serial connections. | +| getUsbDeviceType | hash(String, default=DEFAULT_HASHCODE) | String | Requests that the type of the device. | +| usbserialWrite | ascii(String), connID(String, default="") | void | Sends ASCII characters over the currently open USB Serial connection. | +| usbserialReadReady | connID(String, default="", optional) | Boolean | Returns True if the next read is guaranteed not to block. | +| usbserialRead | connID(String, default=""), bufferSize(Integer, default=4096, optional) | String | Read up to bufferSize ASCII characters. | +| usbserialGetDeviceName | connID(String, default="", optional) | String | Queries a remote device for it's name or null if it can't be resolved | + +--- + +## Summary + +| Facade Class | Number of RPC Methods | +|--------------|----------------------| +| AndroidFacade | 19 | +| EventFacade | 13 | +| LocationFacade | 8 | +| SensorManagerFacade | 11 | +| PhoneFacade | 29 | +| SmsFacade | 8 | +| CameraFacade | 4 | +| MediaRecorderFacade | 12 | +| MediaPlayerFacade | 10 | +| WifiFacade | 15 | +| BluetoothFacade | 25 | +| ContactsFacade | 9 | +| SettingsFacade | 26 | +| SpeechRecognitionFacade | 1 | +| TextToSpeechFacade | 3 | +| ToneGeneratorFacade | 1 | +| WakeLockFacade | 5 | +| BatteryManagerFacade | 13 | +| SignalStrengthFacade | 5 | +| ApplicationManagerFacade | 8 | +| CommonIntentsFacade | 19 | +| PreferencesFacade | 4 | +| DocumentFileFacade | 22 | +| FloatViewFacade | 5 | +| AccessibilityFacade | 5 | +| ActivityResultFacade | 19 | +| CipherFacade | 7 | +| FtpFacade | 6 | +| HarmonyOsFacade | 2 | +| QPyInterfaceFacade | 8 | +| WebCamFacade | 5 | +| USBHostSerialFacade | 12 | + +**Total: 32 Facade Classes with 344 RPC Methods** \ No newline at end of file From f767d1ea9990daf8d5a02d4d932213644bde6ea0 Mon Sep 17 00:00:00 2001 From: "River@devbox" Date: Thu, 19 Mar 2026 18:07:43 +0800 Subject: [PATCH 14/21] Fix path: /sdcard/Android to /storage/emulated/0/Android --- source/en/qsl4a/special/pgptai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/en/qsl4a/special/pgptai.md b/source/en/qsl4a/special/pgptai.md index 86d35b5..f1e671e 100644 --- a/source/en/qsl4a/special/pgptai.md +++ b/source/en/qsl4a/special/pgptai.md @@ -46,7 +46,7 @@ textToSpeech(Text, AutoPlay=True, WavFile=None, VoiceName=None) ## Configuration -The API uses configuration from `/sdcard/Android/data/org.qpython.qpy/files/apigpt.conf`: +The API uses configuration from `/storage/emulated/0/Android/data/org.qpython.qpy/files/apigpt.conf`: ```ini [speech] From 738795b5aec54a28a412bdd4ce42b9b70c2cddac Mon Sep 17 00:00:00 2001 From: "River@devbox" Date: Thu, 19 Mar 2026 18:14:32 +0800 Subject: [PATCH 15/21] Complete wifi.md Usage Example with all methods --- source/en/qsl4a/connectivity/wifi.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/source/en/qsl4a/connectivity/wifi.md b/source/en/qsl4a/connectivity/wifi.md index 0a98c0b..4d21146 100644 --- a/source/en/qsl4a/connectivity/wifi.md +++ b/source/en/qsl4a/connectivity/wifi.md @@ -166,7 +166,29 @@ if dhcp: print(f"Gateway: {dhcp.get('gateway')}") print(f"DNS: {dhcp.get('dns1')}") +# Get simplified connected info +connected = droid.getConnectedInfo().result +print(f"SSID: {connected.get('ssid')}, Signal: {connected.get('level')} dBm") + +# Disconnect and reconnect +droid.wifiDisconnect() +time.sleep(1) +droid.wifiReconnect() + +# Reassociate with access point +droid.wifiReassociate() + +# Check hotspot state +ap_state = droid.wifiGetApState().result +print(f"Hotspot state: {ap_state}") + # Acquire WiFi lock for background operation droid.wifiLockAcquireFull() # ... do work ... droid.wifiLockRelease() + +# Or use scan-only lock for lighter background operation +droid.wifiLockAcquireScanOnly() +# ... do scanning work ... +droid.wifiLockRelease() +``` From cbb62517f77ceaec5df195bee64d26361d5f75d2 Mon Sep 17 00:00:00 2001 From: "River@devbox" Date: Fri, 20 Mar 2026 10:51:25 +0800 Subject: [PATCH 16/21] Update build.sh --- build.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.sh b/build.sh index ab03cba..20c7d5b 100755 --- a/build.sh +++ b/build.sh @@ -105,6 +105,10 @@ if [ -f source/CNAME ]; then cp source/CNAME site/ fi +# Remove "Made with Material for MkDocs" footer from all HTML files +echo "Removing MkDocs Material footer..." +find site -name "*.html" -exec sed -i '' '/Made with/,/<\/a>/d' {} \; + echo "" echo "Build complete!" echo "" From 81e7e2b672c73d55dbe556781de0e6a990d8c87f Mon Sep 17 00:00:00 2001 From: "River@devbox" Date: Fri, 20 Mar 2026 10:59:10 +0800 Subject: [PATCH 17/21] Fixed: replace issue --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 20c7d5b..46ca5ac 100755 --- a/build.sh +++ b/build.sh @@ -107,7 +107,7 @@ fi # Remove "Made with Material for MkDocs" footer from all HTML files echo "Removing MkDocs Material footer..." -find site -name "*.html" -exec sed -i '' '/Made with/,/<\/a>/d' {} \; +find site -name "*.html" -exec sed -i '/Made with/,/<\/a>/d' {} \; echo "" echo "Build complete!" From 3de3aa4e5f0fd68d27dece895117490958c28c71 Mon Sep 17 00:00:00 2001 From: "River@devbox" Date: Fri, 20 Mar 2026 11:46:33 +0800 Subject: [PATCH 18/21] Add Chinese translations for all documentation - Translate 13 main docs and 35 QSL4A API docs to Chinese - Update mkdocs-zh.yml with complete navigation structure - Add MkDocs Material footer removal in build.sh - Fix redirect delay from 1s to 5s in index.html - Fix pgptai.md pip install command --- build.sh | 2 +- mkdocs-zh.yml | 64 +++- source/en/qsl4a/special/pgptai.md | 2 +- source/zh/AIPyApp.md | 73 ++++ source/zh/GraphicalInterface.md | 67 ++++ source/zh/Notebook.md | 47 +++ source/zh/Ollama.md | 120 +++++++ source/zh/Terminal.md | 131 +++++++ source/zh/editor-guide.md | 41 +++ source/zh/external-api.md | 102 ++++++ source/zh/getting-started.md | 233 ++++++++++++ source/zh/qpypi-guide.md | 36 ++ source/zh/qpython-x.md | 68 ++++ source/zh/qsl4a/connectivity/contacts.md | 135 +++++++ source/zh/qsl4a/connectivity/ftp.md | 96 +++++ source/zh/qsl4a/connectivity/location.md | 105 ++++++ source/zh/qsl4a/connectivity/phone.md | 291 +++++++++++++++ .../zh/qsl4a/connectivity/signalstrength.md | 71 ++++ source/zh/qsl4a/connectivity/sms.md | 89 +++++ source/zh/qsl4a/connectivity/wifi.md | 194 ++++++++++ source/zh/qsl4a/core/android-base.md | 129 +++++++ source/zh/qsl4a/core/events.md | 245 +++++++++++++ source/zh/qsl4a/core/intent.md | 334 ++++++++++++++++++ source/zh/qsl4a/hardware/bluetooth.md | 201 +++++++++++ source/zh/qsl4a/hardware/camera.md | 182 ++++++++++ source/zh/qsl4a/hardware/recorder.md | 124 +++++++ source/zh/qsl4a/hardware/usbserial.md | 161 +++++++++ source/zh/qsl4a/hardware/webcam.md | 98 +++++ source/zh/qsl4a/index.md | 91 +++++ source/zh/qsl4a/media/image.md | 94 +++++ source/zh/qsl4a/media/mediaplayer.md | 152 ++++++++ source/zh/qsl4a/special/cipher.md | 143 ++++++++ source/zh/qsl4a/special/pgptai.md | 100 ++++++ source/zh/qsl4a/storage/clipboard.md | 41 +++ source/zh/qsl4a/storage/documentfile.md | 205 +++++++++++ source/zh/qsl4a/storage/preferences.md | 81 +++++ source/zh/qsl4a/system/activityresult.md | 230 ++++++++++++ source/zh/qsl4a/system/application.md | 269 ++++++++++++++ source/zh/qsl4a/system/battery.md | 91 +++++ source/zh/qsl4a/system/qpyinterface.md | 131 +++++++ source/zh/qsl4a/system/sensors.md | 127 +++++++ source/zh/qsl4a/system/settings.md | 234 ++++++++++++ source/zh/qsl4a/system/sysinfo.md | 90 +++++ source/zh/qsl4a/system/wakelock.md | 78 ++++ source/zh/qsl4a/ui/accessibility.md | 123 +++++++ source/zh/qsl4a/ui/dialogs.md | 321 +++++++++++++++++ source/zh/qsl4a/ui/floatview.md | 174 +++++++++ source/zh/qsl4a/ui/fullscreen.md | 171 +++++++++ source/zh/tutorial-hello-world.md | 116 ++++++ source/zh/whats-new.md | 119 +++++++ 50 files changed, 6617 insertions(+), 5 deletions(-) create mode 100644 source/zh/AIPyApp.md create mode 100644 source/zh/GraphicalInterface.md create mode 100644 source/zh/Notebook.md create mode 100644 source/zh/Ollama.md create mode 100644 source/zh/Terminal.md create mode 100644 source/zh/editor-guide.md create mode 100644 source/zh/external-api.md create mode 100644 source/zh/getting-started.md create mode 100644 source/zh/qpypi-guide.md create mode 100644 source/zh/qpython-x.md create mode 100644 source/zh/qsl4a/connectivity/contacts.md create mode 100644 source/zh/qsl4a/connectivity/ftp.md create mode 100644 source/zh/qsl4a/connectivity/location.md create mode 100644 source/zh/qsl4a/connectivity/phone.md create mode 100644 source/zh/qsl4a/connectivity/signalstrength.md create mode 100644 source/zh/qsl4a/connectivity/sms.md create mode 100644 source/zh/qsl4a/connectivity/wifi.md create mode 100644 source/zh/qsl4a/core/android-base.md create mode 100644 source/zh/qsl4a/core/events.md create mode 100644 source/zh/qsl4a/core/intent.md create mode 100644 source/zh/qsl4a/hardware/bluetooth.md create mode 100644 source/zh/qsl4a/hardware/camera.md create mode 100644 source/zh/qsl4a/hardware/recorder.md create mode 100644 source/zh/qsl4a/hardware/usbserial.md create mode 100644 source/zh/qsl4a/hardware/webcam.md create mode 100644 source/zh/qsl4a/index.md create mode 100644 source/zh/qsl4a/media/image.md create mode 100644 source/zh/qsl4a/media/mediaplayer.md create mode 100644 source/zh/qsl4a/special/cipher.md create mode 100644 source/zh/qsl4a/special/pgptai.md create mode 100644 source/zh/qsl4a/storage/clipboard.md create mode 100644 source/zh/qsl4a/storage/documentfile.md create mode 100644 source/zh/qsl4a/storage/preferences.md create mode 100644 source/zh/qsl4a/system/activityresult.md create mode 100644 source/zh/qsl4a/system/application.md create mode 100644 source/zh/qsl4a/system/battery.md create mode 100644 source/zh/qsl4a/system/qpyinterface.md create mode 100644 source/zh/qsl4a/system/sensors.md create mode 100644 source/zh/qsl4a/system/settings.md create mode 100644 source/zh/qsl4a/system/sysinfo.md create mode 100644 source/zh/qsl4a/system/wakelock.md create mode 100644 source/zh/qsl4a/ui/accessibility.md create mode 100644 source/zh/qsl4a/ui/dialogs.md create mode 100644 source/zh/qsl4a/ui/floatview.md create mode 100644 source/zh/qsl4a/ui/fullscreen.md create mode 100644 source/zh/tutorial-hello-world.md create mode 100644 source/zh/whats-new.md diff --git a/build.sh b/build.sh index 46ca5ac..80c0b04 100755 --- a/build.sh +++ b/build.sh @@ -33,7 +33,7 @@ cat > site/index.html << 'EOF' QPython Documentation - +