diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index b06ddbf..b0373cf 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -38,4 +38,5 @@
+
diff --git a/android/app/src/main/java/com/codebridgex/webapp/AppUpdateChecker.java b/android/app/src/main/java/com/codebridgex/webapp/AppUpdateChecker.java
new file mode 100644
index 0000000..deaaba3
--- /dev/null
+++ b/android/app/src/main/java/com/codebridgex/webapp/AppUpdateChecker.java
@@ -0,0 +1,226 @@
+package com.codebridgex.webapp;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.DownloadManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.util.Log;
+
+import androidx.core.content.FileProvider;
+
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class AppUpdateChecker {
+
+ private static final String TAG = "AppUpdateChecker";
+ private static final String API_BASE_URL = "https://api.codebridge-x.com";
+ private static final String API_KEY = "sam-api-key-2025";
+
+ private final Activity activity;
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ public AppUpdateChecker(Activity activity) {
+ this.activity = activity;
+ }
+
+ /**
+ * 서버에서 최신 버전 확인
+ */
+ public void checkForUpdate() {
+ executor.execute(() -> {
+ try {
+ int currentVersionCode = getCurrentVersionCode();
+ String urlStr = API_BASE_URL + "/api/v1/app/version?platform=android¤t_version_code=" + currentVersionCode;
+
+ URL url = new URL(urlStr);
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestMethod("GET");
+ conn.setRequestProperty("X-API-KEY", API_KEY);
+ conn.setRequestProperty("Accept", "application/json");
+ conn.setConnectTimeout(10000);
+ conn.setReadTimeout(10000);
+
+ int responseCode = conn.getResponseCode();
+ if (responseCode != 200) {
+ Log.w(TAG, "Version check failed: HTTP " + responseCode);
+ return;
+ }
+
+ BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ sb.append(line);
+ }
+ reader.close();
+ conn.disconnect();
+
+ JSONObject json = new JSONObject(sb.toString());
+ if (!json.optBoolean("success", false)) return;
+
+ JSONObject data = json.getJSONObject("data");
+ if (!data.optBoolean("has_update", false)) {
+ Log.d(TAG, "No update available");
+ return;
+ }
+
+ JSONObject latestVersion = data.getJSONObject("latest_version");
+ String versionName = latestVersion.getString("version_name");
+ String releaseNotes = latestVersion.optString("release_notes", "");
+ boolean forceUpdate = latestVersion.optBoolean("force_update", false);
+ String downloadUrl = latestVersion.getString("download_url");
+
+ activity.runOnUiThread(() -> showUpdateDialog(versionName, releaseNotes, forceUpdate, downloadUrl));
+
+ } catch (Exception e) {
+ Log.e(TAG, "Update check error", e);
+ }
+ });
+ }
+
+ /**
+ * 업데이트 다이얼로그 표시
+ */
+ private void showUpdateDialog(String versionName, String releaseNotes, boolean forceUpdate, String downloadUrl) {
+ if (activity.isFinishing() || activity.isDestroyed()) return;
+
+ StringBuilder message = new StringBuilder();
+ message.append("새 버전 v").append(versionName).append("이 있습니다.\n");
+ if (releaseNotes != null && !releaseNotes.isEmpty()) {
+ message.append("\n").append(releaseNotes);
+ }
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity)
+ .setTitle("업데이트 알림")
+ .setMessage(message.toString())
+ .setCancelable(!forceUpdate)
+ .setPositiveButton("업데이트", (dialog, which) -> {
+ dialog.dismiss();
+ startDownload(downloadUrl, versionName);
+ });
+
+ if (forceUpdate) {
+ // 강제 업데이트: "나중에" 버튼 없음, 뒤로가기/외부 터치로 닫을 수 없음
+ builder.setOnCancelListener(dialog -> activity.finishAffinity());
+ } else {
+ builder.setNegativeButton("나중에", (dialog, which) -> dialog.dismiss());
+ }
+
+ builder.show();
+ }
+
+ /**
+ * DownloadManager로 APK 다운로드
+ */
+ private void startDownload(String downloadUrl, String versionName) {
+ try {
+ DownloadManager.Request request = new DownloadManager.Request(Uri.parse(downloadUrl));
+ request.addRequestHeader("X-API-KEY", API_KEY);
+ request.setTitle("SAM 업데이트 v" + versionName);
+ request.setDescription("앱 업데이트를 다운로드하고 있습니다...");
+ request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+
+ String fileName = "sam-v" + versionName + ".apk";
+ request.setDestinationInExternalFilesDir(activity, Environment.DIRECTORY_DOWNLOADS, fileName);
+
+ DownloadManager dm = (DownloadManager) activity.getSystemService(Context.DOWNLOAD_SERVICE);
+ long downloadId = dm.enqueue(request);
+
+ // 다운로드 완료 감지
+ registerDownloadReceiver(downloadId, fileName);
+
+ } catch (Exception e) {
+ Log.e(TAG, "Download start error", e);
+ }
+ }
+
+ /**
+ * 다운로드 완료 시 설치 화면 표시
+ */
+ private void registerDownloadReceiver(long downloadId, String fileName) {
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
+ if (id != downloadId) return;
+
+ try {
+ activity.unregisterReceiver(this);
+ } catch (Exception ignored) {}
+
+ installApk(fileName);
+ }
+ };
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ activity.registerReceiver(receiver,
+ new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
+ Context.RECEIVER_NOT_EXPORTED);
+ } else {
+ activity.registerReceiver(receiver,
+ new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
+ }
+ }
+
+ /**
+ * APK 설치 Intent 실행
+ */
+ private void installApk(String fileName) {
+ try {
+ File apkFile = new File(activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), fileName);
+ if (!apkFile.exists()) {
+ Log.e(TAG, "APK file not found: " + apkFile.getAbsolutePath());
+ return;
+ }
+
+ Uri apkUri = FileProvider.getUriForFile(
+ activity,
+ activity.getPackageName() + ".fileprovider",
+ apkFile
+ );
+
+ Intent installIntent = new Intent(Intent.ACTION_VIEW);
+ installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive");
+ installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ activity.startActivity(installIntent);
+
+ } catch (Exception e) {
+ Log.e(TAG, "Install APK error", e);
+ }
+ }
+
+ /**
+ * 현재 앱 버전 코드
+ */
+ private int getCurrentVersionCode() {
+ try {
+ PackageInfo pInfo = activity.getPackageManager()
+ .getPackageInfo(activity.getPackageName(), 0);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ return (int) pInfo.getLongVersionCode();
+ } else {
+ return pInfo.versionCode;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Get version code error", e);
+ return 0;
+ }
+ }
+}
diff --git a/android/app/src/main/java/com/codebridgex/webapp/MainActivity.java b/android/app/src/main/java/com/codebridgex/webapp/MainActivity.java
index 61fbd14..5ce3fab 100644
--- a/android/app/src/main/java/com/codebridgex/webapp/MainActivity.java
+++ b/android/app/src/main/java/com/codebridgex/webapp/MainActivity.java
@@ -26,6 +26,9 @@ public class MainActivity extends BridgeActivity {
super.onCreate(savedInstanceState);
createNotificationChannels();
+ // 인앱 업데이트 체크
+ new AppUpdateChecker(this).checkForUpdate();
+
// WebView 줌 설정 (핀치 줌 활성화)
getBridge().getWebView().getSettings().setSupportZoom(true);
getBridge().getWebView().getSettings().setBuiltInZoomControls(true);
diff --git a/android/app/src/main/res/xml/file_paths.xml b/android/app/src/main/res/xml/file_paths.xml
index bd0c4d8..b27adb7 100644
--- a/android/app/src/main/res/xml/file_paths.xml
+++ b/android/app/src/main/res/xml/file_paths.xml
@@ -2,4 +2,5 @@
+
\ No newline at end of file