diff --git a/android/app/src/main/java/com/codebridgex/webapp/AppUpdateChecker.java b/android/app/src/main/java/com/codebridgex/webapp/AppUpdateChecker.java index 60726fb..3162956 100644 --- a/android/app/src/main/java/com/codebridgex/webapp/AppUpdateChecker.java +++ b/android/app/src/main/java/com/codebridgex/webapp/AppUpdateChecker.java @@ -8,9 +8,11 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Environment; +import android.provider.Settings; import android.util.Log; import androidx.core.content.FileProvider; @@ -33,6 +35,7 @@ public class AppUpdateChecker { private final Activity activity; private final ExecutorService executor = Executors.newSingleThreadExecutor(); + private String pendingInstallFileName; public AppUpdateChecker(Activity activity) { this.activity = activity; @@ -178,9 +181,81 @@ public class AppUpdateChecker { } /** - * APK 설치 Intent 실행 + * APK 설치 Intent 실행 (권한 체크 포함) */ private void installApk(String fileName) { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (!activity.getPackageManager().canRequestPackageInstalls()) { + // 권한 없음 → 설정 화면 안내 + pendingInstallFileName = fileName; + activity.runOnUiThread(() -> showInstallPermissionDialog()); + return; + } + } + + launchInstallIntent(fileName); + + } catch (Exception e) { + Log.e(TAG, "Install APK error", e); + } + } + + /** + * 출처를 알 수 없는 앱 설치 권한 안내 다이얼로그 + */ + private void showInstallPermissionDialog() { + if (activity.isFinishing() || activity.isDestroyed()) return; + + new AlertDialog.Builder(activity) + .setTitle("설치 권한 필요") + .setMessage("앱 업데이트를 설치하려면 '출처를 알 수 없는 앱 설치' 권한을 허용해주세요.\n\n설정 화면으로 이동합니다.") + .setCancelable(false) + .setPositiveButton("설정으로 이동", (dialog, which) -> { + dialog.dismiss(); + openInstallPermissionSettings(); + }) + .setNegativeButton("취소", (dialog, which) -> { + dialog.dismiss(); + pendingInstallFileName = null; + }) + .show(); + } + + /** + * 출처를 알 수 없는 앱 설치 설정 화면 열기 + */ + private void openInstallPermissionSettings() { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES); + intent.setData(Uri.parse("package:" + activity.getPackageName())); + activity.startActivityForResult(intent, 1001); + } + } catch (Exception e) { + Log.e(TAG, "Open install permission settings error", e); + } + } + + /** + * 설정 화면에서 돌아온 후 설치 재시도 + */ + public void onActivityResult(int requestCode, int resultCode) { + if (requestCode == 1001 && pendingInstallFileName != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + && activity.getPackageManager().canRequestPackageInstalls()) { + launchInstallIntent(pendingInstallFileName); + } else { + Log.w(TAG, "Install permission still not granted"); + } + pendingInstallFileName = null; + } + } + + /** + * 실제 APK 설치 Intent 실행 + */ + private void launchInstallIntent(String fileName) { try { File apkFile = new File(activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), fileName); if (!apkFile.exists()) { @@ -202,7 +277,7 @@ public class AppUpdateChecker { activity.startActivity(installIntent); } catch (Exception e) { - Log.e(TAG, "Install APK error", e); + Log.e(TAG, "Launch install intent error", e); } } 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 5ce3fab..71b0dfb 100644 --- a/android/app/src/main/java/com/codebridgex/webapp/MainActivity.java +++ b/android/app/src/main/java/com/codebridgex/webapp/MainActivity.java @@ -6,12 +6,15 @@ import android.content.Context; import android.media.AudioAttributes; import android.net.Uri; import android.os.Build; +import android.content.Intent; import android.os.Bundle; import com.getcapacitor.BridgeActivity; public class MainActivity extends BridgeActivity { + private AppUpdateChecker updateChecker; + // 채널 ID (FCM payload의 android.notification.channel_id 로 사용할 값) public static final String CHANNEL_DEFAULT = "push_default"; public static final String CHANNEL_VENDOR_REGISTER = "push_vendor_register"; @@ -27,7 +30,8 @@ public class MainActivity extends BridgeActivity { createNotificationChannels(); // 인앱 업데이트 체크 - new AppUpdateChecker(this).checkForUpdate(); + updateChecker = new AppUpdateChecker(this); + updateChecker.checkForUpdate(); // WebView 줌 설정 (핀치 줌 활성화) getBridge().getWebView().getSettings().setSupportZoom(true); @@ -137,4 +141,12 @@ public class MainActivity extends BridgeActivity { nm.createNotificationChannel(chPurchaseOrder); nm.createNotificationChannel(chContract); } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (updateChecker != null) { + updateChecker.onActivityResult(requestCode, resultCode); + } + } } \ No newline at end of file