@intentcomputing/koko-agent-sdk 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/CODE_OF_CONDUCT.md +3 -0
- package/CONTRIBUTING.md +9 -0
- package/LICENSE +21 -0
- package/README.md +65 -0
- package/README.zh-CN.md +50 -0
- package/SECURITY.md +5 -0
- package/android/build.gradle +40 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/cn/intentcomputing/kokoagentsdk/KokoAgentSdkModule.java +1356 -0
- package/android/src/main/java/cn/intentcomputing/kokoagentsdk/KokoAgentSdkPackage.java +23 -0
- package/docs/INTEGRATION.md +168 -0
- package/docs/INTEGRATION.zh-CN.md +140 -0
- package/docs/PRIVACY.md +11 -0
- package/docs/PRIVACY.zh-CN.md +11 -0
- package/docs/PUBLISH.md +11 -0
- package/docs/PUBLISH.zh-CN.md +11 -0
- package/ios/KokoAgentSdk/KokoAgentSdk.h +5 -0
- package/ios/KokoAgentSdk/KokoAgentSdk.m +2464 -0
- package/ios/KokoAgentSdk/KokoOcrBounds.h +20 -0
- package/ios/KokoAgentSdk/KokoScrollBounds.h +21 -0
- package/koko-agent-sdk.podspec +23 -0
- package/lib/commonjs/KokoAgent.js +1427 -0
- package/lib/commonjs/KokoAgent.js.map +1 -0
- package/lib/commonjs/assist/OnDeviceAssist.js +94 -0
- package/lib/commonjs/assist/OnDeviceAssist.js.map +1 -0
- package/lib/commonjs/assist/intentClassify.js +100 -0
- package/lib/commonjs/assist/intentClassify.js.map +1 -0
- package/lib/commonjs/assist/nodeRank.js +120 -0
- package/lib/commonjs/assist/nodeRank.js.map +1 -0
- package/lib/commonjs/assist/ocrTarget.js +135 -0
- package/lib/commonjs/assist/ocrTarget.js.map +1 -0
- package/lib/commonjs/assist/webVisualChange.js +29 -0
- package/lib/commonjs/assist/webVisualChange.js.map +1 -0
- package/lib/commonjs/client/ActionOutcomeUnknownError.js +28 -0
- package/lib/commonjs/client/ActionOutcomeUnknownError.js.map +1 -0
- package/lib/commonjs/client/AgentSession.js +858 -0
- package/lib/commonjs/client/AgentSession.js.map +1 -0
- package/lib/commonjs/client/GatewayClient.js +302 -0
- package/lib/commonjs/client/GatewayClient.js.map +1 -0
- package/lib/commonjs/client/OperationReplayGuard.js +62 -0
- package/lib/commonjs/client/OperationReplayGuard.js.map +1 -0
- package/lib/commonjs/client/RunCheckpoint.js +139 -0
- package/lib/commonjs/client/RunCheckpoint.js.map +1 -0
- package/lib/commonjs/client/loopDetect.js +184 -0
- package/lib/commonjs/client/loopDetect.js.map +1 -0
- package/lib/commonjs/client/recentSearch.js +50 -0
- package/lib/commonjs/client/recentSearch.js.map +1 -0
- package/lib/commonjs/client/walletChoiceGuard.js +18 -0
- package/lib/commonjs/client/walletChoiceGuard.js.map +1 -0
- package/lib/commonjs/client/webResultCategory.js +38 -0
- package/lib/commonjs/client/webResultCategory.js.map +1 -0
- package/lib/commonjs/commercial/AccountStore.js +78 -0
- package/lib/commonjs/commercial/AccountStore.js.map +1 -0
- package/lib/commonjs/commercial/AgentDebug.js +104 -0
- package/lib/commonjs/commercial/AgentDebug.js.map +1 -0
- package/lib/commonjs/commercial/ExecutionGuard.js +53 -0
- package/lib/commonjs/commercial/ExecutionGuard.js.map +1 -0
- package/lib/commonjs/commercial/UsageBus.js +42 -0
- package/lib/commonjs/commercial/UsageBus.js.map +1 -0
- package/lib/commonjs/commercial/branding.js +69 -0
- package/lib/commonjs/commercial/branding.js.map +1 -0
- package/lib/commonjs/commercial/devMode.js +52 -0
- package/lib/commonjs/commercial/devMode.js.map +1 -0
- package/lib/commonjs/commercial/trust.js +69 -0
- package/lib/commonjs/commercial/trust.js.map +1 -0
- package/lib/commonjs/confirmation/gate.js +62 -0
- package/lib/commonjs/confirmation/gate.js.map +1 -0
- package/lib/commonjs/cursor/CursorOverlay.js +141 -0
- package/lib/commonjs/cursor/CursorOverlay.js.map +1 -0
- package/lib/commonjs/cursor/PointerHand.js +51 -0
- package/lib/commonjs/cursor/PointerHand.js.map +1 -0
- package/lib/commonjs/cursor/controller.js +91 -0
- package/lib/commonjs/cursor/controller.js.map +1 -0
- package/lib/commonjs/cursor/pointer.js +49 -0
- package/lib/commonjs/cursor/pointer.js.map +1 -0
- package/lib/commonjs/debug/KokoRemoteSmoke.js +611 -0
- package/lib/commonjs/debug/KokoRemoteSmoke.js.map +1 -0
- package/lib/commonjs/explore/ExplorationGraph.js +418 -0
- package/lib/commonjs/explore/ExplorationGraph.js.map +1 -0
- package/lib/commonjs/explore/bootstrapExplore.js +59 -0
- package/lib/commonjs/explore/bootstrapExplore.js.map +1 -0
- package/lib/commonjs/host/HostActions.js +266 -0
- package/lib/commonjs/host/HostActions.js.map +1 -0
- package/lib/commonjs/i18n/index.js +169 -0
- package/lib/commonjs/i18n/index.js.map +1 -0
- package/lib/commonjs/i18n/runtimeMessages.js +184 -0
- package/lib/commonjs/i18n/runtimeMessages.js.map +1 -0
- package/lib/commonjs/index.js +640 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/native/NativeBridge.js +132 -0
- package/lib/commonjs/native/NativeBridge.js.map +1 -0
- package/lib/commonjs/native/navigationLabel.js +16 -0
- package/lib/commonjs/native/navigationLabel.js.map +1 -0
- package/lib/commonjs/native/normalizeViewTree.js +28 -0
- package/lib/commonjs/native/normalizeViewTree.js.map +1 -0
- package/lib/commonjs/native/pruneOffscreen.js +76 -0
- package/lib/commonjs/native/pruneOffscreen.js.map +1 -0
- package/lib/commonjs/native/scrollViewport.js +69 -0
- package/lib/commonjs/native/scrollViewport.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/privacy/redact.js +108 -0
- package/lib/commonjs/privacy/redact.js.map +1 -0
- package/lib/commonjs/privacy/screenEvidence.js +59 -0
- package/lib/commonjs/privacy/screenEvidence.js.map +1 -0
- package/lib/commonjs/privacy/screenModel.js +404 -0
- package/lib/commonjs/privacy/screenModel.js.map +1 -0
- package/lib/commonjs/privacy/sensitiveScreen.js +58 -0
- package/lib/commonjs/privacy/sensitiveScreen.js.map +1 -0
- package/lib/commonjs/privacy/userGate.js +213 -0
- package/lib/commonjs/privacy/userGate.js.map +1 -0
- package/lib/commonjs/protocol/actions.js +112 -0
- package/lib/commonjs/protocol/actions.js.map +1 -0
- package/lib/commonjs/provider/HostAppearanceStore.js +111 -0
- package/lib/commonjs/provider/HostAppearanceStore.js.map +1 -0
- package/lib/commonjs/provider/KokoProvider.js +81 -0
- package/lib/commonjs/provider/KokoProvider.js.map +1 -0
- package/lib/commonjs/provider/KokoUiContext.js +102 -0
- package/lib/commonjs/provider/KokoUiContext.js.map +1 -0
- package/lib/commonjs/selector/match.js +378 -0
- package/lib/commonjs/selector/match.js.map +1 -0
- package/lib/commonjs/task/TaskStore.js +361 -0
- package/lib/commonjs/task/TaskStore.js.map +1 -0
- package/lib/commonjs/task/types.js +2 -0
- package/lib/commonjs/task/types.js.map +1 -0
- package/lib/commonjs/task/useTaskStore.js +14 -0
- package/lib/commonjs/task/useTaskStore.js.map +1 -0
- package/lib/commonjs/types.js +2 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/commonjs/ui/KokoChatSheet.js +885 -0
- package/lib/commonjs/ui/KokoChatSheet.js.map +1 -0
- package/lib/commonjs/ui/KokoComposer.js +161 -0
- package/lib/commonjs/ui/KokoComposer.js.map +1 -0
- package/lib/commonjs/ui/KokoDialogHost.js +254 -0
- package/lib/commonjs/ui/KokoDialogHost.js.map +1 -0
- package/lib/commonjs/ui/KokoEntryButton.js +81 -0
- package/lib/commonjs/ui/KokoEntryButton.js.map +1 -0
- package/lib/commonjs/ui/KokoObserver.js +522 -0
- package/lib/commonjs/ui/KokoObserver.js.map +1 -0
- package/lib/commonjs/ui/KokoOverlayLayer.js +45 -0
- package/lib/commonjs/ui/KokoOverlayLayer.js.map +1 -0
- package/lib/commonjs/ui/KokoSubtitle.js +95 -0
- package/lib/commonjs/ui/KokoSubtitle.js.map +1 -0
- package/lib/commonjs/ui/KokoTaskPanel.js +322 -0
- package/lib/commonjs/ui/KokoTaskPanel.js.map +1 -0
- package/lib/commonjs/ui/hostOverlay.js +126 -0
- package/lib/commonjs/ui/hostOverlay.js.map +1 -0
- package/lib/commonjs/ui/icons.js +132 -0
- package/lib/commonjs/ui/icons.js.map +1 -0
- package/lib/commonjs/ui/progressChrome.js +66 -0
- package/lib/commonjs/ui/progressChrome.js.map +1 -0
- package/lib/commonjs/ui/safeArea.js +96 -0
- package/lib/commonjs/ui/safeArea.js.map +1 -0
- package/lib/commonjs/ui/useOverlayOrigin.js +40 -0
- package/lib/commonjs/ui/useOverlayOrigin.js.map +1 -0
- package/lib/module/KokoAgent.js +1418 -0
- package/lib/module/KokoAgent.js.map +1 -0
- package/lib/module/assist/OnDeviceAssist.js +89 -0
- package/lib/module/assist/OnDeviceAssist.js.map +1 -0
- package/lib/module/assist/intentClassify.js +95 -0
- package/lib/module/assist/intentClassify.js.map +1 -0
- package/lib/module/assist/nodeRank.js +115 -0
- package/lib/module/assist/nodeRank.js.map +1 -0
- package/lib/module/assist/ocrTarget.js +123 -0
- package/lib/module/assist/ocrTarget.js.map +1 -0
- package/lib/module/assist/webVisualChange.js +24 -0
- package/lib/module/assist/webVisualChange.js.map +1 -0
- package/lib/module/client/ActionOutcomeUnknownError.js +23 -0
- package/lib/module/client/ActionOutcomeUnknownError.js.map +1 -0
- package/lib/module/client/AgentSession.js +854 -0
- package/lib/module/client/AgentSession.js.map +1 -0
- package/lib/module/client/GatewayClient.js +293 -0
- package/lib/module/client/GatewayClient.js.map +1 -0
- package/lib/module/client/OperationReplayGuard.js +58 -0
- package/lib/module/client/OperationReplayGuard.js.map +1 -0
- package/lib/module/client/RunCheckpoint.js +135 -0
- package/lib/module/client/RunCheckpoint.js.map +1 -0
- package/lib/module/client/loopDetect.js +177 -0
- package/lib/module/client/loopDetect.js.map +1 -0
- package/lib/module/client/recentSearch.js +46 -0
- package/lib/module/client/recentSearch.js.map +1 -0
- package/lib/module/client/walletChoiceGuard.js +14 -0
- package/lib/module/client/walletChoiceGuard.js.map +1 -0
- package/lib/module/client/webResultCategory.js +34 -0
- package/lib/module/client/webResultCategory.js.map +1 -0
- package/lib/module/commercial/AccountStore.js +75 -0
- package/lib/module/commercial/AccountStore.js.map +1 -0
- package/lib/module/commercial/AgentDebug.js +99 -0
- package/lib/module/commercial/AgentDebug.js.map +1 -0
- package/lib/module/commercial/ExecutionGuard.js +49 -0
- package/lib/module/commercial/ExecutionGuard.js.map +1 -0
- package/lib/module/commercial/UsageBus.js +38 -0
- package/lib/module/commercial/UsageBus.js.map +1 -0
- package/lib/module/commercial/branding.js +63 -0
- package/lib/module/commercial/branding.js.map +1 -0
- package/lib/module/commercial/devMode.js +45 -0
- package/lib/module/commercial/devMode.js.map +1 -0
- package/lib/module/commercial/trust.js +62 -0
- package/lib/module/commercial/trust.js.map +1 -0
- package/lib/module/confirmation/gate.js +54 -0
- package/lib/module/confirmation/gate.js.map +1 -0
- package/lib/module/cursor/CursorOverlay.js +136 -0
- package/lib/module/cursor/CursorOverlay.js.map +1 -0
- package/lib/module/cursor/PointerHand.js +47 -0
- package/lib/module/cursor/PointerHand.js.map +1 -0
- package/lib/module/cursor/controller.js +87 -0
- package/lib/module/cursor/controller.js.map +1 -0
- package/lib/module/cursor/pointer.js +45 -0
- package/lib/module/cursor/pointer.js.map +1 -0
- package/lib/module/debug/KokoRemoteSmoke.js +608 -0
- package/lib/module/debug/KokoRemoteSmoke.js.map +1 -0
- package/lib/module/explore/ExplorationGraph.js +406 -0
- package/lib/module/explore/ExplorationGraph.js.map +1 -0
- package/lib/module/explore/bootstrapExplore.js +55 -0
- package/lib/module/explore/bootstrapExplore.js.map +1 -0
- package/lib/module/host/HostActions.js +255 -0
- package/lib/module/host/HostActions.js.map +1 -0
- package/lib/module/i18n/index.js +159 -0
- package/lib/module/i18n/index.js.map +1 -0
- package/lib/module/i18n/runtimeMessages.js +180 -0
- package/lib/module/i18n/runtimeMessages.js.map +1 -0
- package/lib/module/index.js +44 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/native/NativeBridge.js +128 -0
- package/lib/module/native/NativeBridge.js.map +1 -0
- package/lib/module/native/navigationLabel.js +12 -0
- package/lib/module/native/navigationLabel.js.map +1 -0
- package/lib/module/native/normalizeViewTree.js +25 -0
- package/lib/module/native/normalizeViewTree.js.map +1 -0
- package/lib/module/native/pruneOffscreen.js +72 -0
- package/lib/module/native/pruneOffscreen.js.map +1 -0
- package/lib/module/native/scrollViewport.js +63 -0
- package/lib/module/native/scrollViewport.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/privacy/redact.js +103 -0
- package/lib/module/privacy/redact.js.map +1 -0
- package/lib/module/privacy/screenEvidence.js +50 -0
- package/lib/module/privacy/screenEvidence.js.map +1 -0
- package/lib/module/privacy/screenModel.js +397 -0
- package/lib/module/privacy/screenModel.js.map +1 -0
- package/lib/module/privacy/sensitiveScreen.js +52 -0
- package/lib/module/privacy/sensitiveScreen.js.map +1 -0
- package/lib/module/privacy/userGate.js +202 -0
- package/lib/module/privacy/userGate.js.map +1 -0
- package/lib/module/protocol/actions.js +108 -0
- package/lib/module/protocol/actions.js.map +1 -0
- package/lib/module/provider/HostAppearanceStore.js +101 -0
- package/lib/module/provider/HostAppearanceStore.js.map +1 -0
- package/lib/module/provider/KokoProvider.js +74 -0
- package/lib/module/provider/KokoProvider.js.map +1 -0
- package/lib/module/provider/KokoUiContext.js +96 -0
- package/lib/module/provider/KokoUiContext.js.map +1 -0
- package/lib/module/selector/match.js +366 -0
- package/lib/module/selector/match.js.map +1 -0
- package/lib/module/task/TaskStore.js +356 -0
- package/lib/module/task/TaskStore.js.map +1 -0
- package/lib/module/task/types.js +2 -0
- package/lib/module/task/types.js.map +1 -0
- package/lib/module/task/useTaskStore.js +10 -0
- package/lib/module/task/useTaskStore.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/ui/KokoChatSheet.js +879 -0
- package/lib/module/ui/KokoChatSheet.js.map +1 -0
- package/lib/module/ui/KokoComposer.js +155 -0
- package/lib/module/ui/KokoComposer.js.map +1 -0
- package/lib/module/ui/KokoDialogHost.js +247 -0
- package/lib/module/ui/KokoDialogHost.js.map +1 -0
- package/lib/module/ui/KokoEntryButton.js +74 -0
- package/lib/module/ui/KokoEntryButton.js.map +1 -0
- package/lib/module/ui/KokoObserver.js +516 -0
- package/lib/module/ui/KokoObserver.js.map +1 -0
- package/lib/module/ui/KokoOverlayLayer.js +39 -0
- package/lib/module/ui/KokoOverlayLayer.js.map +1 -0
- package/lib/module/ui/KokoSubtitle.js +90 -0
- package/lib/module/ui/KokoSubtitle.js.map +1 -0
- package/lib/module/ui/KokoTaskPanel.js +316 -0
- package/lib/module/ui/KokoTaskPanel.js.map +1 -0
- package/lib/module/ui/hostOverlay.js +120 -0
- package/lib/module/ui/hostOverlay.js.map +1 -0
- package/lib/module/ui/icons.js +124 -0
- package/lib/module/ui/icons.js.map +1 -0
- package/lib/module/ui/progressChrome.js +58 -0
- package/lib/module/ui/progressChrome.js.map +1 -0
- package/lib/module/ui/safeArea.js +92 -0
- package/lib/module/ui/safeArea.js.map +1 -0
- package/lib/module/ui/useOverlayOrigin.js +36 -0
- package/lib/module/ui/useOverlayOrigin.js.map +1 -0
- package/lib/typescript/commonjs/KokoAgent.d.ts +167 -0
- package/lib/typescript/commonjs/KokoAgent.d.ts.map +1 -0
- package/lib/typescript/commonjs/assist/OnDeviceAssist.d.ts +31 -0
- package/lib/typescript/commonjs/assist/OnDeviceAssist.d.ts.map +1 -0
- package/lib/typescript/commonjs/assist/intentClassify.d.ts +18 -0
- package/lib/typescript/commonjs/assist/intentClassify.d.ts.map +1 -0
- package/lib/typescript/commonjs/assist/nodeRank.d.ts +32 -0
- package/lib/typescript/commonjs/assist/nodeRank.d.ts.map +1 -0
- package/lib/typescript/commonjs/assist/ocrTarget.d.ts +25 -0
- package/lib/typescript/commonjs/assist/ocrTarget.d.ts.map +1 -0
- package/lib/typescript/commonjs/assist/webVisualChange.d.ts +6 -0
- package/lib/typescript/commonjs/assist/webVisualChange.d.ts.map +1 -0
- package/lib/typescript/commonjs/client/ActionOutcomeUnknownError.d.ts +13 -0
- package/lib/typescript/commonjs/client/ActionOutcomeUnknownError.d.ts.map +1 -0
- package/lib/typescript/commonjs/client/AgentSession.d.ts +56 -0
- package/lib/typescript/commonjs/client/AgentSession.d.ts.map +1 -0
- package/lib/typescript/commonjs/client/GatewayClient.d.ts +149 -0
- package/lib/typescript/commonjs/client/GatewayClient.d.ts.map +1 -0
- package/lib/typescript/commonjs/client/OperationReplayGuard.d.ts +13 -0
- package/lib/typescript/commonjs/client/OperationReplayGuard.d.ts.map +1 -0
- package/lib/typescript/commonjs/client/RunCheckpoint.d.ts +57 -0
- package/lib/typescript/commonjs/client/RunCheckpoint.d.ts.map +1 -0
- package/lib/typescript/commonjs/client/loopDetect.d.ts +15 -0
- package/lib/typescript/commonjs/client/loopDetect.d.ts.map +1 -0
- package/lib/typescript/commonjs/client/recentSearch.d.ts +5 -0
- package/lib/typescript/commonjs/client/recentSearch.d.ts.map +1 -0
- package/lib/typescript/commonjs/client/walletChoiceGuard.d.ts +4 -0
- package/lib/typescript/commonjs/client/walletChoiceGuard.d.ts.map +1 -0
- package/lib/typescript/commonjs/client/webResultCategory.d.ts +5 -0
- package/lib/typescript/commonjs/client/webResultCategory.d.ts.map +1 -0
- package/lib/typescript/commonjs/commercial/AccountStore.d.ts +17 -0
- package/lib/typescript/commonjs/commercial/AccountStore.d.ts.map +1 -0
- package/lib/typescript/commonjs/commercial/AgentDebug.d.ts +25 -0
- package/lib/typescript/commonjs/commercial/AgentDebug.d.ts.map +1 -0
- package/lib/typescript/commonjs/commercial/ExecutionGuard.d.ts +18 -0
- package/lib/typescript/commonjs/commercial/ExecutionGuard.d.ts.map +1 -0
- package/lib/typescript/commonjs/commercial/UsageBus.d.ts +18 -0
- package/lib/typescript/commonjs/commercial/UsageBus.d.ts.map +1 -0
- package/lib/typescript/commonjs/commercial/branding.d.ts +25 -0
- package/lib/typescript/commonjs/commercial/branding.d.ts.map +1 -0
- package/lib/typescript/commonjs/commercial/devMode.d.ts +26 -0
- package/lib/typescript/commonjs/commercial/devMode.d.ts.map +1 -0
- package/lib/typescript/commonjs/commercial/trust.d.ts +21 -0
- package/lib/typescript/commonjs/commercial/trust.d.ts.map +1 -0
- package/lib/typescript/commonjs/confirmation/gate.d.ts +18 -0
- package/lib/typescript/commonjs/confirmation/gate.d.ts.map +1 -0
- package/lib/typescript/commonjs/cursor/CursorOverlay.d.ts +13 -0
- package/lib/typescript/commonjs/cursor/CursorOverlay.d.ts.map +1 -0
- package/lib/typescript/commonjs/cursor/PointerHand.d.ts +16 -0
- package/lib/typescript/commonjs/cursor/PointerHand.d.ts.map +1 -0
- package/lib/typescript/commonjs/cursor/controller.d.ts +30 -0
- package/lib/typescript/commonjs/cursor/controller.d.ts.map +1 -0
- package/lib/typescript/commonjs/cursor/pointer.d.ts +23 -0
- package/lib/typescript/commonjs/cursor/pointer.d.ts.map +1 -0
- package/lib/typescript/commonjs/debug/KokoRemoteSmoke.d.ts +96 -0
- package/lib/typescript/commonjs/debug/KokoRemoteSmoke.d.ts.map +1 -0
- package/lib/typescript/commonjs/explore/ExplorationGraph.d.ts +96 -0
- package/lib/typescript/commonjs/explore/ExplorationGraph.d.ts.map +1 -0
- package/lib/typescript/commonjs/explore/bootstrapExplore.d.ts +40 -0
- package/lib/typescript/commonjs/explore/bootstrapExplore.d.ts.map +1 -0
- package/lib/typescript/commonjs/host/HostActions.d.ts +101 -0
- package/lib/typescript/commonjs/host/HostActions.d.ts.map +1 -0
- package/lib/typescript/commonjs/i18n/index.d.ts +20 -0
- package/lib/typescript/commonjs/i18n/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/i18n/runtimeMessages.d.ts +178 -0
- package/lib/typescript/commonjs/i18n/runtimeMessages.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +66 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/native/NativeBridge.d.ts +70 -0
- package/lib/typescript/commonjs/native/NativeBridge.d.ts.map +1 -0
- package/lib/typescript/commonjs/native/navigationLabel.d.ts +4 -0
- package/lib/typescript/commonjs/native/navigationLabel.d.ts.map +1 -0
- package/lib/typescript/commonjs/native/normalizeViewTree.d.ts +4 -0
- package/lib/typescript/commonjs/native/normalizeViewTree.d.ts.map +1 -0
- package/lib/typescript/commonjs/native/pruneOffscreen.d.ts +3 -0
- package/lib/typescript/commonjs/native/pruneOffscreen.d.ts.map +1 -0
- package/lib/typescript/commonjs/native/scrollViewport.d.ts +13 -0
- package/lib/typescript/commonjs/native/scrollViewport.d.ts.map +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/privacy/redact.d.ts +9 -0
- package/lib/typescript/commonjs/privacy/redact.d.ts.map +1 -0
- package/lib/typescript/commonjs/privacy/screenEvidence.d.ts +14 -0
- package/lib/typescript/commonjs/privacy/screenEvidence.d.ts.map +1 -0
- package/lib/typescript/commonjs/privacy/screenModel.d.ts +26 -0
- package/lib/typescript/commonjs/privacy/screenModel.d.ts.map +1 -0
- package/lib/typescript/commonjs/privacy/sensitiveScreen.d.ts +8 -0
- package/lib/typescript/commonjs/privacy/sensitiveScreen.d.ts.map +1 -0
- package/lib/typescript/commonjs/privacy/userGate.d.ts +58 -0
- package/lib/typescript/commonjs/privacy/userGate.d.ts.map +1 -0
- package/lib/typescript/commonjs/protocol/actions.d.ts +7 -0
- package/lib/typescript/commonjs/protocol/actions.d.ts.map +1 -0
- package/lib/typescript/commonjs/provider/HostAppearanceStore.d.ts +64 -0
- package/lib/typescript/commonjs/provider/HostAppearanceStore.d.ts.map +1 -0
- package/lib/typescript/commonjs/provider/KokoProvider.d.ts +39 -0
- package/lib/typescript/commonjs/provider/KokoProvider.d.ts.map +1 -0
- package/lib/typescript/commonjs/provider/KokoUiContext.d.ts +22 -0
- package/lib/typescript/commonjs/provider/KokoUiContext.d.ts.map +1 -0
- package/lib/typescript/commonjs/selector/match.d.ts +27 -0
- package/lib/typescript/commonjs/selector/match.d.ts.map +1 -0
- package/lib/typescript/commonjs/task/TaskStore.d.ts +65 -0
- package/lib/typescript/commonjs/task/TaskStore.d.ts.map +1 -0
- package/lib/typescript/commonjs/task/types.d.ts +66 -0
- package/lib/typescript/commonjs/task/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/task/useTaskStore.d.ts +3 -0
- package/lib/typescript/commonjs/task/useTaskStore.d.ts.map +1 -0
- package/lib/typescript/commonjs/types.d.ts +372 -0
- package/lib/typescript/commonjs/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/KokoChatSheet.d.ts +8 -0
- package/lib/typescript/commonjs/ui/KokoChatSheet.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/KokoComposer.d.ts +13 -0
- package/lib/typescript/commonjs/ui/KokoComposer.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/KokoDialogHost.d.ts +32 -0
- package/lib/typescript/commonjs/ui/KokoDialogHost.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/KokoEntryButton.d.ts +18 -0
- package/lib/typescript/commonjs/ui/KokoEntryButton.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/KokoObserver.d.ts +13 -0
- package/lib/typescript/commonjs/ui/KokoObserver.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/KokoOverlayLayer.d.ts +35 -0
- package/lib/typescript/commonjs/ui/KokoOverlayLayer.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/KokoSubtitle.d.ts +7 -0
- package/lib/typescript/commonjs/ui/KokoSubtitle.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/KokoTaskPanel.d.ts +7 -0
- package/lib/typescript/commonjs/ui/KokoTaskPanel.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/hostOverlay.d.ts +28 -0
- package/lib/typescript/commonjs/ui/hostOverlay.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/icons.d.ts +26 -0
- package/lib/typescript/commonjs/ui/icons.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/progressChrome.d.ts +21 -0
- package/lib/typescript/commonjs/ui/progressChrome.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/safeArea.d.ts +12 -0
- package/lib/typescript/commonjs/ui/safeArea.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/useOverlayOrigin.d.ts +22 -0
- package/lib/typescript/commonjs/ui/useOverlayOrigin.d.ts.map +1 -0
- package/lib/typescript/module/KokoAgent.d.ts +167 -0
- package/lib/typescript/module/KokoAgent.d.ts.map +1 -0
- package/lib/typescript/module/assist/OnDeviceAssist.d.ts +31 -0
- package/lib/typescript/module/assist/OnDeviceAssist.d.ts.map +1 -0
- package/lib/typescript/module/assist/intentClassify.d.ts +18 -0
- package/lib/typescript/module/assist/intentClassify.d.ts.map +1 -0
- package/lib/typescript/module/assist/nodeRank.d.ts +32 -0
- package/lib/typescript/module/assist/nodeRank.d.ts.map +1 -0
- package/lib/typescript/module/assist/ocrTarget.d.ts +25 -0
- package/lib/typescript/module/assist/ocrTarget.d.ts.map +1 -0
- package/lib/typescript/module/assist/webVisualChange.d.ts +6 -0
- package/lib/typescript/module/assist/webVisualChange.d.ts.map +1 -0
- package/lib/typescript/module/client/ActionOutcomeUnknownError.d.ts +13 -0
- package/lib/typescript/module/client/ActionOutcomeUnknownError.d.ts.map +1 -0
- package/lib/typescript/module/client/AgentSession.d.ts +56 -0
- package/lib/typescript/module/client/AgentSession.d.ts.map +1 -0
- package/lib/typescript/module/client/GatewayClient.d.ts +149 -0
- package/lib/typescript/module/client/GatewayClient.d.ts.map +1 -0
- package/lib/typescript/module/client/OperationReplayGuard.d.ts +13 -0
- package/lib/typescript/module/client/OperationReplayGuard.d.ts.map +1 -0
- package/lib/typescript/module/client/RunCheckpoint.d.ts +57 -0
- package/lib/typescript/module/client/RunCheckpoint.d.ts.map +1 -0
- package/lib/typescript/module/client/loopDetect.d.ts +15 -0
- package/lib/typescript/module/client/loopDetect.d.ts.map +1 -0
- package/lib/typescript/module/client/recentSearch.d.ts +5 -0
- package/lib/typescript/module/client/recentSearch.d.ts.map +1 -0
- package/lib/typescript/module/client/walletChoiceGuard.d.ts +4 -0
- package/lib/typescript/module/client/walletChoiceGuard.d.ts.map +1 -0
- package/lib/typescript/module/client/webResultCategory.d.ts +5 -0
- package/lib/typescript/module/client/webResultCategory.d.ts.map +1 -0
- package/lib/typescript/module/commercial/AccountStore.d.ts +17 -0
- package/lib/typescript/module/commercial/AccountStore.d.ts.map +1 -0
- package/lib/typescript/module/commercial/AgentDebug.d.ts +25 -0
- package/lib/typescript/module/commercial/AgentDebug.d.ts.map +1 -0
- package/lib/typescript/module/commercial/ExecutionGuard.d.ts +18 -0
- package/lib/typescript/module/commercial/ExecutionGuard.d.ts.map +1 -0
- package/lib/typescript/module/commercial/UsageBus.d.ts +18 -0
- package/lib/typescript/module/commercial/UsageBus.d.ts.map +1 -0
- package/lib/typescript/module/commercial/branding.d.ts +25 -0
- package/lib/typescript/module/commercial/branding.d.ts.map +1 -0
- package/lib/typescript/module/commercial/devMode.d.ts +26 -0
- package/lib/typescript/module/commercial/devMode.d.ts.map +1 -0
- package/lib/typescript/module/commercial/trust.d.ts +21 -0
- package/lib/typescript/module/commercial/trust.d.ts.map +1 -0
- package/lib/typescript/module/confirmation/gate.d.ts +18 -0
- package/lib/typescript/module/confirmation/gate.d.ts.map +1 -0
- package/lib/typescript/module/cursor/CursorOverlay.d.ts +13 -0
- package/lib/typescript/module/cursor/CursorOverlay.d.ts.map +1 -0
- package/lib/typescript/module/cursor/PointerHand.d.ts +16 -0
- package/lib/typescript/module/cursor/PointerHand.d.ts.map +1 -0
- package/lib/typescript/module/cursor/controller.d.ts +30 -0
- package/lib/typescript/module/cursor/controller.d.ts.map +1 -0
- package/lib/typescript/module/cursor/pointer.d.ts +23 -0
- package/lib/typescript/module/cursor/pointer.d.ts.map +1 -0
- package/lib/typescript/module/debug/KokoRemoteSmoke.d.ts +96 -0
- package/lib/typescript/module/debug/KokoRemoteSmoke.d.ts.map +1 -0
- package/lib/typescript/module/explore/ExplorationGraph.d.ts +96 -0
- package/lib/typescript/module/explore/ExplorationGraph.d.ts.map +1 -0
- package/lib/typescript/module/explore/bootstrapExplore.d.ts +40 -0
- package/lib/typescript/module/explore/bootstrapExplore.d.ts.map +1 -0
- package/lib/typescript/module/host/HostActions.d.ts +101 -0
- package/lib/typescript/module/host/HostActions.d.ts.map +1 -0
- package/lib/typescript/module/i18n/index.d.ts +20 -0
- package/lib/typescript/module/i18n/index.d.ts.map +1 -0
- package/lib/typescript/module/i18n/runtimeMessages.d.ts +178 -0
- package/lib/typescript/module/i18n/runtimeMessages.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +66 -0
- package/lib/typescript/module/index.d.ts.map +1 -0
- package/lib/typescript/module/native/NativeBridge.d.ts +70 -0
- package/lib/typescript/module/native/NativeBridge.d.ts.map +1 -0
- package/lib/typescript/module/native/navigationLabel.d.ts +4 -0
- package/lib/typescript/module/native/navigationLabel.d.ts.map +1 -0
- package/lib/typescript/module/native/normalizeViewTree.d.ts +4 -0
- package/lib/typescript/module/native/normalizeViewTree.d.ts.map +1 -0
- package/lib/typescript/module/native/pruneOffscreen.d.ts +3 -0
- package/lib/typescript/module/native/pruneOffscreen.d.ts.map +1 -0
- package/lib/typescript/module/native/scrollViewport.d.ts +13 -0
- package/lib/typescript/module/native/scrollViewport.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/privacy/redact.d.ts +9 -0
- package/lib/typescript/module/privacy/redact.d.ts.map +1 -0
- package/lib/typescript/module/privacy/screenEvidence.d.ts +14 -0
- package/lib/typescript/module/privacy/screenEvidence.d.ts.map +1 -0
- package/lib/typescript/module/privacy/screenModel.d.ts +26 -0
- package/lib/typescript/module/privacy/screenModel.d.ts.map +1 -0
- package/lib/typescript/module/privacy/sensitiveScreen.d.ts +8 -0
- package/lib/typescript/module/privacy/sensitiveScreen.d.ts.map +1 -0
- package/lib/typescript/module/privacy/userGate.d.ts +58 -0
- package/lib/typescript/module/privacy/userGate.d.ts.map +1 -0
- package/lib/typescript/module/protocol/actions.d.ts +7 -0
- package/lib/typescript/module/protocol/actions.d.ts.map +1 -0
- package/lib/typescript/module/provider/HostAppearanceStore.d.ts +64 -0
- package/lib/typescript/module/provider/HostAppearanceStore.d.ts.map +1 -0
- package/lib/typescript/module/provider/KokoProvider.d.ts +39 -0
- package/lib/typescript/module/provider/KokoProvider.d.ts.map +1 -0
- package/lib/typescript/module/provider/KokoUiContext.d.ts +22 -0
- package/lib/typescript/module/provider/KokoUiContext.d.ts.map +1 -0
- package/lib/typescript/module/selector/match.d.ts +27 -0
- package/lib/typescript/module/selector/match.d.ts.map +1 -0
- package/lib/typescript/module/task/TaskStore.d.ts +65 -0
- package/lib/typescript/module/task/TaskStore.d.ts.map +1 -0
- package/lib/typescript/module/task/types.d.ts +66 -0
- package/lib/typescript/module/task/types.d.ts.map +1 -0
- package/lib/typescript/module/task/useTaskStore.d.ts +3 -0
- package/lib/typescript/module/task/useTaskStore.d.ts.map +1 -0
- package/lib/typescript/module/types.d.ts +372 -0
- package/lib/typescript/module/types.d.ts.map +1 -0
- package/lib/typescript/module/ui/KokoChatSheet.d.ts +8 -0
- package/lib/typescript/module/ui/KokoChatSheet.d.ts.map +1 -0
- package/lib/typescript/module/ui/KokoComposer.d.ts +13 -0
- package/lib/typescript/module/ui/KokoComposer.d.ts.map +1 -0
- package/lib/typescript/module/ui/KokoDialogHost.d.ts +32 -0
- package/lib/typescript/module/ui/KokoDialogHost.d.ts.map +1 -0
- package/lib/typescript/module/ui/KokoEntryButton.d.ts +18 -0
- package/lib/typescript/module/ui/KokoEntryButton.d.ts.map +1 -0
- package/lib/typescript/module/ui/KokoObserver.d.ts +13 -0
- package/lib/typescript/module/ui/KokoObserver.d.ts.map +1 -0
- package/lib/typescript/module/ui/KokoOverlayLayer.d.ts +35 -0
- package/lib/typescript/module/ui/KokoOverlayLayer.d.ts.map +1 -0
- package/lib/typescript/module/ui/KokoSubtitle.d.ts +7 -0
- package/lib/typescript/module/ui/KokoSubtitle.d.ts.map +1 -0
- package/lib/typescript/module/ui/KokoTaskPanel.d.ts +7 -0
- package/lib/typescript/module/ui/KokoTaskPanel.d.ts.map +1 -0
- package/lib/typescript/module/ui/hostOverlay.d.ts +28 -0
- package/lib/typescript/module/ui/hostOverlay.d.ts.map +1 -0
- package/lib/typescript/module/ui/icons.d.ts +26 -0
- package/lib/typescript/module/ui/icons.d.ts.map +1 -0
- package/lib/typescript/module/ui/progressChrome.d.ts +21 -0
- package/lib/typescript/module/ui/progressChrome.d.ts.map +1 -0
- package/lib/typescript/module/ui/safeArea.d.ts +12 -0
- package/lib/typescript/module/ui/safeArea.d.ts.map +1 -0
- package/lib/typescript/module/ui/useOverlayOrigin.d.ts +22 -0
- package/lib/typescript/module/ui/useOverlayOrigin.d.ts.map +1 -0
- package/package.json +153 -0
- package/react-native.config.js +12 -0
- package/src/KokoAgent.ts +1415 -0
- package/src/assist/OnDeviceAssist.ts +133 -0
- package/src/assist/intentClassify.ts +122 -0
- package/src/assist/nodeRank.ts +167 -0
- package/src/assist/ocrTarget.ts +146 -0
- package/src/assist/webVisualChange.ts +24 -0
- package/src/client/ActionOutcomeUnknownError.ts +18 -0
- package/src/client/AgentSession.ts +952 -0
- package/src/client/GatewayClient.ts +439 -0
- package/src/client/OperationReplayGuard.ts +61 -0
- package/src/client/RunCheckpoint.ts +154 -0
- package/src/client/loopDetect.ts +213 -0
- package/src/client/recentSearch.ts +47 -0
- package/src/client/walletChoiceGuard.ts +15 -0
- package/src/client/webResultCategory.ts +28 -0
- package/src/commercial/AccountStore.ts +85 -0
- package/src/commercial/AgentDebug.ts +142 -0
- package/src/commercial/ExecutionGuard.ts +67 -0
- package/src/commercial/UsageBus.ts +44 -0
- package/src/commercial/branding.ts +97 -0
- package/src/commercial/devMode.ts +48 -0
- package/src/commercial/trust.ts +67 -0
- package/src/confirmation/gate.ts +82 -0
- package/src/cursor/CursorOverlay.tsx +138 -0
- package/src/cursor/PointerHand.tsx +49 -0
- package/src/cursor/controller.ts +98 -0
- package/src/cursor/pointer.ts +40 -0
- package/src/debug/KokoRemoteSmoke.tsx +561 -0
- package/src/explore/ExplorationGraph.ts +518 -0
- package/src/explore/bootstrapExplore.ts +89 -0
- package/src/host/HostActions.ts +349 -0
- package/src/i18n/index.ts +229 -0
- package/src/i18n/runtimeMessages.ts +180 -0
- package/src/index.ts +203 -0
- package/src/native/NativeBridge.ts +207 -0
- package/src/native/navigationLabel.ts +11 -0
- package/src/native/normalizeViewTree.ts +16 -0
- package/src/native/pruneOffscreen.ts +72 -0
- package/src/native/scrollViewport.ts +51 -0
- package/src/privacy/redact.ts +115 -0
- package/src/privacy/screenEvidence.ts +55 -0
- package/src/privacy/screenModel.ts +504 -0
- package/src/privacy/sensitiveScreen.ts +55 -0
- package/src/privacy/userGate.ts +233 -0
- package/src/protocol/actions.ts +66 -0
- package/src/provider/HostAppearanceStore.ts +148 -0
- package/src/provider/KokoProvider.tsx +118 -0
- package/src/provider/KokoUiContext.tsx +162 -0
- package/src/selector/match.ts +417 -0
- package/src/task/TaskStore.ts +387 -0
- package/src/task/types.ts +82 -0
- package/src/task/useTaskStore.ts +9 -0
- package/src/types.ts +318 -0
- package/src/ui/KokoChatSheet.tsx +930 -0
- package/src/ui/KokoComposer.tsx +174 -0
- package/src/ui/KokoDialogHost.tsx +249 -0
- package/src/ui/KokoEntryButton.tsx +74 -0
- package/src/ui/KokoObserver.tsx +587 -0
- package/src/ui/KokoOverlayLayer.tsx +53 -0
- package/src/ui/KokoSubtitle.tsx +80 -0
- package/src/ui/KokoTaskPanel.tsx +299 -0
- package/src/ui/hostOverlay.tsx +148 -0
- package/src/ui/icons.tsx +145 -0
- package/src/ui/progressChrome.ts +73 -0
- package/src/ui/safeArea.ts +95 -0
- package/src/ui/useOverlayOrigin.ts +29 -0
|
@@ -0,0 +1,1356 @@
|
|
|
1
|
+
package cn.intentcomputing.kokoagentsdk;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.graphics.Bitmap;
|
|
5
|
+
import android.graphics.Canvas;
|
|
6
|
+
import android.graphics.Rect;
|
|
7
|
+
import android.os.Handler;
|
|
8
|
+
import android.os.Looper;
|
|
9
|
+
import android.os.SystemClock;
|
|
10
|
+
import android.util.Base64;
|
|
11
|
+
import android.view.MotionEvent;
|
|
12
|
+
import android.view.View;
|
|
13
|
+
import android.view.ViewGroup;
|
|
14
|
+
import android.widget.CompoundButton;
|
|
15
|
+
import android.widget.EditText;
|
|
16
|
+
import android.widget.TextView;
|
|
17
|
+
import androidx.annotation.Nullable;
|
|
18
|
+
import com.facebook.react.bridge.Arguments;
|
|
19
|
+
import com.facebook.react.bridge.Promise;
|
|
20
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
21
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
22
|
+
import com.facebook.react.bridge.ReactMethod;
|
|
23
|
+
import com.facebook.react.bridge.ReadableMap;
|
|
24
|
+
import com.facebook.react.bridge.WritableArray;
|
|
25
|
+
import com.facebook.react.bridge.WritableMap;
|
|
26
|
+
import com.google.mlkit.vision.common.InputImage;
|
|
27
|
+
import com.google.mlkit.vision.text.Text;
|
|
28
|
+
import com.google.mlkit.vision.text.TextRecognition;
|
|
29
|
+
import com.google.mlkit.vision.text.TextRecognizer;
|
|
30
|
+
import com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions;
|
|
31
|
+
import com.google.mlkit.vision.text.latin.TextRecognizerOptions;
|
|
32
|
+
import java.io.ByteArrayOutputStream;
|
|
33
|
+
import java.util.ArrayList;
|
|
34
|
+
|
|
35
|
+
public class KokoAgentSdkModule extends ReactContextBaseJavaModule {
|
|
36
|
+
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
|
37
|
+
|
|
38
|
+
public KokoAgentSdkModule(ReactApplicationContext reactContext) {
|
|
39
|
+
super(reactContext);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@Override
|
|
43
|
+
public String getName() {
|
|
44
|
+
return "KokoAgentSdk";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Override
|
|
48
|
+
public java.util.Map<String, Object> getConstants() {
|
|
49
|
+
java.util.HashMap<String, Object> constants = new java.util.HashMap<>();
|
|
50
|
+
String packageName = getReactApplicationContext().getPackageName();
|
|
51
|
+
constants.put("hostAppId", packageName != null ? packageName : "");
|
|
52
|
+
return constants;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@ReactMethod
|
|
56
|
+
public void dumpViewTree(ReadableMap options, Promise promise) {
|
|
57
|
+
runOnUi(
|
|
58
|
+
() -> {
|
|
59
|
+
try {
|
|
60
|
+
View root = rootView();
|
|
61
|
+
if (root == null) {
|
|
62
|
+
promise.reject("no_window", "No root view");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
int maxDepth = options.hasKey("maxDepth") ? options.getInt("maxDepth") : 30;
|
|
66
|
+
boolean skipInvisible =
|
|
67
|
+
!options.hasKey("skipInvisible") || options.getBoolean("skipInvisible");
|
|
68
|
+
WritableMap tree = dumpView(root, "0", 0, maxDepth, skipInvisible);
|
|
69
|
+
promise.resolve(tree);
|
|
70
|
+
} catch (Exception e) {
|
|
71
|
+
promise.reject("dump_failed", e);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@ReactMethod
|
|
77
|
+
public void dispatchTap(double x, double y, Promise promise) {
|
|
78
|
+
runOnUi(
|
|
79
|
+
() -> {
|
|
80
|
+
try {
|
|
81
|
+
View root = rootView();
|
|
82
|
+
if (root == null) {
|
|
83
|
+
promise.reject("no_window", "No root view");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
boolean ok =
|
|
87
|
+
withOverlaysMuted(
|
|
88
|
+
root,
|
|
89
|
+
true,
|
|
90
|
+
true,
|
|
91
|
+
() -> injectTap(root, toPx(x), toPx(y)));
|
|
92
|
+
if (ok) {
|
|
93
|
+
promise.resolve(null);
|
|
94
|
+
} else {
|
|
95
|
+
promise.reject("tap_failed", "Could not dispatch tap");
|
|
96
|
+
}
|
|
97
|
+
} catch (Exception e) {
|
|
98
|
+
promise.reject("tap_failed", e);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@ReactMethod
|
|
104
|
+
public void invokeAlertAction(String title, Promise promise) {
|
|
105
|
+
runOnUi(
|
|
106
|
+
() -> {
|
|
107
|
+
String want = title == null ? "" : title.trim();
|
|
108
|
+
if (want.isEmpty()) {
|
|
109
|
+
promise.reject("bad_title", "title required");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
if (pressLabeled(want)) {
|
|
114
|
+
promise.resolve(null);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
promise.reject(
|
|
118
|
+
"alert_action_failed", "No alert action matching '" + want + "'");
|
|
119
|
+
} catch (Exception e) {
|
|
120
|
+
promise.reject("alert_action_failed", e);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Press a dialog button by its label, front window first.
|
|
127
|
+
*
|
|
128
|
+
* Reaching dialog buttons by coordinate needs the dump and the touch to
|
|
129
|
+
* agree on which window is on top; going by label works even when the
|
|
130
|
+
* planner only knows what the button said. Matches iOS, where alert actions
|
|
131
|
+
* are model objects with no dependable view to aim at.
|
|
132
|
+
*/
|
|
133
|
+
private boolean pressLabeled(String title) {
|
|
134
|
+
String wanted = title.toLowerCase();
|
|
135
|
+
ArrayList<View> roots = allRootViews();
|
|
136
|
+
|
|
137
|
+
for (View root : roots) {
|
|
138
|
+
ArrayList<View> candidates = new ArrayList<>();
|
|
139
|
+
collectLabeled(root, candidates);
|
|
140
|
+
|
|
141
|
+
for (View v : candidates) {
|
|
142
|
+
if (labelOf(v).toLowerCase().equals(wanted)) return pressView(root, v);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Substring either way covers "确认清空" against a button reading "清空",
|
|
146
|
+
// but only when it picks out one label — otherwise it is a guess.
|
|
147
|
+
View only = null;
|
|
148
|
+
int hits = 0;
|
|
149
|
+
for (View v : candidates) {
|
|
150
|
+
String label = labelOf(v).toLowerCase();
|
|
151
|
+
if (label.isEmpty()) continue;
|
|
152
|
+
if (label.contains(wanted) || wanted.contains(label)) {
|
|
153
|
+
only = v;
|
|
154
|
+
hits += 1;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (hits == 1 && only != null && pressView(root, only)) return true;
|
|
158
|
+
}
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Alert actions are not reliably clickable views: an AlertDialog's choices are
|
|
164
|
+
* AdapterView rows whose click goes through the list, not the row. Land a real
|
|
165
|
+
* touch at the label's centre first and only then fall back to performClick.
|
|
166
|
+
*/
|
|
167
|
+
private boolean pressView(View root, View target) {
|
|
168
|
+
int[] loc = new int[2];
|
|
169
|
+
target.getLocationOnScreen(loc);
|
|
170
|
+
float cx = loc[0] + target.getWidth() / 2f;
|
|
171
|
+
float cy = loc[1] + target.getHeight() / 2f;
|
|
172
|
+
if (dispatchTouchAt(root, cx, cy)) return true;
|
|
173
|
+
for (View v = target; v != null; ) {
|
|
174
|
+
if (v.isClickable() && v.isEnabled() && v.performClick()) return true;
|
|
175
|
+
Object parent = v.getParent();
|
|
176
|
+
v = parent instanceof View ? (View) parent : null;
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private String labelOf(View v) {
|
|
182
|
+
CharSequence desc = v.getContentDescription();
|
|
183
|
+
if (desc != null && desc.length() > 0) return desc.toString().trim();
|
|
184
|
+
String text = extractText(v);
|
|
185
|
+
return text == null ? "" : text.trim();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private void collectLabeled(View v, ArrayList<View> out) {
|
|
189
|
+
if (v.getVisibility() != View.VISIBLE || !v.isShown()) return;
|
|
190
|
+
if (isKokoOverlay(v)) return;
|
|
191
|
+
// A ViewGroup's text is only its descendants concatenated, so it is a
|
|
192
|
+
// candidate solely when it carries its own contentDescription — otherwise
|
|
193
|
+
// every ancestor would match and make the substring pass ambiguous.
|
|
194
|
+
boolean ownsLabel =
|
|
195
|
+
!(v instanceof ViewGroup) || (v.getContentDescription() != null
|
|
196
|
+
&& v.getContentDescription().length() > 0);
|
|
197
|
+
if (ownsLabel && v.getWidth() > 0 && v.getHeight() > 0 && !labelOf(v).isEmpty()) {
|
|
198
|
+
out.add(v);
|
|
199
|
+
}
|
|
200
|
+
if (v instanceof ViewGroup) {
|
|
201
|
+
ViewGroup g = (ViewGroup) v;
|
|
202
|
+
for (int i = 0; i < g.getChildCount(); i++) {
|
|
203
|
+
collectLabeled(g.getChildAt(i), out);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
@ReactMethod
|
|
209
|
+
public void dispatchSwipe(
|
|
210
|
+
double fromX, double fromY, double toX, double toY, double durationMs, Promise promise) {
|
|
211
|
+
runOnUi(
|
|
212
|
+
() -> {
|
|
213
|
+
try {
|
|
214
|
+
View root = rootView();
|
|
215
|
+
if (root == null) {
|
|
216
|
+
promise.reject("no_window", "No root view");
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
long duration = Math.max(50L, (long) durationMs);
|
|
220
|
+
boolean ok =
|
|
221
|
+
injectSwipe(
|
|
222
|
+
root, toPx(fromX), toPx(fromY), toPx(toX), toPx(toY), duration);
|
|
223
|
+
if (ok) {
|
|
224
|
+
promise.resolve(null);
|
|
225
|
+
} else {
|
|
226
|
+
promise.reject("swipe_failed", "Could not dispatch swipe");
|
|
227
|
+
}
|
|
228
|
+
} catch (Exception e) {
|
|
229
|
+
promise.reject("swipe_failed", e);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Two-finger pinch at (x,y). scale>1 zoom in, scale<1 zoom out. */
|
|
235
|
+
@ReactMethod
|
|
236
|
+
public void dispatchPinch(
|
|
237
|
+
double x, double y, double scale, double durationMs, Promise promise) {
|
|
238
|
+
runOnUi(
|
|
239
|
+
() -> {
|
|
240
|
+
try {
|
|
241
|
+
View root = rootView();
|
|
242
|
+
if (root == null) {
|
|
243
|
+
promise.reject("no_window", "No root view");
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (!(scale > 0.05 && scale < 20.0) || Double.isNaN(scale) || Double.isInfinite(scale)) {
|
|
247
|
+
promise.reject("bad_scale", "scale must be a finite number around 0.05…20");
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
long duration = Math.max(120L, Math.min(1200L, (long) durationMs));
|
|
251
|
+
boolean ok =
|
|
252
|
+
withOverlaysMuted(
|
|
253
|
+
root,
|
|
254
|
+
true,
|
|
255
|
+
true,
|
|
256
|
+
() -> injectPinch(root, toPx(x), toPx(y), (float) scale, duration));
|
|
257
|
+
if (ok) {
|
|
258
|
+
promise.resolve(null);
|
|
259
|
+
} else {
|
|
260
|
+
promise.reject("pinch_failed", "Could not dispatch pinch");
|
|
261
|
+
}
|
|
262
|
+
} catch (Exception e) {
|
|
263
|
+
promise.reject("pinch_failed", e);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
@ReactMethod
|
|
269
|
+
public void typeText(String text, boolean clear, Promise promise) {
|
|
270
|
+
runOnUi(
|
|
271
|
+
() -> {
|
|
272
|
+
try {
|
|
273
|
+
View root = rootView();
|
|
274
|
+
EditText focused = findFocusedEditText(root);
|
|
275
|
+
if (focused == null) {
|
|
276
|
+
focused = findFirstEditText(root);
|
|
277
|
+
if (focused != null) {
|
|
278
|
+
focused.requestFocus();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (focused == null) {
|
|
282
|
+
promise.reject("no_focus", "No focused text input");
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
focused.requestFocus();
|
|
286
|
+
if (clear) {
|
|
287
|
+
focused.setText("");
|
|
288
|
+
}
|
|
289
|
+
int start = focused.getSelectionStart();
|
|
290
|
+
int end = focused.getSelectionEnd();
|
|
291
|
+
if (start < 0) start = focused.length();
|
|
292
|
+
if (end < 0) end = focused.length();
|
|
293
|
+
focused.getText().replace(Math.min(start, end), Math.max(start, end), text);
|
|
294
|
+
focused.setSelection(Math.min(start, end) + text.length());
|
|
295
|
+
promise.resolve(null);
|
|
296
|
+
} catch (Exception e) {
|
|
297
|
+
promise.reject("type_failed", e);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
@ReactMethod
|
|
303
|
+
public void focusNode(String nodeId, Promise promise) {
|
|
304
|
+
runOnUi(
|
|
305
|
+
() -> {
|
|
306
|
+
try {
|
|
307
|
+
EditText field = findFirstEditText(rootView());
|
|
308
|
+
if (field == null) {
|
|
309
|
+
promise.reject("no_focus", "No text input to focus");
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
field.requestFocus();
|
|
313
|
+
promise.resolve(null);
|
|
314
|
+
} catch (Exception e) {
|
|
315
|
+
promise.reject("focus_failed", e);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
@ReactMethod
|
|
321
|
+
public void captureScreenshot(ReadableMap options, Promise promise) {
|
|
322
|
+
runOnUi(
|
|
323
|
+
() -> {
|
|
324
|
+
try {
|
|
325
|
+
View root = rootView();
|
|
326
|
+
if (root == null) {
|
|
327
|
+
promise.reject("no_window", "No root view");
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
boolean hideOverlays =
|
|
331
|
+
!options.hasKey("hideOverlays") || options.getBoolean("hideOverlays");
|
|
332
|
+
WritableMap result =
|
|
333
|
+
withOverlaysMuted(
|
|
334
|
+
root,
|
|
335
|
+
hideOverlays,
|
|
336
|
+
false,
|
|
337
|
+
() -> {
|
|
338
|
+
int w = root.getWidth();
|
|
339
|
+
int h = root.getHeight();
|
|
340
|
+
if (w <= 0 || h <= 0) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
|
|
344
|
+
Canvas canvas = new Canvas(bitmap);
|
|
345
|
+
root.draw(canvas);
|
|
346
|
+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
347
|
+
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
|
|
348
|
+
String b64 = Base64.encodeToString(baos.toByteArray(), Base64.NO_WRAP);
|
|
349
|
+
WritableMap map = Arguments.createMap();
|
|
350
|
+
map.putString("pngBase64", b64);
|
|
351
|
+
map.putDouble("width", w);
|
|
352
|
+
map.putDouble("height", h);
|
|
353
|
+
map.putDouble("scale", root.getResources().getDisplayMetrics().density);
|
|
354
|
+
return map;
|
|
355
|
+
});
|
|
356
|
+
if (result == null) {
|
|
357
|
+
promise.reject("screenshot_failed", "Could not capture");
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
promise.resolve(result);
|
|
361
|
+
} catch (Exception e) {
|
|
362
|
+
promise.reject("screenshot_failed", e);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* On-device OCR, the counterpart to Vision on iOS.
|
|
369
|
+
*
|
|
370
|
+
* Android has no OCR in the platform, so this needs ML Kit. The dependency
|
|
371
|
+
* is compileOnly: it is several megabytes of bundled models that a host
|
|
372
|
+
* embedding this SDK should not pay for unless it wants text recognition, so
|
|
373
|
+
* a host opts in by declaring it and we report "unavailable" otherwise.
|
|
374
|
+
* Callers already treat the backend as advisory and fall back to the dump.
|
|
375
|
+
*/
|
|
376
|
+
@ReactMethod
|
|
377
|
+
public void recognizeScreenText(ReadableMap options, Promise promise) {
|
|
378
|
+
runOnUi(
|
|
379
|
+
() -> {
|
|
380
|
+
try {
|
|
381
|
+
View root = rootView();
|
|
382
|
+
if (root == null) {
|
|
383
|
+
promise.reject("no_window", "No root view");
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
boolean hideOverlays =
|
|
387
|
+
!options.hasKey("hideOverlays") || options.getBoolean("hideOverlays");
|
|
388
|
+
int maxTokens = options.hasKey("maxTokens") ? options.getInt("maxTokens") : 24;
|
|
389
|
+
Bitmap shot =
|
|
390
|
+
withOverlaysMuted(root, hideOverlays, false, () -> snapshotBitmap(root));
|
|
391
|
+
if (shot == null) {
|
|
392
|
+
promise.reject("ocr_failed", "Could not capture screen");
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
TextRecognizer recognizer = textRecognizer();
|
|
396
|
+
if (recognizer == null) {
|
|
397
|
+
promise.resolve(emptyOcrResult("unavailable"));
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
recognizer
|
|
401
|
+
.process(InputImage.fromBitmap(shot, 0))
|
|
402
|
+
.addOnSuccessListener(text -> promise.resolve(ocrResult(text, maxTokens, root)))
|
|
403
|
+
.addOnFailureListener(e -> promise.reject("ocr_failed", e));
|
|
404
|
+
} catch (LinkageError e) {
|
|
405
|
+
promise.resolve(emptyOcrResult("unavailable"));
|
|
406
|
+
} catch (Exception e) {
|
|
407
|
+
promise.reject("ocr_failed", e);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
@Nullable
|
|
413
|
+
private Bitmap snapshotBitmap(View root) {
|
|
414
|
+
int w = root.getWidth();
|
|
415
|
+
int h = root.getHeight();
|
|
416
|
+
if (w <= 0 || h <= 0) return null;
|
|
417
|
+
Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
|
|
418
|
+
root.draw(new Canvas(bitmap));
|
|
419
|
+
return bitmap;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* The Chinese model also reads Latin script, so prefer it; either artifact
|
|
424
|
+
* may be the one the host declared, and neither may be present.
|
|
425
|
+
*/
|
|
426
|
+
@Nullable
|
|
427
|
+
private TextRecognizer textRecognizer() {
|
|
428
|
+
try {
|
|
429
|
+
return TextRecognition.getClient(new ChineseTextRecognizerOptions.Builder().build());
|
|
430
|
+
} catch (LinkageError | RuntimeException ignored) {
|
|
431
|
+
// Chinese model not on the classpath; try the default one.
|
|
432
|
+
}
|
|
433
|
+
try {
|
|
434
|
+
return TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS);
|
|
435
|
+
} catch (LinkageError | RuntimeException ignored) {
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
private WritableMap ocrResult(Text text, int maxTokens, View root) {
|
|
441
|
+
WritableArray tokens = Arguments.createArray();
|
|
442
|
+
WritableArray texts = Arguments.createArray();
|
|
443
|
+
ArrayList<String> seen = new ArrayList<>();
|
|
444
|
+
int[] origin = new int[2];
|
|
445
|
+
root.getLocationOnScreen(origin);
|
|
446
|
+
for (Text.TextBlock block : text.getTextBlocks()) {
|
|
447
|
+
for (Text.Line line : block.getLines()) {
|
|
448
|
+
String s = line.getText() == null ? "" : line.getText().trim();
|
|
449
|
+
if (s.isEmpty()) continue;
|
|
450
|
+
WritableMap entry = Arguments.createMap();
|
|
451
|
+
entry.putString("text", s);
|
|
452
|
+
entry.putDouble("confidence", lineConfidence(line));
|
|
453
|
+
Rect bounds = line.getBoundingBox();
|
|
454
|
+
if (bounds != null && bounds.width() > 0 && bounds.height() > 0) {
|
|
455
|
+
// ML Kit reports pixels in the root bitmap. Agent taps use
|
|
456
|
+
// window-absolute logical coordinates, so include both root origin
|
|
457
|
+
// and density conversion here at the native boundary.
|
|
458
|
+
WritableMap frame = Arguments.createMap();
|
|
459
|
+
frame.putDouble("x", toLogical(origin[0] + bounds.left));
|
|
460
|
+
frame.putDouble("y", toLogical(origin[1] + bounds.top));
|
|
461
|
+
frame.putDouble("width", toLogical(bounds.width()));
|
|
462
|
+
frame.putDouble("height", toLogical(bounds.height()));
|
|
463
|
+
entry.putMap("frame", frame);
|
|
464
|
+
}
|
|
465
|
+
texts.pushMap(entry);
|
|
466
|
+
if (seen.size() < maxTokens && !seen.contains(s)) {
|
|
467
|
+
seen.add(s);
|
|
468
|
+
tokens.pushString(s);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
WritableMap map = Arguments.createMap();
|
|
473
|
+
map.putArray("tokens", tokens);
|
|
474
|
+
map.putArray("texts", texts);
|
|
475
|
+
map.putString("backend", "mlkit");
|
|
476
|
+
return map;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
private double lineConfidence(Text.Line line) {
|
|
480
|
+
try {
|
|
481
|
+
Float c = line.getConfidence();
|
|
482
|
+
if (c != null) return c;
|
|
483
|
+
} catch (LinkageError | RuntimeException ignored) {
|
|
484
|
+
// Older model artifacts do not report it.
|
|
485
|
+
}
|
|
486
|
+
return 1.0;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
private WritableMap emptyOcrResult(String backend) {
|
|
490
|
+
WritableMap map = Arguments.createMap();
|
|
491
|
+
map.putArray("tokens", Arguments.createArray());
|
|
492
|
+
map.putArray("texts", Arguments.createArray());
|
|
493
|
+
map.putString("backend", backend);
|
|
494
|
+
return map;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
@ReactMethod
|
|
498
|
+
public void focusAt(double x, double y, Promise promise) {
|
|
499
|
+
runOnUi(
|
|
500
|
+
() -> {
|
|
501
|
+
try {
|
|
502
|
+
View root = rootView();
|
|
503
|
+
if (root == null) {
|
|
504
|
+
promise.reject("no_window", "No root view");
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
View hit = findViewAt(root, toPx(x), toPx(y));
|
|
508
|
+
EditText field = null;
|
|
509
|
+
View v = hit;
|
|
510
|
+
while (v != null) {
|
|
511
|
+
if (v instanceof EditText) {
|
|
512
|
+
field = (EditText) v;
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
v = v.getParent() instanceof View ? (View) v.getParent() : null;
|
|
516
|
+
}
|
|
517
|
+
if (field == null) {
|
|
518
|
+
field = findFirstEditText(root);
|
|
519
|
+
}
|
|
520
|
+
if (field == null) {
|
|
521
|
+
promise.reject("no_focus", "No text input to focus at point");
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
field.requestFocus();
|
|
525
|
+
promise.resolve(null);
|
|
526
|
+
} catch (Exception e) {
|
|
527
|
+
promise.reject("focus_failed", e);
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
@ReactMethod
|
|
533
|
+
public void dismissKeyboard(Promise promise) {
|
|
534
|
+
runOnUi(
|
|
535
|
+
() -> {
|
|
536
|
+
try {
|
|
537
|
+
Activity activity = getReactApplicationContext().getCurrentActivity();
|
|
538
|
+
View root = rootView();
|
|
539
|
+
if (root != null) {
|
|
540
|
+
root.clearFocus();
|
|
541
|
+
}
|
|
542
|
+
if (activity != null) {
|
|
543
|
+
android.view.inputmethod.InputMethodManager imm =
|
|
544
|
+
(android.view.inputmethod.InputMethodManager)
|
|
545
|
+
activity.getSystemService(android.content.Context.INPUT_METHOD_SERVICE);
|
|
546
|
+
View focus = activity.getCurrentFocus();
|
|
547
|
+
if (imm != null && focus != null) {
|
|
548
|
+
imm.hideSoftInputFromWindow(focus.getWindowToken(), 0);
|
|
549
|
+
} else if (imm != null && root != null) {
|
|
550
|
+
imm.hideSoftInputFromWindow(root.getWindowToken(), 0);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
promise.resolve(null);
|
|
554
|
+
} catch (Exception e) {
|
|
555
|
+
promise.reject("keyboard_failed", e);
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
@ReactMethod
|
|
561
|
+
public void scrollIntoView(double x, double y, Promise promise) {
|
|
562
|
+
// No-op: page ScrollViews jumped before tap (looked like a secret swipe).
|
|
563
|
+
promise.resolve(null);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Everything crossing the bridge is in RN logical units, matching iOS points,
|
|
568
|
+
* because the JS side lays the cursor and chrome out in those units. Android
|
|
569
|
+
* view geometry is pixels, so convert at the boundary and keep the native
|
|
570
|
+
* helpers pixel-only.
|
|
571
|
+
*/
|
|
572
|
+
private float density() {
|
|
573
|
+
return getReactApplicationContext().getResources().getDisplayMetrics().density;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
private float toPx(double logical) {
|
|
577
|
+
return (float) (logical * density());
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
private double toLogical(float px) {
|
|
581
|
+
return px / density();
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Prefer the front-most decor view (includes dialogs / popups when available).
|
|
586
|
+
*/
|
|
587
|
+
@Nullable
|
|
588
|
+
private View rootView() {
|
|
589
|
+
return topMostRootView();
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
@Nullable
|
|
593
|
+
@SuppressWarnings("unchecked")
|
|
594
|
+
private View topMostRootView() {
|
|
595
|
+
ArrayList<View> roots = allRootViews();
|
|
596
|
+
return roots.isEmpty() ? null : roots.get(0);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Every attached window root, front to back.
|
|
601
|
+
*
|
|
602
|
+
* Dialogs, popups and menus each get their own window, so anything that only
|
|
603
|
+
* looks at the activity's decor view cannot see them.
|
|
604
|
+
*/
|
|
605
|
+
private ArrayList<View> allRootViews() {
|
|
606
|
+
ArrayList<View> out = new ArrayList<>();
|
|
607
|
+
try {
|
|
608
|
+
Class<?> wmg = Class.forName("android.view.WindowManagerGlobal");
|
|
609
|
+
Object instance = wmg.getMethod("getInstance").invoke(null);
|
|
610
|
+
java.lang.reflect.Field viewsField = wmg.getDeclaredField("mViews");
|
|
611
|
+
viewsField.setAccessible(true);
|
|
612
|
+
Object raw = viewsField.get(instance);
|
|
613
|
+
if (raw instanceof java.util.List) {
|
|
614
|
+
java.util.List<View> views = (java.util.List<View>) raw;
|
|
615
|
+
for (int i = views.size() - 1; i >= 0; i--) {
|
|
616
|
+
View v = views.get(i);
|
|
617
|
+
if (v != null && v.getVisibility() == View.VISIBLE && v.isShown()) {
|
|
618
|
+
View root = v.getRootView();
|
|
619
|
+
if (!out.contains(root) && !isKokoOwnedWindow(root)) out.add(root);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
} catch (Throwable ignored) {
|
|
624
|
+
// Hidden API; fall back to the activity below.
|
|
625
|
+
}
|
|
626
|
+
if (out.isEmpty()) {
|
|
627
|
+
Activity activity = getReactApplicationContext().getCurrentActivity();
|
|
628
|
+
if (activity != null && activity.getWindow() != null) {
|
|
629
|
+
out.add(activity.getWindow().getDecorView().getRootView());
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return out;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* A window holding nothing but SDK chrome. Hosts that fall back to the Modal
|
|
637
|
+
* overlay would otherwise put it in front of their own window, and dump /
|
|
638
|
+
* tap would aim at the progress pill instead of the App.
|
|
639
|
+
*/
|
|
640
|
+
private boolean isKokoOwnedWindow(View root) {
|
|
641
|
+
View content = root.findViewById(android.R.id.content);
|
|
642
|
+
return subtreeIsAllKoko(content != null ? content : root);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
private boolean subtreeIsAllKoko(View v) {
|
|
646
|
+
if (isKokoOverlay(v)) return true;
|
|
647
|
+
if (!(v instanceof ViewGroup)) return false;
|
|
648
|
+
ViewGroup g = (ViewGroup) v;
|
|
649
|
+
boolean sawKoko = false;
|
|
650
|
+
for (int i = 0; i < g.getChildCount(); i++) {
|
|
651
|
+
View c = g.getChildAt(i);
|
|
652
|
+
if (c.getVisibility() != View.VISIBLE) continue;
|
|
653
|
+
if (!subtreeIsAllKoko(c)) return false;
|
|
654
|
+
sawKoko = true;
|
|
655
|
+
}
|
|
656
|
+
return sawKoko;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
@Nullable
|
|
660
|
+
private View findViewAt(View root, float x, float y) {
|
|
661
|
+
if (root == null) return null;
|
|
662
|
+
int[] loc = new int[2];
|
|
663
|
+
root.getLocationOnScreen(loc);
|
|
664
|
+
float lx = x - loc[0];
|
|
665
|
+
float ly = y - loc[1];
|
|
666
|
+
return findViewAtLocal(root, lx, ly);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
@Nullable
|
|
670
|
+
private View findViewAtLocal(View view, float x, float y) {
|
|
671
|
+
if (view.getVisibility() != View.VISIBLE) return null;
|
|
672
|
+
if (x < 0 || y < 0 || x > view.getWidth() || y > view.getHeight()) return null;
|
|
673
|
+
if (view instanceof ViewGroup) {
|
|
674
|
+
ViewGroup g = (ViewGroup) view;
|
|
675
|
+
for (int i = g.getChildCount() - 1; i >= 0; i--) {
|
|
676
|
+
View child = g.getChildAt(i);
|
|
677
|
+
float cx = x - child.getLeft() + view.getScrollX();
|
|
678
|
+
float cy = y - child.getTop() + view.getScrollY();
|
|
679
|
+
View hit = findViewAtLocal(child, cx, cy);
|
|
680
|
+
if (hit != null) return hit;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return view;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
private void runOnUi(Runnable block) {
|
|
687
|
+
if (Looper.myLooper() == Looper.getMainLooper()) {
|
|
688
|
+
block.run();
|
|
689
|
+
} else {
|
|
690
|
+
mainHandler.post(block);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
@Nullable
|
|
695
|
+
private WritableMap dumpView(
|
|
696
|
+
View view, String path, int depth, int maxDepth, boolean skipInvisible) {
|
|
697
|
+
if (depth > maxDepth) {
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
700
|
+
if (isKokoOverlay(view)) {
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
703
|
+
boolean visible =
|
|
704
|
+
view.getVisibility() == View.VISIBLE
|
|
705
|
+
&& view.isShown()
|
|
706
|
+
&& view.getWidth() > 0
|
|
707
|
+
&& view.getHeight() > 0;
|
|
708
|
+
if (skipInvisible && !visible) {
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
int[] loc = new int[2];
|
|
713
|
+
view.getLocationOnScreen(loc);
|
|
714
|
+
|
|
715
|
+
WritableArray children = Arguments.createArray();
|
|
716
|
+
if (view instanceof ViewGroup) {
|
|
717
|
+
ViewGroup group = (ViewGroup) view;
|
|
718
|
+
for (int i = 0; i < group.getChildCount(); i++) {
|
|
719
|
+
WritableMap child =
|
|
720
|
+
dumpView(group.getChildAt(i), path + "/" + i, depth + 1, maxDepth, skipInvisible);
|
|
721
|
+
if (child != null) {
|
|
722
|
+
children.pushMap(child);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
WritableMap map = Arguments.createMap();
|
|
728
|
+
map.putString("id", path);
|
|
729
|
+
map.putString("type", view.getClass().getName());
|
|
730
|
+
WritableMap frame = Arguments.createMap();
|
|
731
|
+
frame.putDouble("x", toLogical(loc[0]));
|
|
732
|
+
frame.putDouble("y", toLogical(loc[1]));
|
|
733
|
+
frame.putDouble("width", toLogical(view.getWidth()));
|
|
734
|
+
frame.putDouble("height", toLogical(view.getHeight()));
|
|
735
|
+
map.putMap("frame", frame);
|
|
736
|
+
CharSequence desc = view.getContentDescription();
|
|
737
|
+
boolean clickable = view.isClickable() || view.isLongClickable();
|
|
738
|
+
// RN: labeled + focusable views are often press targets even if clickable flag is false.
|
|
739
|
+
if (!clickable && desc != null && desc.length() > 0 && view.isFocusable()) {
|
|
740
|
+
clickable = true;
|
|
741
|
+
}
|
|
742
|
+
String cls = view.getClass().getName();
|
|
743
|
+
if (!clickable
|
|
744
|
+
&& (cls.contains("Button")
|
|
745
|
+
|| cls.contains("Switch")
|
|
746
|
+
|| cls.contains("CheckBox")
|
|
747
|
+
|| cls.contains("Radio")
|
|
748
|
+
|| cls.contains("Spinner")
|
|
749
|
+
|| cls.contains("Chip")
|
|
750
|
+
|| cls.contains("Tab"))) {
|
|
751
|
+
clickable = view.isEnabled();
|
|
752
|
+
}
|
|
753
|
+
map.putBoolean("clickable", clickable);
|
|
754
|
+
map.putBoolean("enabled", view.isEnabled());
|
|
755
|
+
map.putBoolean("visible", visible);
|
|
756
|
+
map.putBoolean("scrollable", view.canScrollVertically(1) || view.canScrollVertically(-1));
|
|
757
|
+
map.putBoolean("clipsChildren", view instanceof ViewGroup && ((ViewGroup) view).getClipChildren());
|
|
758
|
+
map.putArray("children", children);
|
|
759
|
+
|
|
760
|
+
String text = extractText(view);
|
|
761
|
+
if (text != null && !text.isEmpty()) {
|
|
762
|
+
map.putString("text", text);
|
|
763
|
+
}
|
|
764
|
+
if (desc != null && desc.length() > 0) {
|
|
765
|
+
map.putString("accessibilityLabel", desc.toString());
|
|
766
|
+
}
|
|
767
|
+
if (cls.contains("Switch") || cls.contains("CheckBox") || cls.contains("Toggle")) {
|
|
768
|
+
map.putBoolean("toggle", true);
|
|
769
|
+
}
|
|
770
|
+
if (cls.contains("Spinner")
|
|
771
|
+
|| cls.contains("Picker")
|
|
772
|
+
|| cls.contains("SeekBar")
|
|
773
|
+
|| cls.contains("Rating")
|
|
774
|
+
|| cls.contains("Slider")
|
|
775
|
+
|| cls.contains("Stepper")
|
|
776
|
+
|| cls.contains("NumberPicker")
|
|
777
|
+
|| cls.contains("Segmented")) {
|
|
778
|
+
map.putBoolean("adjustable", true);
|
|
779
|
+
}
|
|
780
|
+
if (view instanceof android.widget.SeekBar) {
|
|
781
|
+
android.widget.SeekBar bar = (android.widget.SeekBar) view;
|
|
782
|
+
map.putDouble("value", bar.getProgress());
|
|
783
|
+
map.putDouble("min", 0);
|
|
784
|
+
map.putDouble("max", bar.getMax());
|
|
785
|
+
}
|
|
786
|
+
if (view instanceof android.widget.NumberPicker) {
|
|
787
|
+
android.widget.NumberPicker np = (android.widget.NumberPicker) view;
|
|
788
|
+
map.putDouble("value", np.getValue());
|
|
789
|
+
map.putDouble("min", np.getMinValue());
|
|
790
|
+
map.putDouble("max", np.getMaxValue());
|
|
791
|
+
}
|
|
792
|
+
if (cls.contains("Dialog") || cls.contains("Popup") || cls.contains("BottomSheet")) {
|
|
793
|
+
map.putBoolean("sheet", true);
|
|
794
|
+
}
|
|
795
|
+
if (view instanceof EditText) {
|
|
796
|
+
int inputType = ((EditText) view).getInputType();
|
|
797
|
+
int variation = inputType & android.text.InputType.TYPE_MASK_VARIATION;
|
|
798
|
+
boolean secure =
|
|
799
|
+
variation == android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD
|
|
800
|
+
|| variation == android.text.InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
|
801
|
+
|| variation == android.text.InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD
|
|
802
|
+
|| variation == android.text.InputType.TYPE_NUMBER_VARIATION_PASSWORD
|
|
803
|
+
|| ((EditText) view).getTransformationMethod()
|
|
804
|
+
instanceof android.text.method.PasswordTransformationMethod;
|
|
805
|
+
if (secure) {
|
|
806
|
+
map.putBoolean("secure", true);
|
|
807
|
+
map.putString("text", "[redacted]");
|
|
808
|
+
map.putString("accessibilityLabel", null);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
Object tag = view.getTag();
|
|
812
|
+
if (tag != null) {
|
|
813
|
+
String tagStr = String.valueOf(tag);
|
|
814
|
+
if (!tagStr.isEmpty() && !tagStr.startsWith("android.")) {
|
|
815
|
+
map.putString("testID", tagStr);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
return map;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
@Nullable
|
|
822
|
+
private String extractText(View view) {
|
|
823
|
+
if (view instanceof EditText) {
|
|
824
|
+
int variation = ((EditText) view).getInputType() & android.text.InputType.TYPE_MASK_VARIATION;
|
|
825
|
+
if (variation == android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD
|
|
826
|
+
|| variation == android.text.InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
|
827
|
+
|| variation == android.text.InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD
|
|
828
|
+
|| variation == android.text.InputType.TYPE_NUMBER_VARIATION_PASSWORD
|
|
829
|
+
|| ((EditText) view).getTransformationMethod() instanceof android.text.method.PasswordTransformationMethod) {
|
|
830
|
+
return "[redacted]";
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (view instanceof TextView) {
|
|
834
|
+
CharSequence t = ((TextView) view).getText();
|
|
835
|
+
if (t != null && t.length() > 0) {
|
|
836
|
+
return t.toString();
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
if (view instanceof ViewGroup) {
|
|
840
|
+
ViewGroup group = (ViewGroup) view;
|
|
841
|
+
ArrayList<String> parts = new ArrayList<>();
|
|
842
|
+
for (int i = 0; i < group.getChildCount(); i++) {
|
|
843
|
+
String t = extractText(group.getChildAt(i));
|
|
844
|
+
if (t != null && !t.isEmpty()) {
|
|
845
|
+
parts.add(t);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
if (!parts.isEmpty()) {
|
|
849
|
+
return join(parts, " ");
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
private static String join(ArrayList<String> parts, String sep) {
|
|
856
|
+
StringBuilder sb = new StringBuilder();
|
|
857
|
+
for (int i = 0; i < parts.size(); i++) {
|
|
858
|
+
if (i > 0) sb.append(sep);
|
|
859
|
+
sb.append(parts.get(i));
|
|
860
|
+
}
|
|
861
|
+
return sb.toString();
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
private boolean isKokoOverlay(View view) {
|
|
865
|
+
String id = kokoOverlayId(view);
|
|
866
|
+
return id != null && id.startsWith("koko-agent-");
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/** Progress / sheet chrome only — cursor stays visible during tap. */
|
|
870
|
+
private boolean isKokoChromeToMute(View view) {
|
|
871
|
+
String id = kokoOverlayId(view);
|
|
872
|
+
return id != null && (id.equals("koko-agent-overlay") || id.startsWith("koko-agent-overlay/"));
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
private String kokoOverlayId(View view) {
|
|
876
|
+
Object tag = view.getTag();
|
|
877
|
+
if (tag != null) {
|
|
878
|
+
String s = String.valueOf(tag);
|
|
879
|
+
if (s.startsWith("koko-agent-")) return s;
|
|
880
|
+
}
|
|
881
|
+
try {
|
|
882
|
+
int resId =
|
|
883
|
+
view.getResources()
|
|
884
|
+
.getIdentifier("react_test_id", "id", view.getContext().getPackageName());
|
|
885
|
+
if (resId == 0) {
|
|
886
|
+
resId =
|
|
887
|
+
view.getResources()
|
|
888
|
+
.getIdentifier("react_test_id", "id", "com.facebook.react");
|
|
889
|
+
}
|
|
890
|
+
if (resId != 0) {
|
|
891
|
+
Object t = view.getTag(resId);
|
|
892
|
+
if (t != null) {
|
|
893
|
+
String s = String.valueOf(t);
|
|
894
|
+
if (s.startsWith("koko-agent-")) return s;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
} catch (Exception ignored) {
|
|
898
|
+
}
|
|
899
|
+
return null;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
private void collectOverlayRoots(View view, ArrayList<View> out, boolean hide) {
|
|
903
|
+
// Captures must also hide the cursor so it cannot obscure OCR targets.
|
|
904
|
+
// Touch dispatch still mutes only interactive chrome and keeps it visible.
|
|
905
|
+
if (hide ? isKokoOverlay(view) : isKokoChromeToMute(view)) {
|
|
906
|
+
out.add(view);
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
if (isKokoOverlay(view)) {
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
if (view instanceof ViewGroup) {
|
|
913
|
+
ViewGroup g = (ViewGroup) view;
|
|
914
|
+
for (int i = 0; i < g.getChildCount(); i++) {
|
|
915
|
+
collectOverlayRoots(g.getChildAt(i), out, hide);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
private interface OverlayAction<T> {
|
|
921
|
+
T run();
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
private <T> T withOverlaysMuted(
|
|
925
|
+
View root, boolean hide, boolean disableInteraction, OverlayAction<T> action) {
|
|
926
|
+
ArrayList<View> overlays = new ArrayList<>();
|
|
927
|
+
collectOverlayRoots(root, overlays, hide);
|
|
928
|
+
ArrayList<Integer> vis = new ArrayList<>();
|
|
929
|
+
ArrayList<Boolean> enabled = new ArrayList<>();
|
|
930
|
+
ArrayList<Boolean> clickable = new ArrayList<>();
|
|
931
|
+
for (View v : overlays) {
|
|
932
|
+
vis.add(v.getVisibility());
|
|
933
|
+
enabled.add(v.isEnabled());
|
|
934
|
+
clickable.add(v.isClickable());
|
|
935
|
+
if (hide) {
|
|
936
|
+
v.setVisibility(View.INVISIBLE);
|
|
937
|
+
}
|
|
938
|
+
if (disableInteraction) {
|
|
939
|
+
v.setEnabled(false);
|
|
940
|
+
v.setClickable(false);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
try {
|
|
944
|
+
return action.run();
|
|
945
|
+
} finally {
|
|
946
|
+
for (int i = 0; i < overlays.size(); i++) {
|
|
947
|
+
View v = overlays.get(i);
|
|
948
|
+
v.setVisibility(vis.get(i));
|
|
949
|
+
v.setEnabled(enabled.get(i));
|
|
950
|
+
v.setClickable(clickable.get(i));
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
private boolean injectTap(View root, float x, float y) {
|
|
956
|
+
View hit = findTopChildAt(root, x, y);
|
|
957
|
+
if (hit != null) {
|
|
958
|
+
CompoundButton toggle = findToggle(hit);
|
|
959
|
+
if (toggle != null) {
|
|
960
|
+
// Prefer a short horizontal slide (thumb motion) over a click.
|
|
961
|
+
int[] loc = new int[2];
|
|
962
|
+
toggle.getLocationOnScreen(loc);
|
|
963
|
+
float midY = loc[1] + toggle.getHeight() / 2f;
|
|
964
|
+
float inset = Math.max(6f, toggle.getWidth() * 0.18f);
|
|
965
|
+
boolean on = toggle.isChecked();
|
|
966
|
+
float fromX = on ? loc[0] + toggle.getWidth() - inset : loc[0] + inset;
|
|
967
|
+
float toX = on ? loc[0] + inset : loc[0] + toggle.getWidth() - inset;
|
|
968
|
+
if (injectSwipe(root, fromX, midY, toX, midY, 220)) {
|
|
969
|
+
if (toggle.isChecked() == on) {
|
|
970
|
+
toggle.setChecked(!on);
|
|
971
|
+
}
|
|
972
|
+
return true;
|
|
973
|
+
}
|
|
974
|
+
toggle.toggle();
|
|
975
|
+
return true;
|
|
976
|
+
}
|
|
977
|
+
View v = hit;
|
|
978
|
+
while (v != null) {
|
|
979
|
+
if (v instanceof android.widget.SeekBar) {
|
|
980
|
+
android.widget.SeekBar bar = (android.widget.SeekBar) v;
|
|
981
|
+
int[] loc = new int[2];
|
|
982
|
+
bar.getLocationOnScreen(loc);
|
|
983
|
+
float span = Math.max(1f, bar.getWidth());
|
|
984
|
+
float ratio = (x - loc[0]) / span;
|
|
985
|
+
ratio = Math.max(0f, Math.min(1f, ratio));
|
|
986
|
+
bar.setProgress(Math.round(ratio * bar.getMax()));
|
|
987
|
+
return true;
|
|
988
|
+
}
|
|
989
|
+
if (v instanceof android.widget.NumberPicker) {
|
|
990
|
+
android.widget.NumberPicker np = (android.widget.NumberPicker) v;
|
|
991
|
+
int[] loc = new int[2];
|
|
992
|
+
np.getLocationOnScreen(loc);
|
|
993
|
+
if (y < loc[1] + np.getHeight() / 2f) {
|
|
994
|
+
np.setValue(Math.min(np.getMaxValue(), np.getValue() + 1));
|
|
995
|
+
} else {
|
|
996
|
+
np.setValue(Math.max(np.getMinValue(), np.getValue() - 1));
|
|
997
|
+
}
|
|
998
|
+
return true;
|
|
999
|
+
}
|
|
1000
|
+
Object parent = v.getParent();
|
|
1001
|
+
v = parent instanceof View ? (View) parent : null;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
// Aim at the press target rather than the text leaf under the finger: RN
|
|
1005
|
+
// puts the handler on the wrapper, and a leaf can sit outside its bounds.
|
|
1006
|
+
View press = bestPressTarget(hit);
|
|
1007
|
+
float px = x;
|
|
1008
|
+
float py = y;
|
|
1009
|
+
if (press != null && press != hit) {
|
|
1010
|
+
int[] loc = new int[2];
|
|
1011
|
+
press.getLocationOnScreen(loc);
|
|
1012
|
+
if (press.getWidth() > 0 && press.getHeight() > 0) {
|
|
1013
|
+
px = loc[0] + press.getWidth() / 2f;
|
|
1014
|
+
py = loc[1] + press.getHeight() / 2f;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
if (dispatchTouchAt(root, px, py)) return true;
|
|
1019
|
+
|
|
1020
|
+
// The event can be swallowed by a scroll container between the root and
|
|
1021
|
+
// the target, which leaves the press target untouched. Call it directly.
|
|
1022
|
+
for (View v = press != null ? press : hit; v != null; ) {
|
|
1023
|
+
if (v.isClickable() && v.isEnabled() && v.performClick()) return true;
|
|
1024
|
+
Object parent = v.getParent();
|
|
1025
|
+
v = parent instanceof View ? (View) parent : null;
|
|
1026
|
+
}
|
|
1027
|
+
return false;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
/**
|
|
1031
|
+
* Screen position of {@code root}.
|
|
1032
|
+
*
|
|
1033
|
+
* dispatchTouchEvent reads events in root-local coordinates while the dump
|
|
1034
|
+
* reports screen ones. They only agree while root sits at the screen origin,
|
|
1035
|
+
* which stops being true as soon as a dialog window is on top.
|
|
1036
|
+
*/
|
|
1037
|
+
private float[] rootOrigin(View root) {
|
|
1038
|
+
int[] loc = new int[2];
|
|
1039
|
+
root.getLocationOnScreen(loc);
|
|
1040
|
+
return new float[] {loc[0], loc[1]};
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/** Send a down/up pair to {@code root}, in that view's coordinate space. */
|
|
1044
|
+
private boolean dispatchTouchAt(View root, float screenX, float screenY) {
|
|
1045
|
+
float[] o = rootOrigin(root);
|
|
1046
|
+
float x = screenX - o[0];
|
|
1047
|
+
float y = screenY - o[1];
|
|
1048
|
+
long downTime = SystemClock.uptimeMillis();
|
|
1049
|
+
MotionEvent down = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, 0);
|
|
1050
|
+
MotionEvent up =
|
|
1051
|
+
MotionEvent.obtain(downTime, downTime + 40, MotionEvent.ACTION_UP, x, y, 0);
|
|
1052
|
+
boolean d = root.dispatchTouchEvent(down);
|
|
1053
|
+
boolean u = root.dispatchTouchEvent(up);
|
|
1054
|
+
down.recycle();
|
|
1055
|
+
up.recycle();
|
|
1056
|
+
return d || u;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* Nearest ancestor that looks like something you press.
|
|
1061
|
+
*
|
|
1062
|
+
* Mirrors the iOS press-target climb so both platforms aim at the same node
|
|
1063
|
+
* for the same tree.
|
|
1064
|
+
*/
|
|
1065
|
+
@Nullable
|
|
1066
|
+
private View bestPressTarget(@Nullable View from) {
|
|
1067
|
+
View v = from;
|
|
1068
|
+
while (v != null) {
|
|
1069
|
+
if (v.isClickable() || v.isLongClickable()) return v;
|
|
1070
|
+
String cls = v.getClass().getName();
|
|
1071
|
+
if (cls.contains("Button") || cls.contains("Pressab") || cls.contains("Touchable")) {
|
|
1072
|
+
return v;
|
|
1073
|
+
}
|
|
1074
|
+
Object parent = v.getParent();
|
|
1075
|
+
v = parent instanceof View ? (View) parent : null;
|
|
1076
|
+
}
|
|
1077
|
+
return from;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
@Nullable
|
|
1081
|
+
private CompoundButton findToggle(@Nullable View view) {
|
|
1082
|
+
View v = view;
|
|
1083
|
+
while (v != null) {
|
|
1084
|
+
if (v instanceof CompoundButton) {
|
|
1085
|
+
String cls = v.getClass().getName();
|
|
1086
|
+
if (cls.contains("Switch") || cls.contains("Toggle") || cls.contains("CheckBox")) {
|
|
1087
|
+
return (CompoundButton) v;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
Object parent = v.getParent();
|
|
1091
|
+
v = parent instanceof View ? (View) parent : null;
|
|
1092
|
+
}
|
|
1093
|
+
return null;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
@Nullable
|
|
1097
|
+
private View findTopChildAt(View root, float x, float y) {
|
|
1098
|
+
if (root instanceof ViewGroup) {
|
|
1099
|
+
ViewGroup g = (ViewGroup) root;
|
|
1100
|
+
for (int i = g.getChildCount() - 1; i >= 0; i--) {
|
|
1101
|
+
View child = g.getChildAt(i);
|
|
1102
|
+
if (child.getVisibility() != View.VISIBLE) continue;
|
|
1103
|
+
int[] loc = new int[2];
|
|
1104
|
+
child.getLocationOnScreen(loc);
|
|
1105
|
+
if (x >= loc[0]
|
|
1106
|
+
&& x <= loc[0] + child.getWidth()
|
|
1107
|
+
&& y >= loc[1]
|
|
1108
|
+
&& y <= loc[1] + child.getHeight()) {
|
|
1109
|
+
View deeper = findTopChildAt(child, x, y);
|
|
1110
|
+
return deeper != null ? deeper : child;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
return root;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
private boolean injectSwipe(
|
|
1118
|
+
View root, float fromX, float fromY, float toX, float toY, long durationMs) {
|
|
1119
|
+
// SeekBar: set progress from end X.
|
|
1120
|
+
View hit = findTopChildAt(root, fromX, fromY);
|
|
1121
|
+
if (hit != null) {
|
|
1122
|
+
View v = hit;
|
|
1123
|
+
while (v != null) {
|
|
1124
|
+
if (v instanceof android.widget.SeekBar) {
|
|
1125
|
+
android.widget.SeekBar bar = (android.widget.SeekBar) v;
|
|
1126
|
+
int[] loc = new int[2];
|
|
1127
|
+
bar.getLocationOnScreen(loc);
|
|
1128
|
+
float span = Math.max(1f, bar.getWidth());
|
|
1129
|
+
float ratio = (toX - loc[0]) / span;
|
|
1130
|
+
ratio = Math.max(0f, Math.min(1f, ratio));
|
|
1131
|
+
int next = Math.round(ratio * bar.getMax());
|
|
1132
|
+
bar.setProgress(next);
|
|
1133
|
+
return true;
|
|
1134
|
+
}
|
|
1135
|
+
Object parent = v.getParent();
|
|
1136
|
+
v = parent instanceof View ? (View) parent : null;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
float[] o = rootOrigin(root);
|
|
1141
|
+
float sx = fromX - o[0];
|
|
1142
|
+
float sy = fromY - o[1];
|
|
1143
|
+
float ex = toX - o[0];
|
|
1144
|
+
float ey = toY - o[1];
|
|
1145
|
+
|
|
1146
|
+
long downTime = SystemClock.uptimeMillis();
|
|
1147
|
+
MotionEvent down =
|
|
1148
|
+
MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, sx, sy, 0);
|
|
1149
|
+
root.dispatchTouchEvent(down);
|
|
1150
|
+
down.recycle();
|
|
1151
|
+
|
|
1152
|
+
int steps = 8;
|
|
1153
|
+
for (int i = 1; i <= steps; i++) {
|
|
1154
|
+
float t = i / (float) steps;
|
|
1155
|
+
float x = sx + (ex - sx) * t;
|
|
1156
|
+
float y = sy + (ey - sy) * t;
|
|
1157
|
+
long moveTime = downTime + durationMs * i / steps;
|
|
1158
|
+
MotionEvent move =
|
|
1159
|
+
MotionEvent.obtain(downTime, moveTime, MotionEvent.ACTION_MOVE, x, y, 0);
|
|
1160
|
+
root.dispatchTouchEvent(move);
|
|
1161
|
+
move.recycle();
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
MotionEvent up =
|
|
1165
|
+
MotionEvent.obtain(downTime, downTime + durationMs, MotionEvent.ACTION_UP, ex, ey, 0);
|
|
1166
|
+
boolean ok = root.dispatchTouchEvent(up);
|
|
1167
|
+
up.recycle();
|
|
1168
|
+
return ok;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
/** Two-finger pinch gesture centered at the screen point (cx, cy). */
|
|
1172
|
+
private boolean injectPinch(View root, float cx, float cy, float scale, long durationMs) {
|
|
1173
|
+
float[] o = rootOrigin(root);
|
|
1174
|
+
cx -= o[0];
|
|
1175
|
+
cy -= o[1];
|
|
1176
|
+
float startHalf = 48f;
|
|
1177
|
+
float endHalf = Math.max(10f, Math.min(260f, startHalf * scale));
|
|
1178
|
+
|
|
1179
|
+
float p1sx = cx - startHalf;
|
|
1180
|
+
float p1sy = cy;
|
|
1181
|
+
float p2sx = cx + startHalf;
|
|
1182
|
+
float p2sy = cy;
|
|
1183
|
+
float p1ex = cx - endHalf;
|
|
1184
|
+
float p1ey = cy;
|
|
1185
|
+
float p2ex = cx + endHalf;
|
|
1186
|
+
float p2ey = cy;
|
|
1187
|
+
|
|
1188
|
+
long downTime = SystemClock.uptimeMillis();
|
|
1189
|
+
MotionEvent.PointerProperties[] props = new MotionEvent.PointerProperties[2];
|
|
1190
|
+
props[0] = new MotionEvent.PointerProperties();
|
|
1191
|
+
props[0].id = 0;
|
|
1192
|
+
props[0].toolType = MotionEvent.TOOL_TYPE_FINGER;
|
|
1193
|
+
props[1] = new MotionEvent.PointerProperties();
|
|
1194
|
+
props[1].id = 1;
|
|
1195
|
+
props[1].toolType = MotionEvent.TOOL_TYPE_FINGER;
|
|
1196
|
+
|
|
1197
|
+
MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[2];
|
|
1198
|
+
coords[0] = new MotionEvent.PointerCoords();
|
|
1199
|
+
coords[0].x = p1sx;
|
|
1200
|
+
coords[0].y = p1sy;
|
|
1201
|
+
coords[0].pressure = 1f;
|
|
1202
|
+
coords[0].size = 1f;
|
|
1203
|
+
coords[1] = new MotionEvent.PointerCoords();
|
|
1204
|
+
coords[1].x = p2sx;
|
|
1205
|
+
coords[1].y = p2sy;
|
|
1206
|
+
coords[1].pressure = 1f;
|
|
1207
|
+
coords[1].size = 1f;
|
|
1208
|
+
|
|
1209
|
+
MotionEvent down0 =
|
|
1210
|
+
MotionEvent.obtain(
|
|
1211
|
+
downTime,
|
|
1212
|
+
downTime,
|
|
1213
|
+
MotionEvent.ACTION_DOWN,
|
|
1214
|
+
1,
|
|
1215
|
+
props,
|
|
1216
|
+
coords,
|
|
1217
|
+
0,
|
|
1218
|
+
0,
|
|
1219
|
+
1f,
|
|
1220
|
+
1f,
|
|
1221
|
+
0,
|
|
1222
|
+
0,
|
|
1223
|
+
0,
|
|
1224
|
+
0);
|
|
1225
|
+
root.dispatchTouchEvent(down0);
|
|
1226
|
+
down0.recycle();
|
|
1227
|
+
|
|
1228
|
+
int pointer1Down =
|
|
1229
|
+
MotionEvent.ACTION_POINTER_DOWN | (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
|
|
1230
|
+
MotionEvent down1 =
|
|
1231
|
+
MotionEvent.obtain(
|
|
1232
|
+
downTime,
|
|
1233
|
+
downTime + 10,
|
|
1234
|
+
pointer1Down,
|
|
1235
|
+
2,
|
|
1236
|
+
props,
|
|
1237
|
+
coords,
|
|
1238
|
+
0,
|
|
1239
|
+
0,
|
|
1240
|
+
1f,
|
|
1241
|
+
1f,
|
|
1242
|
+
0,
|
|
1243
|
+
0,
|
|
1244
|
+
0,
|
|
1245
|
+
0);
|
|
1246
|
+
root.dispatchTouchEvent(down1);
|
|
1247
|
+
down1.recycle();
|
|
1248
|
+
|
|
1249
|
+
int steps = Math.max(4, (int) (durationMs / 30));
|
|
1250
|
+
for (int i = 1; i <= steps; i++) {
|
|
1251
|
+
float t = i / (float) steps;
|
|
1252
|
+
coords[0].x = p1sx + (p1ex - p1sx) * t;
|
|
1253
|
+
coords[0].y = p1sy + (p1ey - p1sy) * t;
|
|
1254
|
+
coords[1].x = p2sx + (p2ex - p2sx) * t;
|
|
1255
|
+
coords[1].y = p2sy + (p2ey - p2sy) * t;
|
|
1256
|
+
long moveTime = downTime + durationMs * i / steps;
|
|
1257
|
+
MotionEvent move =
|
|
1258
|
+
MotionEvent.obtain(
|
|
1259
|
+
downTime,
|
|
1260
|
+
moveTime,
|
|
1261
|
+
MotionEvent.ACTION_MOVE,
|
|
1262
|
+
2,
|
|
1263
|
+
props,
|
|
1264
|
+
coords,
|
|
1265
|
+
0,
|
|
1266
|
+
0,
|
|
1267
|
+
1f,
|
|
1268
|
+
1f,
|
|
1269
|
+
0,
|
|
1270
|
+
0,
|
|
1271
|
+
0,
|
|
1272
|
+
0);
|
|
1273
|
+
root.dispatchTouchEvent(move);
|
|
1274
|
+
move.recycle();
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
coords[0].x = p1ex;
|
|
1278
|
+
coords[0].y = p1ey;
|
|
1279
|
+
coords[1].x = p2ex;
|
|
1280
|
+
coords[1].y = p2ey;
|
|
1281
|
+
int pointer1Up =
|
|
1282
|
+
MotionEvent.ACTION_POINTER_UP | (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
|
|
1283
|
+
MotionEvent up1 =
|
|
1284
|
+
MotionEvent.obtain(
|
|
1285
|
+
downTime,
|
|
1286
|
+
downTime + durationMs,
|
|
1287
|
+
pointer1Up,
|
|
1288
|
+
2,
|
|
1289
|
+
props,
|
|
1290
|
+
coords,
|
|
1291
|
+
0,
|
|
1292
|
+
0,
|
|
1293
|
+
1f,
|
|
1294
|
+
1f,
|
|
1295
|
+
0,
|
|
1296
|
+
0,
|
|
1297
|
+
0,
|
|
1298
|
+
0);
|
|
1299
|
+
root.dispatchTouchEvent(up1);
|
|
1300
|
+
up1.recycle();
|
|
1301
|
+
|
|
1302
|
+
MotionEvent up0 =
|
|
1303
|
+
MotionEvent.obtain(
|
|
1304
|
+
downTime,
|
|
1305
|
+
downTime + durationMs + 10,
|
|
1306
|
+
MotionEvent.ACTION_UP,
|
|
1307
|
+
1,
|
|
1308
|
+
props,
|
|
1309
|
+
coords,
|
|
1310
|
+
0,
|
|
1311
|
+
0,
|
|
1312
|
+
1f,
|
|
1313
|
+
1f,
|
|
1314
|
+
0,
|
|
1315
|
+
0,
|
|
1316
|
+
0,
|
|
1317
|
+
0);
|
|
1318
|
+
boolean ok = root.dispatchTouchEvent(up0);
|
|
1319
|
+
up0.recycle();
|
|
1320
|
+
return ok;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
@Nullable
|
|
1324
|
+
private EditText findFocusedEditText(@Nullable View view) {
|
|
1325
|
+
if (view == null) return null;
|
|
1326
|
+
if (view instanceof EditText && view.isFocused()) {
|
|
1327
|
+
return (EditText) view;
|
|
1328
|
+
}
|
|
1329
|
+
if (view instanceof ViewGroup) {
|
|
1330
|
+
ViewGroup group = (ViewGroup) view;
|
|
1331
|
+
for (int i = 0; i < group.getChildCount(); i++) {
|
|
1332
|
+
EditText hit = findFocusedEditText(group.getChildAt(i));
|
|
1333
|
+
if (hit != null) return hit;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
return null;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
@Nullable
|
|
1340
|
+
private EditText findFirstEditText(@Nullable View view) {
|
|
1341
|
+
if (view == null) return null;
|
|
1342
|
+
if (view instanceof EditText && view.getVisibility() == View.VISIBLE) {
|
|
1343
|
+
return (EditText) view;
|
|
1344
|
+
}
|
|
1345
|
+
if (view instanceof ViewGroup) {
|
|
1346
|
+
ViewGroup group = (ViewGroup) view;
|
|
1347
|
+
EditText best = null;
|
|
1348
|
+
for (int i = 0; i < group.getChildCount(); i++) {
|
|
1349
|
+
EditText hit = findFirstEditText(group.getChildAt(i));
|
|
1350
|
+
if (hit != null) best = hit;
|
|
1351
|
+
}
|
|
1352
|
+
return best;
|
|
1353
|
+
}
|
|
1354
|
+
return null;
|
|
1355
|
+
}
|
|
1356
|
+
}
|