คู่มือการเชื่อมต่อ Android Workspace

คู่มือการเชื่อมต่อ Android Workspace

บทนำ

คู่มือนี้ให้คำแนะนำโดยละเอียดเกี่ยวกับการเชื่อมต่อ workspace ของ GPTBots กับแอป Android รวมถึงการขอสิทธิ์เข้าถึง, การเชื่อมต่อ native-H5 และการตั้งค่าอื่น ๆ ที่เกี่ยวข้อง

GPTBots มีโปรเจกต์ workspace ตัวอย่างสำหรับ Android เพื่อช่วยให้เริ่มต้นได้ง่าย ดาวน์โหลด Android demo project ได้ที่: Android Demo Project

รายการสิทธิ์ที่ต้องใช้

สิทธิ์พื้นฐาน

เพื่อให้ฟีเจอร์ workspace ทำงานได้ถูกต้อง กรุณากำหนดสิทธิ์เหล่านี้ใน AndroidManifest.xml:

การจัดการสิทธิ์

เพื่อให้แอปเข้าถึงสิทธิ์สำคัญ เช่น ไมโครโฟน กล้อง และแกลเลอรี่ ขณะรันไทม์ ควรขอสิทธิ์เหล่านี้เมื่อแอปเริ่มต้นใช้งาน:

// สิทธิ์ที่ต้องขอสำหรับ Android 13 ขึ้นไป private final String[] permissionsForAndroid13 = { Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_AUDIO, Manifest.permission.READ_MEDIA_VIDEO }; // สิทธิ์ที่ต้องขอสำหรับต่ำกว่า Android 13 private final String[] permissionsForBelowAndroid13 = { Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE };
                      
                      // สิทธิ์ที่ต้องขอสำหรับ Android 13 ขึ้นไป
private final String[] permissionsForAndroid13 = {
    Manifest.permission.RECORD_AUDIO,
    Manifest.permission.READ_MEDIA_IMAGES,
    Manifest.permission.READ_MEDIA_AUDIO,
    Manifest.permission.READ_MEDIA_VIDEO
};

// สิทธิ์ที่ต้องขอสำหรับต่ำกว่า Android 13
private final String[] permissionsForBelowAndroid13 = {
    Manifest.permission.RECORD_AUDIO,
    Manifest.permission.READ_EXTERNAL_STORAGE,
    Manifest.permission.WRITE_EXTERNAL_STORAGE
};

                    
บล็อกโค้ดนี้ในหน้าต่างลอย

การตั้งค่า WebView

การตั้งค่าพื้นฐาน

แอปจะสร้างอินสแตนซ์ WebView และตั้งค่าที่จำเป็น:

WebSettings webSettings = webView.getSettings(); webSettings.setJavaScriptEnabled(true); // เปิดใช้ JavaScript webSettings.setDomStorageEnabled(true); // เปิดใช้ DOM storage API webSettings.setAllowFileAccess(true); // อนุญาตให้เข้าถึงไฟล์ webSettings.setAllowContentAccess(true); // อนุญาตให้เข้าถึง URL เนื้อหา webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); // อนุญาตโหลดเนื้อหาผสม webSettings.setMediaPlaybackRequiresUserGesture(false); // เล่นมีเดียโดยไม่ต้อง gesture
                      
                      WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true); // เปิดใช้ JavaScript
webSettings.setDomStorageEnabled(true); // เปิดใช้ DOM storage API
webSettings.setAllowFileAccess(true); // อนุญาตให้เข้าถึงไฟล์
webSettings.setAllowContentAccess(true); // อนุญาตให้เข้าถึง URL เนื้อหา
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); // อนุญาตโหลดเนื้อหาผสม
webSettings.setMediaPlaybackRequiresUserGesture(false); // เล่นมีเดียโดยไม่ต้อง gesture

                    
บล็อกโค้ดนี้ในหน้าต่างลอย

การตั้งค่าบันทึกเสียง

webSettings.setAllowFileAccessFromFileURLs(true); // อนุญาต file URL เข้าถึงไฟล์ webSettings.setAllowUniversalAccessFromFileURLs(true); // อนุญาต universal access webSettings.setDatabaseEnabled(true); // เปิดใช้ database webSettings.setCacheMode(WebSettings.LOAD_DEFAULT); // ตั้งค่า cache mode
                      
                      webSettings.setAllowFileAccessFromFileURLs(true); // อนุญาต file URL เข้าถึงไฟล์
webSettings.setAllowUniversalAccessFromFileURLs(true); // อนุญาต universal access
webSettings.setDatabaseEnabled(true); // เปิดใช้ database
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT); // ตั้งค่า cache mode

                    
บล็อกโค้ดนี้ในหน้าต่างลอย

การตั้งค่า User-Agent

String userAgent = webSettings.getUserAgentString(); webSettings.setUserAgentString(userAgent + " WebViewApp/1.0");
                      
                      String userAgent = webSettings.getUserAgentString();
webSettings.setUserAgentString(userAgent + " WebViewApp/1.0");

                    
บล็อกโค้ดนี้ในหน้าต่างลอย

การเข้าถึง Workspace

การสร้าง URL

การเข้าถึง workspace ต้องใช้ Base URL และพารามิเตอร์ AiToken:

String baseUrl = "https://gptbots.ai/space/h5/home"; String aiToken = "YOUR_AI_TOKEN"; // แทนที่ด้วย AiToken จริง String fullUrl = baseUrl + "?AiToken=" + aiToken; AiToken จะถูกตั้งค่าไว้ล่วงหน้าในแอป หากต้องการเปลี่ยน สามารถกรอกได้ที่หน้าเข้าสู่ระบบ ### การโหลดหน้า webView.loadUrl(fullUrl); ## การเชื่อมต่อ WebView กับ Native ### ลงทะเบียน JavaScript Interface ```java webView.addJavascriptInterface(new JSBridge(), "agentWebBridge");
                      
                      String baseUrl = "https://gptbots.ai/space/h5/home";
String aiToken = "YOUR_AI_TOKEN"; // แทนที่ด้วย AiToken จริง
String fullUrl = baseUrl + "?AiToken=" + aiToken;

AiToken จะถูกตั้งค่าไว้ล่วงหน้าในแอป หากต้องการเปลี่ยน สามารถกรอกได้ที่หน้าเข้าสู่ระบบ

### การโหลดหน้า

webView.loadUrl(fullUrl);

## การเชื่อมต่อ WebView กับ Native

### ลงทะเบียน JavaScript Interface
```java
webView.addJavascriptInterface(new JSBridge(), "agentWebBridge");

                    
บล็อกโค้ดนี้ในหน้าต่างลอย

หน้า H5 จะเรียกใช้ method ฝั่ง native ผ่าน object agentWebBridge ที่ประกาศแบบ global

การจัดการสิทธิ์

การร้องขอสิทธิ์ผ่าน WebView

เมื่อหน้า H5 ขอสิทธิ์พิเศษ (เช่น บันทึกเสียง) ต้องจัดการคำขอสิทธิ์ผ่าน WebView:

webView.setWebChromeClient(new WebChromeClient() { @Override public void onPermissionRequest(PermissionRequest request) { String[] requestedResources = request.getResources(); boolean hasAudioPermission = false; for (String resource : requestedResources) { if (PermissionRequest.RESOURCE_AUDIO_CAPTURE.equals(resource)) { hasAudioPermission = true; break; } } if (hasAudioPermission) { // ตรวจสอบว่าแอปมีสิทธิ์บันทึกเสียงหรือไม่ if (checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) { request.grant(requestedResources); } else { // ขอสิทธิ์ผ่านระบบ requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, REQUEST_CODE); // เก็บ request ไว้รอจัดการหลังได้รับสิทธิ์ pendingPermissionRequest = request; } } else { // อนุญาตสิทธิ์อื่น ๆ ทันที request.grant(requestedResources); } } });
                      
                      webView.setWebChromeClient(new WebChromeClient() {
    @Override
    public void onPermissionRequest(PermissionRequest request) {
        String[] requestedResources = request.getResources();
        boolean hasAudioPermission = false;
        
        for (String resource : requestedResources) {
            if (PermissionRequest.RESOURCE_AUDIO_CAPTURE.equals(resource)) {
                hasAudioPermission = true;
                break;
            }
        }
        
        if (hasAudioPermission) {
            // ตรวจสอบว่าแอปมีสิทธิ์บันทึกเสียงหรือไม่
            if (checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
                request.grant(requestedResources);
            } else {
                // ขอสิทธิ์ผ่านระบบ
                requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, REQUEST_CODE);
                // เก็บ request ไว้รอจัดการหลังได้รับสิทธิ์
                pendingPermissionRequest = request;
            }
        } else {
            // อนุญาตสิทธิ์อื่น ๆ ทันที
            request.grant(requestedResources);
        }
    }
});

                    
บล็อกโค้ดนี้ในหน้าต่างลอย

การจัดการเลือกไฟล์

รองรับการเลือกไฟล์จากหน้า H5:

webView.setWebChromeClient(new WebChromeClient() { @Override public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) { // เก็บ callback this.filePathCallback = filePathCallback; // สร้าง Intent สำหรับเลือกไฟล์ Intent intent = fileChooserParams.createIntent(); startActivityForResult(intent, REQUEST_FILE_CHOOSER); return true; } }); @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_FILE_CHOOSER) { if (filePathCallback != null) { Uri[] results = null; if (resultCode == RESULT_OK && data != null) { // ประมวลผลผลลัพธ์ที่เลือก // ... } filePathCallback.onReceiveValue(results); filePathCallback = null; } } }
                      
                      webView.setWebChromeClient(new WebChromeClient() {
    @Override
    public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
        // เก็บ callback
        this.filePathCallback = filePathCallback;
        
        // สร้าง Intent สำหรับเลือกไฟล์
        Intent intent = fileChooserParams.createIntent();
        startActivityForResult(intent, REQUEST_FILE_CHOOSER);
        return true;
    }
});

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    
    if (requestCode == REQUEST_FILE_CHOOSER) {
        if (filePathCallback != null) {
            Uri[] results = null;
            
            if (resultCode == RESULT_OK && data != null) {
                // ประมวลผลผลลัพธ์ที่เลือก
                // ...
            }
            
            filePathCallback.onReceiveValue(results);
            filePathCallback = null;
        }
    }
}

                    
บล็อกโค้ดนี้ในหน้าต่างลอย

ตัวอย่างฟังก์ชัน

การปิด WebView

หน้า H5 สามารถร้องขอให้ปิด WebView ได้ดังนี้:

var message = { eventType: "click", data: { value: "close" } }; agentWebBridge.callNative(JSON.stringify(message)); ฝั่ง native จัดการดังนี้: public void onClick(JSONObject data) { String value = data.optString("value"); if (TextUtils.equals(value, "close")) { closeWeb(data); } } public void closeWeb(JSONObject data) { // แจ้ง H5 ว่ากำลังจะปิด JSONObject willCloseData = new JSONObject(); willCloseData.put("value", data.optString("value")); willCloseData.put("reason", "user_request"); willCloseData.put("delay", 1000); willCloseData.put("timestamp", System.currentTimeMillis()); webViewBridge.callH5(WebViewBridge.EVENT_CLICK, willCloseData); // ดีเลย์ก่อนปิด Activity webView.postDelayed(() -> { finish(); }, 1000); }
                      
                      var message = {
    eventType: "click",
    data: {
        value: "close"
    }
};
agentWebBridge.callNative(JSON.stringify(message));

ฝั่ง native จัดการดังนี้:

public void onClick(JSONObject data) {
    String value = data.optString("value");
    if (TextUtils.equals(value, "close")) {
        closeWeb(data);
    }
}
public void closeWeb(JSONObject data) {
    // แจ้ง H5 ว่ากำลังจะปิด
    JSONObject willCloseData = new JSONObject();
    willCloseData.put("value", data.optString("value"));
    willCloseData.put("reason", "user_request");
    willCloseData.put("delay", 1000);
    willCloseData.put("timestamp", System.currentTimeMillis());
    webViewBridge.callH5(WebViewBridge.EVENT_CLICK, willCloseData);
    // ดีเลย์ก่อนปิด Activity
    webView.postDelayed(() -> {
        finish();
    }, 1000);
}

                    
บล็อกโค้ดนี้ในหน้าต่างลอย