NestSpace:Android 自研應用程式級容器 + 雙通道 Agent/MCP 技術方案¶
本文是技術設計與實作記錄,供開發與二次開發參考;日常使用請看 NestSpace 使用手冊。
專案:
NestSpace(com.coke.nestspace,Jetpack Compose,minSdk 24 / targetSdk 37) 日期:2026-11-03 限制:一般 APK、不能控制 ROM;自研容器引擎,不重用 VirtualApp 商業版;本機 Agent 與遠端 MCP 雙通道都要支援。 狀態:Stage A/B + Stage C-lite + Stage C-D(獨立處理程序/當機隔離、Job/通知虛擬化、宿主/系統廣播橋接、Binder/UID 權限模型、完整 VPMS、跨虛擬應用程式查詢、權限授予、多執行個體 .so 處理程序隔離)已實作並在 Android 7.1.1 / Android 16 / Android 17(API 37 模擬器)/ MuMu(Android 15, ARM bridge) 驗證;本機 LLM Agent(Claude/OpenAI 相容,共用 chat 協定)已實作並用 mock 與第三方 Anthropic 相容端點在真實網路驗證通過;完整背景 Job/Alarm 喚醒與系統端真實 UID 欺騙仍屬於後續增強
閱讀路徑¶
| 你想先看什麼 | 直接跳到 |
|---|---|
| 結論、可行性邊界、自研限制 | 0 |
| 需求與範圍 | 1 |
| 容器總體架構、處理程序模型、Hook 路線 | 2 |
| 關鍵子系統(PMS/AMS/ClassLoader/Binder/IO) | 3 |
| Agent 怎麼操作虛擬應用程式 | 4 |
| 本機 Agent + 遠端 MCP 雙通道 | 5 |
| 架構、模組、介面、專案改造 | 6 |
| 建設順序、測試矩陣、風險 | 7–9 |
0. 結論¶
0.1 技術路線¶
- 自研應用程式級容器引擎:在宿主應用程式內載入未安裝的 APK,虛擬化 PackageManager/ActivityManager/Binder/IO,讓目標應用程式以為自己在正常系統裡執行。
- 優先進行 Java 層虛擬化,關鍵點使用 native hook:先讓
Instrumentation/ActivityThread/PackageManager/ContextImpl的 Java Hook 驗證可行;Binder/UID/native 檔案路徑等困難點再上 native hook。 - 單一處理程序 MVP → 多處理程序產品:先在一個處理程序裡執行一個簡單 APK;穩定後再做宿主宣告的
:pN處理程序池。 - 雙通道 Agent/MCP:本機 Agent(App 內迴圈)+ 遠端 Agent(MCP),共用同一套
ToolRegistry、權限、租約、稽核。 - AppFunctions 快路徑(可選):宿主應用程式可匯出 AppFunctions;容器內 APK 的 AppFunctions 走本機橋接;不可用時回退 UI 自動化。
- 不做 VirtualApp 商業授權。VirtualApp 只作為架構參考,Clean-room 實作,不複製其 GPL 程式碼。
0.2 三層架構¶
┌─────────────────────────────────────────────────────────────┐
│ 3. Agent / MCP 層 │
│ 本機 Agent(App 內迴圈) | 遠端 Agent(MCP) │
│ 共用 ToolRegistry / 租約 / 核准 / 稽核 │
├─────────────────────────────────────────────────────────────┤
│ 2. 控制層 │
│ ContainerService(虛擬空間/安裝/啟動) │
│ Shizuku / Accessibility / Vision(UI 觀測與操作) │
├─────────────────────────────────────────────────────────────┤
│ 1. 自研容器引擎 │
│ Installer / VPMS / VAMS / ClassLoader / Resources │
│ Binder/UID / IO Redirect / Permissions / Process Pool │
└─────────────────────────────────────────────────────────────┘
0.3 可行性邊界(先說實話)¶
能做:
- 一般 APK、單一處理程序、單一執行個體
- 多開、獨立資料目錄
- 無強完整性校驗的內容/工具/社交類 App
- 用 Shizuku/無障礙/視覺控制虛擬應用程式
- 本機 Agent + 遠端 MCP 雙通道
難做,需要逐項攻克:
- 多處理程序 + Binder/UID 虛擬化
- split APK / AAB
- 16 KB page / 原生程式庫
- 通知、JobScheduler、AlarmManager、ContentProvider
- 背景啟動 Activity(Android 10+ 限制)
- 目標應用程式的簽章校驗 / Keystore
- Android 15–17 的 hidden API 變化
做不了/不承諾:
- 真正的 UID/SELinux 隔離(應用程式級容器共用宿主 UID)
- 金融/銀行/支付/Play Integrity/GMS 強依賴 App
- 強反容器/反 hook/反除錯 App
- 「所有 App 都能跑」
自研的正確目標不是「替代 Android 系統」,而是「在一般 App 裡執行一個受控的、相容性有限的第二個虛擬空間」。產品文案要按這個邊界寫。
0.4 最大技術風險¶
- Hidden API / ART / Binder 版本調適:Android 每個大版本都會變更
ActivityThread、PackageParser、Resources、Binder 交易;沒有「寫一次就能長期穩定」這種事。 - Binder/UID 虛擬化:系統服務看到的是宿主 UID;Java 層能騙過一部分,系統端的權限/AppOps/帳號/支付等仍依宿主 UID 判定。這是應用程式級容器的根本限制。
- 背景啟動 Activity:Android 10+ 限制背景啟動;虛擬應用程式從宿主背景啟動會被攔截。需要前景服務/通知/使用者操作觸發。
- 通知/Job/Provider:系統依套件名稱/UID 歸屬;虛擬化這些比 Activity 難得多,很多 App 的背景能力會退化。
- Keystore/簽章/Play Integrity:同 UID 下 Keystore alias 衝突;簽章校驗/完整性校驗很可能失敗。
- 處理程序模型:
app_process子處理程序會被 phantom process killer 殺掉;用宿主宣告的:pN處理程序池更穩定,但要處理處理程序重用/記憶體/當機隔離。 - 16 KB / split APK:安裝器和原生程式庫載入都要調適。
- 長期維護:這是「迷你 Android 執行環境」,不是一般 App 功能;需要持續跟上 AOSP。
0.5 自研 ≠ 無限制¶
自己寫不需要 VirtualApp 授權,但以下限制仍然存在:
- Clean-room / GPL:不要複製 VirtualApp 的 GPL 程式碼;參考 AOSP(Apache-2.0)和公開文件實作。如果團隊看過 GPL 程式碼,最好做程式碼隔離/法務評估。
- Google Play 容器政策:容器 App 允許,但必須檢查目標應用程式的
REQUIRE_SECURE_ENV,不得代理容器外 App 的 API。 - 目標應用程式的 ToS:自動化操作、重打包、繞過校驗可能違反第三方 App 條款。
- Native hook 程式庫授權:ShadowHook/ByteHook/Dobby/xDL 等要逐個核對授權和商業條款。
- Hidden API 限制:Android 9+ 對非 SDK 介面有限制;不同版本需要不同的繞過策略,穩定性自行負責。
1. 需求與限制¶
1.1 目標¶
- 在 App 內建立多個「虛擬空間」。
- 虛擬空間內安裝 APK、啟動、停止、多開。
- 本機 Agent 用自然語言操作虛擬空間內 App。
- 遠端 Agent 透過 MCP 操作同一個虛擬空間。
- 兩條通道行為一致,不會互相中斷。
1.2 限制¶
- 一般 APK,不能控制 ROM/系統簽章。
- 自研容器引擎;不依賴 VirtualApp 商業版。
- Android 12–17,arm64,盡量不 root。
- 本機 Agent 模型可以是裝置端(Gemini Nano 4/Gemma 4)或雲端(Claude API)。
1.3 功能範圍¶
- 虛擬空間管理:建立、刪除、重新命名、列出
- App 管理:安裝 APK(含 split)、卸載、列出、圖示/名稱
- 執行管理:啟動、停止、前景切換、最近任務
- 觀察:UI 樹、截圖、目前套件名稱/Activity
- 操作:點擊、長按、滑動、捲動、輸入、返回/Home/最近任務
- 等待/驗證:等待文字、等待套件名稱、等待介面閒置
- MCP:遠端 Agent 探索/呼叫工具
- 本機 Agent:自然語言任務 → 工具呼叫迴圈
- 工作階段:租約、核准、稽核、軌跡
- 技能:錄製、回放、參數化
1.4 非功能需求¶
- 單次操作 p50 < 500 ms,p95 < 2 s(不含模型推論)
- 虛擬空間之間資料隔離(邏輯隔離)
- 當機率、ANR、背景存活可觀測
- 安裝/啟動失敗有結構化錯誤
- MCP 預設只綁定 loopback,LAN 需明確開啟
- 本機 Agent 與遠端 Agent 的工具行為一致
1.5 不做(目前版本)¶
- 金融/銀行/GMS/Play Integrity 類 App
- 不可信 APK 的強隔離
- 系統級空間/多使用者/工作設定檔
- 雲端手機/遠端實機
- 「所有 App 相容」的承諾
2. 自研容器總體架構¶
2.1 設計目標¶
- 目標應用程式的
Activity能正常建立、顯示、接收輸入、走 Activity 生命週期。 - 目標應用程式認為自己已安裝:
PackageManager能查到自己的PackageInfo、權限、簽章。 - 目標應用程式的私有資料落到獨立目錄,不污染宿主。
- 多個執行個體可以共存。
- 當機不要拖垮宿主。
- 控制層能觀察/操作目標應用程式的 UI。
2.2 處理程序模型¶
階段 A:單一處理程序(MVP)
- 宿主主處理程序裡載入目標 APK;目標 Activity 執行於宿主處理程序。
- 優點:實作簡單,除錯方便,不需要跨處理程序。
- 缺點:目標應用程式當機會拖垮宿主;多開共用一個處理程序;記憶體壓力大。
- 適合:驗證可行性、簡單 App。
階段 B:宿主宣告的 :pN 處理程序池(產品)
- 在宿主
AndroidManifest.xml裡宣告一組處理程序(:p0、:p1、…),並為每個處理程序宣告一個StubActivity/VClientService。 - 啟動虛擬應用程式時,選擇閒置處理程序,啟動該處理程序裡的
StubActivity;系統會建立正常的 app 處理程序和ActivityThread。 - 在該處理程序裡初始化容器 hook,載入目標 APK,執行目標 Activity。
- 優點:是正常的 app 處理程序,不受 phantom process killer 影響;當機隔離;可並行多個執行個體。
- 缺點:處理程序池大小固定;記憶體/啟動成本;處理程序重用要清理狀態。
不建議用
Runtime.exec("app_process ...")啟動子處理程序:Android 12+ 的 phantom process killer 會殺掉,且權限/處理程序歸屬更複雜。
處理程序分配策略:
- 每個虛擬空間一個處理程序;或每個執行個體一個處理程序;或依記憶體/前景狀態動態分配。
- 容器槽位(槽位處理程序池)預設 2–4 個(依裝置記憶體調整)。
- 前景執行個體優先獨占;背景執行個體可重用處理程序。
- 處理程序當機後標記 dirty,清理後再重用。
2.3 總體圖¶
┌──────────────────────────────────────────────────────────────────┐
│ Host main process │
│ │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────────┐ │
│ │ ContainerService│ │ ToolRegistry │ │ Host UI / MCP │ │
│ │ - Installer │ │ - Local Agent │ │ - Space launcher │ │
│ │ - VPMS │ │ - MCP Server │ │ - Agent console │ │
│ │ - VAMS │ │ - Lease/Approval│ │ - Installer │ │
│ │ - Process Pool │ └────────────────┘ └────────────────────┘ │
│ └───────┬────────┘ │
│ │ IPC (Binder/AIDL) │
│ ┌───────▼─────────────────────────────────────────────────────┐ │
│ │ Client process :p0 (normal app process) │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │
│ │ │ VClient │ │ HookManager │ │ Target Activity │ │ │
│ │ │ (ActivityThread)│ │ Java/Native │ │ (target APK) │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────────┘ │ │
│ │ Virtual ClassLoader / Resources / data dirs │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Client process :p1 ... :pN (same structure) │ │
│ └─────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
2.4 兩條實作路線¶
| 路線 | 做法 | 優點 | 缺點 |
|---|---|---|---|
| Java 層虛擬化(VirtualApp 風格) | Hook Instrumentation、ActivityThread.mH、ApplicationPackageManager、ContextImpl、Binder Java 方法;動態代理/反射 |
不需要 native hook;除錯相對容易;能涵蓋大部分 Java 呼叫 | Hidden API 限制;Binder/UID/native 層騙不過;每個 Android 版本都要調適 |
| Native hook / ART | Hook Binder.execTransact、Process.myUid、open、dlopen、ART 方法 |
更強;能處理 native 程式碼和系統呼叫 | 極複雜;ABI/版本/ROM 差異;穩定性/安全風險高 |
建議:Java-first,native 只處理關鍵點。
- 第一版:Java 層 Hook 驗證 Activity/PMS/Resources/IO 可行。
- 第二版:對 Binder/UID/native 路徑做 native hook。
- 不要一開始就寫「通用 ART hook 框架」;先用成熟 hook 程式庫,讓主要流程順利執行。
2.5 Hook 框架¶
Java 層:
- 反射 + 動態代理(
Proxy/InvocationHandler)替換IPackageManager、IActivityManager等介面。 Unsafe/MethodHandles修改 final 欄位。- Hidden API 豁免:
VMRuntime.setHiddenApiExemptions或「FreeReflection」類方案;Android 10+ 需要依版本處理。 ActivityThread.mH的Handler.Callback替換(注意不能破壞系統訊息)。Instrumentation子類別替換(ActivityThread.mInstrumentation)。
Native 層:
- PLT/GOT hook、inline hook;優先使用維護中的程式庫(ShadowHook、ByteHook、Dobby、xDL),逐一核對授權。
- 關鍵 hook 點:
Binder.execTransact、Process.myUid、open/fopen、dlopen/android_dlopen_ext。 - 版本調適:每個 Android 大版本維護 offset/符號表;用 feature flag 漸進式發布。
版本調適層:
interface AndroidAdapter {
val apiLevel: Int
fun installPackageHooks()
fun installActivityHooks()
fun installResourceHooks()
fun installBinderHooks()
fun installNativeHooks()
}
每個 API level 一個實作;共用邏輯抽到 base;不要在新版本上假設舊 hook 點還在。
3. 關鍵子系統¶
3.1 APK 解析 / 安裝器¶
輸入:
- 單一 APK
- split APK(base + config.arm64_v8a + config.xxhdpi 等)
.apks/.xapk(解包後依 split 處理)
解析:
- 二進位
AndroidManifest.xml(AXML):package、versionCode、minSdk、targetSdk、application、activities、services、providers、receivers、permissions、meta-data、REQUIRE_SECURE_ENVproperty。 resources.arsc:資源表、設定、split 資源。- 簽章:V1/V2/V3;簽署者/憑證摘要。
- 原生程式庫:ABI、16 KB 對齊。
- split 中繼資料:
splitNames、splitTypes、configForSplit、isFeatureSplit。
實作選擇:
- 使用 AOSP
PackageParser/ParsingPackageUtils(hidden API,版本差異大);或 - 使用開源 APK 解析程式庫(如 ARSCLib/apk-parser)自行建立模型;或
- 兩者結合:解析用開源程式庫,執行階段
PackageInfo自行建構。
安裝:
- 預檢(附錄 A)。
- 複製 APK/splits 到虛擬空間私有目錄。
- 解析並建構內部
PackageInfo/ActivityInfo/ServiceInfo/ProviderInfo。 - 建立
data/、files/、cache/、databases/、shared_prefs/、lib/。 - 註冊到 VPMS。
- 建立啟動器/捷徑。
- 首次啟動時初始化
Application。
必須檢查:
REQUIRE_SECURE_ENV=1→ 拒絕載入。- 未對齊 16 KB 的原生程式庫 → 在 16 KB 裝置上拒絕/提示。
- 不完整 split → 拒絕。
- 低 targetSdk → 是否允許,依 Android 版本策略。
3.2 虛擬 PackageManager(VPMS)¶
目標:透過 PackageManager 虛擬化,讓目標應用程式從 Context.getPackageManager() / ActivityThread.getPackageManager() 看到「自己已安裝」。
要虛擬化的方法(至少):
getPackageInfo/getApplicationInfo/getActivityInfo/getServiceInfo/getProviderInforesolveActivity/queryIntentActivities/queryIntentServicesgetInstalledPackages/getInstalledApplicationsgetPackagesForUid/getPackageUid/getNameForUid/getPackageGidscheckPermission/checkSignatures/getPackageInfo(GET_SIGNATURES)getLaunchIntentForPackagegetApplicationEnabledSetting/setApplicationEnabledSettinggetResourcesForApplication/getResourcesForActivitygetPackagesHoldingPermissions
實作方式:
- Context 層級包裝:給目標應用程式一個自訂
Context/ContextWrapper,getPackageManager()傳回VPackageManager;getResources()傳回目標 Resources;getClassLoader()傳回目標 ClassLoader。 - ActivityThread/IPackageManager 代理:替換
ActivityThread.getPackageManager()傳回的IPackageManager動態代理;非虛擬套件轉發給系統,虛擬套件查 VPMS。 - ApplicationPackageManager hook:對直接
new ApplicationPackageManager(...)或靜態呼叫的情境做補充。
優先做 1 + 2;3 作為備援。
IPackageManager是 AIDL 介面,Android 版本間方法會變,代理要依版本產生/調適。
簽章:
- 傳回目標 APK 的真實簽章(如果可信),或依產品策略偽造;後者會觸發完整性校驗,不建議。
checkSignatures對自己傳回MATCH;對系統套件依實際結果。
3.3 虛擬 ActivityManager(VAMS)¶
目標:讓目標應用程式的 startActivity/startService/bindService/sendBroadcast 能啟動到虛擬元件,而不是被系統拒絕。
核心:Stub Activity 方案
- 宿主 Manifest 宣告一組
StubActivity0..N(可帶android:process=":pX"、不同launchMode/taskAffinity)。 - Hook
Instrumentation.execStartActivity: - 判斷目標 Intent 是否指向虛擬套件;
- 是:把 component 替換成某個
StubActivity,把原始 Intent/目標元件/執行個體 id 存到 extras 或 VAMS 對應表; - 否:放行。
- 系統啟動
StubActivity;ActivityThread建立它。 - Hook
Instrumentation.newActivity/ActivityThread.mH: - 用目標 APK 的
ClassLoader具現化目標 Activity; - 替換
StubActivity的執行個體/欄位,或讓StubActivity代理生命週期; - 注入目標
Resources、Context、Application、Window。 - Activity 生命週期回呼轉發到目標 Activity;
onBackPressed/onNewIntent/onActivityResult也要處理。 - 任務堆疊:把 StubActivity 對應回目標 Activity;
finish/moveTaskToBack/最近任務顯示目標名稱/圖示。
已實作:Stage C-lite(2026-11-03)
沒有引入 StubActivity0..N 陣列,而是重用 StageAActivity 本身作為「通用 Stub」:
- 每個目標 Activity 對應一個真實
StageAActivity執行個體,透過host.startActivity/startActivityForResult入堆疊;Android 自己的任務堆疊負責返回。 ContainerInstrumentation宣告 AOSP 的隱藏方法execStartActivity(Context, IBinder, IBinder, Activity, Intent, int, Bundle)(不加override,先安裝 HiddenApiBypass 豁免),執行階段由 ART 虛擬分派到它。- 目標套件內的顯式 ComponentName(含 activity-alias)→ 新建
StageAActivity,extras 攜帶 sessionId、目標類別、原始 Intent;外部 Intent → 交給目前宿主啟動。 ContainerSessions依 sessionId 共用 target Application/ClassLoader/Resources;每個目標 Activity 只建立自己的 Activity 執行個體、Theme 和 decorView。finish()使用真實 host token,所以只關閉目前目標對應的StageAActivity,上一個宿主自然恢復;Splash → 首頁、startActivityForResult結果回傳均已驗證。- 生命週期由宿主轉發:onCreate/onStart/onRestart/onResume/onPause/onStop/onDestroy,以及
onActivityResult(反射呼叫目標 Activity 的 protected 方法)。 - 尚未實作:IntentFilter/隱式 Intent 解析、
onNewIntent的 singleTop/CLEAR_TOP 語意、Activity options/轉場、requestPermissions的 token 對應、最近任務顯示目標名稱/圖示。 - 真實 App 相容補充(MuMu / Android 15 驗證):
- 目標 APK 可能自帶 AndroidX(高德即如此)。
DexClassLoader預設 parent-first,會讓目標類別連結到宿主的 AndroidX,出現欄位缺失(ViewCompat.a)。ContainerClassLoader改為 child-first,先查目標 dex,再回退宿主/系統。 - AMap 的
PageSupportActivity.attachBaseContext會呼叫createConfigurationContext處理 locale;原本實作傳回宿主資源。ContainerContext現在用目標 APK 的 AssetManager + 新 Configuration 重建 Resources,並傳回一個保持目標套件名稱/ClassLoader/目錄的 ContextWrapper。 - 高德仍卡在
ICloudConfigService為空和 AMSPermission Denial,需要 Service/Provider/Binder 虛擬化才能繼續。
已實作:容器內 Service / Provider(Stage C-lite+)
- Service:
ContainerContext覆寫startService/startForegroundService/bindService/unbindService/stopService;目標套件顯式 Service 交給ContainerServiceManager,外部 Service 回退宿主。- 目標 Service 用目標
ContainerClassLoader具現化,attachBaseContext(ContainerContext),設定Service.mApplication,直接驅動onCreate/onStartCommand/onBind/onUnbind/onDestroy。 bindService把onBind傳回的IBinder透過主執行緒onServiceConnected交給目標ServiceConnection;startActivityForResult與 Service 回呼都在目標處理程序內完成。- 僅限顯式 ComponentName;隱式 Service Intent 未解析。
startForeground/stopSelf仍可能觸達宿主 AMS。 - ContentProvider:
- 工作階段啟動時(Application.onCreate 之前)依 manifest 建立目標 Provider,
attachInfo(ContainerContext, ProviderInfo)會執行onCreate。 - 用
ContentResolver.wrap(dispatcherProvider)得到目標 resolver;dispatcher 依 URI authority 路由到本機 Provider,外部 authority 回退宿主 application context 的真實 resolver。 - 覆寫 query/insert/update/delete/bulkInsert/getType/call/openFile/openAssetFile/openTypedAssetFile;目標 Provider 之間可以互相查詢。
-
Provider 權限、處理程序隔離、
FileProvider的 PackageManager meta-data 查詢未虛擬化。 已實作:Stage C-D(2026-11-04) -
處理程序模型:
ContainerProcessPool把每個package@instance固定到:c1..:c4之一;容器槽位重用前強制 kill 並等待結束,ContainerSessions在槽位處理程序內拒絕第二個工作階段。目標 Java 未攔截例外由TargetCrashGuard吞掉並寫入當機標記;native crash/System.exit只殺掉該槽位處理程序,主處理程序與其他執行個體不受影響。下次space_launch傳回restarted_after_crash/crash_reason。 - JobScheduler:
ContainerContext.getSystemService(JOB_SCHEDULER_SERVICE)傳回ContainerJobScheduler;目標JobService用目標 ClassLoader 具現化,驅動onCreate/onStartJob/onStopJob,支援最小延遲、週期、退避和網路/充電/低電量/閒置限制(無法判斷的限制視為已滿足)。處理程序存活期間有效。 - 通知:
INotificationManager代理把套件名稱改成宿主套件,tag/channel/group 加上虛擬套件前綴,small icon 重繪為 bitmap,目標 PendingIntent 換成宿主ContainerLaunchProxyActivity跳板;channel 查詢結果去掉前綴。 - 廣播橋接:目標靜態/動態 Receiver 的 action 會註冊到宿主;系統 sticky/一般廣播注入容器,目標發出的非自身作用域廣播在本機分送後繼續發給宿主,並帶 origin extra 防止回送迴圈。
- 權限:
checkSelfPermission/checkPermission/enforce*/PackageManager 權限查詢走虛擬授權表;改寫ContainerPackageManager.buildRequestPermissionsIntent/getPermissionControllerPackageName並宣告隱藏的Context.getPermissionRequestState,使requestPermissions能被容器接管。宿主宣告的權限會彈出真實系統對話框,結果依套件持久化;MCPspace_permissions支援 list/grant/revoke。 - 完整 VPMS:
VirtualPackageRegistry從state.json建立輕量索引,依需求解析同虛擬空間其他 APK;getInstalledPackages/getInstalledApplications、顯式元件、隱式 Intent、resolve*/query*、標籤/圖示/Resources/啟動 Intent/簽章/權限宣告均涵蓋跨虛擬應用程式。其他應用程式使用穩定合成 UID。 - 效能限制:清單/標籤/UID/簽章走輕量索引;未指定套件的隱式 Intent 不掃描所有虛擬應用程式,只有顯式套件/元件查詢才解析對應 APK。否則知乎啟動時解析網易雲 100MB+ APK 會阻塞主執行緒。
- 相容性限制:
WindowManager動態代理只對網易雲啟用(Toast 套件名稱改寫)。全域代理會被知乎 native SDK 傳入 JNI,CheckJNI 回報invalid jobject並中止容器處理程序。 - 多執行個體
.so隔離:同套件不同執行個體分配不同容器槽位;原生程式庫依執行個體目錄擷取;容器槽位重用時先殺掉舊的槽位處理程序,避免同名.so與 JNI 全域狀態混用。
其他元件:
- BroadcastReceiver:靜態/動態註冊;靜態 receiver 需要在 VPMS 裡解析,動態註冊 hook
registerReceiver;宿主端橋接見 Stage C-D。 - JobScheduler/通知已做容器內虛擬化(見上);WorkManager 若走自身的
SystemJobScheduler會使用容器 JobScheduler,依賴系統喚醒的持久背景任務仍會退化。startForeground前景服務仍可能觸達宿主 AMS。
背景啟動 Activity:
- Android 10+ 限制背景啟動;宿主必須在前景,或有通知/前景服務/使用者互動。
- 方案:
SpaceLaunchActivity透明前景 Activity + 通知;或在使用者點擊時啟動;不要假設背景 Agent 隨時能拉起介面。 - Android 14+ 對
PendingIntent背景啟動還有額外限制,需逐版本驗證。
3.4 ClassLoader / Resources¶
ClassLoader:
- 用
DexClassLoader/PathClassLoader載入目標 APK 的 dex(base + splits)。 - 父 ClassLoader:宿主 ClassLoader(能共用 AndroidX/Kotlin 執行環境)或
ClassLoader.getSystemClassLoader();注意類別衝突(目標應用程式自帶同名程式庫)。 - 多 dex:
BaseDexClassLoader支援 dex 清單;split 的 dex 都要加入。 - 目標應用程式的
Application/Activity類別透過目標 ClassLoader 載入。 LoadedApk建構:反射建立/填入mApplicationInfo、mClassLoader、mResources、mAppDir、mNativeLibraryDir、mDataDir;註冊到ActivityThread.mPackages。
Resources:
AssetManager加入 base + 所有 split 的資源路徑。- 用目標
DisplayMetrics/Configuration建立Resources;設定Resources.Theme。 - 處理
resources.arsc的 split/overlay;資源 id 衝突要依 split 合併。 Context.getResources()/getAssets()/getTheme()傳回目標資源。- 圖示/名稱:從目標
ApplicationInfo/Resources讀取,用於虛擬空間 UI。
Application:
- 呼叫
LoadedApk.makeApplication或手動Instrumentation.newApplication。 attachBaseContext(targetContext)→onCreate()。- 目標應用程式的
ContentProvider初始化順序要模擬(installContentProviders)。
3.5 Binder / UID 虛擬化(最難)¶
目標:
- 目標應用程式呼叫
Binder.getCallingUid()/Process.myUid()/UserHandle.myUserId()時,看到虛擬 UID/user。 - 目標應用程式呼叫系統服務時,系統認為它是宿主 UID(現實),但 App 端以為自己是虛擬 UID。
Java 層能做的:
- Hook
Process.myUid()、Binder.getCallingUid()、UserHandle.myUserId()、Process.myUserHandle()的傳回值。 - Hook
PackageManager.getPackagesForUid/getNameForUid/getPackageUid傳回虛擬結果。 - Hook
Context.getUserId()/getPackageName()/getOpPackageName()。
做不到的:
- 系統服務端(AMS/PMS/AccountManager/Location/Notification)仍依宿主 UID 判定權限/AppOps;你無法在不改系統的前提下讓系統認一個不存在的 UID。
- Binder 交易裡的
uid/pid是核心填入的;Java 層改不了。要改只能 native hookBinder.execTransact/Parcel,風險極高。 - 因此:應用程式級容器無法提供真正的 UID 隔離;它只能「騙 App 的 Java 層」,不能「騙系統」。
建議:
- 第一版不做 UID 虛擬化;只做 package/component 虛擬化。
- 第二版對
Process.myUid/Binder.getCallingUid做 Java hook,涵蓋常見檢查。 - 第三版再評估 native Binder hook;不要作為 MVP 前置條件。
3.6 IO / 資料隔離¶
要重新導向的路徑:
Context.getFilesDir()/getCacheDir()/getDir()/getDatabasePath()/getSharedPreferences()ContextImpl.getDataDir()/getDatabasesDir()/getSharedPreferencesPath()SQLiteDatabase.openDatabase/openOrCreateDatabaseEnvironment.getExternalStorageDirectory()/getExternalFilesDir()System.loadLibrary/System.load/dlopenRuntime.exec的工作目錄(選用)
實作:
- 給目標應用程式一個
ContextWrapper,覆寫上述方法,傳回執行個體目錄。 - 對靜態/內部呼叫,hook
ContextImpl對應方法。 - 對
SharedPreferencesImpl/SQLiteDatabase的直接路徑呼叫,hook 開啟方法。 - 對 native 檔案存取,native hook
open/fopen/stat(選用,後期)。 - 每個執行個體一個根目錄:
/data/user/0/<host>/spaces/<spaceId>/instances/<instanceId>/<pkg>/。
注意:
- 這不是安全隔離;native 程式碼/絕對路徑可以繞過。
- 外部儲存/MediaStore 的隔離有限;產品上要說明。
- 備份/還原只涵蓋允許的目錄。
3.7 權限 / AppOps / 通知¶
權限:
- 目標應用程式的
checkPermission/checkCallingPermission走虛擬權限管理員:先查目標應用程式的 manifest 權限,再查宿主是否已授予對應系統權限,最後依產品策略傳回。 - 執行階段權限:hook
Activity.requestPermissions/Fragment.requestPermissions,宿主彈出視窗;使用者允許後記錄虛擬授權,並在需要時向系統申請宿主權限。 AppOpsManager:目標應用程式的操作會依宿主套件名稱/UID 記錄;如需精確,hookAppOpsManager的 Java 方法(有限)。- 權限對應表要維護:目標權限 → 宿主權限 → 是否必須。
通知:
- 目標應用程式的
NotificationManager.notify需要改寫Notification的 package/tag/id,或用宿主套件名稱發通知並保留目標圖示/文案。 PendingIntent的 package/UID 要對應回虛擬元件;Android 12+ 通知 trampoline 限制。- Android 13+
POST_NOTIFICATIONS執行階段權限由宿主申請。 - 第一版可以先不支援目標通知;提供「虛擬空間內通知中心」作為替代。
3.8 處理程序模型 / phantom process¶
- 用宿主宣告的
:pN處理程序,不用app_process子處理程序。 StubActivity/VClientService宣告在:pN,啟動時系統建立正常 app 處理程序。- 處理程序池大小依裝置記憶體:低階 1–2,高階 3–4。
- 處理程序重用前清理:目標 Activity/Service、ClassLoader、Resources、靜態狀態、檔案鎖。
- 處理程序當機:標記 dirty,重新啟動;宿主 UI 顯示「空间已重启」(空間已重新啟動)。
- 前景服務保持運作:宿主主處理程序 + 目前虛擬空間處理程序;避免所有處理程序常駐。
- Android 12+ 的 phantom process killer 不適用於正常 app 處理程序,但背景處理程序仍會被 LMK 殺掉;用前景服務/使用者可見狀態。
3.9 原生程式庫 / 16 KB¶
- 安裝時依 ABI 選擇 split;把
.so解壓縮到執行個體lib/目錄。 ApplicationInfo.nativeLibraryDir指向該目錄;LoadedApk/ClassLoader使用它。- Hook
System.loadLibrary/System.load:依目標應用程式的nativeLibraryDir解析。 - 16 KB page:檢查 ELF
p_align;不支援則拒絕/提示。 dlopen命名空間:目標.so依賴的系統程式庫由 linker 解析;自有.so用絕對路徑。- JNI:目標應用程式的
System.loadLibrary和RegisterNatives通常在目標 ClassLoader 下運作;注意全域 JNI 狀態衝突。 - 不要在同一個處理程序載入兩個版本不相容的同一個
.so(多執行個體可能衝突);必要時依處理程序隔離。
目前實作(Stage B+):
ApkInspector掃描 base/split 中的lib/<abi>/*.so,得到可用 ABI 集合。NativeLibs依Build.SUPPORTED_ABIS選第一個相符項目,把該 ABI 的.so平鋪擷取到vspace/data/<pkg>/<instance>/native-libs,用 stamp 快取避免每次啟動重複複製。DexClassLoader(dexPath, codeCache, nativeLibsDir, parent),並設定ApplicationInfo.nativeLibraryDir。- 安裝不因 ABI 失敗;啟動前檢查,若無相符 ABI 傳回
ABI_MISMATCH並提供裝置/應用程式 ABI。僅 arm64 的 APK 在 x86_64 模擬器上會停在這裡。 - 尚未實作:16 KB
p_align檢查、dlopen/System.loadhook、多執行個體同名.so隔離。
3.10 背景 / Job / Provider / WebView¶
這些是「產品化」繞不過去的困難點,建議分階段:
| 能力 | 難度 | 第一版策略 |
|---|---|---|
| JobScheduler | 高 | 不支援;目標應用程式的背景任務退化 |
| AlarmManager | 高 | 部分支援;依宿主套件名稱排程,喚醒後轉發 |
| ContentProvider | 高 | 先不支援跨 App provider;內部 provider 嘗試 hook |
| 靜態 BroadcastReceiver | 中 | VPMS 解析 + 宿主 receiver 轉發 |
| 通知 | 中 | 第一版不做;後續代理 |
| WebView | 中 | WebView.setDataDirectorySuffix 是處理程序級;多執行個體需獨立處理程序 |
| 帳號/同步 | 高 | 不承諾 |
| 桌面捷徑 | 中 | 宿主 shortcut 指向 StubActivity |
3.11 Hook / 版本調適¶
- Java hook 點:
ActivityThread、Instrumentation、LoadedApk、ApplicationPackageManager、ContextImpl、Binder、Process、UserHandle、ResourcesManager。 - Native hook 點:
Binder.execTransact、Process.myUid、open/fopen、dlopen/android_dlopen_ext。 - 版本調適:每個 API level 一個
AndroidAdapter;hook 點缺失時降級/停用功能。 - 漸進式發布:依裝置/Android 版本/目標應用程式開關;線上當機可回滾。
- 測試:每個 Android 版本至少一台實機;AOSP/Pixel 優先,OEM 後補。
3.12 安全邊界¶
- 應用程式級容器共用宿主 UID;惡意 App 可以繞過 Java 層重新導向讀取宿主/其他執行個體資料。
- 建議只安裝自己熟悉的 App,不要把容器當作隔離不可信應用程式的手段。
- Agent 能看到螢幕敏感資訊;預設去識別化、預設不記錄密碼/支付。
- 提示注入:螢幕文字是不可信輸入;工具允許清單 + 危險操作確認。
- MCP 預設 loopback;LAN 模式明確開啟 + token(目前 HTTP,TLS 待補)。
4. 控制層¶
4.1 控制通路¶
| 通路 | 前提 | 觀測 | 動作 | 延遲 | Android 17 風險 | 適用 |
|---|---|---|---|---|---|---|
| Shizuku + shell | 安裝 Shizuku,無線偵錯啟動 | uiautomator dump、screencap |
input tap/swipe/text/keyevent |
100–500 ms | 不受 AAPM 影響 | 主力(如果使用者願意安裝 Shizuku) |
| Shizuku + UiAutomation | 同上,UserService 內執行 UiAutomation | 節點樹、截圖 | 注入事件 | 50–150 ms | 不受 AAPM 影響 | 低延遲情境 |
| AccessibilityService | 使用者手動開啟 | 節點樹、事件 | dispatchGesture、ACTION_SET_TEXT |
50–100 ms | AAPM 會限制非無障礙類 App | 主力(如果不想安裝 Shizuku) |
| Vision + 座標 | 截圖權限 | 像素 | 座標點擊(需另一通路注入) | 1–3 s | 不受 AAPM 影響 | 遊戲/Canvas/Flutter 備援 |
| 容器 in-process | 自研 hook | 目標 View 樹(可行時) | 直接呼叫/注入 | 最低 | 不受 AAPM 影響 | 虛擬空間操作;UI 控制待驗證 |
建議:
- 虛擬空間操作走
ContainerService(安裝/啟動/停止/多開)。 - UI 控制做兩個 adapter:
ShizukuChannel和AccessibilityChannel,依可用性自動選擇。 - Vision 只做備援。
- 容器 in-process 只用於自研引擎內部能穩定取得的介面,不作為第一版 UI 控制主路徑。
4.2 觀測協定¶
{
"snapshot_id": "s_123",
"space_id": "sp_1",
"package": "com.example.app",
"activity": ".MainActivity",
"screen": { "width": 1080, "height": 2400, "rotation": 0 },
"nodes": [
{
"id": "n_1",
"role": "button",
"text": "搜索",
"desc": null,
"bounds": [820, 120, 1040, 200],
"clickable": true,
"editable": false,
"scrollable": false,
"enabled": true,
"focused": false,
"selected": false
}
],
"screenshot": "sha256:...",
"timestamp_ms": 1730000000000
}
建議:
- 節點扁平化,只保留可見/可點擊/可編輯/有文字/有描述的節點。
- 預設
max_nodes=120;需要時detail=full。 - 截圖預設不傳送;需要視覺時再取得。
- 節點 id 單次 snapshot 內穩定;跨 snapshot 用
(package, activity, viewId, text, bounds bucket)近似比對。 - 截圖來源:AccessibilityService.takeScreenshot → MediaProjection → PixelCopy。
4.3 動作協定¶
| 動作 | 參數 | 說明 |
|---|---|---|
tap |
node_id / bounds / text | 優先 node_id |
long_press |
同上 + duration | 長按選單 |
swipe |
from/to/duration | 滑動/翻頁 |
scroll |
direction/amount | 優先 ACTION_SCROLL_FORWARD |
set_text |
node_id/text | ACTION_SET_TEXT → 剪貼簿貼上 → 逐鍵輸入 |
key |
keycode | back/home/recents/enter/delete |
launch_app |
package/activity | 走 ContainerService |
wait_for |
text/package/activity/idle | 帶逾時 |
intent |
action/data/extras | 深層連結/系統 Intent |
clipboard |
get/set | 敏感操作要確認 |
執行後必須:重新 observe → 驗證預期變化 → 失敗重試一次 → 再失敗走視覺/人工。
4.4 視覺備援¶
適用於遊戲、Flutter、Canvas、部分 WebView(節點樹為空/不可靠)。
- 截圖降取樣到 720p/1080p。
- 多模態模型輸出座標/區域,或 ML Kit OCR + 範本比對。
- 座標正規化到螢幕比例。
- 透過 Shizuku/無障礙注入點擊。
視覺是備援,不是主路徑;成本和延遲都高。
4.5 錄製 / 回放 / 技能¶
- 錄製:
(action, snapshot_hash, timestamp, result)。 - 產生:穩定序列轉成帶斷言的 YAML/JSON 指令碼。
- 回放:每步檢查前置條件;不滿足則回退 Agent。
- 技能庫:依 App 組織,如
wechat.send_message、settings.open_wifi。 - 技能是降低 token 成本的關鍵:常見任務走指令碼+驗證,不走大型模型。
4.6 Android 17 Advanced Protection Mode(AAPM)¶
- 開啟 AAPM 後,非無障礙類 App 無法使用 AccessibilityService,已有權限會被撤銷。
- 應對:
- 控制層做成雙 adapter:Shizuku 優先,無障礙次之,Vision 備援。
- 偵測
AdvancedProtectionManager,提示使用者「關閉 AAPM 或使用 Shizuku 模式」。 - 不要把「無障礙自動化」當作唯一賣點。
- 上架 Google Play 時,核心功能要能在不依賴無障礙的情況下運作。
4.7 安全邊界¶
- 容器共用宿主 UID;建議只安裝自己熟悉的 App。
- Agent 能看到螢幕敏感資訊;預設去識別化,預設不記錄密碼/支付頁面。
- 提示注入:螢幕文字是不可信輸入;工具允許清單 + 危險操作確認。
- MCP 預設 loopback;LAN 模式明確開啟 + token(目前 HTTP,TLS 待補)。
- 所有安裝/卸載/支付/權限變更二次確認並稽核。
5. 雙通道 Agent / MCP¶
5.1 兩種控制通道(都要支援)¶
| 通道 | Agent 在哪 | 模型 | 連線方式 | 優點 | 缺點 |
|---|---|---|---|---|---|
| 本機 Agent | 手機 App 內 | 裝置端(Gemini Nano 4 / Gemma 4 / LiteRT-LM)或雲端(Claude API) | 處理程序內直接呼叫 ToolRegistry |
獨立、離線(裝置端模型)、低延遲 | 本機模型能力/機型受限;雲端模型需網路 |
| 遠端 Agent | 手機外(Claude Desktop/Code/雲端) | 由遠端 Agent 決定 | MCP over Streamable HTTP(USB/LAN) | 模型能力最強、容易除錯 | 需要 PC/網路;暴露介面需身分驗證 |
兩條通道不是二選一,而是共用同一套工具和權限模型:
- 本機 Agent:Agent 迴圈在 App 內,直接呼叫
ToolRegistry。 - 遠端 Agent:Agent 迴圈在手機外,透過 MCP adapter 呼叫同一套
ToolRegistry。 - App 對兩者都是「工具執行器 + 虛擬空間控制器」。
「本機 Agent」指 Agent 迴圈在手機上,不代表模型一定在手機上:模型可以是 Gemini Nano 4(裝置端),也可以是 Claude API(雲端)。兩者都透過
ModelAdapter接入。
5.2 統一工具層¶
data class ToolSpec(
val name: String,
val description: String,
val inputSchema: JsonObject,
val mutating: Boolean,
val requiredScope: Scope
)
class ToolRegistry(
private val tools: Map<String, RegisteredTool>,
private val leaseManager: LeaseManager,
private val approvalGate: ApprovalGate,
private val audit: AuditLog
) {
fun list(): List<ToolSpec>
suspend fun execute(
name: String,
args: JsonObject,
ctx: ToolContext
): ToolResult
}
data class ToolContext(
val sessionId: String,
val caller: Caller, // LOCAL_AGENT / REMOTE_MCP / USER
val spaceId: SpaceId,
val scope: Scope
)
- MCP adapter:把
ToolSpec對應成 MCPtools/list+tools/call,JSON Schema 驗證。 - 本機 Agent adapter:把同一份
ToolSpec轉成 Gemini/Claude 的 function declaration,執行仍走ToolRegistry。 - 兩邊共用:參數驗證、錯誤碼、核准、租約、稽核、軌跡。新增工具只寫一次。
5.3 MCP Server(遠端通道)¶
本機 Agent 不走 MCP,直接呼叫
ToolRegistry;但兩者的工具定義、權限、租約、稽核完全一致。MCP Server 只是遠端 Agent 的傳輸轉接層。
實作策略:
- 基礎版:內建
ServerSocket最小 HTTP server(POST /mcp),只用java.net/java.io,相容 API 24/25。不要用 Ktor 3.x:它依賴java.nio.file(API 26+),在 Android 7.x 會啟動失敗。 - 正式版:官方 MCP Kotlin SDK(
io.modelcontextprotocol:kotlin-sdk-server)+ Ktor 3.x;此時 minSdk 需要 ≥ 26,或做相容處理。 - Transport:Streamable HTTP(
/mcp),支援 POST、GET SSE 心跳和 DELETE 工作階段;不用 stdio(Android 沒有 stdio host)。 - 狀態:每個用戶端獨立
Mcp-Session-Id,30 分鐘閒置回收;未知/過期工作階段傳回 404;CORS 供 MCP Inspector 除錯。 - 限制:請求主體上限 40 MB、請求標頭 64 KB、並行連線 32;身分驗證在讀取 body 前完成,Bearer Token 使用常數時間比較。
Android 端:
- 前景服務(
dataSync或specialUse)+ 常駐通知。 - 電池最佳化允許清單;Doze 下保持連線。
- 預設綁定
127.0.0.1:8765;首次啟動產生隨機 token。 - LAN 模式:明確開關 + token;目前實作為 HTTP(TLS 待補),Android 17 需要
ACCESS_LOCAL_NETWORK。儲存庫提供零依賴 CLI:tools/nestspace-mcp。
連線:
# USB(建議)
adb forward tcp:8765 tcp:8765
# Claude Code
claude mcp add --transport http space http://127.0.0.1:8765/mcp \
--header "Authorization: Bearer <token>"
5.4 本機 Agent Runtime¶
AgentLoop:observe → plan → tool call → verify → repeat;與遠端 Agent 使用同一套工具。ModelAdapter三選一:GeminiNanoAdapter:ML Kit GenAI Agent API(Alpha;裝置支援有限)。LiteRTAdapter:Gemma 4 / 自訂模型(離線,資源需求高)。ClaudeAdapter:手機直接呼叫 Anthropic Messages API(能力最強,需網路/key)。- 前景服務 + 通知;長時間任務可取消;螢幕關閉時保持 wake lock。
- UI:任務輸入、即時軌跡、核准彈出視窗、緊急停止。
- 失敗/逾時:回退到遠端 Agent 或人工。
已實作(NestSpace 0.3):
- 共用 chat 協定(參考 go-agent-sdk 的
chat.Service/Messages/Block抽象): ChatMessage= role +List<ChatBlock>;ChatBlock=Text/ToolUse/ToolResult。ChatService.chat(ChatRequest): ChatResponse是 provider 邊界;ChatRequest攜帶 model/system/messages/tools/maxTokens/temperature。AnthropicChatService與OpenAiChatService只負責共享模型與各自 wire JSON 的互轉。LlmAgentRuntime只有一份 provider-neutral 迴圈:呼叫模型 → 收集ToolUse→ 執行ToolRegistry→ 回傳ToolResult→ 直到沒有工具呼叫。- 對話使用
ChatService.streamChat的 SSE 串流輸出;Anthropic 與 OpenAI 相容端點都依增量事件組裝文字和工具呼叫,Agent 迴圈不變。 - 「设置」(設定)→ 「大模型 / 本地 Agent」(大型語言模型 / 本機 Agent) 提供兩種協定;設定儲存在應用程式私有目錄,API Key 透過 Android Keystore 加密儲存。
- Claude 相容:
POST {base}/v1/messages,headerx-api-key+anthropic-version,原生tools/tool_use/tool_result。 - OpenAI 相容:
POST {base}/v1/chat/completions,Authorization: Bearer,原生tool_calls/ role=tool訊息。 - 已移除第三方自訂範本協定;舊版本儲存的
CUSTOM設定會被忽略並回退到 Claude 預設值,避免殘留的自訂 endpoint 混入新協定。 LlmAgentRuntime重用ToolRegistry,ToolContext(sessionId, LOCAL_AGENT, "virtual-1"),排除 session/approval 工具,最多 16 輪工具呼叫,單次工具結果截斷 12k 字元。- 未設定時回退到規則
LocalAgentRuntime;規則 Agent 仍保留系統空間能力,大型語言模型 Agent 只控制 virtual-1。 AssistantConversation統一維護助手頁與懸浮球語音助手共用的訊息流和最近 24 輪文字對話;LlmAgentRuntime.run(history = ...)在每次請求前重播歷史,實現多輪追問。語音入口預設隱藏文字輸入,最終辨識結果自動提交,回覆後自動恢復聆聽。- 語音辨識使用內建 Vosk 離線模型(
com.alphacephei:vosk-android),不經過雲端 STT。模型在首次使用時下載,也可在懸浮球設定中管理;下載前平行存取 HF 中國鏡像、Hugging Face 和 Vosk 官方網站首頁測速,選擇回應最快的線路,失敗自動切換。中文/English 小模型分別快取,模型就緒後SpeechService持續提供 partial/final 結果。 LocalSpeechEngine在處理程序內快取已載入的Model;下載完成立即背景載入,App 啟動時若模型已快取也會預先載入,語音頁只建立Recognizer/SpeechService,開啟即可聆聽。切換語言時先載入新模型;舊模型保留到處理程序結束,避免暫停中的語音頁仍持有舊 Recognizer 時被提前釋放。- 已用本機 mock 分別驗證通過兩種協定的核心轉換與工具呼叫往返;使用者設定的 Anthropic 相容端點在模擬器真實網路驗證通過
space_list+space_list_apps多輪工具呼叫。 - 單元測試:
app/src/test/java/com/coke/nestspace/llm/AnthropicChatServiceTest.kt、OpenAiChatServiceTest.kt。
5.5 工作階段與租約¶
enum class Caller { LOCAL_AGENT, REMOTE_MCP, USER }
enum class LeaseMode { EXCLUSIVE, OBSERVE }
data class ControlLease(
val spaceId: SpaceId,
val leaseId: String,
val owner: Caller,
val sessionId: String,
val mode: LeaseMode,
val expiresAt: Long
)
規則:
- 修改類工具(tap/type/launch/install/uninstall)必須先取得
EXCLUSIVE租約。 - 觀察類工具(observe/screenshot)可以共用。
- 另一個通道呼叫修改類工具時傳回
SPACE_BUSY(帶 holder 資訊),可排隊/等待/請求使用者接管。 - 使用者可以在 UI 強制回收租約。
- 租約閒置逾時自動釋放;MCP 中斷連線/本機任務結束立即釋放。
- 所有租約變更寫入稽核記錄。
5.6 核准流程¶
| 情境 | 本機 Agent | 遠端 MCP |
|---|---|---|
| 支付/卸載/權限/安裝 | App 內彈出視窗確認 | approval_request + 手機通知;遠端 Agent 輪詢 approval_wait |
| 唯讀觀察 | 預設允許 | 預設允許(需 token scope) |
| 高風險 shell | 預設關閉 | 預設關閉;需使用者開啟 + 二次確認 |
- 核准要求進入同一個
ApprovalGate,UI 顯示「本地 Agent 请求 / 远程 Agent 请求」(本機 Agent 要求 / 遠端 Agent 要求)。 - 核准結果寫入稽核;逾時預設拒絕。
5.7 工具清單¶
虛擬空間管理
| 工具 | 用途 |
|---|---|
space_list |
列出虛擬空間及狀態 |
space_create |
建立虛擬空間 |
space_delete |
刪除虛擬空間 |
space_install_apk |
安裝 APK/split 到虛擬空間 |
space_uninstall |
卸載 |
space_list_apps |
列出虛擬空間內 App |
space_launch |
啟動 App/Activity |
space_stop |
停止 App |
space_status |
虛擬空間/前景 App 狀態 |
UI 控制
| 工具 | 用途 |
|---|---|
ui_observe |
傳回節點樹(可選截圖) |
ui_screenshot |
傳回截圖 |
ui_tap / ui_long_press |
點擊/長按 |
ui_swipe / ui_scroll |
滑動/捲動 |
ui_set_text |
輸入文字 |
ui_key |
按鍵(back/home/recents/enter) |
ui_wait_for |
等待條件 |
ui_find |
在最近 snapshot 中尋找節點 |
應用程式/系統
| 工具 | 用途 |
|---|---|
app_intent |
傳送 Intent/深層連結 |
sys_shell |
Shizuku shell(預設關閉,命令允許清單) |
sys_logcat |
讀取 logcat(依套件/pid) |
sys_clipboard_get/set |
剪貼簿(敏感,需確認) |
AppFunctions 快路徑
| 工具 | 用途 |
|---|---|
appfn_status |
回報 Android 16+/AppFunctionManager/EXECUTE_APP_FUNCTIONS 可用性 |
appfn_list |
列出目標套件公開的 AppFunctions(宿主/容器內) |
appfn_execute |
執行 AppFunction;失敗/不可用時回退 UI 自動化 |
工作階段/核准
| 工具 | 用途 |
|---|---|
session_acquire |
取得虛擬空間獨占/觀察租約 |
session_release |
釋放租約 |
session_status |
查看目前 holder |
approval_request |
請求人工確認 |
approval_wait |
等待核准結果 |
任務/技能
| 工具 | 用途 |
|---|---|
task_run |
本機 Agent 執行自然語言任務 |
task_status / task_cancel |
查詢/取消 |
skill_list / skill_run |
技能清單/執行 |
trajectory_get |
取得執行軌跡 |
設計原則:
- 所有工具帶
space_id(space_list/space_create除外)。 - 傳回結構化錯誤:
{code, message, retryable, hint}。 - 安裝/啟動冪等;
idempotency_key防重複。 - 危險操作必須
confirm=true或人工確認。 ui_observe預設max_nodes=120、不傳回截圖。sys_shell預設不暴露;開啟後僅能執行允許清單中的命令。
5.8 工具 schema 範例¶
{
"name": "space_install_apk",
"description": "Install an APK or split APK set into a space. Validates REQUIRE_SECURE_ENV, ABI, and 16KB alignment before install.",
"inputSchema": {
"type": "object",
"properties": {
"space_id": { "type": "string" },
"apk_paths": {
"type": "array",
"items": { "type": "string" },
"description": "Absolute paths or content URIs. For split APKs, include base + all splits."
},
"grant_runtime_permissions": { "type": "boolean", "default": false },
"idempotency_key": { "type": "string" }
},
"required": ["space_id", "apk_paths"]
}
}
5.9 模型選擇¶
截至 2026-10,Anthropic 第一方 API 價格(美元/百萬 token,輸入/輸出):
| 模型 | Model ID | 價格 | 建議用途 |
|---|---|---|---|
| Claude Opus 5.5 | claude-opus-5-5 |
$4 / $20 | 預設;複雜規劃、長任務 |
| Claude Sonnet 5.5 | claude-sonnet-5-5 |
$2 / $10 | 性價比/速度 |
| Claude Haiku 5.5 | claude-haiku-5-5 |
$0.10 / $0.50 | 簡單分類、等待、摘要 |
| Claude Fable 5.1 | claude-fable-5-1 |
$10 / $50 | 最難任務;成本高 |
裝置端模型:
- Gemini Nano 4 / ML Kit GenAI Agent API(Alpha):裝置端工具呼叫迴圈;裝置支援有限。
- Gemma 4 + LiteRT-LM:可自帶微調模型;整合/效能成本高。
建議:遠端 Agent 用 Claude Opus 5.5;本機 Agent 第一版先用 ClaudeAdapter 驗證迴圈可行,再接 Gemini Nano 4 / Gemma 4;簡單子步驟用 Haiku 5.5。
5.10 Agent 迴圈與失敗處理¶
state = observe(space)
for turn in 1..MAX_TURNS:
decision = model.decide(goal, state, tools, history)
if decision.done: return result
if decision.needs_user_confirmation:
approval = approvalGate.request(decision)
if not approval.approved: return cancelled
result = execute(decision.tool, decision.args)
state = result.observation ?? observe(space)
history.append(compact(result))
要點:
- 系統提示寫清楚:角色、可用 App、工具語意、安全規則、輸出格式。
- 上下文:緊湊節點樹 + 依需求截圖 + 技能提示 + 歷史摘要。
- 限制輪數、token、單步逾時;支援取消。
- 每步驗證;連續 2 次失敗 → 重新規劃;3 次 → 人工接管。
- 失敗處理:
| 失敗 | 處理 |
|---|---|
| 找不到節點 | 重新 observe;文字/座標備援 |
| 點擊無效 | 檢查可點擊;座標;等待動畫 |
| 輸入失敗 | ACTION_SET_TEXT → 剪貼簿 → 逐鍵輸入 |
| App 當機/ANR | 殺掉處理程序重新啟動;備用路徑;人工 |
| 權限彈出視窗 | 辨識系統彈出視窗;依策略允許/拒絕/詢問 |
| 載入緩慢 | wait_for + 逾時 + 重試 |
| 驗證失敗 | 回退上一步;重新規劃 |
5.11 AppFunctions 快路徑(宿主 / 容器內 APK)¶
Android 16+ 的 AppFunctions 是「系統級工具註冊」:App 用 @AppFunction 宣告能力,KSP 產生 service/XML schema,系統註冊;Agent 透過 AppFunctionManager 探索/執行。
三種情況,權限和能力不同:
| 情境 | 能否使用 | 說明 |
|---|---|---|
| 宿主應用程式自行公開 AppFunctions | ✅ 可以 | 宿主是 function owner,可以執行自己的函式;系統 Agent/Gemini 也能探索 |
| 容器內 APK 公開 AppFunctions | ✅ 需要本機橋接 | 虛擬套件沒有註冊到系統,AppFunctionManager 看不到;安裝時解析 AppFunctions 中繼資料,在容器處理程序內呼叫產生的 service/entry point |
| 系統裡其他 App 的 AppFunctions | ⚠️ 通常不行 | 呼叫方需要 EXECUTE_APP_FUNCTIONS,是特權/系統級權限;一般 APK 通常拿不到 |
架構:
ToolSource抽象:BuiltinToolSource(虛擬空間/UI 工具)、AppFunctionsToolSource(宿主/容器 AppFunctions)、SkillToolSource。ToolRegistry彙整工具,命名空間appfn.<package>.<functionId>;同名衝突用套件名稱區分。- Agent 規劃時優先快路徑:
appfn_execute→ 不可用/失敗 → UI 自動化。 - 容器引擎在安裝 APK 時解析其 AppFunctions XML/metadata;如果存在,註冊到
AppFunctionsToolSource;執行時在容器處理程序內呼叫,避免依賴系統註冊。 - 宿主應用程式也可以用
@AppFunction匯出space.list/install/launch、ui.observe/tap,讓系統 Agent/Gemini 直接呼叫宿主。這是「匯出」,不是「匯入」。
權限/合規:
- 不要假設一般 APK 能呼叫第三方 App 的 AppFunctions;
EXECUTE_APP_FUNCTIONS是 privileged。 - 宿主匯出的 AppFunctions 仍然要走租約/核准/稽核;不要繞過安全模型。
- 容器內 AppFunctions 仍受容器的 Binder/UID 限制;依賴系統服務的函式可能失敗。
- AppFunctions 是可選快路徑,不是唯一控制面;失敗必須回退 UI 自動化。
基礎版:
- 已加入
AppFunctionsBridge偵測(API 36、AppFunctionManager、權限)與appfn_status/list/execute工具預留位置。 - 宿主匯出、容器內本機橋接放在 Stage F。
6. 架構¶
6.1 分層圖¶
┌──────────────────────────┐ ┌──────────────────────────┐
│ 本機 Agent Runtime │ │ 遠端 Agent │
│ (on-device loop) │ │ (Claude Desktop/Code/雲) │
└────────────┬─────────────┘ └────────────┬─────────────┘
│ ToolRegistry │ MCP Server
│ │ (Streamable HTTP)
└───────────────┬───────────────────┘
▼
┌──────────────────────────────────────────────────────────────┐
│ UI (Compose): 虛擬空間 / 安裝器 / Agent 主控台 / 核准 / 軌跡 │
└──────────────────────────────┬───────────────────────────────┘
│
┌──────────────────────────────▼───────────────────────────────┐
│ Domain │
│ SpaceRepository ControlRepository AgentRepository │
│ ToolRegistry LeaseManager ApprovalGate AuditLog │
│ AppFunctionsBridge(快路徑) │
└───────────────┬──────────────────────────────┬───────────────┘
│ │
┌───────────────▼──────────────┐ ┌────────────▼───────────────┐
│ ContainerEngine │ │ ControlChannel │
│ Installer / VPMS / VAMS │ │ ShizukuChannel │
│ ClassLoader / Resources │ │ AccessibilityChannel │
│ Binder / IO / ProcessPool │ │ VisionChannel │
└───────────────┬──────────────┘ └────────────┬───────────────┘
│ │
┌───────────────▼──────────────────────────────▼───────────────┐
│ Infrastructure │
│ 宿主宣告的 :pN 處理程序 | MCP Server | 本機 Agent │
│ AccessibilityService / MediaProjection / Shizuku │
│ Room / DataStore / WorkManager │
└──────────────────────────────────────────────────────────────┘
6.2 模組劃分¶
app/
src/main/java/com/coke/virtual/
core/model/ # SpaceInfo, AppInfo, UiNode, UiSnapshot, Target, Condition
core/space/ # SpaceBackend, SpaceRepository
core/control/ # ControlChannel, AccessibilityService, ShizukuBridge
core/tools/ # ToolRegistry, ToolSpec, validation, scopes
core/session/ # ControlLease, SessionManager, ApprovalGate, AuditLog
core/mcp/ # MCP server adapter (remote), auth
core/agent/ # Shared AgentLoop, prompt, skills
core/agent/model/ # ClaudeAdapter, GeminiNanoAdapter, LiteRTAdapter
engine/installer/ # APK/split 解析、預檢、安裝
engine/vpms/ # 虛擬 PackageManager
engine/vams/ # 虛擬 ActivityManager、StubActivity
engine/classloader/ # ClassLoader/LoadedApk
engine/resources/ # Resources/AssetManager
engine/binder/ # Binder/UID 虛擬化
engine/io/ # 資料目錄重新導向
engine/process/ # :pN 處理程序池、VClientService
engine/hooks/ # Java/Native hook、版本調適
fastpath/ # AppFunctions 偵測、宿主匯出、容器內本機橋接
feature/spaces/ # Compose UI: 虛擬空間清單、App 網格
feature/installer/ # APK 選擇、預檢、安裝進度
feature/agent/ # Agent 主控台、軌跡、核准
service/ # ForegroundService, AccessibilityService
6.3 關鍵介面¶
// 雙通道共用:本機 Agent 直接呼叫 ToolRegistry;遠端 Agent 透過 MCP adapter 呼叫同一個 ToolRegistry
data class ToolSpec(
val name: String,
val description: String,
val inputSchema: JsonObject,
val mutating: Boolean,
val requiredScope: Scope
)
interface ToolRegistry {
fun list(): List<ToolSpec>
suspend fun execute(name: String, args: JsonObject, ctx: ToolContext): ToolResult
}
interface AgentRuntime {
suspend fun run(goal: String, spaceId: SpaceId, sessionId: String): TaskResult
fun cancel(sessionId: String)
}
data class ControlLease(
val spaceId: SpaceId,
val leaseId: String,
val owner: Caller,
val sessionId: String,
val mode: LeaseMode,
val expiresAt: Long
)
interface ContainerEngine {
suspend fun install(spaceId: SpaceId, apk: ApkSource): InstallResult
suspend fun uninstall(spaceId: SpaceId, packageName: String)
suspend fun listApps(spaceId: SpaceId): List<AppInfo>
suspend fun launch(spaceId: SpaceId, packageName: String, activity: String? = null): LaunchResult
suspend fun stop(spaceId: SpaceId, packageName: String)
suspend fun status(spaceId: SpaceId): SpaceStatus
}
interface ControlChannel {
suspend fun observe(id: SpaceId, options: ObserveOptions = ObserveOptions()): UiSnapshot
suspend fun screenshot(id: SpaceId): ImageBitmap
suspend fun tap(id: SpaceId, target: Target)
suspend fun longPress(id: SpaceId, target: Target, durationMs: Long = 800)
suspend fun swipe(id: SpaceId, from: Point, to: Point, durationMs: Long = 300)
suspend fun setText(id: SpaceId, target: Target, text: String)
suspend fun key(id: SpaceId, key: Key)
suspend fun waitFor(id: SpaceId, condition: Condition, timeoutMs: Long): Boolean
}
6.4 一次完整流程¶
遠端 Agent 走 MCP Server → ToolRegistry;本機 Agent 直接走 ToolRegistry。下面的 Engine/Control 步驟兩者相同。
Agent MCP Server / ToolRegistry Engine / Control
│ │ │
│ space_install_apk │ │
│─────────────────────────────►│ 驗證 APK / REQUIRE_SECURE_ENV │
│ │───────────────────────────────►│ Installer
│ │◄───────────────────────────────│ ok
│ space_launch │ │
│─────────────────────────────►│───────────────────────────────►│ VAMS + ProcessPool
│ │◄───────────────────────────────│ foreground
│ ui_observe │ │
│─────────────────────────────►│───────────────────────────────►│ dump tree
│◄─────────────────────────────│ nodes + screenshot hash │
│ ui_tap(node_id=n_7) │ │
│─────────────────────────────►│───────────────────────────────►│ tap
│ ui_wait_for(text="結果") │ │
│─────────────────────────────►│───────────────────────────────►│ poll
│◄─────────────────────────────│ matched / timeout │
6.5 目前專案改造建議¶
core/model+core/tools+core/session:模型、ToolRegistry、租約/核准先實作。service/SpaceAccessibilityService.kt+res/xml/accessibility_service_config.xml:觀察/點擊。core/control/ShizukuBridge.kt:接 Shizuku API(dev.rikka.shizuku:api+provider),做input/screencap。core/mcp/:基礎版用內建最小 HTTP server(ServerSocket),先 5 個工具(observe/tap/launch/list_apps/screenshot);正式版換 Ktor + kotlin-sdk。service/McpForegroundService.kt:前景服務 + 通知 + token。core/agent/:本機 Agent 骨架(ClaudeAdapter或假模型),重用同一套 ToolRegistry。engine/+container/:Stage A/B 單處理程序容器、安裝器、多開和執行個體資料目錄已實作(見第 7 節)。- 下一步:處理程序池、原生程式庫、Binder/UID、Provider/通知/Job、Android 9+ 調適。
7. 建設順序(不設工期)¶
| 階段 | 目標 | 內容 | 完成標準(能力門檻) |
|---|---|---|---|
| S0 控制層 + 雙通道骨架 | 先把「控制」做成獨立層 | ToolRegistry、MCP Server、本機 Agent 骨架、Shizuku/無障礙、租約/核准、軌跡 | 遠端 MCP 和本機 Agent 都能控制已安裝 App 完成 3 類任務;工具行為一致 |
| A 單處理程序最小容器 ✅ 已驗證 | 驗證一個簡單 APK 可執行 | AXML/資源解析、ClassLoader、Resources、StubActivity、Instrumentation/ActivityThread hook、Context 包裝 | 一個無原生程式庫/無 GMS 的簡單 APK 能在宿主處理程序啟動並顯示 UI(API 25 模擬器已驗證) |
| B 安裝器 + 多開 ✅ 已驗證(API 25 / API 36) | 能安裝、能多執行個體 | split APK、簽章驗證、REQUIRE_SECURE_ENV、IO 重新導向、VPMS 清單/啟動、執行個體資料目錄 |
簡單 App 能安裝、啟動、多開;資料互不污染(testapp + Android 16 已驗證;base+合成 split 已驗證,真實 config split 資源 overlay 待驗證) |
| C 處理程序池 + Native | 當機隔離、native App | :pN 處理程序池、VClientService、原生程式庫擷取/載入、16 KB、Binder/UID Java hook |
5 個 App 在獨立處理程序執行;原生程式庫正常;16 KB 裝置可安裝 |
| D 背景/系統能力 | 主流 App 可用 | 通知、靜態 Receiver、Job/Alarm 部分代理、Provider、WebView 處理程序隔離、Android 15–17 調適 | 8/10 目標應用程式啟動;核心流程 7/10;無致命當機 |
| E Agent 自主性 + 安全 | 可上線 | 裝置端/雲端模型、技能庫、驗證/重試、人工接管、稽核、去識別化、MCP 身分驗證 | 常見任務成功率達標;安全審查通過 |
| F AppFunctions 快路徑 | 可選加速 | 宿主 @AppFunction 匯出、容器 APK 中繼資料解析、容器內本機執行、權限偵測、UI 回退 |
宿主工具可被系統 Agent 呼叫;容器內 AppFunctions 可執行;不可用時回退 UI |
7.1 Stage A 的關鍵驗收¶
在 Android 7.1.1 x86 模擬器驗證(testapp APK 未安裝,直接載入):
- ✅
Activity的onCreate/onStart/onResume執行,版面顯示,findViewById正常。 - ✅ 目標資源生效:
@string、@layout正常解析,點擊按鈕後 TextView 更新為目標字串。 - ✅ 點擊/繪製不當機、不黑畫面、不 ANR。
- ✅ 目標
Application的attachBaseContext/onCreate已由 Stage B 接入(testapp 驗證)。 - ⏳ 目標
getPackageName()已傳回目標套件名稱;getPackageManager()查詢仍是宿主 PM,留給 Stage C/D 的 VPMS。 - ⏳ 僅驗證 API 25;Android 9+ hidden API 限制可能阻擋
Activity.attach/AssetManager.addAssetPath,需依版本調適。
7.2 Stage B 的關鍵驗收¶
在 Android 7.1.1 x86 模擬器驗證:
- ✅ 單一 APK 安裝成功並持久化;base + 合成 split APK 透過
space_install_apk安裝並啟動,split_apks已寫入磁碟。真實 config split 的資源 overlay 仍待驗證。 - ✅ 簽章不一致拒絕:
SIGNATURE_MISMATCH。 - ✅
REQUIRE_SECURE_ENV=1被拒絕:SECURE_ENV_REQUIRED。 - ✅ 多開
instance-1/instance-2,資料目錄獨立;file/prefs count互不影響。 - ✅ 切換執行個體後舊執行個體資料保留;殺掉處理程序重新啟動 App 後安裝記錄和執行個體仍存在。
- ✅ 卸載清理 APK、執行個體資料、state;目錄為空。
- ✅ 啟動第二個執行個體時,第一個執行個體在單處理程序模型下切換到背景/被取代;資料不遺失。
- ✅ 目標 Context 的
getPackageName、getFilesDir、getSharedPreferences、getApplicationInfo已重新導向。 - ✅ 純淨桌面:預設僅顯示虛擬應用程式 + 「空间管理」(空間管理);系統應用程式需在空間管理裡勾選後才上桌面;支援設定/清除壁紙,重新啟動後保留。
- ✅ Android 16(API 36)複測:桌面、容器、多開、執行個體資料隔離正常。
- ✅ Android 14+ 可寫 dex 限制:安裝後的 APK 設為唯讀後,
DexClassLoader正常載入。 - ✅ Android 9+ hidden API:接入 HiddenApiBypass 6.2,
Activity.attach等反射可用;未豁免時Activity.attach not found。 - ⏳ Android 17(API 37)模擬器在本機 SurfaceFlinger 當機(
hasReadColorBufferDma),PackageManager/ActivityManager 隨後重新啟動,未能完成裝置級驗證;需要實機或圖形堆疊正常的模擬器。 - ⏳
3 個簡單 App的相容性樣本還不足(目前以 testapp + 多開驗證);建議 Stage C 前補更多 APK 樣本。 - ⏳ 單一處理程序、僅入口 Activity;
startActivity、Provider、Binder/UID、native 仍待 Stage C/D。 - ✅ 引擎內的虛擬應用程式清單/啟動(非系統 PMS/AMS hook)和桌面式入口已接入;目標
PackageManager查詢仍是宿主 PM。
實作位置:engine/MiniContainerEngine.kt、container/ContainerContext.kt、container/MiniContainer.kt、ui/VirtualApp.kt。
7.3 Stage C 的關鍵驗收¶
- 兩個執行個體在不同
:pN處理程序,當機互不影響。 - 目標應用程式的原生程式庫能載入。
- 16 KB 裝置上對齊的原生程式庫正常;未對齊的提供明確錯誤。
Process.myUid/Binder.getCallingUid的 Java hook 至少涵蓋常見檢查(不做系統端 UID 欺騙)。
7.4 Stage D 的關鍵驗收¶
- 通知能顯示(或明確降級)。
- 靜態 Receiver 能收到。
- Job/Alarm 至少不當機(可以退化)。
- WebView 多執行個體不串資料(獨立處理程序)。
- Android 15/16/17 各一台裝置通過。
7.5 Stage F 的關鍵驗收(AppFunctions 快路徑)¶
- 宿主應用程式用
@AppFunction公開至少 3 個工具(如space_list、space_launch、ui_observe),系統 Agent 或adb shell cmd app_function能探索。 - 容器內測試 APK 帶 AppFunctions;安裝時解析到中繼資料;執行時能在容器處理程序內呼叫。
- 一般 APK 呼叫系統其他 App 的 AppFunctions 被明確拒絕(
EXECUTE_APP_FUNCTIONS),並回退 UI。 - 快路徑失敗不影響 UI 自動化。
每個階段都可以獨立示範;不要等「全功能」再驗證。Stage A 失敗,表示 hook 路線/版本調適有問題,先解決再往下走。
8. 測試矩陣¶
8.1 裝置¶
| 類型 | 範例 | 關注點 |
|---|---|---|
| Pixel | Pixel 8/9/10(Android 15/16/17) | 原生行為、hidden API、AAPM |
| Samsung | S24/S25/S26(One UI) | 背景限制、Knox |
| 小米 | 14/15(HyperOS) | 背景、權限、套件管理 |
| OPPO/vivo | Find X / X 系列 | 權限、背景、無障礙 |
| 華為 | 無 GMS 機型 | GMS 相依、HMS |
| 16 KB page | Android 15+ 新機 | 原生程式庫對齊 |
8.2 App¶
- 自研測試 APK(最小 Activity、Resources、原生程式庫、split、Service、Receiver)
- 開源簡單 App(F-Droid 上的小工具)
- 微信、支付寶、淘寶、抖音、小紅書(相容性目標,不承諾)
- 遊戲(Unity/UE,Surface/Canvas)
- Flutter App
- 以 WebView 為主的 App
- 銀行 App(預期失敗,用於驗證降級提示)
8.3 情境¶
- 安裝:單一 APK、split APK、XAPK、低 targetSdk、16 KB
- 啟動:冷啟動、熱啟動、背景喚起、多執行個體切換
- 生命週期:旋轉、分割畫面、鎖定螢幕、返回、最近任務
- 權限:首次啟動、執行階段權限、拒絕/允許
- 資料:files/cache/db/sp、外部儲存、卸載清理
- 元件:Activity、Service、Receiver、Provider、Notification
- 例外:斷網、逾時、當機、ANR、低記憶體
- 雙通道:同一任務分別走本機 Agent 和遠端 MCP,結果一致
- 並行:本機 Agent 持有租約時,遠端 MCP 修改類呼叫傳回
SPACE_BUSY;觀察類不受影響 - 核准:兩條通道都能觸發同一個核准 UI;逾時拒絕
8.4 指標¶
- 安裝成功率、啟動成功率、任務成功率
- 單步延遲 p50/p95
- 當機率、ANR
- 每次任務 token 成本
- 誤點率、人工接管比例
- 容器:Activity 啟動成功率、資源載入成功率、原生程式庫載入成功率、16 KB 通過率、處理程序存活
- 雙通道:工具一致性通過率、租約衝突/排隊/接管次數、核准逾時率
9. 風險清單¶
| 風險 | 影響 | 緩解 |
|---|---|---|
| Hidden API 變化 | hook 失效、啟動當機 | 版本調適層;漸進式發布;固定測試矩陣 |
| Binder/UID 限制 | 系統服務依宿主 UID 判定,部分 App 功能異常 | 不承諾強 UID 隔離;Java hook 涵蓋常見檢查;明確邊界 |
| 背景啟動限制 | Agent 無法從背景拉起介面 | 前景服務/通知/使用者觸發;設計「可見任務」 |
| 通知/Job/Provider | 目標應用程式背景能力退化 | 分階段支援;產品文案說明 |
| Keystore/簽章/Play Integrity | 金融/強校驗 App 不可用 | 不承諾;偵測後提示 |
| 16 KB / split APK | 安裝/載入失敗 | 預檢;明確錯誤;測試矩陣 |
| 處理程序池/記憶體 | 多開卡頓、被殺 | 處理程序池上限;前景優先;低記憶體降級 |
| 容器安全 | 惡意 App 讀取宿主/其他執行個體資料 | 只安裝可信 APK;不宣傳安全隔離 |
| 提示注入 | Agent 被螢幕內容操控 | 螢幕文字視為資料;工具允許清單;危險操作確認 |
| 雙通道並行 | 本機/遠端同時操作,互相中斷 | 租約 + 工作階段 + 使用者接管 |
| 本機 Agent 模型能力 | 裝置端模型規劃/工具呼叫較弱 | ClaudeAdapter 備援;裝置端只做簡單任務 |
| API key 洩漏 | 費用/濫用 | Keystore 儲存;代理服務;限額 |
| 自動化違反 ToS | 帳號停權 | 使用者告知;限速;不碰支付/風險控制 |
| OEM 限制 | 背景/權限/無障礙不可用 | 機型矩陣;降級方案 |
| 效能/電量 | 體驗差 | 前景服務最佳化;截圖降頻;依需求喚醒 |
| MCP 暴露 | 區域網路被控 | 預設 loopback;token;TLS;關閉 LAN 開關 |
| 隱私 | 螢幕內容含敏感資訊 | 去識別化;不寫入磁碟;稽核;使用者可關閉截圖 |
| 法律/合規 | GPL/Play/第三方 ToS | Clean-room;法務評估;遵守 REQUIRE_SECURE_ENV |
10. 參考專案與資料¶
AOSP / Android¶
- AOSP
ActivityThread:https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/app/ActivityThread.java - AOSP
Instrumentation:https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/app/Instrumentation.java - AOSP
LoadedApk:https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/app/LoadedApk.java - AOSP
ContextImpl:https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/app/ContextImpl.java - Android 17 開發人員預覽:https://developer.android.com/about/versions/17
- 16 KB page size:https://developer.android.com/guide/practices/page-sizes
- Hidden API 限制:https://developer.android.com/guide/app-compatibility/restrictions-non-sdk-interfaces
AppFunctions¶
- AppFunctions 概覽:https://developer.android.com/ai/appfunctions
- 將 AppFunctions 加入應用程式:https://developer.android.com/ai/appfunctions/add-appfunctions
AppFunctionManager參考:https://developer.android.com/reference/android/app/appfunctions/AppFunctionManager- androidx.appfunctions:https://developer.android.com/reference/androidx/appfunctions/package-summary
- 官方範例:https://github.com/android/appfunctions
- 社群範例(探索/呼叫):https://github.com/FilipFan/AppFunctionsPilot
參考實作(僅架構參考,GPL,勿複製)¶
- VirtualApp(asLody):https://github.com/asLody/VirtualApp
- BlackBox / NewBlackbox:https://github.com/ALEX5402/NewBlackbox
Hook / Native¶
- ShadowHook:https://github.com/bytedance/android-inline-hook
- ByteHook:https://github.com/bytedance/bhook
- Dobby:https://github.com/jmpews/Dobby
- xDL:https://github.com/hexhacking/xDL
控制 / Agent¶
- Shizuku:https://github.com/RikkaApps/Shizuku
- MCP Kotlin SDK:https://github.com/modelcontextprotocol/kotlin-sdk
- MCP Inspector:https://github.com/modelcontextprotocol/inspector
政策¶
- Google Play 容器 App 與 REQUIRE_SECURE_ENV:https://support.google.com/googleplay/android-developer/answer/13609005
- 限制容器載入(Android Developers):https://developer.android.com/training/basics/intents/limit-play-loading
- Android 17 AAPM 報導:https://www.androidauthority.com/android-17-beta-2-advanced-protection-mode-accessibility-apps-3648860/
裝置端模型¶
- ML Kit GenAI Agent API:https://developers.google.com/ml-kit/genai/agent/android
- Gemma 4 本機 Agent:https://android-developers.googleblog.com/2026/04/gemma-4-new-standard-for-local-agentic-intelligence.html
附錄 A:APK 預檢清單¶
[ ] packageName / versionCode / versionName
[ ] minSdk / targetSdk
[ ] ABI(arm64-v8a / armeabi-v7a / x86_64)
[ ] 是否為 split APK(base + config.*)
[ ] 原生程式庫是否 16 KB 對齊(Android 15+)
[ ] 是否宣告 REQUIRE_SECURE_ENV=1 → 是則拒絕
[ ] 是否依賴 GMS / Play Integrity
[ ] 是否使用 Android Keystore
[ ] 是否使用自訂簽章驗證
[ ] 是否包含多處理程序/前景服務/JobScheduler
[ ] 是否包含 WebView / Flutter / Unity(影響控制方式)
[ ] 危險權限清單
[ ] 安裝包簽章(是否可信來源)
附錄 B:Hook 點地圖¶
| 子系統 | Java hook | Native fallback | 版本注意 |
|---|---|---|---|
| PackageManager | ApplicationPackageManager、IPackageManager、ActivityThread.getPackageManager |
Binder.execTransact |
AIDL 方法隨版本變化 |
| Activity | Instrumentation.execStartActivity、ActivityThread.mH、IActivityManager |
Binder.execTransact |
Android 10+ 背景啟動限制;Android 14+ PendingIntent |
| ClassLoader | LoadedApk、PathClassLoader、DexClassLoader |
— | split/multi-dex |
| Resources | ResourcesManager、AssetManager.addAssetPath |
open |
split/overlay |
| Binder/UID | Binder.getCallingUid、Process.myUid、UserHandle |
Binder.execTransact |
系統端 UID 無法偽造 |
| IO | ContextImpl、SharedPreferencesImpl、SQLiteDatabase |
open/fopen |
scoped storage |
| 通知 | NotificationManager、PendingIntent |
— | Android 12 trampoline;Android 13 權限 |
| 處理程序 | 宿主 :pN、StubActivity、VClientService |
— | 不要用 app_process 子處理程序 |
| 原生程式庫 | System.loadLibrary、System.load |
dlopen/android_dlopen_ext |
16 KB;ABI |
| WebView | WebView.setDataDirectorySuffix(處理程序級) |
— | 多執行個體需獨立處理程序 |
附錄 C:Clean-room 清單¶
[ ] 不複製 VirtualApp/BlackBox 的 GPL 程式碼
[ ] 參考 AOSP(Apache-2.0)和公開文件
[ ] 記錄每個 hook 點的來源(AOSP 類別/方法/版本)
[ ] native hook 程式庫逐一核對授權
[ ] 法務評估:GPL、Play 容器政策、第三方 App ToS
[ ] 程式碼隔離:看過 GPL 程式碼的人不參與 Clean-room 實作(可選但更穩)
[ ] 不在產品中聲明「安全隔離」
[ ] 遵守 REQUIRE_SECURE_ENV
附錄 D:最小可行 Demo(建議先做這個)¶
目標:同一個任務,分別由遠端 Claude Code(MCP)和本機 Agent 控制手機上的設定 App,完成「開啟設定 → 搜尋 WLAN → 開啟 WLAN」。
範圍:
- 不做容器
- 不做安裝器
- 只做:
ToolRegistry:3 個工具(ui_observe、ui_tap、ui_key),參數驗證 + scopeSpaceAccessibilityService(observe + tap + back)McpForegroundService(內建最小 HTTP server + token)LocalAgentRuntime(先用ClaudeAdapter或假模型;迴圈在 App 內)ControlLease+ApprovalGate(至少實作獨占租約和確認彈出視窗)
驗收:
- 遠端 Claude Code 能列出/呼叫工具,完成 3 個任務
- 本機 Agent 能用同一套工具完成同樣任務
- 兩條通道的工具行為一致(同一輸入 → 同一結果/錯誤碼)
- 本機 Agent 持有租約時,遠端修改類呼叫傳回
SPACE_BUSY;觀察類不受影響 - 單步 p50 < 2 s
- 失敗時有結構化錯誤
這個 Demo 驗證通過後,再進入 Stage A(單一處理程序最小容器)。