package com.okithaki.autoapp;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.DownloadManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.ClipData;
import android.content.Intent;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.DhcpInfo;
import android.net.NetworkInfo;
import android.net.Uri;
import android.net.Network;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioFocusRequest;
import android.media.MediaRecorder;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.provider.MediaStore;
import android.database.Cursor;
import android.provider.OpenableColumns;
import android.provider.DocumentsContract;
import android.util.Base64;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Environment;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.webkit.JavascriptInterface;
import android.webkit.CookieManager;
import android.webkit.PermissionRequest;
import android.webkit.RenderProcessGoneDetail;
import android.webkit.GeolocationPermissions;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.net.http.SslError;
import android.webkit.SslErrorHandler;
import android.provider.Settings;
import android.webkit.WebViewClient;
import android.webkit.WebResourceResponse;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceError;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import android.widget.TextView;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.URLEncoder;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.json.JSONObject;
import org.json.JSONArray;

import androidx.core.content.FileProvider;
import androidx.webkit.WebViewAssetLoader;
import androidx.core.app.NotificationCompat;
import com.google.firebase.messaging.FirebaseMessaging;

public class MainActivity extends Activity {
    private static final String PREFS = "ok_ithaki_prefs";
    private static final String PREF_SERVER = "server_base";
    private static final String PREF_CONNECTION_VERSION = "connection_version";
    private static final String PUBLIC_APP_TOKEN_FALLBACK = "OKITHAKI_PUBLIC_BETA_TOKEN_SERVER_ONLY_2026";
    private static final String DEFAULT_PUBLIC_SERVER = "https://ok-ithaki.gr";
    private static final int APK_BUILD_V249442 = 249531;
    private static final int FILE_CHOOSER_REQ = 4301;
    private static final int RECOVERY_TREE_REQ = 4302;
    private static final int RECOVERY_IMPORT_REQ = 4303;

    private WebView webView;
    private TextView statusText;
    private View connectionOverlay;
    private volatile boolean mainFrameLoadFailedV249442 = false;
    private ExecutorService executor;
    private String serverBase = "";
    private String publicAppToken = PUBLIC_APP_TOKEN_FALLBACK;
    private ValueCallback<Uri[]> filePathCallback;
    private volatile boolean fileChooserOpenV249400 = false;
    private volatile long fileChooserOpenedAtV249400 = 0L;
    private Uri cameraCaptureUri;
    private String pendingChooserMode = "";
    private PermissionRequest pendingWebPermissionRequest;
    private MediaRecorder nativeAudioRecorder;
    private File nativeAudioFile;
    private long nativeAudioStartedAt = 0L;
    private Ringtone activeCallRingtone;
    private AudioFocusRequest callAudioFocusRequest;
    private volatile boolean localNetworkSearchRunning = false;
    private Handler reconnectHandler;
    private ConnectivityManager.NetworkCallback networkCallback;
    private long lastNetworkReconnectMs = 0L;
    private volatile boolean torSafeModeRequestedV249435 = false;
    private String pendingLaunchQuery = "";
    private String lastLaunchQueryV249463 = "";
    private boolean localFrontendFallbackTriedV249463 = false;
    private final Handler localFrontendWatchdogV249463 = new Handler(Looper.getMainLooper());
    // V249465: never request camera/microphone/media during boot. Permission dialogs are
    // strictly user-action driven, and a WebView renderer recovery waits until the dialog closes.
    private volatile boolean runtimePermissionDialogActiveV249465 = false;
    private volatile boolean pendingRendererRecoveryV249465 = false;
    private volatile boolean notificationPromptScheduledV249465 = false;
    private volatile boolean externalCenterLaunchPendingV249477 = false;
    private volatile boolean nativeRemoteShareBusyV249522 = false;
    private final Handler foregroundHeartbeatHandlerV249531 = new Handler(Looper.getMainLooper());
    private final Runnable foregroundHeartbeatV249531 = new Runnable() {
        @Override public void run() {
            try { getSharedPreferences(PREFS, MODE_PRIVATE).edit().putBoolean("ok_app_foreground_v249531", true).putLong("ok_app_foreground_at_v249531", System.currentTimeMillis()).apply(); } catch (Exception ignored) { }
            try { foregroundHeartbeatHandlerV249531.postDelayed(this, 10000L); } catch (Exception ignored) { }
        }
    };

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        executor = Executors.newFixedThreadPool(48);
        // V249468: Tor/proxy must never run during application startup.
        // Clear only saved Tor state. Do not call ProxyController before WebView is ready.
        try {
            getSharedPreferences(PREFS, MODE_PRIVATE).edit()
                    .putBoolean("tor_safe_requested_v249435", false)
                    .putBoolean("tor_proxy_applied_v249435", false)
                    .putBoolean("tor_verified_v249435", false)
                    .putString("tor_last_error_v249435", "")
                    .apply();
        } catch (Exception ignored) { }
        torSafeModeRequestedV249435 = false;
        clearBadSavedServerBase();
        enforceCanonicalPublicServerV249470();
        applyV249471ClosedMessageDefaults();
        buildUi();
        setupWebView();
        disableAutomaticNativeMeshV249439();

        JSONObject torBoot = new JSONObject();
        torSafeModeRequestedV249435 = false;
        if (!torSafeModeRequestedV249435) {
            boolean strictRelayV249436 = IthakiPrivateRelayV249436.isStrict(this);
            try { FirebaseMessaging.getInstance().setAutoInitEnabled(!strictRelayV249436); } catch (Exception ignored) { }
            // V249439: native Mesh never starts during application boot.
            if (!strictRelayV249436) { startStayConnectedAtBootV249383(); refreshFcmAtBootV249383(); }
        }
        String assetToken = assetConfigPublicToken();
        if (assetToken != null && !assetToken.trim().isEmpty()) publicAppToken = assetToken.trim();
        serverBase = initialStartupBase();
        pendingLaunchQuery = launchQueryFromIntent(getIntent());
        applyIncomingCallWindowV249463(getIntent());
        processExternalMessageIntentV249477(getIntent());

        if (torSafeModeRequestedV249435 && !torBoot.optBoolean("native_proxy_active", false)) {
            status("TOR Safe Mode: κλειστή σύνδεση. Άνοιξε το Orbot και περίμενε να συνδεθεί — δεν γίνεται απευθείας σύνδεση.");
            webView.loadDataWithBaseURL(null,
                    "<html><meta name='viewport' content='width=device-width'><body style='background:#07131f;color:#fff;font-family:sans-serif;padding:28px'><h2>🧅 TOR Safe Mode</h2><p>Η απευθείας σύνδεση έχει αποκλειστεί.</p><p>Άνοιξε το Orbot, περίμενε να ολοκληρώσει τη σύνδεση και πάτησε ξανά Ενεργοποίηση TOR από το Κέντρο Ασφάλειας.</p></body></html>",
                    "text/html", "UTF-8", null);
        } else {
            status(torSafeModeRequestedV249435 ? "TOR Safe Mode ενεργό — WebView μέσω Orbot, FCM/κλήσεις/Mesh κλειστά." : "OK ITHAKI V249477 — Επαγγελματικό Κέντρο, Stories και τοπικά εξωτερικά μηνύματα έτοιμα.");
            loadApp(serverBase);
        }
        if (!torSafeModeRequestedV249435) {
            // V249470: observe network changes, but never scan LAN IPs or old tunnels.
            registerNetworkReconnectWatcher();
        }
    }


// V249471: restore reliable closed-device message delivery. This changes only
// local notification preferences; database, messages, calls and server data stay intact.
private void applyV249471ClosedMessageDefaults() {
    try {
        SharedPreferences sp = getSharedPreferences(PREFS, MODE_PRIVATE);
        if (sp.getBoolean("v249471_closed_message_defaults_done", false)) return;
        sp.edit()
                .putString("ithaki_push_mode_v249258", "fcm_default")
                .putBoolean("ok_fcm_enabled", true)
                .putBoolean("v249471_closed_message_defaults_done", true)
                .remove("notification_prompt_attempted_v249471")
                .apply();
        try { FirebaseMessaging.getInstance().setAutoInitEnabled(true); } catch (Exception ignored) { }
    } catch (Exception ignored) { }
}

    private void enforceCanonicalPublicServerV249470() {
        try {
            getSharedPreferences(PREFS, MODE_PRIVATE).edit()
                    .putString(PREF_SERVER, DEFAULT_PUBLIC_SERVER)
                    .putString(PREF_CONNECTION_VERSION, "249470")
                    .remove("lan_api_url")
                    .remove("local_quick_ip_url")
                    .remove("last_detected_lan_url")
                    .remove("public_tunnel_url")
                    .remove("ngrok_public_url")
                    .remove("tunnel_url")
                    .apply();
        } catch (Exception ignored) { }
    }

    private void disableAutomaticNativeMeshV249439() {
        try {
            getSharedPreferences(PREFS, MODE_PRIVATE).edit()
                    .putBoolean("mesh_enabled_v249435", false)
                    .remove("mesh_shared_code_v249435")
                    .remove("mesh_security_mode_v249438")
                    .apply();
            Intent stop = new Intent(this, EmergencyNeighborhoodService.class);
            stop.setAction(EmergencyNeighborhoodService.ACTION_STOP);
            try { stopService(stop); } catch (Exception ignored) { }
        } catch (Exception ignored) { }
    }

    @Override protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        try {
            setIntent(intent);
            pendingLaunchQuery = launchQueryFromIntent(intent);
            applyIncomingCallWindowV249463(intent);
            processExternalMessageIntentV249477(intent);
            if (webView != null) {
                // V249259: notification tap reloads through the normal loader so server/token
                // parameters stay intact and the WebView opens the call screen, not a blank domain.
                loadApp(serverBase == null || serverBase.trim().isEmpty() ? DEFAULT_PUBLIC_SERVER : serverBase);
            }
        } catch (Exception ignored) { }
    }

    private String enc(String s) {
        try { return URLEncoder.encode(s == null ? "" : s, "UTF-8"); } catch (Exception e) { return ""; }
    }

    private String launchQueryFromIntent(Intent intent) {
        if (intent == null) return "";
        try {
            String kind = intent.getStringExtra("kind");
            if (kind == null || kind.trim().isEmpty()) kind = intent.getStringExtra("fcm_kind");
            String url = intent.getStringExtra("url");
            String peer = intent.getStringExtra("peer_id");
            if (peer == null || peer.trim().isEmpty()) peer = intent.getStringExtra("sender_id");
            if (peer == null || peer.trim().isEmpty()) peer = intent.getStringExtra("from_id");
            String session = intent.getStringExtra("session_id");
            String callType = intent.getStringExtra("call_type");
            String channel = intent.getStringExtra("channel");
            String messageId = intent.getStringExtra("message_id");
            Uri data = intent.getData();
            if ((url == null || url.trim().isEmpty()) && data != null) url = data.toString();
            StringBuilder q = new StringBuilder();
            q.append("from_push=1");
            String callAction = intent.getStringExtra("call_action");
            if (kind != null && !kind.trim().isEmpty()) q.append("&kind=").append(enc(kind));
            if (peer != null && !peer.trim().isEmpty()) q.append("&dm=").append(enc(peer)).append("&direct_open_dm=1&open_messages=1");
            if ("message".equalsIgnoreCase(kind) || "1".equals(intent.getStringExtra("open_messages"))) q.append("&open_messages=1");
            if (session != null && !session.trim().isEmpty()) q.append("&call=1&session_id=").append(enc(session));
            if (callType != null && !callType.trim().isEmpty()) q.append("&call_type=").append(enc(callType));
            if (callAction != null && !callAction.trim().isEmpty()) q.append("&call_action=").append(enc(callAction));
            if (channel != null && !channel.trim().isEmpty()) q.append("&channel=").append(enc(channel));
            if (messageId != null && !messageId.trim().isEmpty()) q.append("&message_id=").append(enc(messageId));
            if (url != null && !url.trim().isEmpty()) {
                try {
                    Uri u = Uri.parse(url);
                    String dm = u.getQueryParameter("dm");
                    String sid = u.getQueryParameter("session_id");
                    if (dm != null && !dm.trim().isEmpty() && (peer == null || peer.trim().isEmpty())) q.append("&dm=").append(enc(dm));
                    if (sid != null && !sid.trim().isEmpty() && (session == null || session.trim().isEmpty())) q.append("&call=1&session_id=").append(enc(sid));
                } catch (Exception ignored) { }
            }
            return q.toString();
        } catch (Exception e) { return ""; }
    }


    private void startLocalNetworkSearchAsync() {
        // V249470 compatibility entry point: production-domain check only.
        if (executor == null || localNetworkSearchRunning) return;
        localNetworkSearchRunning = true;
        executor.execute(() -> {
            try {
                final boolean ok = isOkServer(DEFAULT_PUBLIC_SERVER, 3500);
                runOnUiThread(() -> {
                    serverBase = DEFAULT_PUBLIC_SERVER;
                    saveServerBase(DEFAULT_PUBLIC_SERVER);
                    status(ok ? "Σύνδεση παραγωγής OK: " + DEFAULT_PUBLIC_SERVER
                              : "Η εφαρμογή άνοιξε. Αναμονή σύνδεσης με " + DEFAULT_PUBLIC_SERVER);
                });
            } finally {
                localNetworkSearchRunning = false;
            }
        });
    }


    private void registerNetworkReconnectWatcher() {
        if (TorIntegrationV249435.isRequested(this)) return;
        try {
            reconnectHandler = new Handler(Looper.getMainLooper());
            ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
            if (cm == null || Build.VERSION.SDK_INT < 24) return;
            networkCallback = new ConnectivityManager.NetworkCallback() {
                @Override public void onAvailable(Network network) { scheduleNativeReconnect("network available"); }
                @Override public void onLost(Network network) { scheduleNativeReconnect("network lost"); }
            };
            cm.registerDefaultNetworkCallback(networkCallback);
        } catch (Exception ignored) { }
    }

    private void scheduleNativeReconnect(String reason) {
        try {
            if (fileChooserOpenV249400) {
                if (reconnectHandler == null) reconnectHandler = new Handler(Looper.getMainLooper());
                reconnectHandler.postDelayed(() -> scheduleNativeReconnect("deferred after file chooser"), 1800L);
                return;
            }
            long now = System.currentTimeMillis();
            if (now - lastNetworkReconnectMs < 2500L) return;
            lastNetworkReconnectMs = now;
            if (reconnectHandler == null) reconnectHandler = new Handler(Looper.getMainLooper());
            reconnectHandler.postDelayed(() -> {
                serverBase = DEFAULT_PUBLIC_SERVER;
                saveServerBase(DEFAULT_PUBLIC_SERVER);
                try { status("Δίκτυο διαθέσιμο — έλεγχος ok-ithaki.gr χωρίς αναζήτηση IP ή tunnel."); } catch (Exception ignored) { }
                startLocalNetworkSearchAsync();
                evalJs("try{if(window.ITH&&ITH.state){ITH.state.api='https://ok-ithaki.gr';}if(window.ITH&&ITH.checkConnections)ITH.checkConnections();}catch(e){}");
            }, 1200L);
        } catch (Exception ignored) { }
    }

    private void unregisterNetworkReconnectWatcher() {
        try {
            if (networkCallback != null) {
                ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
                if (cm != null) cm.unregisterNetworkCallback(networkCallback);
            }
        } catch (Exception ignored) { }
        networkCallback = null;
    }


    // V249531: explicit foreground state shared with the data-only FCM owner.
    // Open-app WebSocket events use in-app sound/UI; closed-app FCM owns the single system notification.
    @Override protected void onResume() {
        super.onResume();
        try { getSharedPreferences(PREFS, MODE_PRIVATE).edit()
                .putBoolean("ok_app_foreground_v249531", true)
                .putLong("ok_app_foreground_at_v249531", System.currentTimeMillis()).apply();
            foregroundHeartbeatHandlerV249531.removeCallbacks(foregroundHeartbeatV249531);
            foregroundHeartbeatHandlerV249531.post(foregroundHeartbeatV249531); } catch (Exception ignored) { }
    }

    @Override protected void onPause() {
        try { foregroundHeartbeatHandlerV249531.removeCallbacks(foregroundHeartbeatV249531); } catch (Exception ignored) { }
        try { getSharedPreferences(PREFS, MODE_PRIVATE).edit()
                .putBoolean("ok_app_foreground_v249531", false)
                .putLong("ok_app_background_at_v249531", System.currentTimeMillis()).apply(); } catch (Exception ignored) { }
        super.onPause();
    }

    @Override protected void onDestroy() {
        unregisterNetworkReconnectWatcher();
        if (executor != null) executor.shutdownNow();
        super.onDestroy();
    }

    private void requestRuntimePermissions(boolean needVideo) {
        if (Build.VERSION.SDK_INT < 23) return;
        List<String> p = new ArrayList<>();
        // Calls always need microphone. Video calls also need camera. No location permission.
        if (needVideo && checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) p.add(Manifest.permission.CAMERA);
        if (checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) p.add(Manifest.permission.RECORD_AUDIO);
        // Notifications are requested at first app start, not during call/camera.
        requestPermissionsSafelyV249465(p, 2026);
    }



    private void requestPermissionList(boolean needCamera, boolean needMicrophone, boolean needMedia, boolean needNotifications) {
        if (Build.VERSION.SDK_INT < 23) return;
        List<String> p = new ArrayList<>();
        if (needCamera && checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) p.add(Manifest.permission.CAMERA);
        if (needMicrophone && checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) p.add(Manifest.permission.RECORD_AUDIO);
        if (needMedia) {
            if (Build.VERSION.SDK_INT >= 33) {
                try { if (checkSelfPermission(Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) p.add(Manifest.permission.READ_MEDIA_IMAGES); } catch (Exception ignored) { }
                try { if (checkSelfPermission(Manifest.permission.READ_MEDIA_VIDEO) != PackageManager.PERMISSION_GRANTED) p.add(Manifest.permission.READ_MEDIA_VIDEO); } catch (Exception ignored) { }
                try { if (checkSelfPermission(Manifest.permission.READ_MEDIA_AUDIO) != PackageManager.PERMISSION_GRANTED) p.add(Manifest.permission.READ_MEDIA_AUDIO); } catch (Exception ignored) { }
            } else {
                try { if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) p.add(Manifest.permission.READ_EXTERNAL_STORAGE); } catch (Exception ignored) { }
            }
        }
        if (needNotifications && Build.VERSION.SDK_INT >= 33) {
            try { if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) p.add(Manifest.permission.POST_NOTIFICATIONS); } catch (Exception ignored) { }
        }
        requestPermissionsSafelyV249465(p, 2027);
    }



    private void requestPermissionsSafelyV249465(List<String> permissions, int requestCode) {
        if (permissions == null || permissions.isEmpty()) {
            notifyPermissionStateChanged();
            return;
        }
        if (Build.VERSION.SDK_INT < 23) {
            notifyPermissionStateChanged();
            return;
        }
        // Android permits one runtime-permission request at a time. Ignore duplicate boot/JS calls.
        if (runtimePermissionDialogActiveV249465) return;
        runtimePermissionDialogActiveV249465 = true;
        try {
            requestPermissions(permissions.toArray(new String[0]), requestCode);
        } catch (Throwable t) {
            runtimePermissionDialogActiveV249465 = false;
            notifyPermissionStateChanged();
        }
    }

    private void requestInitialIthakiPermissions() {
        // V249465: notification permission only, after the first complete page is visible.
        // Camera and microphone are requested only when the user starts a call/recording/capture.
        if (Build.VERSION.SDK_INT < 33) return;
        try {
            SharedPreferences sp = getSharedPreferences(PREFS, MODE_PRIVATE);
            if (sp.getBoolean("notification_prompt_attempted_v249471", false)) return;
            if (checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
                sp.edit().putBoolean("notification_prompt_attempted_v249471", true).apply();
                return;
            }
            sp.edit().putBoolean("notification_prompt_attempted_v249471", true).apply();
            List<String> p = new ArrayList<>();
            p.add(Manifest.permission.POST_NOTIFICATIONS);
            requestPermissionsSafelyV249465(p, 249471);
        } catch (Throwable ignored) { }
    }

    private void scheduleInitialNotificationPermissionV249465() {
        // Never cover an incoming-call screen with the notification permission dialog.
        if (lastLaunchQueryV249463 != null && lastLaunchQueryV249463.contains("call=1")) return;
        if (notificationPromptScheduledV249465) return;
        notificationPromptScheduledV249465 = true;
        localFrontendWatchdogV249463.postDelayed(() -> {
            try {
                if (!isFinishing() && !isDestroyed()) requestInitialIthakiPermissions();
            } catch (Throwable ignored) { }
        }, 1800L);
    }

    private boolean hasPermissionName(String permission) {
        if (Build.VERSION.SDK_INT < 23) return true;
        try { return checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED; } catch (Exception e) { return false; }
    }

    private String permissionStateJson() {
        try {
            JSONObject o = new JSONObject();
            o.put("camera", hasPermissionName(Manifest.permission.CAMERA));
            o.put("microphone", hasPermissionName(Manifest.permission.RECORD_AUDIO));
            if (Build.VERSION.SDK_INT >= 33) {
                o.put("photos", hasPermissionName(Manifest.permission.READ_MEDIA_IMAGES));
                o.put("videos", hasPermissionName(Manifest.permission.READ_MEDIA_VIDEO));
                o.put("audioFiles", hasPermissionName(Manifest.permission.READ_MEDIA_AUDIO));
                o.put("notifications", hasPermissionName(Manifest.permission.POST_NOTIFICATIONS));
            } else {
                o.put("photos", hasPermissionName(Manifest.permission.READ_EXTERNAL_STORAGE));
                o.put("videos", hasPermissionName(Manifest.permission.READ_EXTERNAL_STORAGE));
                o.put("audioFiles", hasPermissionName(Manifest.permission.READ_EXTERNAL_STORAGE));
                o.put("notifications", true);
            }
            return o.toString();
        } catch (Exception e) { return "{}"; }
    }

    private void notifyPermissionStateChanged() {
        evalJs("window.onOkIthakiPermissionChanged&&window.onOkIthakiPermissionChanged(" + JSONObject.quote(permissionStateJson()) + ")");
    }

    private void openAppSettingsScreen() {
        try {
            Intent i = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
            i.setData(Uri.parse("package:" + getPackageName()));
            i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(i);
        } catch (Exception ignored) { }
    }

    private boolean hasRuntimePermissions(boolean needAudio) {
        return hasRuntimePermissions(true, needAudio);
    }

    private boolean hasRuntimePermissions(boolean needCamera, boolean needAudio) {
        if (Build.VERSION.SDK_INT < 23) return true;
        if (needCamera && checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) return false;
        if (needAudio && checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) return false;
        return true;
    }

    private void launchPendingFileChooserMode(String mode) {
        try {
            if ("image".equals(mode)) {
                Intent camera = makeImageCaptureIntent();
                if (camera != null) {
                    startActivityForResult(camera, FILE_CHOOSER_REQ);
                    return;
                }
            }
            if ("video".equals(mode)) {
                Intent video = makeVideoCaptureIntent();
                if (video != null) {
                    startActivityForResult(video, FILE_CHOOSER_REQ);
                    return;
                }
            }
        } catch (Exception ignored) { }
        try {
            if (filePathCallback != null) filePathCallback.onReceiveValue(null);
        } catch (Exception ignored) { }
        filePathCallback = null;
        cameraCaptureUri = null;
    }


    private boolean requestNeedsWebResource(PermissionRequest request, String resource) {
        if (request == null || Build.VERSION.SDK_INT < 21) return false;
        try {
            for (String r : request.getResources()) if (resource.equals(r)) return true;
        } catch (Exception ignored) { }
        return false;
    }

    private void grantOrAskWebMediaPermission(final PermissionRequest request) {
        if (request == null || Build.VERSION.SDK_INT < 21) return;
        try {
            boolean needVideo = requestNeedsWebResource(request, PermissionRequest.RESOURCE_VIDEO_CAPTURE);
            boolean needAudio = requestNeedsWebResource(request, PermissionRequest.RESOURCE_AUDIO_CAPTURE);
            if (!needVideo && !needAudio) { request.deny(); return; }
            boolean haveCamera = !needVideo || hasPermissionName(Manifest.permission.CAMERA);
            boolean haveMic = !needAudio || hasPermissionName(Manifest.permission.RECORD_AUDIO);
            if (!haveCamera || !haveMic) {
                pendingWebPermissionRequest = request;
                requestPermissionList(needVideo, needAudio, false, false);
                return;
            }
            java.util.ArrayList<String> allowed = new java.util.ArrayList<>();
            if (needVideo) allowed.add(PermissionRequest.RESOURCE_VIDEO_CAPTURE);
            if (needAudio) allowed.add(PermissionRequest.RESOURCE_AUDIO_CAPTURE);
            request.grant(allowed.toArray(new String[0]));
            if (pendingWebPermissionRequest == request) pendingWebPermissionRequest = null;
        } catch (Exception e) {
            try { request.deny(); } catch (Exception ignored) { }
            if (pendingWebPermissionRequest == request) pendingWebPermissionRequest = null;
        }
    }

    private void tryGrantPendingWebPermissionRequest() {
        if (pendingWebPermissionRequest == null || Build.VERSION.SDK_INT < 21) return;
        PermissionRequest r = pendingWebPermissionRequest;
        pendingWebPermissionRequest = null;
        grantOrAskWebMediaPermission(r);
    }

    @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        runtimePermissionDialogActiveV249465 = false;
        notifyPermissionStateChanged();
        // V24793: if camera/mic was permanently denied (no dialog will ever show again),
        // tell the web layer so it can guide the user to open app settings.
        try {
            if (Build.VERSION.SDK_INT >= 23) {
                boolean camDenied = checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED
                        && !shouldShowRequestPermissionRationale(Manifest.permission.CAMERA);
                boolean micDenied = checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED
                        && !shouldShowRequestPermissionRationale(Manifest.permission.RECORD_AUDIO);
                boolean asked = false;
                for (String p : permissions) { if (Manifest.permission.CAMERA.equals(p) || Manifest.permission.RECORD_AUDIO.equals(p)) asked = true; }
                if (asked && (camDenied || micDenied)) {
                    evalJs("window.onOkIthakiPermissionBlocked&&window.onOkIthakiPermissionBlocked()");
                }
            }
        } catch (Exception ignored) { }
        tryGrantPendingWebPermissionRequest();
        if ((requestCode == 2026 || requestCode == 2027) && filePathCallback != null && pendingChooserMode != null && !pendingChooserMode.isEmpty()) {
            String mode = pendingChooserMode;
            pendingChooserMode = "";
            boolean needAudio = "video".equals(mode);
            if (hasRuntimePermissions(needAudio)) launchPendingFileChooserMode(mode);
            else {
                try { filePathCallback.onReceiveValue(null); } catch (Exception ignored) { }
                filePathCallback = null;
                cameraCaptureUri = null;
            }
        }
        if (pendingRendererRecoveryV249465) {
            pendingRendererRecoveryV249465 = false;
            new Handler(Looper.getMainLooper()).postDelayed(() -> {
                try { if (!isFinishing() && !isDestroyed()) MainActivity.this.recreate(); } catch (Throwable ignored) { }
            }, 300L);
        }
    }

    private void openEmergencyNeighborhoodNativeV249442() {
        runOnUiThread(() -> {
            try {
                Intent i = new Intent(MainActivity.this, EmergencyNeighborhoodActivity.class);
                startActivity(i);
            } catch (Throwable t) {
                status("Δεν άνοιξε η Γειτονιά: " + (t.getMessage() == null ? t.getClass().getSimpleName() : t.getMessage()));
            }
        });
    }

    private void showOfflineEmergencyOverlayV249442(String message) {
        runOnUiThread(() -> {
            try {
                if (connectionOverlay != null) connectionOverlay.setVisibility(View.VISIBLE);
                if (statusText != null) statusText.setText(
                        (message == null || message.trim().isEmpty() ? "Δεν υπάρχει σύνδεση με τον κεντρικό server." : message)
                        + "\n\nΗ Γειτονιά έκτακτης ανάγκης λειτουργεί τοπικά χωρίς internet.");
            } catch (Exception ignored) { }
        });
    }

    private void buildUi() {
        FrameLayout root = new FrameLayout(this);
        webView = new WebView(this);
        root.addView(webView, new FrameLayout.LayoutParams(-1, -1));

        FrameLayout overlay = new FrameLayout(this);
        connectionOverlay = overlay;
        overlay.setTag("overlay");
        overlay.setBackgroundColor(0xff071019);
        ProgressBar bar = new ProgressBar(this);
        FrameLayout.LayoutParams bp = new FrameLayout.LayoutParams(96, 96);
        bp.gravity = Gravity.CENTER;
        overlay.addView(bar, bp);

        statusText = new TextView(this);
        statusText.setTextColor(0xffeef7ff);
        statusText.setTextSize(12);
        statusText.setGravity(Gravity.CENTER);
        statusText.setPadding(28, 28, 28, 28);
        FrameLayout.LayoutParams tp = new FrameLayout.LayoutParams(-1, -2);
        tp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
        tp.setMargins(0, 0, 0, 120);
        overlay.addView(statusText, tp);

        Button emergencyOffline = new Button(this);
        emergencyOffline.setText("🛜 Γειτονιά χωρίς Internet");
        emergencyOffline.setAllCaps(false);
        emergencyOffline.setTextSize(16f);
        emergencyOffline.setTextColor(0xffffffff);
        emergencyOffline.setBackgroundColor(0xff174a63);
        emergencyOffline.setOnClickListener(v -> openEmergencyNeighborhoodNativeV249442());
        FrameLayout.LayoutParams ep = new FrameLayout.LayoutParams(-1, 132);
        ep.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
        ep.setMargins(40, 0, 40, 28);
        overlay.addView(emergencyOffline, ep);

        root.addView(overlay, new FrameLayout.LayoutParams(-1, -1));
        setContentView(root);
    }

    // V24691: Mobile Feed video fix.
    // Android WebView video tags cannot add the ngrok-skip-browser-warning header by themselves.
    // Intercept only OK ITHAKI media-file requests and replay them with the required headers,
    // preserving Range so video streaming/seek works on mobile.
    private WebResourceResponse interceptIthakiMediaRequest(String url, Map<String, String> requestHeaders) {
        try {
            if (url == null) return null;
            String lower = url.toLowerCase();
            if (!lower.contains("/api/media/file/")) return null;
            // V24691D: do not proxy server media through WebResourceResponse.
            // Android WebView video/audio stack is more stable when it streams the real HTTP URL itself.
            if (true) return null;
            URL u = new URL(url);
            HttpURLConnection c = (HttpURLConnection) u.openConnection();
            c.setInstanceFollowRedirects(true);
            c.setConnectTimeout(15000);
            c.setReadTimeout(0);
                        c.setRequestProperty("X-ITHAKI-TOKEN", (publicAppToken == null || publicAppToken.trim().isEmpty()) ? PUBLIC_APP_TOKEN_FALLBACK : publicAppToken);
            c.setRequestProperty("Accept", "video/*,audio/*,image/*,*/*");
            c.setRequestProperty("User-Agent", "OK ITHAKI Android WebView");
            if (requestHeaders != null) {
                String range = requestHeaders.get("Range");
                if (range == null) range = requestHeaders.get("range");
                if (range != null && range.trim().length() > 0) c.setRequestProperty("Range", range);
            }
            int code = c.getResponseCode();
            InputStream in = (code >= 400) ? c.getErrorStream() : c.getInputStream();
            if (in == null) return null;
            String ct = c.getContentType();
            if (ct == null || ct.trim().isEmpty()) ct = "application/octet-stream";
            String mime = ct.split(";", 2)[0].trim();
            String enc = c.getContentEncoding();
            WebResourceResponse resp = new WebResourceResponse(mime, enc, in);
            if (Build.VERSION.SDK_INT >= 21) {
                Map<String, String> headers = new HashMap<>();
                String[] keep = new String[]{"Accept-Ranges","Content-Range","Content-Length","Content-Disposition","Cache-Control","ETag","Last-Modified"};
                for (String k : keep) {
                    String v = c.getHeaderField(k);
                    if (v != null) headers.put(k, v);
                }
                headers.put("Access-Control-Allow-Origin", "*");
                headers.put("Access-Control-Expose-Headers", "Accept-Ranges, Content-Range, Content-Length, Content-Type");
                resp.setResponseHeaders(headers);
                String msg = c.getResponseMessage();
                if (msg == null || msg.trim().isEmpty()) msg = (code == 206 ? "Partial Content" : (code == 200 ? "OK" : "OK"));
                if (code >= 100 && code <= 599) resp.setStatusCodeAndReasonPhrase(code, msg);
            }
            return resp;
        } catch (Exception e) {
            return null;
        }
    }

    private void startStayConnectedAtBootV249383() {
        if (TorIntegrationV249435.isRequested(this)) return;
        try {
            Intent i = new Intent(this, StayConnectedService.class);
            if (Build.VERSION.SDK_INT >= 26) startForegroundService(i); else startService(i);
        } catch (Exception ignored) { }
    }

    private void refreshFcmAtBootV249383() {
        if (TorIntegrationV249435.isRequested(this)) return;
        try {
            final SharedPreferences sp = getSharedPreferences(PREFS, MODE_PRIVATE);
            final String room = sp.getString("ok_fcm_room", "ithaki-phone-test");
            final String device = sp.getString("ok_fcm_device", "");
            final String nick = sp.getString("ok_fcm_nickname", "");
            final String base = sp.getString("ok_fcm_server", DEFAULT_PUBLIC_SERVER);
            FirebaseMessaging.getInstance().getToken().addOnCompleteListener(task -> {
                try {
                    if (task.isSuccessful() && task.getResult() != null) {
                        registerFcmTokenToServerInternal(room, device, nick, base, task.getResult());
                    }
                } catch (Exception ignored) { }
            });
        } catch (Exception ignored) { }
    }

    @SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface"})
    private void setupWebView() {
        WebSettings s = webView.getSettings();
        s.setJavaScriptEnabled(true);
        s.setDomStorageEnabled(true);
        s.setCacheMode(WebSettings.LOAD_NO_CACHE); // V249391: unified friend communication, stable selected peer and secure lock; bypass stale WebView cache.
        try { webView.clearCache(true); } catch (Exception ignored) {}
        s.setMediaPlaybackRequiresUserGesture(false);
        s.setJavaScriptCanOpenWindowsAutomatically(true);
        s.setDatabaseEnabled(true);
        s.setAllowFileAccess(true);
        s.setAllowContentAccess(true);
        s.setAllowFileAccessFromFileURLs(true);
        s.setAllowUniversalAccessFromFileURLs(true);
        s.setUseWideViewPort(true);
        s.setLoadWithOverviewMode(true);
        // Compact mobile layout: slightly smaller text, CSS handles the rest.
        s.setTextZoom(92);
        s.setGeolocationEnabled(false);
        if (Build.VERSION.SDK_INT >= 21) s.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
        webView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
        final WebViewAssetLoader assetLoaderV249461 = new WebViewAssetLoader.Builder()
                .setDomain("appassets.androidplatform.net")
                .addPathHandler("/", new WebViewAssetLoader.AssetsPathHandler(this))
                .build();
        webView.setWebViewClient(new WebViewClient() {
            // V24793: allow self-signed HTTPS ONLY on private LAN hosts, so camera/mic
            // (getUserMedia) can work over Wi-Fi without exposing the public domain to MITM.
            @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                try {
                    String host = (error != null && error.getUrl() != null) ? android.net.Uri.parse(error.getUrl()).getHost() : null;
                    boolean lan = host != null && (host.startsWith("192.168.") || host.startsWith("10.")
                            || host.matches("^172\\.(1[6-9]|2[0-9]|3[0-1])\\..*")
                            || host.equals("localhost") || host.equals("127.0.0.1"));
                    if (lan) { handler.proceed(); return; }
                } catch (Exception ignored) { }
                try { handler.cancel(); } catch (Exception ignored) { }
            }
            @Override public void onPageStarted(WebView view, String url, android.graphics.Bitmap favicon) {
                mainFrameLoadFailedV249442 = false;
                super.onPageStarted(view, url, favicon);
            }

            @Override public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
                try {
                    if (request != null && request.isForMainFrame()) {
                        mainFrameLoadFailedV249442 = true;
                        String failing = request.getUrl() == null ? "" : request.getUrl().toString();
                        String description = error == null ? "σφάλμα φόρτωσης" : String.valueOf(error.getDescription());
                        if (failing.startsWith("file:///android_asset/index.html") || failing.startsWith("https://appassets.androidplatform.net/")) { loadFileFrontendFallbackV249463(description); return; }
                        showOfflineEmergencyOverlayV249442("Η ενσωματωμένη οθόνη δεν φόρτωσε: " + description);
                    }
                } catch (Exception ignored) { }
                super.onReceivedError(view, request, error);
            }

            @SuppressWarnings("deprecation")
            @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
                if (failingUrl != null && (failingUrl.startsWith("file:///android_asset/index.html") || failingUrl.startsWith("https://appassets.androidplatform.net/frontend/index.html"))) {
                    mainFrameLoadFailedV249442 = true;
                    loadFileFrontendFallbackV249463(description == null ? String.valueOf(errorCode) : description);
                    return;
                }
                super.onReceivedError(view, errorCode, description, failingUrl);
            }

            @Override public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                boolean localV249468 = url != null && (url.startsWith("file:///android_asset/") || url.startsWith("https://appassets.androidplatform.net/"));
                if (localV249468) {
                    mainFrameLoadFailedV249442 = false;
                    status("Έλεγχος πλήρους ενσωματωμένης οθόνης OK ITHAKI V249470...");
                } else if (mainFrameLoadFailedV249442) {
                    showOfflineEmergencyOverlayV249442("Δεν υπάρχει internet ή ο κεντρικός server δεν απαντά.");
                    return;
                }
                evalJs("document.documentElement.style.visibility='visible';document.documentElement.style.opacity='1';if(document.body){document.body.style.visibility='visible';document.body.style.opacity='1';document.body.style.display='';}window.ITH&&ITH.openPreviewV249400&&document.documentElement.setAttribute('data-native-preview-recovery','V249400-ready')");
                localFrontendWatchdogV249463.postDelayed(() -> verifyVisibleFrontendV249463(), 500L);
                localFrontendWatchdogV249463.postDelayed(() -> openCallScreenImmediatelyV249463(), 350L);
            }

            @Override public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail detail) {
                fileChooserOpenV249400 = false;
                try { status("Η προβολή Android επανεκκινείται αυτόματα — τα δεδομένα του server δεν επηρεάζονται."); } catch (Exception ignored) { }
                if (runtimePermissionDialogActiveV249465) {
                    pendingRendererRecoveryV249465 = true;
                    return true;
                }
                new Handler(Looper.getMainLooper()).postDelayed(() -> {
                    try { if (!isFinishing() && !isDestroyed()) MainActivity.this.recreate(); } catch (Throwable ignored) { }
                }, 250L);
                return true;
            }

            @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
                try {
                    if (Build.VERSION.SDK_INT >= 21 && request != null && request.getUrl() != null) {
                        WebResourceResponse localAsset = assetLoaderV249461.shouldInterceptRequest(request.getUrl());
                        if (localAsset != null) return localAsset;
                        WebResourceResponse media = interceptIthakiMediaRequest(request.getUrl().toString(), request.getRequestHeaders());
                        if (media != null) return media;
                    }
                } catch (Exception ignored) { }
                return super.shouldInterceptRequest(view, request);
            }

            @Override public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
                try {
                    if (url != null) {
                        WebResourceResponse localAsset = assetLoaderV249461.shouldInterceptRequest(Uri.parse(url));
                        if (localAsset != null) return localAsset;
                    }
                    WebResourceResponse r = interceptIthakiMediaRequest(url, null);
                    if (r != null) return r;
                } catch (Exception ignored) { }
                return super.shouldInterceptRequest(view, url);
            }
        });
        webView.setDownloadListener((url, userAgent, contentDisposition, mimetype, contentLength) -> {
            try {
                String name = "OK_ITHAKI_" + System.currentTimeMillis();
                int slash = url == null ? -1 : url.lastIndexOf('/');
                if (slash >= 0 && slash < url.length()-1) name = safeFileName(url.substring(slash+1).split("\\?")[0]);
                enqueueDownload(url, name, mimetype);
            } catch (Exception ignored) { }
        });
        webView.setWebChromeClient(new WebChromeClient() {
            @Override public void onPermissionRequest(final PermissionRequest request) {
                if (Build.VERSION.SDK_INT >= 21) {
                    runOnUiThread(() -> grantOrAskWebMediaPermission(request));
                }
            }
            @Override public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
                try { callback.invoke(origin, false, false); } catch (Exception ignored) { }
            }
            @Override public boolean onShowFileChooser(WebView view, ValueCallback<Uri[]> callback, FileChooserParams params) {
                fileChooserOpenV249400 = true;
                fileChooserOpenedAtV249400 = System.currentTimeMillis();
                status("Επιλογή αρχείου — η ΙΘΑΚΗ παραμένει ανοιχτή...");
                if (filePathCallback != null) filePathCallback.onReceiveValue(null);
                filePathCallback = callback;
                cameraCaptureUri = null;
                try {
                    String[] accepts = params == null ? new String[0] : params.getAcceptTypes();
                    boolean wantsImage = acceptsContain(accepts, "image") || acceptsContain(accepts, ".jpg") || acceptsContain(accepts, ".jpeg") || acceptsContain(accepts, ".png");
                    boolean wantsVideo = acceptsContain(accepts, "video");
                    boolean wantsAudio = acceptsContain(accepts, "audio");
                    boolean capture = params != null && params.isCaptureEnabled();

                    if (capture && wantsImage) {
                        if (!hasRuntimePermissions(true, false)) {
                            pendingChooserMode = "image";
                            requestPermissionList(true, false, false, false);
                            return true;
                        }
                        Intent camera = makeImageCaptureIntent();
                        if (camera != null) {
                            startActivityForResult(camera, FILE_CHOOSER_REQ);
                            return true;
                        }
                    }
                    if (capture && wantsVideo) {
                        if (!hasRuntimePermissions(true, true)) {
                            pendingChooserMode = "video";
                            requestPermissionList(true, true, false, false);
                            return true;
                        }
                        Intent video = makeVideoCaptureIntent();
                        if (video != null) {
                            startActivityForResult(video, FILE_CHOOSER_REQ);
                            return true;
                        }
                    }

                    Intent pick = new Intent(Intent.ACTION_OPEN_DOCUMENT);
                    pick.addCategory(Intent.CATEGORY_OPENABLE);
                    if (wantsVideo && !wantsImage && !wantsAudio) {
                        pick.setType("video/*");
                        pick.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"video/*"});
                    } else if (wantsImage && !wantsVideo && !wantsAudio) {
                        pick.setType("image/*");
                        pick.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"image/*"});
                    } else if (wantsAudio && !wantsImage && !wantsVideo) {
                        pick.setType("audio/*");
                        pick.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{"audio/*"});
                    } else {
                        pick.setType("*/*");
                        pick.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{
                                "image/*", "video/*", "audio/*", "application/pdf", "text/plain", "text/csv",
                                "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
                                "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                                "application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation",
                                "application/zip", "application/json", "application/octet-stream"
                        });
                    }
                    pick.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false);

                    // For file inputs, open the Android file/document picker.
                    // Photo/video buttons use capture=true and are handled above with direct camera intents.
                    // V249517: open the system document list directly, without an app/source chooser.
                    startActivityForResult(pick, FILE_CHOOSER_REQ);
                    return true;
                } catch (Exception e) {
                    try {
                        Intent fallback = new Intent(Intent.ACTION_GET_CONTENT);
                        fallback.addCategory(Intent.CATEGORY_OPENABLE);
                        fallback.setType("*/*");
                        startActivityForResult(fallback, FILE_CHOOSER_REQ);
                        return true;
                    } catch (Exception ignored) {
                        filePathCallback = null;
                        fileChooserOpenV249400 = false;
                        return false;
                    }
                }
            }
        });
        Bridge bridge = new Bridge();
        webView.addJavascriptInterface(bridge, "OKITHAKI_ANDROID");
        webView.addJavascriptInterface(bridge, "AndroidBridge");
    }

    @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == RECOVERY_TREE_REQ) {
            if (resultCode == RESULT_OK && data != null && data.getData() != null) {
                Uri tree = data.getData();
                int flags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
                try { if (Build.VERSION.SDK_INT >= 19) getContentResolver().takePersistableUriPermission(tree, flags & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION)); } catch (Exception ignored) { }
                getSharedPreferences(PREFS, MODE_PRIVATE).edit().putString("ok_recovery_tree_uri", tree.toString()).apply();
                evalJs("window.onOkIthakiRecoveryFolderSelected&&window.onOkIthakiRecoveryFolderSelected(" + JSONObject.quote(tree.toString()) + ")");
            } else {
                evalJs("window.onOkIthakiRecoveryFolderSelected&&window.onOkIthakiRecoveryFolderSelected('')");
            }
            return;
        }
        if (requestCode == RECOVERY_IMPORT_REQ) {
            if (resultCode == RESULT_OK && data != null && data.getData() != null) {
                String txt = readTextFromUri(data.getData());
                evalJs("window.onOkIthakiRecoveryImported&&window.onOkIthakiRecoveryImported(" + JSONObject.quote(txt == null ? "" : txt) + ")");
            } else {
                evalJs("window.onOkIthakiRecoveryImported&&window.onOkIthakiRecoveryImported('')");
            }
            return;
        }
        if (requestCode == FILE_CHOOSER_REQ) {
            final ValueCallback<Uri[]> callback = filePathCallback;
            Uri[] parsed = null;
            try {
                if (resultCode == RESULT_OK && Build.VERSION.SDK_INT >= 21) {
                    parsed = WebChromeClient.FileChooserParams.parseResult(resultCode, data);
                }
            } catch (Exception ignored) { }
            if (resultCode == RESULT_OK && (parsed == null || parsed.length == 0)) {
                if (data != null && data.getClipData() != null) {
                    int n = data.getClipData().getItemCount();
                    parsed = new Uri[n];
                    for (int i = 0; i < n; i++) parsed[i] = data.getClipData().getItemAt(i).getUri();
                } else if (data != null && data.getData() != null) {
                    parsed = new Uri[]{data.getData()};
                } else if (cameraCaptureUri != null) {
                    parsed = new Uri[]{cameraCaptureUri};
                }
            }
            if (parsed != null && data != null) for (Uri uri : parsed) takePersistable(uri, data);
            final Uri[] result = parsed;
            filePathCallback = null;
            cameraCaptureUri = null;
            fileChooserOpenV249400 = false;
            if (callback != null) {
                webView.post(() -> {
                    try {
                        callback.onReceiveValue(result);
                        status(result != null && result.length > 0 ? "Το αρχείο επιλέχθηκε — ανοίγει η προεπισκόπηση..." : "Η επιλογή αρχείου ακυρώθηκε.");
                        evalJs("(function(){var i=document.getElementById('globalFile');if(window.ITH&&ITH.nativeFileChooserReturnedV249517){ITH.nativeFileChooserReturnedV249517();}else if(window.ITH&&ITH.previewChooserReturnedV249400){ITH.previewChooserReturnedV249400();}else if(i){try{i.dispatchEvent(new Event('change',{bubbles:true}));}catch(e){}}})()");
                    } catch (Exception ignored) { }
                });
            }
            return;
        }
    }

    private void status(final String msg) { runOnUiThread(() -> { if (statusText != null) statusText.setText(msg); }); }

    private void hideOverlay() {
        runOnUiThread(() -> {
            View parent = (View) webView.getParent();
            if (parent instanceof FrameLayout) {
                FrameLayout f = (FrameLayout) parent;
                for (int i = 0; i < f.getChildCount(); i++) if ("overlay".equals(f.getChildAt(i).getTag())) f.getChildAt(i).setVisibility(View.GONE);
            }
        });
    }

    private void autoDetectAndLoad() {
        // v2.1.6: disabled automatic server switching to stop Wi-Fi/ngrok flip-flop.
        runOnUiThread(() -> {
            String base = serverBase == null || serverBase.trim().isEmpty() ? DEFAULT_PUBLIC_SERVER : serverBase.trim();
            loadApp(base);
            status("Stable server / Σταθερός server: " + base);
        });
    }

    private String cleanBase(String base) {
        if (base == null) return "";
        String b = base.trim();
        if (b.endsWith("/")) b = b.substring(0, b.length() - 1);
        return b;
    }

    private boolean sameBase(String a, String b) {
        return cleanBase(a).equalsIgnoreCase(cleanBase(b));
    }

    private String initialStartupBase() {
        // V249470: production domain is the only automatic startup route.
        saveServerBase(DEFAULT_PUBLIC_SERVER);
        return DEFAULT_PUBLIC_SERVER;
    }

    private void applyIncomingCallWindowV249463(Intent intent) {
        try {
            if (intent == null) return;
            String kind = intent.getStringExtra("kind");
            if (kind == null || kind.trim().isEmpty()) kind = intent.getStringExtra("fcm_kind");
            String sid = intent.getStringExtra("session_id");
            boolean call = (sid != null && !sid.trim().isEmpty()) || "video_call".equals(kind) || "voice_call".equals(kind) || "call_invite".equals(kind);
            if (!call) return;
            if (Build.VERSION.SDK_INT >= 27) { setShowWhenLocked(true); setTurnScreenOn(true); }
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
        } catch (Exception ignored) { }
    }

    private String localFrontendUrlV249463(boolean fileFallback) {
        try {
            String apiBase = IthakiPrivateRelayV249436.isEnabled(this) ? IthakiPrivateRelayV249436.entryBase(this) : cleanBase(serverBase);
            if (apiBase == null || apiBase.trim().isEmpty()) apiBase = DEFAULT_PUBLIC_SERVER;
            String encBase = URLEncoder.encode(apiBase.trim(), "UTF-8");
            String encToken = URLEncoder.encode(publicAppToken == null || publicAppToken.trim().isEmpty() ? PUBLIC_APP_TOKEN_FALLBACK : publicAppToken.trim(), "UTF-8");
            String extra = lastLaunchQueryV249463 == null || lastLaunchQueryV249463.trim().isEmpty() ? "" : ("&" + lastLaunchQueryV249463.trim());
            String root = fileFallback
                    ? "https://appassets.androidplatform.net/frontend/index.html"
                    : "https://appassets.androidplatform.net/index.html";
            return root + "?v=249550&force_build=249550&apk_build=249550&server=" + encBase + "&token=" + encToken + "&room=ithaki-phone-test&apk=1&connection_mode=" + (fileFallback ? "android_appassets_frontend_fallback_v249550" : "android_appassets_primary_v249550") + extra;
        } catch (Exception e) { return "https://appassets.androidplatform.net/index.html?v=249550&apk=1"; }
    }

    private void loadFileFrontendFallbackV249463(String reason) {
        runOnUiThread(() -> {
            try {
                if (localFrontendFallbackTriedV249463) { showOfflineEmergencyOverlayV249442("Η τοπική οθόνη Android δεν φόρτωσε: " + reason); return; }
                localFrontendFallbackTriedV249463 = true;
                status("Εναλλακτική τοπική φόρτωση Android — τα δεδομένα παραμένουν στον server.");
                webView.loadUrl(localFrontendUrlV249463(true));
            } catch (Exception e) { showOfflineEmergencyOverlayV249442("Η τοπική οθόνη Android δεν άνοιξε."); }
        });
    }

    private void verifyVisibleFrontendV249463() {
        try {
            String audit = "(function(){try{"
                    + "var b=document.body,s=b&&getComputedStyle(b),app=document.querySelector('.app');"
                    + "var ith=window.ITH,core=!!(ith&&(typeof ith.page==='function'||typeof ith.stableRouteV249429==='function'||typeof ith.renderNav==='function'||typeof ith.renderProfile==='function'));"
                    + "var navigation=!!document.querySelector('.nav,.bottomNav,[data-v249461-route],.ithHomeV249461,#homePage,#nav');"
                    + "var visible=!!(b&&s&&s.display!=='none'&&s.visibility!=='hidden'&&s.opacity!=='0');"
                    + "var text=String(b&&b.innerText||'').replace(/\\s+/g,' ').trim();"
                    + "return !!(visible&&app&&core&&navigation&&b.innerHTML.length>1000&&text.length>40);"
                    + "}catch(e){return false}})()";
            webView.evaluateJavascript(audit, value -> {
                boolean ok = "true".equalsIgnoreCase(String.valueOf(value));
                if (ok) {
                    hideOverlay();
                    status("OK ITHAKI V249470 έτοιμη.");
                    scheduleInitialNotificationPermissionV249465();
                    openCallScreenImmediatelyV249463();
                } else {
                    loadFileFrontendFallbackV249463("blank_or_incomplete_functional_frontend");
                }
            });
        } catch (Exception e) { loadFileFrontendFallbackV249463("frontend_watchdog_error"); }
    }

    private void openCallScreenImmediatelyV249463() {
        try {
            if (lastLaunchQueryV249463 == null || !lastLaunchQueryV249463.contains("call=1")) return;
            evalJs("(function(){var n=0;function go(){n++;try{if(window.ITH){if(ITH.v249251OpenLaunchFromUrl)ITH.v249251OpenLaunchFromUrl();if(ITH.v249251OpenCallFromUrl)ITH.v249251OpenCallFromUrl();document.documentElement.setAttribute('data-call-open-v249463','attempt-'+n);return}}catch(e){}if(n<30)setTimeout(go,250)}go()})()");
        } catch (Exception ignored) { }
    }

    private void loadApp(String base) {
        try {
            if (isBadMobileBase(base)) base = "";
            serverBase = base == null ? "" : base.trim();
            lastLaunchQueryV249463 = pendingLaunchQuery == null ? "" : pendingLaunchQuery.trim();
            pendingLaunchQuery = "";
            localFrontendFallbackTriedV249463 = false;
            mainFrameLoadFailedV249442 = false;
            IthakiPrivateRelayV249436.applyAccessCookie(this, webView);
            status("Φόρτωση πλήρους ενσωματωμένης οθόνης OK ITHAKI V249470...");
            webView.loadUrl(localFrontendUrlV249463(false));
            localFrontendWatchdogV249463.postDelayed(() -> verifyVisibleFrontendV249463(), 8000L);
        } catch (Exception e) {
            loadFileFrontendFallbackV249463("primary_local_frontend_exception");
        }
    }

    private String findServer() {
        return findServerLocalFirst();
    }

    private String findServerLocalFirst() {
        // V249470: never scan IP ranges and never use an obsolete public tunnel.
        saveServerBase(DEFAULT_PUBLIC_SERVER);
        return DEFAULT_PUBLIC_SERVER;
    }

    private class ProbeTask implements Callable<String> {
        private final String base;
        private final int timeout;
        ProbeTask(String b){base=b; timeout=500;}
        ProbeTask(String b, int t){base=b; timeout=t;}
        @Override public String call(){return isOkServer(base,timeout)?base:null;}
    }



    private List<String> buildLanCandidateBases(String saved, String assetBase, String assetLan) {
        Set<String> out = new LinkedHashSet<>();
        // V249158: configured toner/hotspot IP first; saved old IP only after.
        if (isPrivateLanBase(assetLan)) addBase(out, assetLan);
        if (isPrivateLanBase(assetBase)) addBase(out, assetBase);
        if (isPrivateLanBase(saved)) addBase(out, saved);
        String gw = dhcpGatewayAddress();
        if (gw != null && gw.contains(".")) { addBase(out, "http://" + gw + ":8010"); }
        addTetherHotspotCandidates(out);
        String ip = wifiIpAddress();
        if (ip != null && ip.contains(".")) {
            String pref = ip.substring(0, ip.lastIndexOf('.') + 1);
            int[] likely = {1,2,5,10,20,30,40,50,60,70,80,81,90,100,101,110,120,150,180,200,220,240,254};
            for (int x : likely) { addBase(out, "http://" + pref + x + ":8010"); }
        }
        return new ArrayList<>(out);
    }

    private void addTetherHotspotCandidates(Set<String> out) {
        int[] likely = {1,2,5,10,20,30,40,50,60,70,80,81,90,100,101,110,120,150,180,200,220,240,254};
        String[] prefixes = {"192.168.43.", "192.168.42.", "192.168.49.", "172.20.10.", "10.0.0.", "192.168.1.", "192.168.0.", "192.168.137."};
        for (String p : prefixes) for (int x : likely) addBase(out, "http://" + p + x + ":8010");
    }

    private List<String> buildRemoteCandidateBases(String saved, String assetBase) {
        Set<String> out = new LinkedHashSet<>();
        addBase(out, DEFAULT_PUBLIC_SERVER);
        return new ArrayList<>(out);
    }

    private List<String> buildCandidateBases() {
        Set<String> out = new LinkedHashSet<>();
        String saved = getSharedPreferences(PREFS, MODE_PRIVATE).getString(PREF_SERVER, "");
        String assetBase = assetConfigServerBase();
        for (String b : buildLanCandidateBases(saved, assetBase, assetConfigValue("lan_api_url"))) addBase(out, b);
        for (String b : buildRemoteCandidateBases(saved, assetBase)) addBase(out, b);
        return new ArrayList<>(out);
    }

    private List<String> buildFullSubnetCandidates() {
        Set<String> out = new LinkedHashSet<>();
        Set<String> prefixes = new LinkedHashSet<>();
        String ip = wifiIpAddress();
        if (ip != null && ip.contains(".")) prefixes.add(ip.substring(0, ip.lastIndexOf('.') + 1));
        String gw = dhcpGatewayAddress();
        if (gw != null && gw.contains(".")) prefixes.add(gw.substring(0, gw.lastIndexOf('.') + 1));
        for (String a : localIpv4Addresses()) if (a != null && a.contains(".")) prefixes.add(a.substring(0, a.lastIndexOf('.') + 1));
        if (prefixes.isEmpty()) {
            prefixes.add("192.168.43."); prefixes.add("192.168.42."); prefixes.add("192.168.49."); prefixes.add("172.20.10.");
            prefixes.add("192.168.1."); prefixes.add("192.168.0."); prefixes.add("192.168.178."); prefixes.add("192.168.10.");
        }
        for (String p : prefixes) for (int n = 1; n <= 254; n++) { addBase(out, "http://" + p + n + ":8010"); }
        return new ArrayList<>(out);
    }


    private boolean isPrivateLanBase(String base) {
        if (base == null) return false;
        String b = base.trim().toLowerCase();
        return b.startsWith("http://192.168.") || b.startsWith("http://10.") || b.matches("^http://172\\.(1[6-9]|2[0-9]|3[0-1])\\..*");
    }

    private boolean isTestTunnelBase(String base) {
        if (base == null) return false;
        String b = base.trim().toLowerCase();
        return b.contains(".ngrok-free.dev") || b.contains(".ngrok-free.app");
    }

    private boolean isDomainReadyBase(String base) {
        if (base == null) return false;
        String b = base.trim().toLowerCase();
        if (b.endsWith("/")) b = b.substring(0, b.length()-1);
        return b.equals("https://ok-ithaki.gr") || b.equals("https://www.ok-ithaki.gr");
    }

    private boolean isBadMobileBase(String base) {
        if (base == null) return false;
        String b = base.trim().toLowerCase();
        return b.contains("127.0.0.1") || b.contains("localhost") || b.contains("0.0.0.0");
    }

    private boolean isOldTemporaryPublicBase(String base) {
        if (base == null) return false;
        String b = base.trim().toLowerCase();
        // V249158: keep VPS IP disabled; keep ok-ithaki.gr available only as last domain-ready candidate.
        if (b.contains("185.185.80.71")) return true;
        if (b.startsWith("http") && !isPrivateLanBase(base) && !isTestTunnelBase(base) && !isDomainReadyBase(base)) return true;
        return false;
    }

    private void clearBadSavedServerBase() {
        SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
        if (!"249470".equals(prefs.getString(PREF_CONNECTION_VERSION, ""))) {
            prefs.edit().remove(PREF_SERVER).putString(PREF_CONNECTION_VERSION, "249470").apply();
        }
        String saved = prefs.getString(PREF_SERVER, "");
        if (isBadMobileBase(saved) || isOldTemporaryPublicBase(saved)) {
            prefs.edit().remove(PREF_SERVER).apply();
        }
    }

    private void addBase(Set<String> set, String base) {
        if (base == null) return;
        base = base.trim();
        if (base.endsWith("/")) base = base.substring(0, base.length()-1);
        if (isBadMobileBase(base) || isOldTemporaryPublicBase(base)) return;
        if (base.startsWith("http://") || base.startsWith("https://")) set.add(base);
    }

    private boolean isOkServer(String base, int timeoutMs) {
        if (base == null || base.trim().isEmpty()) return false;
        String[] paths = {"/api/app/bootstrap", "/health", "/api/version_v24678", "/mobile_test"};
        for (String path : paths) {
            HttpURLConnection c = null;
            try {
                c = (HttpURLConnection) new URL(base + path).openConnection();
                c.setRequestMethod("GET");
                c.setConnectTimeout(timeoutMs); c.setReadTimeout(timeoutMs);
                c.setRequestProperty("Accept", "application/json");
                c.setRequestProperty("User-Agent", "OK-ITHAKI-Android/2.4.6.7");
                                c.setRequestProperty("X-ITHAKI-TOKEN", publicAppToken == null || publicAppToken.trim().isEmpty() ? PUBLIC_APP_TOKEN_FALLBACK : publicAppToken.trim());
                int code = c.getResponseCode();
                if (code >= 200 && code < 300) {
                    BufferedReader br = new BufferedReader(new InputStreamReader(c.getInputStream(), StandardCharsets.UTF_8));
                    StringBuilder sb = new StringBuilder(); String line;
                    while ((line = br.readLine()) != null && sb.length() < 4096) sb.append(line);
                    String body = sb.toString();
                    try {
                        JSONObject jb = new JSONObject(body);
                        String tok = jb.optString("public_app_token", "");
                        if (tok == null || tok.trim().isEmpty()) {
                            JSONObject boot = jb.optJSONObject("bootstrap");
                            if (boot != null) tok = boot.optString("public_app_token", "");
                        }
                        if (tok != null && !tok.trim().isEmpty()) publicAppToken = tok.trim();
                        if (jb.optBoolean("ok", false) || "ok".equalsIgnoreCase(jb.optString("status", "")) || "healthy".equalsIgnoreCase(jb.optString("status", ""))) return true;
                    } catch (Exception ignored) { }
                    String lowBody = body == null ? "" : body.toLowerCase();
                    String compactBody = lowBody.replace(" ", "").replace("\n", "").replace("\r", "").replace("\t", "");
                    if (lowBody.contains("ok ithaki") || lowBody.contains("ithaki") || compactBody.contains("\"ok\":true") || compactBody.contains("\"status\":\"ok\"")) return true;
                }
            } catch (Exception ignored) { } finally { if (c != null) c.disconnect(); }
        }
        return false;
    }

    private String wifiIpAddress() {
        try {
            ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo ni = cm == null ? null : cm.getActiveNetworkInfo();
            if (ni == null || !ni.isConnected()) return null;
            WifiManager wm = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
            if (wm != null) {
                WifiInfo wi = wm.getConnectionInfo();
                int ip = wi == null ? 0 : wi.getIpAddress();
                if (ip != 0) return (ip & 0xff) + "." + ((ip >> 8) & 0xff) + "." + ((ip >> 16) & 0xff) + "." + ((ip >> 24) & 0xff);
            }
        } catch (Exception ignored) { }
        try {
            for (String a : localIpv4Addresses()) return a;
        } catch (Exception ignored) { }
        return null;
    }

    private String dhcpGatewayAddress() {
        try {
            WifiManager wm = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
            if (wm == null) return null;
            DhcpInfo d = wm.getDhcpInfo();
            int gw = d == null ? 0 : d.gateway;
            if (gw == 0) return null;
            return (gw & 0xff) + "." + ((gw >> 8) & 0xff) + "." + ((gw >> 16) & 0xff) + "." + ((gw >> 24) & 0xff);
        } catch (Exception e) { return null; }
    }

    private Set<String> localIpv4Addresses() {
        Set<String> out = new LinkedHashSet<>();
        try {
            Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
            while (nets != null && nets.hasMoreElements()) {
                NetworkInterface ni = nets.nextElement();
                try {
                    if (!ni.isUp() || ni.isLoopback()) continue;
                } catch (Exception ignored) { }
                Enumeration<InetAddress> addrs = ni.getInetAddresses();
                while (addrs != null && addrs.hasMoreElements()) {
                    InetAddress a = addrs.nextElement();
                    if (a instanceof Inet4Address && !a.isLoopbackAddress()) {
                        String host = a.getHostAddress();
                        if (host != null && !host.startsWith("127.") && !host.startsWith("169.254.")) out.add(host);
                    }
                }
            }
        } catch (Exception ignored) { }
        return out;
    }

    private void saveServerBase(String base) {
        if (isBadMobileBase(base)) return;
        getSharedPreferences(PREFS, MODE_PRIVATE).edit().putString(PREF_SERVER, base == null ? "" : base.trim()).apply();
    }

    private String readAssetText(String name) {
        try {
            InputStream in = getAssets().open(name);
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            byte[] buf = new byte[4096];
            int n;
            while ((n = in.read(buf)) > 0 && out.size() < 256000) out.write(buf, 0, n);
            in.close();
            return new String(out.toByteArray(), StandardCharsets.UTF_8);
        } catch (Exception e) { return ""; }
    }


    private String assetConfigValue(String key) {
        try {
            String txt = readAssetText("app_connection_config.json");
            if (txt == null || txt.trim().isEmpty()) return "";
            JSONObject o = new JSONObject(txt);
            String v = o.optString(key, "");
            if (v == null) return "";
            v = v.trim();
            if (isBadMobileBase(v) || isOldTemporaryPublicBase(v)) return "";
            return v;
        } catch (Exception ignored) { }
        return "";
    }

    private String assetConfigServerBase() {
        try {
            String txt = readAssetText("app_connection_config.json");
            if (txt == null || txt.trim().isEmpty()) return "";
            JSONObject o = new JSONObject(txt);
            String[] keys = {"lan_api_url", "local_quick_ip_url", "active_api_url", "mobile_api_url", "default_api_url", "server_base", "public_tunnel_url", "ngrok_public_url", "tunnel_url", "public_api_url"};
            for (String k : keys) {
                String v = o.optString(k, "");
                if (v != null && v.trim().startsWith("http") && !isBadMobileBase(v) && !isOldTemporaryPublicBase(v)) return v.trim();
            }
        } catch (Exception ignored) { }
        return "";
    }

    private String assetConfigPublicToken() {
        try {
            String txt = readAssetText("app_connection_config.json");
            if (txt == null || txt.trim().isEmpty()) return "";
            JSONObject o = new JSONObject(txt);
            String v = o.optString("public_app_token", "");
            return v == null ? "" : v.trim();
        } catch (Exception ignored) { }
        return "";
    }


    private boolean acceptsContain(String[] arr, String token) {
        if (arr == null || token == null) return false;
        String t = token.toLowerCase();
        for (String a : arr) if (a != null && a.toLowerCase().contains(t)) return true;
        return false;
    }

    private File ensureSharedDir() {
        File dir = new File(getCacheDir(), "okithaki_shared_files");
        if (!dir.exists()) dir.mkdirs();
        return dir;
    }

    private Uri uriForFile(File f) {
        return FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", f);
    }

    private Intent makeImageCaptureIntent() {
        try {
            Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            File f = new File(ensureSharedDir(), "camera_photo_" + System.currentTimeMillis() + ".jpg");
            cameraCaptureUri = uriForFile(f);
            i.putExtra(MediaStore.EXTRA_OUTPUT, cameraCaptureUri);
            i.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
            return i;
        } catch (Exception e) { return null; }
    }

    private Intent makeVideoCaptureIntent() {
        try {
            Intent i = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
            File f = new File(ensureSharedDir(), "camera_video_" + System.currentTimeMillis() + ".mp4");
            cameraCaptureUri = uriForFile(f);
            i.putExtra(MediaStore.EXTRA_OUTPUT, cameraCaptureUri);
            i.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
            i.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
            return i;
        } catch (Exception e) { return null; }
    }

    private void takePersistable(Uri uri, Intent data) {
        try {
            if (uri == null || data == null) return;
            final int flags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            if (Build.VERSION.SDK_INT >= 19 && flags != 0) getContentResolver().takePersistableUriPermission(uri, flags);
        } catch (Exception ignored) { }
    }

    private String safeFileName(String raw) {
        String n = raw == null ? "ok_ithaki_file" : raw.trim();
        n = n.replaceAll("[\\\\/:*?\"<>|]", "_");
        if (n.length() < 1) n = "ok_ithaki_file";
        if (n.length() > 120) n = n.substring(0, 120);
        return n;
    }

    private String stripDataPrefix(String b64) {
        if (b64 == null) return "";
        int comma = b64.indexOf(',');
        if (b64.startsWith("data:") && comma >= 0) return b64.substring(comma + 1);
        return b64;
    }


    private File okIthakiRootDir() {
        File base = null;
        try { base = getExternalFilesDir(null); } catch (Exception ignored) { }
        if (base == null) base = getFilesDir();
        File root = new File(base, "OK_ITHAKI_MEMORIES");
        if (!root.exists()) root.mkdirs();
        return root;
    }

    private File okIthakiProfileDir(String profileName) {
        String n = safeFileName(profileName == null || profileName.trim().isEmpty() ? "Επισκέπτης" : profileName.trim());
        File dir = new File(okIthakiRootDir(), n);
        if (!dir.exists()) dir.mkdirs();
        new File(dir, "photos").mkdirs();
        new File(dir, "videos").mkdirs();
        new File(dir, "audio").mkdirs();
        new File(dir, "files").mkdirs();
        new File(dir, "memories").mkdirs();
        return dir;
    }

    private File okIthakiTypedDir(String profileName, String type) {
        String t = safeFileName(type == null || type.trim().isEmpty() ? "files" : type.trim().toLowerCase());
        if (!("photos".equals(t) || "videos".equals(t) || "audio".equals(t) || "files".equals(t) || "memories".equals(t))) t = "files";
        File dir = new File(okIthakiProfileDir(profileName), t);
        if (!dir.exists()) dir.mkdirs();
        return dir;
    }

    private void evalJs(final String js) {
        try { runOnUiThread(() -> { try { if (webView != null) webView.evaluateJavascript(js, null); } catch (Exception ignored) { } }); } catch (Exception ignored) { }
    }

    private String readTextFromUri(Uri uri) {
        try {
            InputStream in = getContentResolver().openInputStream(uri);
            if (in == null) return "";
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            byte[] buf = new byte[8192];
            int n;
            while ((n = in.read(buf)) > 0 && out.size() < 25_000_000) out.write(buf, 0, n);
            in.close();
            return new String(out.toByteArray(), StandardCharsets.UTF_8);
        } catch (Exception e) { return ""; }
    }

    private Uri selectedRecoveryTreeUri() {
        try {
            String raw = getSharedPreferences(PREFS, MODE_PRIVATE).getString("ok_recovery_tree_uri", "");
            if (raw == null || raw.trim().isEmpty()) return null;
            return Uri.parse(raw);
        } catch (Exception e) { return null; }
    }

    private Uri treeRootDocumentUri(Uri treeUri) {
        try {
            return DocumentsContract.buildDocumentUriUsingTree(treeUri, DocumentsContract.getTreeDocumentId(treeUri));
        } catch (Exception e) { return null; }
    }

    private String writeTextToRecoveryFolder(String filename, String mime, String content) {
        try {
            Uri tree = selectedRecoveryTreeUri();
            if (tree == null) return "NO_RECOVERY_FOLDER";
            Uri parent = treeRootDocumentUri(tree);
            if (parent == null) return "NO_RECOVERY_PARENT";
            String name = safeFileName(filename == null || filename.trim().isEmpty() ? "ok_ithaki_recovery.json" : filename.trim());
            Uri doc = DocumentsContract.createDocument(getContentResolver(), parent, mime == null ? "application/json" : mime, name);
            if (doc == null) return "CREATE_FAILED";
            OutputStream out = getContentResolver().openOutputStream(doc, "w");
            if (out == null) return "OPEN_FAILED";
            out.write((content == null ? "" : content).getBytes(StandardCharsets.UTF_8));
            out.close();
            return doc.toString();
        } catch (Exception e) { return "error: " + e.getMessage(); }
    }

    private String enqueueDownload(String url, String filename, String mime) {
        if (TorIntegrationV249435.isRequested(this)) return "tor_safe_mode_blocks_system_download";
        try {
            String name = safeFileName(filename == null || filename.trim().isEmpty() ? ("OK_ITHAKI_" + System.currentTimeMillis()) : filename.trim());
            DownloadManager.Request r = new DownloadManager.Request(Uri.parse(url));
            r.setTitle(name);
            r.setDescription("OK ITHAKI download");
            r.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
            r.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "OK_ITHAKI/" + name);
            if (mime != null && !mime.trim().isEmpty()) r.setMimeType(mime);
            DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
            if (dm != null) {
                long id = dm.enqueue(r);
                return "download:" + id;
            }
        } catch (Exception e) { return "error: " + e.getMessage(); }
        return "error";
    }

    private String shareFileUri(Uri uri, String mime, String title) {
        try {
            if (uri == null) return "empty";
            String type = (mime == null || mime.trim().isEmpty()) ? "application/octet-stream" : mime.trim();
            Intent send = new Intent(Intent.ACTION_SEND);
            send.setType(type);
            send.putExtra(Intent.EXTRA_STREAM, uri);
            send.putExtra(Intent.EXTRA_TITLE, title == null ? "OK ITHAKI" : title);
            send.setClipData(ClipData.newRawUri(title == null ? "OK ITHAKI" : title, uri));
            send.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Intent chooser = Intent.createChooser(send, "Κοινοποίηση αρχείου OK ITHAKI");
            chooser.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            startActivity(chooser);
            return "ok";
        } catch (Exception e) { return "error: " + e.getClass().getSimpleName() + ": " + e.getMessage(); }
    }


    private String startNativeAudioRecordInternal() {
        try {
            if (Build.VERSION.SDK_INT >= 23 && checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
                requestPermissionList(false, true, false, false);
                JSONObject o = new JSONObject();
                o.put("ok", false); o.put("need_permission", true); o.put("message", "microphone permission required");
                return o.toString();
            }
            stopNativeAudioRecordInternal(false);
            File dir = okIthakiTypedDir(getSharedPreferences(PREFS, MODE_PRIVATE).getString("ok_profile_folder_name", "Επισκέπτης"), "audio");
            nativeAudioFile = new File(dir, "ithaki_audio_" + System.currentTimeMillis() + ".m4a");
            MediaRecorder r;
            if (Build.VERSION.SDK_INT >= 31) r = new MediaRecorder(this); else r = new MediaRecorder();
            r.setAudioSource(MediaRecorder.AudioSource.MIC);
            r.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
            r.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
            try { r.setAudioSamplingRate(44100); } catch (Exception ignored) { }
            try { r.setAudioEncodingBitRate(128000); } catch (Exception ignored) { }
            r.setOutputFile(nativeAudioFile.getAbsolutePath());
            r.prepare();
            r.start();
            nativeAudioRecorder = r;
            nativeAudioStartedAt = System.currentTimeMillis();
            JSONObject o = new JSONObject();
            o.put("ok", true); o.put("path", nativeAudioFile.getAbsolutePath()); o.put("name", nativeAudioFile.getName()); o.put("mime", "audio/mp4");
            return o.toString();
        } catch (Exception e) {
            try { if (nativeAudioRecorder != null) nativeAudioRecorder.release(); } catch (Exception ignored) { }
            nativeAudioRecorder = null;
            JSONObject o = new JSONObject();
            try { o.put("ok", false); o.put("error", e.getMessage()); } catch (Exception ignored) { }
            return o.toString();
        }
    }

    private String stopNativeAudioRecordInternal(boolean withData) {
        File f = nativeAudioFile;
        try {
            if (nativeAudioRecorder != null) {
                try { nativeAudioRecorder.stop(); } catch (Exception ignored) { }
                try { nativeAudioRecorder.release(); } catch (Exception ignored) { }
            }
        } catch (Exception ignored) { }
        nativeAudioRecorder = null;
        nativeAudioFile = null;
        long dur = nativeAudioStartedAt > 0 ? Math.max(0, System.currentTimeMillis() - nativeAudioStartedAt) : 0;
        nativeAudioStartedAt = 0L;
        JSONObject o = new JSONObject();
        try {
            if (f == null || !f.exists() || f.length() < 1) { o.put("ok", false); o.put("error", "empty recording"); return o.toString(); }
            o.put("ok", true); o.put("path", f.getAbsolutePath()); o.put("name", f.getName()); o.put("mime", "audio/mp4"); o.put("size", f.length()); o.put("duration_ms", dur);
            if (withData) {
                InputStream in = new java.io.FileInputStream(f);
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                byte[] buf = new byte[8192]; int n;
                while ((n = in.read(buf)) > 0 && out.size() < 60_000_000) out.write(buf, 0, n);
                in.close();
                String b64 = Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP);
                o.put("dataUrl", "data:audio/mp4;base64," + b64);
            }
        } catch (Exception e) { try { o.put("ok", false); o.put("error", e.getMessage()); } catch (Exception ignored) { } }
        return o.toString();
    }




    // V249442: the old raw port-8099 socket transport was removed from MainActivity.
    // All old WebView entry points are compatibility routes into the isolated Nearby activity.
    private String disableMeshV249435() {
        try {
            getSharedPreferences(PREFS, MODE_PRIVATE).edit()
                    .putBoolean("mesh_enabled_v249435", false)
                    .remove("mesh_shared_code_v249435")
                    .remove("mesh_security_mode_v249438")
                    .apply();
            Intent stop = new Intent(this, EmergencyNeighborhoodService.class);
            stop.setAction(EmergencyNeighborhoodService.ACTION_STOP);
            stopService(stop);
        } catch (Exception ignored) { }
        return "{\"ok\":true,\"enabled\":false,\"legacy_raw_socket_removed\":true}";
    }

    private void openSettingsAction(String action) {
        try { Intent i = new Intent(action); i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(i); }
        catch (Exception e) { try { Intent i = new Intent(Settings.ACTION_SETTINGS); i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(i); } catch (Exception ignored) { } }
    }

    private String getMeshNetworkSummaryInternal() {
        JSONObject o = new JSONObject();
        try {
            o.put("ok", true);
            o.put("version", "V249462-language-complete-no-old-home");
            o.put("transport", "Nearby Connections P2P_CLUSTER");
            o.put("internet_required", false);
            o.put("manual_start_only", true);
            o.put("main_app_raw_server_socket", false);
            o.put("main_app_port_8099_listener", false);
            o.put("separate_process", ":neighborhood");
            o.put("store_forward_24h", true);
            o.put("duplicate_guard", true);
            o.put("ttl_hop_limit", true);
            o.put("physical_two_phone_test_completed", false);
        } catch (Exception ignored) { }
        return o.toString();
    }

    private void stopCallAlertInternal() {
        try {
            if (activeCallRingtone != null && activeCallRingtone.isPlaying()) activeCallRingtone.stop();
        } catch (Exception ignored) { }
        activeCallRingtone = null;
        try {
            Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
            if (v != null) v.cancel();
        } catch (Exception ignored) { }
        try {
            NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            if (nm != null) nm.cancel(4916);
        } catch (Exception ignored) { }
    }

    // V249253: compile fix for V249251/V249252 call push cancel bridge.
    // JavaScript and FCM control pushes can call this safely with or without a session id.
    private void cancelCallNotificationInternal(String sessionId) {
        try { stopCallAlertInternal(); } catch (Exception ignored) { }
        try {
            NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            if (nm != null) {
                nm.cancel(4916);
                try {
                    Set<String> ids = getSharedPreferences(PREFS, MODE_PRIVATE).getStringSet("ok_active_call_notification_ids", null);
                    if (ids != null) for (String x : ids) { try { nm.cancel(Integer.parseInt(x)); } catch (Exception ignored) { } }
                    getSharedPreferences(PREFS, MODE_PRIVATE).edit().remove("ok_active_call_notification_ids").apply();
                } catch (Exception ignored) { }
                String sid = sessionId == null ? "" : sessionId.trim();
                if (!sid.isEmpty()) {
                    nm.cancel(Math.abs(("call_" + sid).hashCode()));
                    nm.cancel(Math.abs(("pi_" + sid).hashCode()));
                }
            }
        } catch (Exception ignored) { }
    }

    private void setCallAudioModeInternal(boolean active) {
        try {
            AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
            if (am == null) return;
            if (active) {
                try { stopCallAlertInternal(); } catch (Exception ignored) { }
                try {
                    if (Build.VERSION.SDK_INT >= 26) {
                        AudioAttributes attrs = new AudioAttributes.Builder()
                                .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
                                .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                                .build();
                        callAudioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
                                .setAudioAttributes(attrs)
                                .setAcceptsDelayedFocusGain(false)
                                .build();
                        am.requestAudioFocus(callAudioFocusRequest);
                    } else {
                        am.requestAudioFocus(null, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
                    }
                } catch (Exception ignored) { }
                am.setMode(AudioManager.MODE_IN_COMMUNICATION);
                try { am.setSpeakerphoneOn(true); } catch (Exception ignored) { }
                try { am.setMicrophoneMute(false); } catch (Exception ignored) { }
                try { am.setStreamVolume(AudioManager.STREAM_VOICE_CALL, Math.max(1, am.getStreamVolume(AudioManager.STREAM_VOICE_CALL)), 0); } catch (Exception ignored) { }
                try { am.setStreamVolume(AudioManager.STREAM_MUSIC, Math.max(1, am.getStreamVolume(AudioManager.STREAM_MUSIC)), 0); } catch (Exception ignored) { }
            } else {
                try { am.setSpeakerphoneOn(false); } catch (Exception ignored) { }
                try { am.setMicrophoneMute(false); } catch (Exception ignored) { }
                am.setMode(AudioManager.MODE_NORMAL);
                try {
                    if (Build.VERSION.SDK_INT >= 26 && callAudioFocusRequest != null) am.abandonAudioFocusRequest(callAudioFocusRequest);
                    else am.abandonAudioFocus(null);
                } catch (Exception ignored) { }
                callAudioFocusRequest = null;
            }
        } catch (Exception ignored) { }
    }

    private void playAlertSoundInternal(String kind) {
        try {
            boolean isCall = "call".equalsIgnoreCase(kind) || "ring".equalsIgnoreCase(kind);
            if (isCall) stopCallAlertInternal();
            Uri uri = RingtoneManager.getDefaultUri(isCall ? RingtoneManager.TYPE_RINGTONE : RingtoneManager.TYPE_NOTIFICATION);
            if (uri == null) uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
            Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), uri);
            if (r != null) {
                if (Build.VERSION.SDK_INT >= 21) {
                    r.setAudioAttributes(new AudioAttributes.Builder()
                            .setUsage(isCall ? AudioAttributes.USAGE_NOTIFICATION_RINGTONE : AudioAttributes.USAGE_NOTIFICATION)
                            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                            .build());
                }
                r.play();
                if (isCall) {
                    activeCallRingtone = r;
                    try { new Handler(Looper.getMainLooper()).postDelayed(() -> MainActivity.this.stopCallAlertInternal(), 3500); } catch (Exception ignored) { }
                }
            }
        } catch (Exception ignored) { }
        try {
            Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
            if (v != null && v.hasVibrator()) {
                boolean isCall = "call".equalsIgnoreCase(kind) || "ring".equalsIgnoreCase(kind);
                if (Build.VERSION.SDK_INT >= 26) {
                    long[] pattern = isCall ? new long[]{0, 260, 120, 260, 120, 380} : new long[]{0, 120};
                    v.vibrate(VibrationEffect.createWaveform(pattern, -1));
                } else {
                    v.vibrate(isCall ? 700 : 140);
                }
            }
        } catch (Exception ignored) { }
    }

    private boolean foregroundEventDuplicateV249531(String title, String body, String kind) {
        try {
            String key = String.valueOf(kind) + "|" + String.valueOf(title) + "|" + String.valueOf(body);
            key = Integer.toHexString(key.hashCode());
            SharedPreferences sp = getSharedPreferences(PREFS, MODE_PRIVATE);
            long now = System.currentTimeMillis();
            String last = sp.getString("ok_foreground_event_key_v249531", "");
            long at = sp.getLong("ok_foreground_event_at_v249531", 0L);
            sp.edit().putString("ok_foreground_event_key_v249531", key).putLong("ok_foreground_event_at_v249531", now).apply();
            return key.equals(last) && now - at < 3500L;
        } catch (Exception ignored) { return false; }
    }

    private void notifyForegroundEventV249531(String title, String body, String kind) {
        try {
            String k = kind == null ? "message" : kind.trim().toLowerCase();
            if (foregroundEventDuplicateV249531(title, body, k)) return;
            boolean master = getSharedPreferences(PREFS, MODE_PRIVATE).getBoolean("ok_notifications_enabled_v249525", true);
            boolean sound = getSharedPreferences(PREFS, MODE_PRIVATE).getBoolean("ok_notification_sound_v249525", true);
            if (!master || !sound) return;
            playAlertSoundInternal("call".equals(k) ? "call" : "message");
        } catch (Exception ignored) { }
    }

    private void notifyIncomingCallInternalV249531(String title, String body, String sessionId, String callType) {
        try {
            String sid = sessionId == null ? "" : sessionId.trim();
            String type = "voice".equalsIgnoreCase(callType) ? "voice" : "video";
            if (sid.isEmpty()) sid = "legacy_" + Integer.toHexString((String.valueOf(title) + "|" + String.valueOf(body) + "|" + type).hashCode());
            SharedPreferences sp = getSharedPreferences(PREFS, MODE_PRIVATE);
            long now = System.currentTimeMillis();
            String lastSid = sp.getString("ok_call_alert_session_v249531", "");
            long lastAt = sp.getLong("ok_call_alert_at_v249531", 0L);
            boolean alreadyAlerted = sid.equals(lastSid) && now - lastAt < 120000L;
            sp.edit().putString("ok_call_alert_session_v249531", sid).putLong("ok_call_alert_at_v249531", now).apply();

            if (!alreadyAlerted) playAlertSoundInternal("call");
            if (Build.VERSION.SDK_INT >= 33 && checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
                requestPermissionList(false, false, false, true);
                return;
            }
            final String channelId = "ok_ithaki_fcm_calls_v249531";
            NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            Uri ring = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
            if (Build.VERSION.SDK_INT >= 26 && nm != null) {
                boolean en = "en".equals(sp.getString("ok_ui_language", "el"));
                NotificationChannel ch = new NotificationChannel(channelId, en ? "OK ITHAKI Calls" : "OK ITHAKI Κλήσεις", NotificationManager.IMPORTANCE_HIGH);
                ch.enableVibration(true);
                try { ch.setSound(ring, new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE).setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).build()); } catch (Exception ignored) { }
                nm.createNotificationChannel(ch);
            }
            Intent intent = new Intent(MainActivity.this, MainActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
            intent.putExtra("kind", "voice".equals(type) ? "voice_call" : "video_call");
            intent.putExtra("fcm_kind", "voice".equals(type) ? "voice_call" : "video_call");
            intent.putExtra("call_type", type);
            intent.putExtra("session_id", sid);
            intent.putExtra("call", "1");
            int requestCode = Math.abs(("pi_" + sid).hashCode());
            int flags = Build.VERSION.SDK_INT >= 23 ? (PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT) : PendingIntent.FLAG_UPDATE_CURRENT;
            PendingIntent pi = PendingIntent.getActivity(MainActivity.this, requestCode, intent, flags);
            int notificationId = Math.abs(("call_" + sid).hashCode());
            try {
                Set<String> ids = new LinkedHashSet<>();
                Set<String> old = sp.getStringSet("ok_active_call_notification_ids", null);
                if (old != null) ids.addAll(old);
                ids.add(String.valueOf(notificationId));
                sp.edit().putStringSet("ok_active_call_notification_ids", ids).apply();
            } catch (Exception ignored) { }
            NotificationCompat.Builder b = new NotificationCompat.Builder(MainActivity.this, channelId)
                    .setSmallIcon(android.R.drawable.sym_call_incoming)
                    .setContentTitle(title == null || title.trim().isEmpty() ? ("voice".equals(type) ? "OK ITHAKI — φωνητική κλήση" : "OK ITHAKI — βιντεοκλήση") : title)
                    .setContentText(body == null ? "Εισερχόμενη κλήση" : body)
                    .setAutoCancel(false).setOngoing(true).setOnlyAlertOnce(true)
                    .setCategory(NotificationCompat.CATEGORY_CALL).setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
                    .setPriority(NotificationCompat.PRIORITY_MAX).setContentIntent(pi).setTimeoutAfter(90000L);
            if (!alreadyAlerted) b.setSound(ring).setVibrate(new long[]{0,700,350,700,350,700});
            else b.setSound(null).setVibrate(new long[]{0L});
            if (nm != null) nm.notify(notificationId, b.build());
        } catch (Exception ignored) { }
    }

    private void notifyMessageInternal(String title, String body, boolean forceCall) {
        try {
            String joined = ((title == null ? "" : title) + " " + (body == null ? "" : body)).toLowerCase();
            boolean isCall = forceCall || joined.contains("call") || joined.contains("κλή") || joined.contains("klis");
            if (isCall) { notifyIncomingCallInternalV249531(title, body, "", joined.contains("φων") || joined.contains("voice") ? "voice" : "video"); return; }
            playAlertSoundInternal("message");
            if (Build.VERSION.SDK_INT >= 33 && checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
                requestPermissionList(false, false, false, true); return;
            }
            String channelId = "ok_ithaki_messages_v249467";
            NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            if (Build.VERSION.SDK_INT >= 26 && nm != null) {
                boolean en = "en".equals(getSharedPreferences(PREFS, MODE_PRIVATE).getString("ok_ui_language", "el"));
                nm.createNotificationChannel(new NotificationChannel(channelId, en ? "OK ITHAKI Messages" : "OK ITHAKI Μηνύματα", NotificationManager.IMPORTANCE_DEFAULT));
            }
            Intent intent = new Intent(MainActivity.this, MainActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
            int key = Math.abs((String.valueOf(title) + "|" + String.valueOf(body)).hashCode());
            int flags = Build.VERSION.SDK_INT >= 23 ? (PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT) : PendingIntent.FLAG_UPDATE_CURRENT;
            PendingIntent pi = PendingIntent.getActivity(MainActivity.this, key, intent, flags);
            NotificationCompat.Builder b = new NotificationCompat.Builder(MainActivity.this, channelId)
                    .setSmallIcon(android.R.drawable.ic_dialog_email)
                    .setContentTitle(title == null ? "OK ITHAKI" : title)
                    .setContentText(body == null ? "Νέο μήνυμα" : body)
                    .setAutoCancel(true).setCategory(NotificationCompat.CATEGORY_MESSAGE)
                    .setVisibility(NotificationCompat.VISIBILITY_PUBLIC).setPriority(NotificationCompat.PRIORITY_HIGH)
                    .setDefaults(NotificationCompat.DEFAULT_ALL).setOnlyAlertOnce(true).setContentIntent(pi);
            if (nm != null) nm.notify(Math.abs(("msg_local_" + key).hashCode()), b.build());
        } catch (Exception ignored) { }
    }

    // V249255: register FCM token immediately at app boot. This removes the old
    // behavior where closed-app push sometimes worked only after opening the bell.
    private void registerFcmTokenToServerInternal(String roomId, String deviceId, String nickname, String base, String token) {
        if (TorIntegrationV249435.isRequested(this)) return;
        try {
            final String r = roomId == null || roomId.trim().isEmpty() ? "ithaki-phone-test" : roomId.trim();
            final String d = deviceId == null ? "" : deviceId.trim();
            final String n = nickname == null ? "" : nickname.trim();
            final String b = base == null || base.trim().isEmpty() ? DEFAULT_PUBLIC_SERVER : base.trim();
            final String t = token == null ? "" : token.trim();
            getSharedPreferences(PREFS, MODE_PRIVATE).edit()
                    .putString("ok_fcm_room", r)
                    .putString("ok_fcm_device", d)
                    .putString("ok_fcm_nickname", n)
                    .putString("ok_fcm_server", b)
                    .putString("ok_fcm_token", t)
                    .apply();
            if (d.isEmpty() || t.isEmpty() || executor == null) return;
            executor.execute(() -> {
                HttpURLConnection conn = null;
                try {
                    URL url = new URL(b.replaceAll("/+$", "") + "/api/push/fcm/register");
                    conn = (HttpURLConnection) url.openConnection();
                    conn.setRequestMethod("POST");
                    conn.setConnectTimeout(7000);
                    conn.setReadTimeout(7000);
                    conn.setDoOutput(true);
                    conn.setRequestProperty("Content-Type", "application/json; charset=utf-8");
                    JSONObject o = new JSONObject();
                    o.put("room_id", r); o.put("device_id", d); o.put("nickname", n);
                    o.put("token", t); o.put("platform", "android"); o.put("source", "android-boot-v249383");
                    byte[] bytes = o.toString().getBytes(StandardCharsets.UTF_8);
                    OutputStream os = conn.getOutputStream(); os.write(bytes); os.close();
                    try { conn.getResponseCode(); } catch (Exception ignored) { }
                } catch (Exception ignored) {
                } finally { try { if (conn != null) conn.disconnect(); } catch (Exception ignored) { } }
            });
        } catch (Exception ignored) { }
    }


    // V249477: external-message content is encrypted and stored only on this Android device.
    private void processExternalMessageIntentV249477(Intent intent) {
        if (intent == null) return;
        try {
            String action = intent.getAction();
            if ("com.okithaki.OPEN_EXTERNAL_CENTER".equals(action)) {
                externalCenterLaunchPendingV249477 = true;
                return;
            }
            if (!Intent.ACTION_SEND.equals(action) && !Intent.ACTION_SEND_MULTIPLE.equals(action)) return;
            JSONArray imported = new JSONArray();
            String text = intent.getStringExtra(Intent.EXTRA_TEXT);
            if (text != null && !text.trim().isEmpty()) {
                JSONObject item = new JSONObject();
                item.put("source", "shared"); item.put("item_type", "message");
                item.put("title", intent.getStringExtra(Intent.EXTRA_SUBJECT)); item.put("body", text);
                item.put("created_at", System.currentTimeMillis()); item.put("unread", false);
                item.put("important", false); item.put("archived", false); item.put("imported_by_user", true);
                imported.put(ExternalMessageStoreV249477.put(this, item));
            }
            if (Intent.ACTION_SEND.equals(action)) {
                Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
                if (uri != null) imported.put(importSharedUriV249477(uri, intent.getType()));
            } else {
                ArrayList<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
                if (uris != null) for (Uri uri : uris) if (uri != null) imported.put(importSharedUriV249477(uri, intent.getType()));
            }
            if (imported.length() > 0) externalCenterLaunchPendingV249477 = true;
        } catch (Exception ignored) { }
    }

    private JSONObject importSharedUriV249477(Uri uri, String mime) {
        JSONObject item = new JSONObject();
        try {
            String name = sharedDisplayNameV249477(uri);
            File dir = new File(getFilesDir(), "external_message_center_v249477");
            if (!dir.exists()) dir.mkdirs();
            File outFile = new File(dir, System.currentTimeMillis() + "_" + safeFileName(name));
            try (InputStream in = getContentResolver().openInputStream(uri); FileOutputStream out = new FileOutputStream(outFile)) {
                if (in == null) throw new IllegalStateException("shared_file_unavailable");
                byte[] buf = new byte[65536]; int n; while ((n = in.read(buf)) > 0) out.write(buf, 0, n);
            }
            item.put("source", "shared"); item.put("item_type", "attachment"); item.put("title", name);
            item.put("body", ""); item.put("local_path", outFile.getAbsolutePath());
            item.put("mime_type", mime == null ? "application/octet-stream" : mime);
            item.put("created_at", System.currentTimeMillis()); item.put("unread", false);
            item.put("important", false); item.put("archived", false); item.put("imported_by_user", true);
            return ExternalMessageStoreV249477.put(this, item);
        } catch (Exception e) { return item; }
    }

    private String sharedDisplayNameV249477(Uri uri) {
        String name = "shared_file";
        Cursor c = null;
        try {
            c = getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null);
            if (c != null && c.moveToFirst()) { String v = c.getString(0); if (v != null && !v.trim().isEmpty()) name = v; }
        } catch (Exception ignored) { }
        finally { try { if (c != null) c.close(); } catch (Exception ignored) { } }
        return name;
    }

    private void notifyNativeShareResultV249521(boolean ok, String message) {
        runOnUiThread(() -> {
            try {
                if (webView == null) return;
                String msg = JSONObject.quote(message == null ? "" : message);
                String js = "window.dispatchEvent(new CustomEvent('ithaki:native-share-result',{detail:{ok:" + (ok ? "true" : "false") + ",message:" + msg + "}}));";
                webView.evaluateJavascript(js, null);
            } catch (Exception ignored) { }
        });
    }

    public class Bridge {
        @JavascriptInterface public String getServerBase() { return serverBase == null ? "" : serverBase; }
        @JavascriptInterface public String getPublicTokenFallback() { return publicAppToken == null || publicAppToken.trim().isEmpty() ? PUBLIC_APP_TOKEN_FALLBACK : publicAppToken.trim(); }
        @JavascriptInterface public String setUiLanguage(String lang) {
            String next = lang != null && lang.trim().toLowerCase().startsWith("en") ? "en" : "el";
            getSharedPreferences(PREFS, MODE_PRIVATE).edit().putString("ok_ui_language", next).apply();
            return next;
        }
        @JavascriptInterface public String getUiLanguage() {
            return getSharedPreferences(PREFS, MODE_PRIVATE).getString("ok_ui_language", "el");
        }
        @JavascriptInterface public void requestMediaPermissions(boolean needVideo) { runOnUiThread(() -> MainActivity.this.requestPermissionList(needVideo, true, false, false)); }
        @JavascriptInterface public boolean hasMediaPermissions(boolean needVideo) { return MainActivity.this.hasRuntimePermissions(needVideo, true); }
        @JavascriptInterface public void requestVoiceCallPermissions() { runOnUiThread(() -> MainActivity.this.requestPermissionList(false, true, false, false)); }
        @JavascriptInterface public void requestVideoCallPermissions() { runOnUiThread(() -> MainActivity.this.requestPermissionList(true, true, false, false)); }
        @JavascriptInterface public void openAppSettings() { runOnUiThread(() -> MainActivity.this.openAppSettingsScreen()); }
        @JavascriptInterface public void requestCameraPermission() { runOnUiThread(() -> MainActivity.this.requestPermissionList(true, false, false, false)); }
        @JavascriptInterface public void requestMicrophonePermission() { runOnUiThread(() -> MainActivity.this.requestPermissionList(false, true, false, false)); }
        @JavascriptInterface public void requestFilesPermission() { runOnUiThread(() -> MainActivity.this.requestPermissionList(false, false, true, false)); }
        @JavascriptInterface public void requestNotificationPermission() { runOnUiThread(() -> MainActivity.this.requestPermissionList(false, false, false, true)); }
        @JavascriptInterface public void requestAllProfilePermissions() { runOnUiThread(() -> MainActivity.this.requestPermissionList(true, true, true, true)); }
        @JavascriptInterface public String getPermissionState() { return MainActivity.this.permissionStateJson(); }
        @JavascriptInterface public String startNativeAudioRecord() { return MainActivity.this.startNativeAudioRecordInternal(); }
        @JavascriptInterface public String stopNativeAudioRecord() { return MainActivity.this.stopNativeAudioRecordInternal(true); }
        @JavascriptInterface public String cancelNativeAudioRecord() { return MainActivity.this.stopNativeAudioRecordInternal(false); }

        @JavascriptInterface public String requestEmergencyMeshPermissions() { return openEmergencyNeighborhoodV249442(); }
        @JavascriptInterface public void openHotspotSettings() { runOnUiThread(() -> MainActivity.this.openSettingsAction("android.settings.WIFI_TETHER_SETTINGS")); }
        @JavascriptInterface public void openWifiSettings() { runOnUiThread(() -> MainActivity.this.openSettingsAction(Settings.ACTION_WIFI_SETTINGS)); }
        @JavascriptInterface public String getMeshNetworkSummary() { return MainActivity.this.getMeshNetworkSummaryInternal(); }
        @JavascriptInterface public String meshStatusV249435() { return MainActivity.this.getMeshNetworkSummaryInternal(); }
        @JavascriptInterface public String enableMeshV249435(String sharedCode) { return openEmergencyNeighborhoodV249442(); }
        @JavascriptInterface public String disableMeshV249435() {
            runOnUiThread(() -> {
                try {
                    Intent stop = new Intent(MainActivity.this, EmergencyNeighborhoodService.class);
                    stop.setAction(EmergencyNeighborhoodService.ACTION_STOP);
                    MainActivity.this.stopService(stop);
                } catch (Exception ignored) { }
                try { MainActivity.this.disableMeshV249435(); } catch (Exception ignored) { }
            });
            return "{\"ok\":true,\"isolated_neighborhood_stop_requested\":true}";
        }

        // V249442: the emergency neighborhood runs in a separate :neighborhood process.
        // The legacy raw socket implementation is absent from MainActivity; only the isolated Nearby activity can start.
        @JavascriptInterface public String openEmergencyNeighborhoodV249442() {
            runOnUiThread(() -> {
                try { MainActivity.this.startActivity(new Intent(MainActivity.this, EmergencyNeighborhoodActivity.class)); }
                catch (Exception ignored) { }
            });
            return "{\"ok\":true,\"version\":\"249442\",\"isolated_process\":true}";
        }
        @JavascriptInterface public String startPhoneMicroServer() { return openEmergencyNeighborhoodV249442(); }
        @JavascriptInterface public String getPhoneNodeInfo() { return "{\"ok\":false,\"moved_to\":\"EmergencyNeighborhoodActivityV249442\",\"automatic_start\":false}"; }
        @JavascriptInterface public String scanPhoneNodes() { return openEmergencyNeighborhoodV249442(); }
        @JavascriptInterface public String sendPhoneNodeMessage(String base, String text, String toId) { return openEmergencyNeighborhoodV249442(); }
        @JavascriptInterface public String getPhoneNodeInbox() { return "[]"; }
        @JavascriptInterface public String startLocalNetworkSearch() {
            MainActivity.this.startLocalNetworkSearchAsync();
            return "{\"ok\":true,\"phone_microserver_started\":false,\"version\":\"249442\"}";
        }
        @JavascriptInterface public void saveServerBase(String base) { MainActivity.this.saveServerBase(base); serverBase = base; }
        @JavascriptInterface public void setServerAndReload(String base) { runOnUiThread(() -> { try { MainActivity.this.saveServerBase(base); serverBase = base == null ? "" : base.trim(); MainActivity.this.loadApp(serverBase); } catch (Exception ignored) { } }); }
        @JavascriptInterface public void clearServerBase() { MainActivity.this.saveServerBase(""); serverBase = ""; }
        @JavascriptInterface public void reloadServer() { runOnUiThread(() -> MainActivity.this.loadApp(serverBase == null || serverBase.trim().isEmpty() ? DEFAULT_PUBLIC_SERVER : serverBase)); }
        @JavascriptInterface public void startStayConnected() { if (TorIntegrationV249435.isRequested(MainActivity.this)) return; runOnUiThread(() -> { try { android.content.Intent i = new android.content.Intent(MainActivity.this, StayConnectedService.class); if (android.os.Build.VERSION.SDK_INT >= 26) MainActivity.this.startForegroundService(i); else MainActivity.this.startService(i); } catch (Exception ignored) {} }); }
        @JavascriptInterface public void stopStayConnected() { runOnUiThread(() -> { try { MainActivity.this.stopService(new android.content.Intent(MainActivity.this, StayConnectedService.class)); } catch (Exception ignored) {} }); }
        @JavascriptInterface public void openExternal(String url) { try { String v=url==null?"":url.trim().toLowerCase(); if (TorIntegrationV249435.isRequested(MainActivity.this) && (v.startsWith("http://") || v.startsWith("https://"))) return; startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); } catch (Exception ignored) { } }
        @JavascriptInterface public void save(String key, String value) { getSharedPreferences(PREFS, MODE_PRIVATE).edit().putString(key, value == null ? "" : value).apply(); }
        @JavascriptInterface public String load(String key) { return getSharedPreferences(PREFS, MODE_PRIVATE).getString(key, ""); }
        @JavascriptInterface public void playAlertSound(String kind) { runOnUiThread(() -> MainActivity.this.playAlertSoundInternal(kind)); }
        @JavascriptInterface public void notifyInAppEvent(String title, String body, String kind) { runOnUiThread(() -> MainActivity.this.notifyForegroundEventV249531(title, body, kind)); }
        @JavascriptInterface public void notifyMessage(String title, String body) { runOnUiThread(() -> MainActivity.this.notifyMessageInternal(title, body, false)); }
        @JavascriptInterface public void notifyCall(String title, String body) {
            try { runOnUiThread(() -> MainActivity.this.notifyIncomingCallInternalV249531(title, body, "", "video")); } catch (Exception ignored) { }
        }
        @JavascriptInterface public void notifyIncomingCallV249531(String title, String body, String sessionId, String callType) {
            try { runOnUiThread(() -> MainActivity.this.notifyIncomingCallInternalV249531(title, body, sessionId, callType)); } catch (Exception ignored) { }
        }
        @JavascriptInterface public void stopCallAlert() {
            try { runOnUiThread(() -> MainActivity.this.stopCallAlertInternal()); } catch (Exception ignored) { }
        }
        @JavascriptInterface public void cancelCallNotification(String sessionId) {
            try { runOnUiThread(() -> MainActivity.this.cancelCallNotificationInternal(sessionId)); } catch (Exception ignored) { }
        }
        @JavascriptInterface public void setCallAudioMode(boolean active) {
            try { runOnUiThread(() -> MainActivity.this.setCallAudioModeInternal(active)); } catch (Exception ignored) { }
        }

        @JavascriptInterface public String setProfileFolder(String profileName) {
            try {
                String n = safeFileName(profileName == null || profileName.trim().isEmpty() ? "Επισκέπτης" : profileName.trim());
                getSharedPreferences(PREFS, MODE_PRIVATE).edit().putString("ok_profile_folder_name", n).apply();
                File dir = okIthakiProfileDir(n);
                return dir.getAbsolutePath();
            } catch (Exception e) { return ""; }
        }
        @JavascriptInterface public String getProfileFolder() {
            try {
                String n = getSharedPreferences(PREFS, MODE_PRIVATE).getString("ok_profile_folder_name", "Επισκέπτης");
                return okIthakiProfileDir(n).getAbsolutePath();
            } catch (Exception e) { return ""; }
        }
        @JavascriptInterface public String saveBase64ToProfile(String type, String filename, String mime, String base64Data) {
            try {
                String profile = getSharedPreferences(PREFS, MODE_PRIVATE).getString("ok_profile_folder_name", "Επισκέπτης");
                File dir = okIthakiTypedDir(profile, type);
                String name = safeFileName(filename == null || filename.trim().isEmpty() ? ("ok_ithaki_" + System.currentTimeMillis()) : filename.trim());
                File f = new File(dir, name);
                if (f.exists()) {
                    int dot = name.lastIndexOf('.');
                    String base = dot > 0 ? name.substring(0, dot) : name;
                    String ext = dot > 0 ? name.substring(dot) : "";
                    f = new File(dir, base + "_" + System.currentTimeMillis() + ext);
                }
                byte[] data = Base64.decode(stripDataPrefix(base64Data), Base64.DEFAULT);
                FileOutputStream out = new FileOutputStream(f);
                out.write(data);
                out.close();
                return f.getAbsolutePath();
            } catch (Exception e) {
                return "error: " + e.getMessage();
            }
        }
        @JavascriptInterface public void setScreenBrightness(float value) {
            try {
                runOnUiThread(() -> {
                    try {
                        WindowManager.LayoutParams lp = getWindow().getAttributes();
                        lp.screenBrightness = value; // 1.0 outdoor, -1.0 system default
                        getWindow().setAttributes(lp);
                    } catch (Exception ignored) { }
                });
            } catch (Exception ignored) { }
        }
        // OK ITHAKI V249389: real native secure-DM bridge.
        @JavascriptInterface public String secureMessagingStatusV249389() {
            try { return SecureMessageCryptoV249389.status(MainActivity.this).toString(); }
            catch (Exception e) { return "{\"ok\":false,\"error\":\"status_failed\"}"; }
        }
        @JavascriptInterface public String getSecurePublicKeyV249389() {
            try { return SecureMessageCryptoV249389.publicKeyBase64(); }
            catch (Exception e) { return ""; }
        }
        @JavascriptInterface public String encryptSecureMessageV249389(String plaintext, String recipientPublicKeyB64, String aadJson) {
            try { return SecureMessageCryptoV249389.encryptEnvelope(plaintext, recipientPublicKeyB64, aadJson); }
            catch (Exception e) {
                try { return new JSONObject().put("ok", false).put("error", String.valueOf(e.getMessage())).toString(); }
                catch (Exception ignored) { return "{\"ok\":false}"; }
            }
        }
        @JavascriptInterface public String decryptSecureMessageV249389(String envelopeJson) {
            try { return new JSONObject().put("ok", true).put("plaintext", SecureMessageCryptoV249389.decryptEnvelope(envelopeJson)).toString(); }
            catch (Exception e) {
                try { return new JSONObject().put("ok", false).put("error", String.valueOf(e.getMessage())).toString(); }
                catch (Exception ignored) { return "{\"ok\":false}"; }
            }
        }
        @JavascriptInterface public String getSecureMessageSettingsV249389() {
            try { return SecureMessageCryptoV249389.getSettings(MainActivity.this).toString(); }
            catch (Exception e) { return "{}"; }
        }
        @JavascriptInterface public String setSecureMessageSettingsV249389(String json) {
            try { return SecureMessageCryptoV249389.saveSettings(MainActivity.this, json).toString(); }
            catch (Exception e) { return "{\"ok\":false}"; }
        }
        @JavascriptInterface public void setSecureScreenV249389(boolean enabled) {
            runOnUiThread(() -> {
                try {
                    if (enabled) {
                        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
                    } else {
                        getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
                    }
                    getSharedPreferences(PREFS, MODE_PRIVATE).edit()
                            .putBoolean("secure_screen_active_v249389", enabled).apply();
                } catch (Exception ignored) { }
            });
        }
        // OK ITHAKI V249396: honest Tor-ready bridge. No proxy is claimed active.
        @JavascriptInterface public String torStatusV249396() { return torStatusV249435(); }
        @JavascriptInterface public String openTorSettingsV249396() { return openOrbotV249435(); }
        // OK ITHAKI V249435: real fail-closed WebView Tor transport through Orbot.
        @JavascriptInterface public String torStatusV249435() { try{return TorIntegrationV249435.status(MainActivity.this).toString();}catch(Exception e){return "{\"ok\":false,\"native_proxy_active\":false}";} }
        @JavascriptInterface public String openOrbotV249435() { try{return TorIntegrationV249435.openOrbot(MainActivity.this).toString();}catch(Exception e){return "{\"ok\":false,\"opened\":false}";} }
        @JavascriptInterface public String enableTorSafeModeV249435() {
            try {
                runOnUiThread(() -> { try { webView.loadUrl("about:blank"); stopService(new Intent(MainActivity.this, StayConnectedService.class)); } catch(Exception ignored){} });
                String result=TorIntegrationV249435.enableBlocking(MainActivity.this).toString(); JSONObject o=new JSONObject(result);torSafeModeRequestedV249435=true;
                try { stopService(new Intent(MainActivity.this, EmergencyNeighborhoodService.class)); } catch (Exception ignored) { } unregisterNetworkReconnectWatcher();
                if(o.optBoolean("native_proxy_active",false))runOnUiThread(()->loadApp(serverBase==null||serverBase.trim().isEmpty()?DEFAULT_PUBLIC_SERVER:serverBase));
                else runOnUiThread(()->webView.loadDataWithBaseURL(null,"<html><meta name='viewport' content='width=device-width'><body style='background:#07131f;color:#fff;font-family:sans-serif;padding:28px'><h2>🧅 TOR Safe Mode</h2><p>Δεν έγινε απευθείας σύνδεση. Άνοιξε το Orbot και περίμενε να συνδεθεί.</p></body></html>","text/html","UTF-8",null));
                return result;
            } catch(Exception e){return "{\"ok\":false,\"reason\":\"tor_enable_error\"}";}
        }
        @JavascriptInterface public String disableTorSafeModeV249435() {
            try { String result=TorIntegrationV249435.disableBlocking(MainActivity.this).toString();torSafeModeRequestedV249435=false;runOnUiThread(()->{try{FirebaseMessaging.getInstance().setAutoInitEnabled(true);}catch(Exception ignored){}startStayConnectedAtBootV249383();refreshFcmAtBootV249383();registerNetworkReconnectWatcher();loadApp(serverBase==null||serverBase.trim().isEmpty()?DEFAULT_PUBLIC_SERVER:serverBase);});return result; }
            catch(Exception e){return "{\"ok\":false,\"reason\":\"tor_disable_error\"}";}
        }
        // OK ITHAKI V249440: feed autoplay/resume and current wall; V249290 neighborhood preserved; no automatic native Mesh.
        @JavascriptInterface public String privateRelayStatusV249436() { try { return IthakiPrivateRelayV249436.status(MainActivity.this).toString(); } catch(Exception e) { return "{\"ok\":false}"; } }
        @JavascriptInterface public String testPrivateRelayV249436(String entry, String code) { try { return IthakiPrivateRelayV249436.test(entry, code).toString(); } catch(Exception e) { return "{\"ok\":false,\"reason\":\"test_failed\"}"; } }
        @JavascriptInterface public String enablePrivateRelayV249436(String entry, String code, boolean strict) {
            try {
                JSONObject o = IthakiPrivateRelayV249436.enable(MainActivity.this, entry, code, strict);
                if (!o.optBoolean("ok", false)) return o.toString();
                if (strict) {
                    try { FirebaseMessaging.getInstance().setAutoInitEnabled(false); FirebaseMessaging.getInstance().deleteToken(); } catch(Exception ignored) { }
                    try { stopService(new Intent(MainActivity.this, StayConnectedService.class)); } catch(Exception ignored) { }
                }
                runOnUiThread(() -> { IthakiPrivateRelayV249436.applyAccessCookie(MainActivity.this, webView); loadApp(IthakiPrivateRelayV249436.entryBase(MainActivity.this)); });
                return o.toString();
            } catch(Exception e) { return "{\"ok\":false,\"reason\":\"enable_failed\"}"; }
        }
        @JavascriptInterface public String disablePrivateRelayV249436() {
            try {
                JSONObject o = IthakiPrivateRelayV249436.disable(MainActivity.this);
                try { FirebaseMessaging.getInstance().setAutoInitEnabled(true); } catch(Exception ignored) { }
                runOnUiThread(() -> { startStayConnectedAtBootV249383(); refreshFcmAtBootV249383(); loadApp(DEFAULT_PUBLIC_SERVER); });
                return o.toString();
            } catch(Exception e) { return "{\"ok\":false,\"reason\":\"disable_failed\"}"; }
        }
        @JavascriptInterface public String getFcmToken() {
            return getSharedPreferences(PREFS, MODE_PRIVATE).getString("ok_fcm_token", "");
        }
        @JavascriptInterface public void requestFcmToken(String roomId, String deviceId, String nickname, String serverBase) {
            if (TorIntegrationV249435.isRequested(MainActivity.this) || IthakiPrivateRelayV249436.isStrict(MainActivity.this)) return;
            try {
                final String r = roomId == null ? "ithaki-phone-test" : roomId;
                final String d = deviceId == null ? "" : deviceId;
                final String n = nickname == null ? "" : nickname;
                final String b = serverBase == null || serverBase.trim().isEmpty() ? DEFAULT_PUBLIC_SERVER : serverBase;
                getSharedPreferences(PREFS, MODE_PRIVATE).edit()
                    .putString("ok_fcm_room", r).putString("ok_fcm_device", d)
                    .putString("ok_fcm_nickname", n).putString("ok_fcm_server", b).apply();
                FirebaseMessaging.getInstance().getToken().addOnCompleteListener(task -> {
                    try {
                        if (task.isSuccessful() && task.getResult() != null) {
                            MainActivity.this.registerFcmTokenToServerInternal(r, d, n, b, task.getResult());
                        }
                    } catch (Exception ignored) { }
                });
            } catch (Exception ignored) { }
        }
        @JavascriptInterface public void chooseRecoveryFolder() {
            try {
                Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
                i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
                startActivityForResult(i, RECOVERY_TREE_REQ);
            } catch (Exception e) {
                evalJs("window.onOkIthakiRecoveryFolderSelected&&window.onOkIthakiRecoveryFolderSelected('')");
            }
        }
        @JavascriptInterface public String getRecoveryFolderUri() {
            return getSharedPreferences(PREFS, MODE_PRIVATE).getString("ok_recovery_tree_uri", "");
        }
        @JavascriptInterface public String exportRecoverySnapshot(String filename, String jsonData) {
            String res = writeTextToRecoveryFolder(filename, "application/json", jsonData);
            if (res == null || res.startsWith("NO_") || res.startsWith("CREATE_FAILED") || res.startsWith("OPEN_FAILED") || res.startsWith("error:")) {
                try {
                    File dir = okIthakiTypedDir(getSharedPreferences(PREFS, MODE_PRIVATE).getString("ok_profile_folder_name", "Επισκέπτης"), "memories");
                    File f = new File(dir, safeFileName(filename == null ? "ok_ithaki_recovery.json" : filename));
                    FileOutputStream out = new FileOutputStream(f);
                    out.write((jsonData == null ? "" : jsonData).getBytes(StandardCharsets.UTF_8));
                    out.close();
                    return f.getAbsolutePath();
                } catch (Exception e) { return "error: " + e.getMessage(); }
            }
            return res;
        }
        @JavascriptInterface public void importRecoverySnapshot() {
            try {
                Intent pick = new Intent(Intent.ACTION_OPEN_DOCUMENT);
                pick.addCategory(Intent.CATEGORY_OPENABLE);
                pick.setType("application/json");
                pick.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
                startActivityForResult(Intent.createChooser(pick, "OK ITHAKI recovery file"), RECOVERY_IMPORT_REQ);
            } catch (Exception e) { evalJs("window.onOkIthakiRecoveryImported&&window.onOkIthakiRecoveryImported('')"); }
        }
        @JavascriptInterface public void openRecoveryFolder() {
            try {
                Uri tree = selectedRecoveryTreeUri();
                if (tree == null) return;
                Intent i = new Intent(Intent.ACTION_VIEW);
                i.setData(tree);
                i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(i);
            } catch (Exception ignored) { }
        }
        @JavascriptInterface public String downloadUrlToProfile(String url, String filename, String folder) {
            return enqueueDownload(url, filename, null);
        }
        @JavascriptInterface public String openFilePath(String path, String mime) {
            try {
                if (path == null || path.trim().isEmpty()) return "empty";
                File f = new File(path);
                if (!f.exists()) return "missing";
                Uri uri = uriForFile(f);
                Intent i = new Intent(Intent.ACTION_VIEW);
                i.setDataAndType(uri, (mime == null || mime.trim().isEmpty()) ? "application/octet-stream" : mime.trim());
                i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(Intent.createChooser(i, "Open OK ITHAKI file"));
                return "ok";
            } catch (Exception e) { return "error: " + e.getMessage(); }
        }
        @JavascriptInterface public String shareUrl(String url, String title) {
            try {
                Intent i = new Intent(Intent.ACTION_SEND);
                i.setType("text/plain");
                i.putExtra(Intent.EXTRA_TEXT, url == null ? "" : url);
                i.putExtra(Intent.EXTRA_TITLE, title == null ? "OK ITHAKI" : title);
                startActivity(Intent.createChooser(i, "Share OK ITHAKI link"));
                return "ok";
            } catch (Exception e) { return "error: " + e.getMessage(); }
        }
        @JavascriptInterface public String shareFilePath(String path, String mime) {
            try {
                if (path == null || path.trim().isEmpty()) return "empty";
                File f = new File(path);
                if (!f.exists()) return "missing";
                return shareFileUri(uriForFile(f), mime, f.getName());
            } catch (Exception e) { return "error: " + e.getMessage(); }
        }
        @JavascriptInterface public String shareBase64File(String filename, String mime, String base64Data) {
            try {
                String name = safeFileName(filename);
                byte[] data = Base64.decode(stripDataPrefix(base64Data), Base64.DEFAULT);
                File f = new File(ensureSharedDir(), name);
                FileOutputStream out = new FileOutputStream(f);
                out.write(data);
                out.close();
                return shareFileUri(uriForFile(f), mime, name);
            } catch (Exception e) { return "error: " + e.getMessage(); }
        }
        @JavascriptInterface public String shareRemoteFileV249521(String url, String filename, String mime) {
            try {
                if (nativeRemoteShareBusyV249522) return "busy: preparing_file";
                final String remote = url == null ? "" : url.trim();
                if (!remote.startsWith("http://") && !remote.startsWith("https://")) return "error: invalid_url";
                nativeRemoteShareBusyV249522 = true;
                final String name = safeFileName(filename == null || filename.trim().isEmpty() ? ("ITHAKI_file_" + System.currentTimeMillis()) : filename.trim());
                final String type = mime == null || mime.trim().isEmpty() ? "application/octet-stream" : mime.trim();
                new Thread(() -> {
                    HttpURLConnection conn = null;
                    try {
                        URL u = new URL(remote);
                        conn = (HttpURLConnection) u.openConnection();
                        conn.setConnectTimeout(20000);
                        conn.setReadTimeout(120000);
                        conn.setInstanceFollowRedirects(true);
                        conn.setRequestProperty("User-Agent", "OK-ITHAKI-Android-V249522");
                        String token = publicAppToken == null ? "" : publicAppToken.trim();
                        if (!token.isEmpty()) conn.setRequestProperty("X-ITHAKI-TOKEN", token);
                        int code = conn.getResponseCode();
                        if (code < 200 || code >= 300) throw new IllegalStateException("HTTP_" + code);
                        File f = new File(ensureSharedDir(), name);
                        try (InputStream in = conn.getInputStream(); FileOutputStream out = new FileOutputStream(f)) {
                            byte[] buf = new byte[131072]; int n;
                            while ((n = in.read(buf)) > 0) out.write(buf, 0, n);
                        }
                        final Uri uri = uriForFile(f);
                        runOnUiThread(() -> {
                            String result = shareFileUri(uri, type, name);
                            boolean ok = result != null && result.toLowerCase().startsWith("ok");
                            notifyNativeShareResultV249521(ok, ok ? "ready" : result);
                        });
                    } catch (Exception e) {
                        notifyNativeShareResultV249521(false, e.getMessage() == null ? "share_failed" : e.getMessage());
                    } finally {
                        nativeRemoteShareBusyV249522 = false;
                        if (conn != null) conn.disconnect();
                    }
                }, "ithaki-share-v249522").start();
                return "queued";
            } catch (Exception e) { return "error: " + e.getMessage(); }
        }
        @JavascriptInterface public String getIthakiNativeBuildV249524() {
            return "V249524|versionCode=249525|actual-share-choice|single-bil|single-community";
        }

        @JavascriptInterface public String shareBase64FileV249524(String filename, String mime, String base64Data) {
            return shareBase64FileV249523(filename, mime, base64Data);
        }

        @JavascriptInterface public String shareRemoteFileV249524(String url, String filename, String mime, String webToken, String deviceId, String roomId) {
            return shareRemoteFileV249523(url, filename, mime, webToken, deviceId, roomId);
        }

        @JavascriptInterface public String getIthakiNativeBuildV249523() {
            try {
                JSONObject o = new JSONObject();
                o.put("ok", true); o.put("version_name", "2.4.9.523-apk-sync-actual-share-single-bil-home");
                o.put("version_code", 249524); o.put("share_bridge", "v249523");
                return o.toString();
            } catch (Exception e) { return "{\"ok\":false}"; }
        }
        @JavascriptInterface public String shareBase64FileV249523(String filename, String mime, String base64Data) {
            return shareBase64File(filename, mime, base64Data);
        }
        @JavascriptInterface public String shareRemoteFileV249523(String url, String filename, String mime, String webToken, String deviceId, String roomId) {
            try {
                final String remote = url == null ? "" : url.trim();
                if (!remote.startsWith("http://") && !remote.startsWith("https://")) return "error: invalid_url";
                final String originalName = safeFileName(filename == null || filename.trim().isEmpty() ? ("ITHAKI_file_" + System.currentTimeMillis()) : filename.trim());
                final String typeHint = mime == null || mime.trim().isEmpty() ? "application/octet-stream" : mime.trim();
                final String tokenHint = webToken == null ? "" : webToken.trim();
                final String deviceHint = deviceId == null ? "" : deviceId.trim();
                final String roomHint = roomId == null ? "" : roomId.trim();
                new Thread(() -> {
                    HttpURLConnection conn = null;
                    try {
                        URL u = new URL(remote);
                        conn = (HttpURLConnection) u.openConnection();
                        conn.setConnectTimeout(25000);
                        conn.setReadTimeout(180000);
                        conn.setInstanceFollowRedirects(true);
                        conn.setRequestProperty("User-Agent", "OK-ITHAKI-Android-V249523");
                        conn.setRequestProperty("Accept", "*/*");
                        String tok = !tokenHint.isEmpty() ? tokenHint : (publicAppToken == null ? "" : publicAppToken.trim());
                        if (!tok.isEmpty()) { conn.setRequestProperty("X-ITHAKI-TOKEN", tok); conn.setRequestProperty("Authorization", "Bearer " + tok); }
                        if (!deviceHint.isEmpty()) { conn.setRequestProperty("X-ITHAKI-DEVICE-ID", deviceHint); conn.setRequestProperty("X-DEVICE-ID", deviceHint); }
                        if (!roomHint.isEmpty()) conn.setRequestProperty("X-ITHAKI-ROOM", roomHint);
                        try { String cookie = CookieManager.getInstance().getCookie(remote); if (cookie != null && !cookie.trim().isEmpty()) conn.setRequestProperty("Cookie", cookie); } catch (Exception ignored) { }
                        int code = conn.getResponseCode();
                        if (code < 200 || code >= 300) throw new IllegalStateException("HTTP_" + code);
                        String serverType = conn.getContentType();
                        final String shareType = (typeHint.equals("application/octet-stream") && serverType != null && !serverType.trim().isEmpty()) ? serverType.split(";")[0].trim() : typeHint;
                        File f = new File(ensureSharedDir(), System.currentTimeMillis() + "_" + originalName);
                        try (InputStream in = conn.getInputStream(); FileOutputStream out = new FileOutputStream(f)) {
                            byte[] buf = new byte[131072]; int count; long total = 0;
                            while ((count = in.read(buf)) > 0) { out.write(buf, 0, count); total += count; if (total > 2_147_000_000L) throw new IllegalStateException("file_too_large"); }
                        }
                        final Uri uri = uriForFile(f);
                        runOnUiThread(() -> {
                            String result = shareFileUri(uri, shareType, originalName);
                            boolean ok = result != null && result.toLowerCase().startsWith("ok");
                            notifyNativeShareResultV249521(ok, ok ? "share_sheet_opened" : result);
                        });
                    } catch (Exception e) {
                        notifyNativeShareResultV249521(false, e.getClass().getSimpleName() + ": " + (e.getMessage() == null ? "share_failed" : e.getMessage()));
                    } finally { if (conn != null) conn.disconnect(); }
                }, "ithaki-share-v249523-" + System.currentTimeMillis()).start();
                return "queued";
            } catch (Exception e) { return "error: " + e.getClass().getSimpleName() + ": " + e.getMessage(); }
        }
        @JavascriptInterface public boolean externalMessageCenterNativeV249477() { return true; }
        @JavascriptInterface public String getExternalMessageCenterItemsV249477() { return ExternalMessageStoreV249477.list(MainActivity.this).toString(); }
        @JavascriptInterface public int dedupeExternalMessageCenterV249529() { return ExternalMessageStoreV249477.dedupeNow(MainActivity.this); }
        @JavascriptInterface public String upsertExternalMessageCenterItemV249477(String json) {
            try { return ExternalMessageStoreV249477.put(MainActivity.this, new JSONObject(json == null ? "{}" : json)).toString(); }
            catch (Exception e) { return "{}"; }
        }
        @JavascriptInterface public boolean deleteExternalMessageCenterItemV249477(String id) { return ExternalMessageStoreV249477.delete(MainActivity.this, id == null ? "" : id); }
        @JavascriptInterface public boolean clearExternalMessageCenterV249477() { return ExternalMessageStoreV249477.clear(MainActivity.this); }
        @JavascriptInterface public void setExternalSourceConsentV249477(String source, boolean enabled) { ExternalMessageStoreV249477.consent(MainActivity.this, source == null ? "" : source, enabled); }
        @JavascriptInterface public boolean getExternalSourceConsentV249477(String source) { return ExternalMessageStoreV249477.consent(MainActivity.this, source == null ? "" : source); }
        @JavascriptInterface public boolean consumeExternalCenterLaunchV249477() { boolean v=externalCenterLaunchPendingV249477; externalCenterLaunchPendingV249477=false; return v; }
        @JavascriptInterface public boolean isNotificationAccessEnabledV249477() {
            try { String s=Settings.Secure.getString(getContentResolver(), "enabled_notification_listeners"); return s != null && s.contains(getPackageName()); }
            catch(Exception e){ return false; }
        }
        @JavascriptInterface public void openNotificationAccessSettingsV249477() { runOnUiThread(() -> { try { startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS")); } catch(Exception e) { openAppSettingsScreen(); } }); }
        @JavascriptInterface public boolean openExternalNotificationV249477(String id) { return ExternalNotificationListenerServiceV249477.open(MainActivity.this, id == null ? "" : id); }
        @JavascriptInterface public boolean openExternalPackageV249527(String packageName) {
            String pkg=packageName==null?"":packageName.trim();
            if(pkg.isEmpty()) return false;
            try {
                if(Build.VERSION.SDK_INT>=33){
                    IntentSender sender=getPackageManager().getLaunchIntentSenderForPackage(pkg);
                    sender.sendIntent(MainActivity.this,0,null,null,null);
                    return true;
                }
                Intent i=getPackageManager().getLaunchIntentForPackage(pkg);
                if(i!=null){i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(i);return true;}
            } catch(Exception ignored) { }
            return false;
        }
        @JavascriptInterface public boolean openExternalSourceAppV249477(String source) {
            try {
                String x=source==null?"":source.toLowerCase();
                if(x.contains(".")) return openExternalPackageV249527(x);
                String[] pkgs;
                if("viber".equals(x)) pkgs=new String[]{"com.viber.voip"};
                else if("email".equals(x)) pkgs=new String[]{"com.google.android.gm","com.microsoft.office.outlook"};
                else if("other".equals(x)) pkgs=new String[]{"com.whatsapp","org.telegram.messenger","com.facebook.orca","org.thoughtcrime.securesms","com.Slack","com.microsoft.teams","com.discord"};
                else pkgs=new String[]{"com.google.android.apps.messaging","com.samsung.android.messaging"};
                for(String pkg:pkgs) if(openExternalPackageV249527(pkg)) return true;
            } catch(Exception ignored) { }
            return false;
        }
        @JavascriptInterface public void setExternalMirrorEnabledV249527(boolean enabled) {
            getSharedPreferences("ok_ithaki_prefs",MODE_PRIVATE).edit().putBoolean("ok_external_mirror_enabled_v249527",enabled).apply();
        }
        @JavascriptInterface public boolean getExternalMirrorEnabledV249527() {
            return getSharedPreferences("ok_ithaki_prefs",MODE_PRIVATE).getBoolean("ok_external_mirror_enabled_v249527",false);
        }
        @JavascriptInterface public String getExternalNotificationDiagnosticsV249527() {
            try {
                JSONArray arr=ExternalMessageStoreV249477.list(MainActivity.this); JSONObject counts=new JSONObject();
                for(int i=0;i<arr.length();i++){JSONObject x=arr.optJSONObject(i);if(x==null)continue;String k=x.optString("source","other");counts.put(k,counts.optInt(k,0)+1);}
                JSONObject o=new JSONObject();o.put("ok",true);o.put("version_name","2.4.9.526r9-manual-wifi-cell-phone-microserver");o.put("version_code",249530);o.put("notification_access",isNotificationAccessEnabledV249477());o.put("stored_count",arr.length());o.put("counts",counts);o.put("email_consent",ExternalMessageStoreV249477.consent(MainActivity.this,"email"));o.put("mirror_enabled",getExternalMirrorEnabledV249527());return o.toString();
            } catch(Exception e){return "{\"ok\":false}";}
        }
        @JavascriptInterface public String exportExternalMessageCenterV249477() {
            try {
                File f=new File(ensureSharedDir(),"OK_ITHAKI_EXTERNAL_MESSAGES_LOCAL_ENCRYPTED_V249477.ithaki");
                try(FileOutputStream out=new FileOutputStream(f)){out.write(ExternalMessageStoreV249477.encryptedBlob(MainActivity.this).getBytes(StandardCharsets.UTF_8));}
                return f.getAbsolutePath();
            } catch(Exception e){ return ""; }
        }

        @JavascriptInterface public String getIthakiNativeBuildV249525() {
            try { JSONObject o=new JSONObject(); o.put("ok",true); o.put("version_name","2.4.9.526r9-manual-wifi-cell-phone-microserver"); o.put("version_code",249530); o.put("notification_owner","v249527"); return o.toString(); }
            catch(Exception e){ return "{\"ok\":false}"; }
        }
        @JavascriptInterface public void setNotificationMasterEnabledV249525(boolean enabled) {
            getSharedPreferences("ok_ithaki_prefs", MODE_PRIVATE).edit().putBoolean("ok_notifications_enabled_v249525", enabled).apply();
        }
        @JavascriptInterface public boolean getNotificationMasterEnabledV249525() {
            return getSharedPreferences("ok_ithaki_prefs", MODE_PRIVATE).getBoolean("ok_notifications_enabled_v249525", true);
        }
        @JavascriptInterface public void setNotificationSoundEnabledV249525(boolean enabled) {
            getSharedPreferences("ok_ithaki_prefs", MODE_PRIVATE).edit().putBoolean("ok_notification_sound_v249525", enabled).apply();
        }
        @JavascriptInterface public boolean getNotificationSoundEnabledV249525() {
            return getSharedPreferences("ok_ithaki_prefs", MODE_PRIVATE).getBoolean("ok_notification_sound_v249525", true);
        }
        @JavascriptInterface public boolean playNotificationSoundV249525() {
            try {
                if (!getNotificationMasterEnabledV249525() || !getNotificationSoundEnabledV249525()) return false;
                Uri u=RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); Ringtone r=RingtoneManager.getRingtone(MainActivity.this,u); if(r!=null){r.play();return true;}
            } catch(Exception ignored) { }
            return false;
        }
        @JavascriptInterface public boolean resetNotificationBadgeV249525() {
            try {
                getSharedPreferences("ok_ithaki_prefs", MODE_PRIVATE).edit().putInt("ok_ithaki_unread_badge",0).apply();
                return true;
            } catch(Exception ignored) { return false; }
        }
        @JavascriptInterface public void setNotificationPreferencesR10F40(String json) {
            try {
                JSONObject o = new JSONObject(json == null ? "{}" : json);
                SharedPreferences.Editor e = getSharedPreferences(PREFS, MODE_PRIVATE).edit();
                e.putString("ok_notification_preferences_r10f40", o.toString());
                e.putBoolean("ok_notifications_enabled_v249525", o.optBoolean("master", true));
                e.putBoolean("ok_notification_sound_v249525", o.optBoolean("sound", true));
                e.putBoolean("ok_notification_vibration_r10f40", o.optBoolean("vibration", true));
                e.putString("ithaki_push_mode_v249258", o.optString("nativeMode", "fcm_default"));
                e.apply();
            } catch (Exception ignored) { }
        }
        @JavascriptInterface public String getNotificationPreferencesR10F40() {
            return getSharedPreferences(PREFS, MODE_PRIVATE).getString("ok_notification_preferences_r10f40", "{}");
        }
        @JavascriptInterface public String getNotificationDiagnosticsR10F40() {
            try {
                SharedPreferences sp = getSharedPreferences(PREFS, MODE_PRIVATE);
                JSONObject o = new JSONObject();
                o.put("ok", true);
                o.put("version", "249526r10f40");
                o.put("version_code", 249569);
                o.put("master", sp.getBoolean("ok_notifications_enabled_v249525", true));
                o.put("sound", sp.getBoolean("ok_notification_sound_v249525", true));
                o.put("vibration", sp.getBoolean("ok_notification_vibration_r10f40", true));
                o.put("mode", sp.getString("ithaki_push_mode_v249258", "fcm_default"));
                o.put("fcm_token_present", !sp.getString("ok_fcm_token", "").trim().isEmpty());
                o.put("permission", new JSONObject(MainActivity.this.permissionStateJson()));
                o.put("single_owner", true);
                return o.toString();
            } catch (Exception e) { return "{\"ok\":false}"; }
        }
        @JavascriptInterface public String getNotificationDiagnosticsR10F42() {
            try {
                SharedPreferences sp = getSharedPreferences(PREFS, MODE_PRIVATE);
                JSONObject o = new JSONObject();
                android.app.NotificationManager nm = (android.app.NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
                boolean enabled = androidx.core.app.NotificationManagerCompat.from(MainActivity.this).areNotificationsEnabled();
                boolean fullScreen = true;
                if (Build.VERSION.SDK_INT >= 34 && nm != null) fullScreen = nm.canUseFullScreenIntent();
                o.put("ok", true);
                o.put("version", "249526r10f42");
                o.put("version_code", 249571);
                o.put("notifications_enabled_by_system", enabled);
                o.put("full_screen_intent_allowed", fullScreen);
                o.put("fcm_token_present", !sp.getString("ok_fcm_token", "").trim().isEmpty());
                o.put("fcm_token_length", sp.getString("ok_fcm_token", "").length());
                o.put("last_fcm_at", sp.getLong("r10f42_last_fcm_at", 0L));
                o.put("last_fcm_stage", sp.getString("r10f42_last_fcm_stage", ""));
                o.put("last_fcm_kind", sp.getString("r10f42_last_fcm_kind", ""));
                o.put("last_fcm_reason", sp.getString("r10f42_last_fcm_reason", ""));
                o.put("last_fcm_event_id", sp.getString("r10f42_last_fcm_event_id", ""));
                o.put("last_fcm_message_id", sp.getString("r10f42_last_fcm_message_id", ""));
                o.put("last_fcm_session_id", sp.getString("r10f42_last_fcm_session_id", ""));
                o.put("last_payload_complete", sp.getBoolean("r10f42_last_payload_complete", false));
                o.put("last_call_action_at", sp.getLong("r10f42_last_call_action_at", 0L));
                o.put("last_call_action", sp.getString("r10f42_last_call_action", ""));
                o.put("permission", new JSONObject(MainActivity.this.permissionStateJson()));
                o.put("single_owner", true);
                o.put("closed_app_requires_signed_v249571_apk", true);
                return o.toString();
            } catch (Exception e) { return "{\"ok\":false,\"error\":\"diagnostics_failed\"}"; }
        }
        @JavascriptInterface public void refreshFcmDiagnosticsR10F42() { runOnUiThread(() -> refreshFcmAtBootV249383()); }
        @JavascriptInterface public void openFullScreenIntentSettingsR10F42() {
            runOnUiThread(() -> {
                try {
                    if (Build.VERSION.SDK_INT >= 34) {
                        Intent i = new Intent(android.provider.Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT, Uri.parse("package:" + getPackageName()));
                        startActivity(i);
                    } else openAppSettingsScreen();
                } catch (Exception e) { openAppSettingsScreen(); }
            });
        }
        @JavascriptInterface public void openBatteryOptimizationSettingsR10F42() {
            runOnUiThread(() -> {
                try { startActivity(new Intent(android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)); }
                catch (Exception e) { openAppSettingsScreen(); }
            });
        }
        @JavascriptInterface public void clearNotificationHistoryR10F40() {
            getSharedPreferences(PREFS, MODE_PRIVATE).edit()
                    .remove("fcm_seen_events_v249531")
                    .putInt("ok_ithaki_unread_badge", 0)
                    .apply();
        }
        @JavascriptInterface public void clearAll() { getSharedPreferences(PREFS, MODE_PRIVATE).edit().clear().apply(); }
        @JavascriptInterface public String openBase64File(String filename, String mime, String base64Data) {
            try {
                String name = safeFileName(filename);
                byte[] data = Base64.decode(stripDataPrefix(base64Data), Base64.DEFAULT);
                File f = new File(ensureSharedDir(), name);
                FileOutputStream out = new FileOutputStream(f);
                out.write(data);
                out.close();
                Uri uri = uriForFile(f);
                String type = (mime == null || mime.trim().isEmpty()) ? "application/octet-stream" : mime.trim();
                Intent i = new Intent(Intent.ACTION_VIEW);
                i.setDataAndType(uri, type);
                i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(Intent.createChooser(i, "Open with"));
                return "ok";
            } catch (Exception e) {
                return "error: " + e.getMessage();
            }
        }
    }
    private void fallbackNativeBackV249392() {
        try {
            if (webView != null && webView.canGoBack()) {
                webView.goBack();
                return;
            }
        } catch (Exception ignored) { }
        super.onBackPressed();
    }

    @Override public void onBackPressed() {
        if (webView == null) {
            fallbackNativeBackV249392();
            return;
        }
        try {
            webView.evaluateJavascript(
                "(function(){try{return !!(window.ITH&&ITH.handleBackV249392&&ITH.handleBackV249392());}catch(e){return false;}})();",
                value -> {
                    boolean handled = value != null && "true".equalsIgnoreCase(value.replace("\"", "").trim());
                    if (!handled) fallbackNativeBackV249392();
                }
            );
        } catch (Exception e) {
            fallbackNativeBackV249392();
        }
    }

}

