diff --git a/androidExample/README.md b/androidExample/README.md deleted file mode 100644 index 2b621e0..0000000 --- a/androidExample/README.md +++ /dev/null @@ -1,180 +0,0 @@ -# Android Agent调用示例 - -这是一个Android示例项目,演示如何调用情感分析Agent。 - -## 📋 项目结构 - -``` -androidExample/ -├── app/ -│ ├── src/ -│ │ └── main/ -│ │ ├── java/com/example/agentclient/ -│ │ │ ├── MainActivity.kt -│ │ │ ├── AgentService.kt -│ │ │ ├── models/ -│ │ │ │ ├── AgentRequest.kt -│ │ │ │ ├── AgentResponse.kt -│ │ │ │ └── ExecutionResponse.kt -│ │ │ └── utils/ -│ │ │ └── ApiClient.kt -│ │ └── res/ -│ │ ├── layout/ -│ │ │ └── activity_main.xml -│ │ └── values/ -│ │ └── strings.xml -│ └── build.gradle.kts -├── build.gradle.kts -├── settings.gradle.kts -└── README.md -``` - -## 🚀 快速开始 - -### 1. 配置API地址 - -在 `app/src/main/java/com/example/agentclient/utils/ApiClient.kt` 中修改: - -```kotlin -private const val BASE_URL = "http://your-server-ip:8037" -``` - -### 2. 配置Agent ID - -在 `MainActivity.kt` 中修改: - -```kotlin -private val AGENT_ID = "your-agent-id" // 情感分析Agent的ID -``` - -### 3. 运行项目 - -1. 使用Android Studio打开项目 -2. 同步Gradle依赖 -3. 运行到Android设备或模拟器 - -## 📱 功能特性 - -- ✅ 用户登录 -- ✅ 调用Agent API -- ✅ 实时显示执行状态 -- ✅ 显示Agent回复 -- ✅ 错误处理 - -## 🔧 依赖库 - -- Retrofit2:网络请求 -- OkHttp:HTTP客户端 -- Gson:JSON解析 -- Coroutines:异步处理 - -## 📝 使用说明 - -1. **登录** - - 应用启动后会自动登录(使用配置的用户名和密码) - - 登录成功后可以开始使用Agent - -2. **发送消息** - - 在输入框中输入文本 - - 点击"发送"按钮 - - 等待Agent处理并返回结果 - -3. **查看结果** - - Agent的回复会显示在消息列表中 - - 执行状态会实时更新 - -## 🔑 API说明 - -### 登录API -``` -POST /api/v1/auth/login -Content-Type: application/x-www-form-urlencoded - -username=admin&password=123456 -``` - -### 执行Agent API -``` -POST /api/v1/executions -Authorization: Bearer -Content-Type: application/json - -{ - "agent_id": "agent-id", - "input_data": { - "query": "用户输入", - "USER_INPUT": "用户输入" - } -} -``` - -### 获取执行状态API -``` -GET /api/v1/executions/{execution_id}/status -Authorization: Bearer -``` - -### 获取执行结果API -``` -GET /api/v1/executions/{execution_id} -Authorization: Bearer -``` - -## 🎯 示例:调用情感分析Agent - -```kotlin -// 1. 登录获取token -val token = agentService.login("admin", "123456") - -// 2. 执行Agent -val execution = agentService.executeAgent( - agentId = "sentiment-analysis-agent-id", - userInput = "这个产品真的很棒!" -) - -// 3. 轮询获取结果 -while (true) { - val status = agentService.getExecutionStatus(execution.id) - if (status.status == "completed") { - val result = agentService.getExecutionResult(execution.id) - println("情感分析结果: ${result.output_data}") - break - } - delay(1000) // 等待1秒 -} -``` - -## 📦 构建要求 - -- Android Studio Hedgehog | 2023.1.1 或更高版本 -- JDK 17 或更高版本 -- Android SDK API 24 或更高版本 -- Gradle 8.0 或更高版本 - -## 🔒 安全注意事项 - -1. **不要硬编码密码**:生产环境应该使用安全的认证方式 -2. **使用HTTPS**:生产环境必须使用HTTPS -3. **Token管理**:妥善保管和刷新Token -4. **网络安全配置**:Android 9+需要配置网络安全策略 - -## 🐛 故障排除 - -### 问题1:网络连接失败 -- 检查API地址是否正确 -- 检查设备网络连接 -- 检查服务器是否运行 - -### 问题2:登录失败 -- 检查用户名和密码是否正确 -- 检查服务器认证服务是否正常 - -### 问题3:Agent执行失败 -- 检查Agent ID是否正确 -- 检查Agent是否已发布 -- 查看服务器日志获取详细错误信息 - -## 📚 相关文档 - -- [Agent使用说明](../Agent使用说明.md) -- [API文档](../backend/API_DOCUMENTATION.md) diff --git a/androidExample/app/build.gradle.kts b/androidExample/app/build.gradle.kts deleted file mode 100644 index c3d86d5..0000000 --- a/androidExample/app/build.gradle.kts +++ /dev/null @@ -1,70 +0,0 @@ -plugins { - id("com.android.application") - id("org.jetbrains.kotlin.android") -} - -android { - namespace = "com.example.agentclient" - compileSdk = 34 - - defaultConfig { - applicationId = "com.example.agentclient" - minSdk = 24 - targetSdk = 34 - versionCode = 1 - versionName = "1.0" - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - kotlinOptions { - jvmTarget = "17" - } - - buildFeatures { - viewBinding = true - } -} - -dependencies { - // Android Core - implementation("androidx.core:core-ktx:1.12.0") - implementation("androidx.appcompat:appcompat:1.6.1") - implementation("com.google.android.material:material:1.10.0") - implementation("androidx.constraintlayout:constraintlayout:2.1.4") - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") - implementation("androidx.activity:activity-ktx:1.8.1") - - // Coroutines - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") - - // Retrofit & OkHttp - implementation("com.squareup.retrofit2:retrofit:2.9.0") - implementation("com.squareup.retrofit2:converter-gson:2.9.0") - implementation("com.squareup.okhttp3:okhttp:4.12.0") - implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") - - // Gson - implementation("com.google.code.gson:gson:2.10.1") - - // Testing - testImplementation("junit:junit:4.13.2") - androidTestImplementation("androidx.test.ext:junit:1.1.5") - androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") -} diff --git a/androidExample/app/src/main/java/com/example/agentclient/models/AgentExecutionRequest.kt b/androidExample/app/src/main/java/com/example/agentclient/models/AgentExecutionRequest.kt deleted file mode 100644 index a88bb81..0000000 --- a/androidExample/app/src/main/java/com/example/agentclient/models/AgentExecutionRequest.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.example.agentclient.models - -import com.google.gson.annotations.SerializedName - -/** - * Agent执行请求模型 - */ -data class AgentExecutionRequest( - @SerializedName("agent_id") - val agentId: String, - - @SerializedName("input_data") - val inputData: Map -) diff --git a/androidExample/app/src/main/java/com/example/agentclient/models/ExecutionResponse.kt b/androidExample/app/src/main/java/com/example/agentclient/models/ExecutionResponse.kt deleted file mode 100644 index 4a405e2..0000000 --- a/androidExample/app/src/main/java/com/example/agentclient/models/ExecutionResponse.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.example.agentclient.models - -import com.google.gson.annotations.SerializedName - -/** - * 执行记录响应模型 - */ -data class ExecutionResponse( - val id: String, - - @SerializedName("agent_id") - val agentId: String?, - - @SerializedName("workflow_id") - val workflowId: String?, - - val status: String, - - @SerializedName("input_data") - val inputData: Map?, - - @SerializedName("output_data") - val outputData: Any?, - - @SerializedName("created_at") - val createdAt: String -) diff --git a/androidExample/app/src/main/java/com/example/agentclient/models/ExecutionStatusResponse.kt b/androidExample/app/src/main/java/com/example/agentclient/models/ExecutionStatusResponse.kt deleted file mode 100644 index bb389ce..0000000 --- a/androidExample/app/src/main/java/com/example/agentclient/models/ExecutionStatusResponse.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.example.agentclient.models - -import com.google.gson.annotations.SerializedName - -/** - * 执行状态响应模型 - */ -data class ExecutionStatusResponse( - val status: String, - val progress: Int = 0, - - @SerializedName("current_node") - val currentNode: CurrentNode?, - - val error: String? = null -) - -/** - * 当前执行节点 - */ -data class CurrentNode( - @SerializedName("node_id") - val nodeId: String, - - @SerializedName("node_name") - val nodeName: String?, - - @SerializedName("node_type") - val nodeType: String? -) diff --git a/androidExample/app/src/main/java/com/example/agentclient/models/TokenResponse.kt b/androidExample/app/src/main/java/com/example/agentclient/models/TokenResponse.kt deleted file mode 100644 index f52042b..0000000 --- a/androidExample/app/src/main/java/com/example/agentclient/models/TokenResponse.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.example.agentclient.models - -import com.google.gson.annotations.SerializedName - -/** - * Token响应模型 - */ -data class TokenResponse( - @SerializedName("access_token") - val accessToken: String, - - @SerializedName("token_type") - val tokenType: String = "bearer" -) diff --git a/androidExample/app/src/main/java/com/example/agentclient/utils/ApiClient.kt b/androidExample/app/src/main/java/com/example/agentclient/utils/ApiClient.kt deleted file mode 100644 index 6eea4e5..0000000 --- a/androidExample/app/src/main/java/com/example/agentclient/utils/ApiClient.kt +++ /dev/null @@ -1,99 +0,0 @@ -package com.example.agentclient.utils - -import com.example.agentclient.models.* -import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor -import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory -import retrofit2.http.* -import java.util.concurrent.TimeUnit - -/** - * API客户端配置 - * - * 使用说明: - * 1. 修改 BASE_URL 为你的服务器地址 - * 2. 确保服务器允许跨域请求(CORS) - */ -object ApiClient { - // TODO: 修改为你的服务器地址 - private const val BASE_URL = "http://101.43.95.130:8037" - - // 创建OkHttp客户端 - private val okHttpClient = OkHttpClient.Builder() - .connectTimeout(30, TimeUnit.SECONDS) - .readTimeout(60, TimeUnit.SECONDS) - .writeTimeout(60, TimeUnit.SECONDS) - .addInterceptor(HttpLoggingInterceptor().apply { - level = HttpLoggingInterceptor.Level.BODY - }) - .build() - - // 创建Retrofit实例 - private val retrofit = Retrofit.Builder() - .baseUrl(BASE_URL) - .client(okHttpClient) - .addConverterFactory(GsonConverterFactory.create()) - .build() - - // 创建API服务 - val agentService: AgentService = retrofit.create(AgentService::class.java) -} - -/** - * Agent API接口 - */ -interface AgentService { - /** - * 用户登录 - * - * @param username 用户名 - * @param password 密码 - * @return Token响应 - */ - @FormUrlEncoded - @POST("/api/v1/auth/login") - suspend fun login( - @Field("username") username: String, - @Field("password") password: String - ): TokenResponse - - /** - * 执行Agent - * - * @param request Agent执行请求 - * @param token 认证Token - * @return 执行记录 - */ - @POST("/api/v1/executions") - suspend fun executeAgent( - @Body request: AgentExecutionRequest, - @Header("Authorization") token: String - ): ExecutionResponse - - /** - * 获取执行状态 - * - * @param executionId 执行ID - * @param token 认证Token - * @return 执行状态 - */ - @GET("/api/v1/executions/{execution_id}/status") - suspend fun getExecutionStatus( - @Path("execution_id") executionId: String, - @Header("Authorization") token: String - ): ExecutionStatusResponse - - /** - * 获取执行结果 - * - * @param executionId 执行ID - * @param token 认证Token - * @return 执行详情 - */ - @GET("/api/v1/executions/{execution_id}") - suspend fun getExecutionResult( - @Path("execution_id") executionId: String, - @Header("Authorization") token: String - ): ExecutionDetailResponse -} diff --git a/androidExample/build.gradle.kts b/androidExample/build.gradle.kts deleted file mode 100644 index e2539ba..0000000 --- a/androidExample/build.gradle.kts +++ /dev/null @@ -1,9 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -plugins { - id("com.android.application") version "8.1.0" apply false - id("org.jetbrains.kotlin.android") version "1.9.0" apply false -} - -tasks.register("clean", Delete::class) { - delete(rootProject.buildDir) -} diff --git a/androidExampleDemo/.gitignore b/androidExampleDemo/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/androidExampleDemo/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/androidExampleDemo/README.md b/androidExampleDemo/README.md new file mode 100644 index 0000000..cf4f8c0 --- /dev/null +++ b/androidExampleDemo/README.md @@ -0,0 +1,230 @@ +# Android Agent调用示例 (Java版本) + +这是一个使用Java开发的Android示例项目,演示如何调用情感分析Agent。 + +## 📋 项目结构 + +``` +androidExampleDemo/ +├── app/ +│ ├── src/ +│ │ └── main/ +│ │ ├── java/com/example/agentclient/ +│ │ │ ├── MainActivity.java +│ │ │ ├── models/ +│ │ │ │ ├── Agent.java +│ │ │ │ ├── AgentExecutionRequest.java +│ │ │ │ ├── ExecutionResponse.java +│ │ │ │ ├── ExecutionStatusResponse.java +│ │ │ │ └── TokenResponse.java +│ │ │ └── utils/ +│ │ │ └── ApiClient.java +│ │ ├── res/ +│ │ │ ├── layout/ +│ │ │ │ └── activity_main.xml +│ │ │ ├── values/ +│ │ │ │ └── strings.xml +│ │ │ └── xml/ +│ │ │ └── network_security_config.xml +│ │ └── AndroidManifest.xml +│ └── build.gradle +├── build.gradle +├── settings.gradle +├── gradle.properties +└── README.md +``` + +## 🚀 快速开始 + +### 1. 配置API地址 + +在 `app/src/main/java/com/example/agentclient/utils/ApiClient.java` 中修改: + +```java +private static final String BASE_URL = "http://your-server-ip:8037"; +``` + +### 2. 配置登录信息 + +在 `MainActivity.java` 中修改: + +```java +private static final String USERNAME = "admin"; +private static final String PASSWORD = "123456"; +private static final String AGENT_NAME = "情感分析Agent"; // Agent名称 +``` + +### 3. 运行项目 + +1. 使用Android Studio打开项目 +2. 同步Gradle依赖 +3. 运行到Android设备或模拟器 + +## 📱 功能特性 + +- ✅ 用户登录 +- ✅ 调用Agent API +- ✅ 实时显示执行状态 +- ✅ 显示Agent回复 +- ✅ 错误处理 + +## 🔧 依赖库 + +- Retrofit2:网络请求 +- OkHttp:HTTP客户端 +- Gson:JSON解析 +- AndroidX:Android支持库 + +## 📝 使用说明 + +1. **登录** + - 应用启动后会自动登录(使用配置的用户名和密码) + - 登录成功后可以开始使用Agent + +2. **发送消息** + - 在输入框中输入文本 + - 点击"发送"按钮 + - 等待Agent处理并返回结果 + +3. **查看结果** + - Agent的回复会显示在消息列表中 + - 执行状态会实时更新 + +## 🔑 API说明 + +### 登录API +``` +POST /api/v1/auth/login +Content-Type: application/x-www-form-urlencoded + +username=admin&password=123456 +``` + +### 执行Agent API +``` +POST /api/v1/executions +Authorization: Bearer +Content-Type: application/json + +{ + "agent_id": "agent-id", + "input_data": { + "query": "用户输入", + "USER_INPUT": "用户输入" + } +} +``` + +### 获取执行状态API +``` +GET /api/v1/executions/{execution_id}/status +Authorization: Bearer +``` + +### 获取执行结果API +``` +GET /api/v1/executions/{execution_id} +Authorization: Bearer +``` + +## 🎯 示例:调用情感分析Agent + +```java +// 1. 登录获取token +Call loginCall = ApiClient.getService().login("admin", "123456"); +loginCall.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + String token = "Bearer " + response.body().getAccessToken(); + + // 2. 通过名称查找Agent + Call> agentsCall = ApiClient.getService() + .getAgents("情感分析Agent", 100, token); + agentsCall.enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + String agentId = response.body().get(0).getId(); + + // 3. 执行Agent + Map inputData = new HashMap<>(); + inputData.put("query", "这个产品真的很棒!"); + inputData.put("USER_INPUT", "这个产品真的很棒!"); + + AgentExecutionRequest request = new AgentExecutionRequest(agentId, inputData); + Call execCall = ApiClient.getService() + .executeAgent(request, token); + execCall.enqueue(new Callback() { + @Override + public void onResponse(Call call, + Response response) { + if (response.isSuccessful()) { + String executionId = response.body().getId(); + // 4. 轮询获取结果(见MainActivity.pollExecutionResult方法) + } + } + + @Override + public void onFailure(Call call, Throwable t) { + // 处理错误 + } + }); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + // 处理错误 + } + }); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + // 处理错误 + } +}); +``` + +## 📦 构建要求 + +- Android Studio Hedgehog | 2023.1.1 或更高版本 +- JDK 17 或更高版本 +- Android SDK API 24 或更高版本 +- Gradle 8.0 或更高版本 + +## 💻 开发语言 + +- **Java 17** - 主要开发语言 +- 使用Retrofit进行网络请求 +- 使用Gson进行JSON解析 + +## 🔒 安全注意事项 + +1. **不要硬编码密码**:生产环境应该使用安全的认证方式 +2. **使用HTTPS**:生产环境必须使用HTTPS +3. **Token管理**:妥善保管和刷新Token +4. **网络安全配置**:Android 9+需要配置网络安全策略 + +## 🐛 故障排除 + +### 问题1:网络连接失败 +- 检查API地址是否正确 +- 检查设备网络连接 +- 检查服务器是否运行 + +### 问题2:登录失败 +- 检查用户名和密码是否正确 +- 检查服务器认证服务是否正常 + +### 问题3:Agent执行失败 +- 检查Agent ID是否正确 +- 检查Agent是否已发布 +- 查看服务器日志获取详细错误信息 + +## 📚 相关文档 + +- [Agent使用说明](../Agent使用说明.md) +- [API文档](../backend/API_DOCUMENTATION.md) diff --git a/androidExampleDemo/app/.gitignore b/androidExampleDemo/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/androidExampleDemo/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/androidExampleDemo/app/build.gradle b/androidExampleDemo/app/build.gradle new file mode 100644 index 0000000..a369e63 --- /dev/null +++ b/androidExampleDemo/app/build.gradle @@ -0,0 +1,58 @@ +plugins { + id 'com.android.application' +} + +android { + namespace 'com.example.agentclient' + compileSdk 34 + + defaultConfig { + applicationId "com.example.agentclient" + minSdk 24 + targetSdk 34 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + + buildFeatures { + viewBinding true + } +} + +dependencies { + // Android Core + implementation 'androidx.core:core:1.12.0' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.10.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.lifecycle:lifecycle-runtime:2.6.2' + implementation 'androidx.activity:activity:1.8.1' + + // Retrofit & OkHttp + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.retrofit2:converter-gson:2.9.0' + implementation 'com.squareup.okhttp3:okhttp:4.12.0' + implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0' + + // Gson + implementation 'com.google.code.gson:gson:2.10.1' + + // Testing + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' +} diff --git a/androidExampleDemo/app/proguard-rules.pro b/androidExampleDemo/app/proguard-rules.pro new file mode 100644 index 0000000..695c294 --- /dev/null +++ b/androidExampleDemo/app/proguard-rules.pro @@ -0,0 +1,33 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. + +# Retrofit +-keepattributes Signature, InnerClasses, EnclosingMethod +-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations +-keepclassmembers,allowshrinking,allowobfuscation interface * { + @retrofit2.http.* ; +} +-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement +-dontwarn javax.annotation.** +-dontwarn kotlin.Unit +-dontwarn retrofit2.KotlinExtensions +-dontwarn retrofit2.KotlinExtensions$* + +# OkHttp +-dontwarn okhttp3.** +-dontwarn okio.** +-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase + +# Gson +-keepattributes Signature +-keepattributes *Annotation* +-dontwarn sun.misc.** +-keep class com.google.gson.** { *; } +-keep class * implements com.google.gson.TypeAdapter +-keep class * implements com.google.gson.TypeAdapterFactory +-keep class * implements com.google.gson.JsonSerializer +-keep class * implements com.google.gson.JsonDeserializer + +# Keep model classes +-keep class com.example.agentclient.models.** { *; } diff --git a/androidExampleDemo/app/src/androidTest/java/com/example/obd/ExampleInstrumentedTest.java b/androidExampleDemo/app/src/androidTest/java/com/example/obd/ExampleInstrumentedTest.java new file mode 100644 index 0000000..ce711fe --- /dev/null +++ b/androidExampleDemo/app/src/androidTest/java/com/example/obd/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.example.obd; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.example.obd", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/androidExampleDemo/app/src/main/AndroidManifest.xml b/androidExampleDemo/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f85b76b --- /dev/null +++ b/androidExampleDemo/app/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + diff --git a/androidExampleDemo/app/src/main/java/com/example/agentclient/MainActivity.java b/androidExampleDemo/app/src/main/java/com/example/agentclient/MainActivity.java new file mode 100644 index 0000000..483fe94 --- /dev/null +++ b/androidExampleDemo/app/src/main/java/com/example/agentclient/MainActivity.java @@ -0,0 +1,430 @@ +package com.example.agentclient; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ProgressBar; +import android.widget.ScrollView; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; + +import com.example.agentclient.models.*; +import com.example.agentclient.utils.ApiClient; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +/** + * 主Activity - 调用情感分析Agent示例 + * + * 功能: + * 1. 自动登录 + * 2. 通过Agent名称查找Agent ID + * 3. 发送文本进行情感分析 + * 4. 轮询获取执行结果 + */ +public class MainActivity extends AppCompatActivity { + + // TODO: 修改为你的配置 + private static final String USERNAME = "admin"; + private static final String PASSWORD = "123456"; + private static final String AGENT_NAME = "情感分析Agent"; // Agent名称 + + private TextView tvStatus; + private TextView tvMessages; + private EditText etInput; + private Button btnSend; + private ProgressBar progressBar; + private ScrollView scrollView; + + private String accessToken; + private String agentId; + private Handler mainHandler; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + initViews(); + mainHandler = new Handler(Looper.getMainLooper()); + + // 启动时自动登录 + login(); + } + + private void initViews() { + tvStatus = findViewById(R.id.tvStatus); + tvMessages = findViewById(R.id.tvMessages); + etInput = findViewById(R.id.etInput); + btnSend = findViewById(R.id.btnSend); + progressBar = findViewById(R.id.progressBar); + scrollView = findViewById(R.id.scrollView); + + btnSend.setOnClickListener(v -> { + String input = etInput.getText().toString().trim(); + if (input.isEmpty()) { + Toast.makeText(this, "请输入要分析的文本", Toast.LENGTH_SHORT).show(); + return; + } + sendMessage(input); + }); + + updateStatus("准备登录..."); + addMessage("系统", "正在连接服务器..."); + } + + /** + * 登录 + */ + private void login() { + updateStatus("正在登录..."); + addMessage("系统", "正在登录..."); + + ApiClient.getService().login(USERNAME, PASSWORD).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + accessToken = "Bearer " + response.body().getAccessToken(); + updateStatus("登录成功"); + addMessage("系统", "登录成功!正在查找Agent..."); + + // 登录成功后查找Agent + findAgentByName(AGENT_NAME); + } else { + String errorMsg = "登录失败: " + response.code(); + try { + if (response.errorBody() != null) { + errorMsg += "\n" + response.errorBody().string(); + } + } catch (IOException e) { + e.printStackTrace(); + } + updateStatus("登录失败"); + addMessage("系统", errorMsg); + Toast.makeText(MainActivity.this, errorMsg, Toast.LENGTH_LONG).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + String errorMsg = "登录失败: " + t.getMessage(); + updateStatus("登录失败"); + addMessage("系统", errorMsg); + Toast.makeText(MainActivity.this, errorMsg, Toast.LENGTH_LONG).show(); + } + }); + } + + /** + * 通过名称查找Agent + */ + private void findAgentByName(String agentName) { + updateStatus("正在查找Agent: " + agentName); + + ApiClient.getService().getAgents(agentName, 100, accessToken) + .enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + List agents = response.body(); + + // 精确匹配名称 + Agent foundAgent = null; + for (Agent agent : agents) { + if (agentName.equals(agent.getName())) { + foundAgent = agent; + break; + } + } + + if (foundAgent != null) { + agentId = foundAgent.getId(); + String status = foundAgent.getStatus(); + updateStatus("已找到Agent: " + agentName); + addMessage("系统", String.format( + "找到Agent: %s\nID: %s\n状态: %s", + foundAgent.getName(), + agentId, + status + )); + + if (!"published".equals(status) && !"running".equals(status)) { + addMessage("系统", "警告: Agent状态为 '" + status + "',可能无法执行"); + } + + addMessage("系统", "可以开始发送文本进行情感分析了!"); + } else { + updateStatus("未找到Agent"); + addMessage("系统", "未找到名称为 '" + agentName + "' 的Agent"); + if (!agents.isEmpty()) { + addMessage("系统", "找到以下相似的Agent:"); + for (int i = 0; i < Math.min(5, agents.size()); i++) { + Agent agent = agents.get(i); + addMessage("系统", String.format( + " - %s (ID: %s, 状态: %s)", + agent.getName(), + agent.getId(), + agent.getStatus() + )); + } + } + } + } else { + String errorMsg = "查找Agent失败: " + response.code(); + updateStatus("查找Agent失败"); + addMessage("系统", errorMsg); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + String errorMsg = "查找Agent失败: " + t.getMessage(); + updateStatus("查找Agent失败"); + addMessage("系统", errorMsg); + } + }); + } + + /** + * 发送消息到Agent + */ + private void sendMessage(String userInput) { + if (agentId == null || agentId.isEmpty()) { + Toast.makeText(this, "Agent未找到,请先查找Agent", Toast.LENGTH_SHORT).show(); + return; + } + + if (accessToken == null || accessToken.isEmpty()) { + Toast.makeText(this, "未登录,请先登录", Toast.LENGTH_SHORT).show(); + login(); + return; + } + + // 禁用发送按钮 + btnSend.setEnabled(false); + progressBar.setVisibility(View.VISIBLE); + + // 显示用户输入 + addMessage("我", userInput); + updateStatus("正在发送请求..."); + + // 构建请求数据 + Map inputData = new HashMap<>(); + inputData.put("query", userInput); + inputData.put("USER_INPUT", userInput); + + AgentExecutionRequest request = new AgentExecutionRequest(agentId, inputData); + + // 执行Agent + ApiClient.getService().executeAgent(request, accessToken) + .enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + ExecutionResponse execution = response.body(); + String executionId = execution.getId(); + updateStatus("请求已发送,执行ID: " + executionId); + addMessage("系统", "正在处理,请稍候..."); + + // 轮询获取结果 + pollExecutionResult(executionId); + } else { + String errorMsg = "执行失败: " + response.code(); + try { + if (response.errorBody() != null) { + errorMsg += "\n" + response.errorBody().string(); + } + } catch (IOException e) { + e.printStackTrace(); + } + updateStatus("执行失败"); + addMessage("系统", errorMsg); + btnSend.setEnabled(true); + progressBar.setVisibility(View.GONE); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + String errorMsg = "执行失败: " + t.getMessage(); + updateStatus("执行失败"); + addMessage("系统", errorMsg); + btnSend.setEnabled(true); + progressBar.setVisibility(View.GONE); + } + }); + } + + /** + * 轮询获取执行结果 + */ + private void pollExecutionResult(String executionId) { + ApiClient.getService().getExecutionStatus(executionId, accessToken) + .enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + ExecutionStatusResponse statusResponse = response.body(); + String status = statusResponse.getStatus(); + + // 显示当前节点信息 + if (statusResponse.getCurrentNode() != null) { + ExecutionStatusResponse.CurrentNode node = statusResponse.getCurrentNode(); + updateStatus(String.format("执行中: %s (%s)", + node.getNodeName() != null ? node.getNodeName() : node.getNodeId(), + node.getNodeType())); + } + + if ("completed".equals(status)) { + // 执行完成,获取结果 + getExecutionResult(executionId); + } else if ("failed".equals(status) || "error".equals(status)) { + // 执行失败 + String errorMsg = statusResponse.getError(); + if (errorMsg == null || errorMsg.isEmpty()) { + errorMsg = "执行失败"; + } + updateStatus("执行失败"); + addMessage("系统", "执行失败: " + errorMsg); + btnSend.setEnabled(true); + progressBar.setVisibility(View.GONE); + } else { + // 继续轮询 + mainHandler.postDelayed(() -> pollExecutionResult(executionId), 1000); + } + } else { + // 继续轮询 + mainHandler.postDelayed(() -> pollExecutionResult(executionId), 1000); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + // 继续轮询 + mainHandler.postDelayed(() -> pollExecutionResult(executionId), 1000); + } + }); + } + + /** + * 获取执行结果 + */ + private void getExecutionResult(String executionId) { + ApiClient.getService().getExecutionResult(executionId, accessToken) + .enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + btnSend.setEnabled(true); + progressBar.setVisibility(View.GONE); + + if (response.isSuccessful() && response.body() != null) { + ExecutionResponse execution = response.body(); + Object outputData = execution.getOutputData(); + + updateStatus("执行完成"); + + // 格式化输出 + String result = formatOutput(outputData); + addMessage("Agent", result); + } else { + String errorMsg = "获取结果失败: " + response.code(); + updateStatus("获取结果失败"); + addMessage("系统", errorMsg); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + btnSend.setEnabled(true); + progressBar.setVisibility(View.GONE); + String errorMsg = "获取结果失败: " + t.getMessage(); + updateStatus("获取结果失败"); + addMessage("系统", errorMsg); + } + }); + } + + /** + * 格式化输出数据 + */ + private String formatOutput(Object outputData) { + if (outputData == null) { + return "无输出"; + } + + if (outputData instanceof String) { + return (String) outputData; + } + + if (outputData instanceof Map) { + @SuppressWarnings("unchecked") + Map map = (Map) outputData; + + // 优先提取output字段 + if (map.containsKey("output") && map.get("output") instanceof String) { + return (String) map.get("output"); + } + + // 如果只有一个key,直接返回其值 + if (map.size() == 1) { + Object value = map.values().iterator().next(); + if (value instanceof String) { + return (String) value; + } + } + + // 否则转换为字符串 + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : map.entrySet()) { + if (!entry.getKey().startsWith("_")) { + sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); + } + } + return sb.toString().trim(); + } + + return outputData.toString(); + } + + /** + * 更新状态栏 + */ + private void updateStatus(String status) { + mainHandler.post(() -> { + if (tvStatus != null) { + tvStatus.setText("状态: " + status); + } + }); + } + + /** + * 添加消息到消息列表 + */ + private void addMessage(String sender, String message) { + mainHandler.post(() -> { + if (tvMessages != null) { + String currentText = tvMessages.getText().toString(); + String newMessage = String.format("[%s] %s\n\n", sender, message); + tvMessages.append(newMessage); + + // 自动滚动到底部 + if (scrollView != null) { + scrollView.post(() -> scrollView.fullScroll(View.FOCUS_DOWN)); + } + } + }); + } +} diff --git a/androidExampleDemo/app/src/main/java/com/example/agentclient/models/Agent.java b/androidExampleDemo/app/src/main/java/com/example/agentclient/models/Agent.java new file mode 100644 index 0000000..92fa7ce --- /dev/null +++ b/androidExampleDemo/app/src/main/java/com/example/agentclient/models/Agent.java @@ -0,0 +1,56 @@ +package com.example.agentclient.models; + +import com.google.gson.annotations.SerializedName; + +/** + * Agent模型 + */ +public class Agent { + private String id; + private String name; + private String description; + private String status; + + @SerializedName("created_at") + private String createdAt; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } +} diff --git a/androidExampleDemo/app/src/main/java/com/example/agentclient/models/AgentExecutionRequest.java b/androidExampleDemo/app/src/main/java/com/example/agentclient/models/AgentExecutionRequest.java new file mode 100644 index 0000000..e102a32 --- /dev/null +++ b/androidExampleDemo/app/src/main/java/com/example/agentclient/models/AgentExecutionRequest.java @@ -0,0 +1,36 @@ +package com.example.agentclient.models; + +import com.google.gson.annotations.SerializedName; +import java.util.Map; + +/** + * Agent执行请求模型 + */ +public class AgentExecutionRequest { + @SerializedName("agent_id") + private String agentId; + + @SerializedName("input_data") + private Map inputData; + + public AgentExecutionRequest(String agentId, Map inputData) { + this.agentId = agentId; + this.inputData = inputData; + } + + public String getAgentId() { + return agentId; + } + + public void setAgentId(String agentId) { + this.agentId = agentId; + } + + public Map getInputData() { + return inputData; + } + + public void setInputData(Map inputData) { + this.inputData = inputData; + } +} diff --git a/androidExampleDemo/app/src/main/java/com/example/agentclient/models/ExecutionResponse.java b/androidExampleDemo/app/src/main/java/com/example/agentclient/models/ExecutionResponse.java new file mode 100644 index 0000000..c24332e --- /dev/null +++ b/androidExampleDemo/app/src/main/java/com/example/agentclient/models/ExecutionResponse.java @@ -0,0 +1,84 @@ +package com.example.agentclient.models; + +import com.google.gson.annotations.SerializedName; +import java.util.Map; + +/** + * 执行记录响应模型 + */ +public class ExecutionResponse { + private String id; + + @SerializedName("agent_id") + private String agentId; + + @SerializedName("workflow_id") + private String workflowId; + + private String status; + + @SerializedName("input_data") + private Map inputData; + + @SerializedName("output_data") + private Object outputData; + + @SerializedName("created_at") + private String createdAt; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getAgentId() { + return agentId; + } + + public void setAgentId(String agentId) { + this.agentId = agentId; + } + + public String getWorkflowId() { + return workflowId; + } + + public void setWorkflowId(String workflowId) { + this.workflowId = workflowId; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public Map getInputData() { + return inputData; + } + + public void setInputData(Map inputData) { + this.inputData = inputData; + } + + public Object getOutputData() { + return outputData; + } + + public void setOutputData(Object outputData) { + this.outputData = outputData; + } + + public String getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } +} diff --git a/androidExampleDemo/app/src/main/java/com/example/agentclient/models/ExecutionStatusResponse.java b/androidExampleDemo/app/src/main/java/com/example/agentclient/models/ExecutionStatusResponse.java new file mode 100644 index 0000000..2a59291 --- /dev/null +++ b/androidExampleDemo/app/src/main/java/com/example/agentclient/models/ExecutionStatusResponse.java @@ -0,0 +1,86 @@ +package com.example.agentclient.models; + +import com.google.gson.annotations.SerializedName; + +/** + * 执行状态响应模型 + */ +public class ExecutionStatusResponse { + private String status; + private int progress = 0; + + @SerializedName("current_node") + private CurrentNode currentNode; + + private String error; + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public int getProgress() { + return progress; + } + + public void setProgress(int progress) { + this.progress = progress; + } + + public CurrentNode getCurrentNode() { + return currentNode; + } + + public void setCurrentNode(CurrentNode currentNode) { + this.currentNode = currentNode; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + /** + * 当前执行节点 + */ + public static class CurrentNode { + @SerializedName("node_id") + private String nodeId; + + @SerializedName("node_name") + private String nodeName; + + @SerializedName("node_type") + private String nodeType; + + public String getNodeId() { + return nodeId; + } + + public void setNodeId(String nodeId) { + this.nodeId = nodeId; + } + + public String getNodeName() { + return nodeName; + } + + public void setNodeName(String nodeName) { + this.nodeName = nodeName; + } + + public String getNodeType() { + return nodeType; + } + + public void setNodeType(String nodeType) { + this.nodeType = nodeType; + } + } +} diff --git a/androidExampleDemo/app/src/main/java/com/example/agentclient/models/TokenResponse.java b/androidExampleDemo/app/src/main/java/com/example/agentclient/models/TokenResponse.java new file mode 100644 index 0000000..146680b --- /dev/null +++ b/androidExampleDemo/app/src/main/java/com/example/agentclient/models/TokenResponse.java @@ -0,0 +1,30 @@ +package com.example.agentclient.models; + +import com.google.gson.annotations.SerializedName; + +/** + * Token响应模型 + */ +public class TokenResponse { + @SerializedName("access_token") + private String accessToken; + + @SerializedName("token_type") + private String tokenType = "bearer"; + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getTokenType() { + return tokenType; + } + + public void setTokenType(String tokenType) { + this.tokenType = tokenType; + } +} diff --git a/androidExampleDemo/app/src/main/java/com/example/agentclient/utils/ApiClient.java b/androidExampleDemo/app/src/main/java/com/example/agentclient/utils/ApiClient.java new file mode 100644 index 0000000..e98e908 --- /dev/null +++ b/androidExampleDemo/app/src/main/java/com/example/agentclient/utils/ApiClient.java @@ -0,0 +1,126 @@ +package com.example.agentclient.utils; + +import com.example.agentclient.models.*; +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; +import retrofit2.http.*; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * API客户端配置 + * + * 使用说明: + * 1. 修改 BASE_URL 为你的服务器地址 + * 2. 确保服务器允许跨域请求(CORS) + */ +public class ApiClient { + // TODO: 修改为你的服务器地址 + private static final String BASE_URL = "http://101.43.95.130:8037"; + + private static Retrofit retrofit; + private static AgentService agentService; + + static { + // 创建OkHttp客户端 + HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); + logging.setLevel(HttpLoggingInterceptor.Level.BODY); + + OkHttpClient okHttpClient = new OkHttpClient.Builder() + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(60, TimeUnit.SECONDS) + .writeTimeout(60, TimeUnit.SECONDS) + .addInterceptor(logging) + .build(); + + // 创建Retrofit实例 + retrofit = new Retrofit.Builder() + .baseUrl(BASE_URL) + .client(okHttpClient) + .addConverterFactory(GsonConverterFactory.create()) + .build(); + + // 创建API服务 + agentService = retrofit.create(AgentService.class); + } + + public static AgentService getService() { + return agentService; + } + + /** + * Agent API接口 + */ + public interface AgentService { + /** + * 用户登录 + * + * @param username 用户名 + * @param password 密码 + * @return Token响应 + */ + @FormUrlEncoded + @POST("/api/v1/auth/login") + retrofit2.Call login( + @Field("username") String username, + @Field("password") String password + ); + + /** + * 获取Agent列表(支持搜索) + * + * @param search 搜索关键词(Agent名称) + * @param limit 返回数量限制 + * @param token 认证Token + * @return Agent列表 + */ + @GET("/api/v1/agents") + retrofit2.Call> getAgents( + @Query("search") String search, + @Query("limit") Integer limit, + @Header("Authorization") String token + ); + + /** + * 执行Agent + * + * @param request Agent执行请求 + * @param token 认证Token + * @return 执行记录 + */ + @POST("/api/v1/executions") + retrofit2.Call executeAgent( + @Body AgentExecutionRequest request, + @Header("Authorization") String token + ); + + /** + * 获取执行状态 + * + * @param executionId 执行ID + * @param token 认证Token + * @return 执行状态 + */ + @GET("/api/v1/executions/{execution_id}/status") + retrofit2.Call getExecutionStatus( + @Path("execution_id") String executionId, + @Header("Authorization") String token + ); + + /** + * 获取执行结果 + * + * @param executionId 执行ID + * @param token 认证Token + * @return 执行详情 + */ + @GET("/api/v1/executions/{execution_id}") + retrofit2.Call getExecutionResult( + @Path("execution_id") String executionId, + @Header("Authorization") String token + ); + } +} diff --git a/androidExampleDemo/app/src/main/res/drawable/button_outline.xml b/androidExampleDemo/app/src/main/res/drawable/button_outline.xml new file mode 100644 index 0000000..3007ba8 --- /dev/null +++ b/androidExampleDemo/app/src/main/res/drawable/button_outline.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/androidExampleDemo/app/src/main/res/drawable/button_primary.xml b/androidExampleDemo/app/src/main/res/drawable/button_primary.xml new file mode 100644 index 0000000..4c35579 --- /dev/null +++ b/androidExampleDemo/app/src/main/res/drawable/button_primary.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/androidExampleDemo/app/src/main/res/drawable/button_secondary.xml b/androidExampleDemo/app/src/main/res/drawable/button_secondary.xml new file mode 100644 index 0000000..0e9391f --- /dev/null +++ b/androidExampleDemo/app/src/main/res/drawable/button_secondary.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/androidExampleDemo/app/src/main/res/drawable/data_card_background.xml b/androidExampleDemo/app/src/main/res/drawable/data_card_background.xml new file mode 100644 index 0000000..104a453 --- /dev/null +++ b/androidExampleDemo/app/src/main/res/drawable/data_card_background.xml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/androidExampleDemo/app/src/main/res/drawable/ic_launcher_background.xml b/androidExampleDemo/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/androidExampleDemo/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/androidExampleDemo/app/src/main/res/drawable/ic_launcher_foreground.xml b/androidExampleDemo/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/androidExampleDemo/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/androidExampleDemo/app/src/main/res/layout/activity_device_discovery.xml b/androidExampleDemo/app/src/main/res/layout/activity_device_discovery.xml new file mode 100644 index 0000000..d9c0b5d --- /dev/null +++ b/androidExampleDemo/app/src/main/res/layout/activity_device_discovery.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + +