package com.dewemoji.app;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ServiceInfo;
import android.graphics.PixelFormat;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.os.IBinder;
import android.provider.Settings;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Toast;

import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;

public class OverlayBubbleService extends Service {
    private static final String TAG = "DewemojiBubbleService";
    public static final String ACTION_START = "com.dewemoji.app.action.BUBBLE_START";
    public static final String ACTION_STOP = "com.dewemoji.app.action.BUBBLE_STOP";
    public static final String ACTION_OPEN = "com.dewemoji.app.action.BUBBLE_OPEN";

    public static final String PREFS_NAME = "dewemoji_native_state";
    public static final String PREF_BUBBLE_X = "bubblePositionX";
    public static final String PREF_BUBBLE_Y = "bubblePositionY";
    public static final String NOTIFICATION_CHANNEL_ID = "dewemoji_bubble";

    private static final int NOTIFICATION_ID = 41001;
    private static volatile boolean running = false;

    private WindowManager windowManager;
    private ImageView bubbleView;
    private View panelBackdropView;
    private View panelView;
    private WebView panelWebView;
    private WindowManager.LayoutParams bubbleParams;
    private WindowManager.LayoutParams panelBackdropParams;
    private WindowManager.LayoutParams panelParams;
    private SharedPreferences prefs;

    public static boolean isRunning() {
        return running;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
        ensureNotificationChannel();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        final String action = intent != null ? intent.getAction() : null;

        if (ACTION_STOP.equals(action)) {
            stopSelf();
            return START_NOT_STICKY;
        }

        if (ACTION_OPEN.equals(action)) {
            openMainActivity();
            if (running) {
                startForegroundCompat();
                return START_STICKY;
            }
        }

        if (!canDrawOverlays()) {
            Toast.makeText(this, "Overlay permission is required for Dewemoji bubble", Toast.LENGTH_SHORT).show();
            stopSelf();
            return START_NOT_STICKY;
        }

        try {
            startForegroundCompat();
            ensureBubbleView();
            // `isAttachedToWindow()` can be false immediately after addView on some
            // devices/OEM builds even though the overlay is successfully added.
            running = bubbleView != null;
            if (!running) {
                Toast.makeText(this, "Could not show Dewemoji bubble", Toast.LENGTH_SHORT).show();
                stopSelf();
                return START_NOT_STICKY;
            }
            return START_STICKY;
        } catch (Exception ex) {
            Log.e(TAG, "Failed to start bubble service", ex);
            Toast.makeText(this, "Failed to start Dewemoji bubble", Toast.LENGTH_SHORT).show();
            stopSelf();
            return START_NOT_STICKY;
        }
    }

    @Override
    public void onDestroy() {
        running = false;
        removePanelView();
        removeBubbleView();
        stopForeground(STOP_FOREGROUND_REMOVE);
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    private boolean canDrawOverlays() {
        return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(this);
    }

    private void ensureNotificationChannel() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            return;
        }

        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        if (manager == null) {
            return;
        }

        NotificationChannel channel = manager.getNotificationChannel(NOTIFICATION_CHANNEL_ID);
        if (channel != null) {
            return;
        }

        channel = new NotificationChannel(
            NOTIFICATION_CHANNEL_ID,
            "Dewemoji Bubble",
            NotificationManager.IMPORTANCE_LOW
        );
        channel.setDescription("Quick access bubble for search and copy");
        manager.createNotificationChannel(channel);
    }

    private Notification buildNotification() {
        PendingIntent openPendingIntent = PendingIntent.getActivity(
            this,
            1001,
            buildMainActivityIntent(),
            pendingIntentFlags(true)
        );

        Intent stopIntent = new Intent(this, OverlayBubbleService.class);
        stopIntent.setAction(ACTION_STOP);
        PendingIntent stopPendingIntent = PendingIntent.getService(
            this,
            1002,
            stopIntent,
            pendingIntentFlags(false)
        );

        return new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
            .setContentTitle("Dewemoji bubble is running")
            .setContentText("Tap bubble to open Dewemoji, copy emoji, then paste in your other app")
            .setSmallIcon(android.R.drawable.ic_dialog_info)
            .setContentIntent(openPendingIntent)
            .setOngoing(true)
            .setOnlyAlertOnce(true)
            .addAction(android.R.drawable.ic_menu_view, "Open Dewemoji", openPendingIntent)
            .addAction(android.R.drawable.ic_menu_close_clear_cancel, "Stop Bubble", stopPendingIntent)
            .build();
    }

    private void startForegroundCompat() {
        Notification notification = buildNotification();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            startForeground(
                NOTIFICATION_ID,
                notification,
                ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
            );
            return;
        }
        startForeground(NOTIFICATION_ID, notification);
    }

    private int pendingIntentFlags(boolean updateCurrent) {
        int flags = updateCurrent ? PendingIntent.FLAG_UPDATE_CURRENT : 0;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            flags |= PendingIntent.FLAG_IMMUTABLE;
        }
        return flags;
    }

    private Intent buildMainActivityIntent() {
        Intent launch = new Intent(this, MainActivity.class);
        launch.setAction(Intent.ACTION_MAIN);
        launch.addCategory(Intent.CATEGORY_LAUNCHER);
        launch.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
        launch.putExtra("dewemoji_opened_from_bubble", true);
        return launch;
    }

    private void openMainActivity() {
        try {
            hidePanelView();
            startActivity(buildMainActivityIntent());
        } catch (Exception ex) {
            Toast.makeText(this, "Could not open Dewemoji", Toast.LENGTH_SHORT).show();
        }
    }

    private void ensureBubbleView() {
        if (bubbleView != null && bubbleView.isAttachedToWindow()) {
            return;
        }
        if (windowManager == null) {
            return;
        }

        bubbleView = new ImageView(this);
        bubbleView.setImageResource(R.drawable.dewemoji_bubble_mark);
        bubbleView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        int padding = dp(3);
        bubbleView.setPadding(padding, padding, padding, padding);

        GradientDrawable background = new GradientDrawable();
        background.setShape(GradientDrawable.OVAL);
        background.setColor(0xFFFFFFFF);
        background.setStroke(dp(1), 0x22000000);
        bubbleView.setBackground(background);
        bubbleView.setElevation(dp(6));
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            bubbleView.setClipToOutline(true);
        }

        int type = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
            ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
            : WindowManager.LayoutParams.TYPE_PHONE;

        bubbleParams = new WindowManager.LayoutParams(
            dp(64),
            dp(64),
            type,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
            PixelFormat.TRANSLUCENT
        );
        bubbleParams.gravity = Gravity.TOP | Gravity.START;
        bubbleParams.x = prefs.getInt(PREF_BUBBLE_X, dp(10));
        bubbleParams.y = prefs.getInt(PREF_BUBBLE_Y, dp(180));
        clampBubblePosition();

        bubbleView.setOnTouchListener(new View.OnTouchListener() {
            private int startX;
            private int startY;
            private float downRawX;
            private float downRawY;
            private long downTime;
            private boolean moved;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (bubbleParams == null || windowManager == null) {
                    return false;
                }

                switch (event.getActionMasked()) {
                    case MotionEvent.ACTION_DOWN:
                        startX = bubbleParams.x;
                        startY = bubbleParams.y;
                        downRawX = event.getRawX();
                        downRawY = event.getRawY();
                        downTime = System.currentTimeMillis();
                        moved = false;
                        return true;
                    case MotionEvent.ACTION_MOVE:
                        int dx = Math.round(event.getRawX() - downRawX);
                        int dy = Math.round(event.getRawY() - downRawY);
                        if (Math.abs(dx) > dp(3) || Math.abs(dy) > dp(3)) {
                            moved = true;
                        }
                        bubbleParams.x = startX + dx;
                        bubbleParams.y = startY + dy;
                        clampBubblePosition();
                        try {
                            windowManager.updateViewLayout(bubbleView, bubbleParams);
                        } catch (Exception ignored) {}
                        return true;
                    case MotionEvent.ACTION_UP:
                    case MotionEvent.ACTION_CANCEL:
                        persistBubblePosition();
                        long pressDuration = System.currentTimeMillis() - downTime;
                        if (!moved && pressDuration < 325) {
                            togglePanelView();
                        } else if (moved) {
                            positionPanelNearBubble(false);
                            try {
                                if (panelView != null && panelView.isAttachedToWindow()) {
                                    windowManager.updateViewLayout(panelView, panelParams);
                                }
                            } catch (Exception ignored) {}
                        }
                        return true;
                    default:
                        return false;
                }
            }
        });

        try {
            windowManager.addView(bubbleView, bubbleParams);
        } catch (Exception ex) {
            Log.e(TAG, "windowManager.addView failed", ex);
            String reason = ex.getClass().getSimpleName();
            String message = ex.getMessage();
            if (message != null && !message.trim().isEmpty()) {
                String oneLine = message.replace('\n', ' ').trim();
                if (oneLine.length() > 90) oneLine = oneLine.substring(0, 90) + "…";
                reason = reason + ": " + oneLine;
            }
            Toast.makeText(this, reason, Toast.LENGTH_LONG).show();
            bubbleView = null;
        }
    }

    private void persistBubblePosition() {
        if (bubbleParams == null || prefs == null) {
            return;
        }
        prefs.edit()
            .putInt(PREF_BUBBLE_X, bubbleParams.x)
            .putInt(PREF_BUBBLE_Y, bubbleParams.y)
            .apply();
    }

    private void togglePanelView() {
        if (panelView != null && panelView.isAttachedToWindow()) {
            hidePanelView();
            return;
        }
        showPanelView();
    }

    private void showPanelView() {
        if (windowManager == null) return;
        ensurePanelBackdropView();
        ensurePanelView();
        if (panelView == null || panelParams == null) return;

        showPanelBackdrop();
        positionPanelNearBubble(true);
        try {
            if (panelView.isAttachedToWindow()) {
                windowManager.updateViewLayout(panelView, panelParams);
            } else {
                windowManager.addView(panelView, panelParams);
            }
        } catch (Exception ex) {
            Log.e(TAG, "windowManager.addView(panel) failed", ex);
            Toast.makeText(this, "Failed to show panel", Toast.LENGTH_SHORT).show();
        }
    }

    private void hidePanelView() {
        if (windowManager != null && panelView != null) {
            try {
                if (panelView.isAttachedToWindow()) {
                    windowManager.removeView(panelView);
                }
            } catch (Exception ignored) {
            }
        }
        hidePanelBackdrop();
    }

    private void removePanelView() {
        hidePanelView();
        if (panelWebView != null) {
            try {
                panelWebView.stopLoading();
                panelWebView.loadUrl("about:blank");
                panelWebView.destroy();
            } catch (Exception ignored) {
            }
        }
        panelWebView = null;
        panelView = null;
        panelParams = null;
        panelBackdropView = null;
        panelBackdropParams = null;
    }

    private void ensurePanelBackdropView() {
        if (panelBackdropView != null && panelBackdropParams != null) return;
        if (windowManager == null) return;

        View backdrop = new View(this);
        backdrop.setBackgroundColor(0x26000000);
        backdrop.setClickable(true);
        backdrop.setFocusable(false);
        backdrop.setOnTouchListener((v, event) -> {
            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
                hidePanelView();
            }
            return true;
        });
        panelBackdropView = backdrop;

        int type = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
            ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
            : WindowManager.LayoutParams.TYPE_PHONE;

        panelBackdropParams = new WindowManager.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT,
            type,
            WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
            PixelFormat.TRANSLUCENT
        );
        panelBackdropParams.gravity = Gravity.TOP | Gravity.START;
        panelBackdropParams.x = 0;
        panelBackdropParams.y = 0;
    }

    private void showPanelBackdrop() {
        if (windowManager == null || panelBackdropView == null || panelBackdropParams == null) return;
        try {
            if (panelBackdropView.isAttachedToWindow()) {
                windowManager.updateViewLayout(panelBackdropView, panelBackdropParams);
            } else {
                windowManager.addView(panelBackdropView, panelBackdropParams);
            }
        } catch (Exception ex) {
            Log.w(TAG, "Failed to show panel backdrop", ex);
        }
    }

    private void hidePanelBackdrop() {
        if (windowManager == null || panelBackdropView == null) return;
        try {
            if (panelBackdropView.isAttachedToWindow()) {
                windowManager.removeView(panelBackdropView);
            }
        } catch (Exception ignored) {
        }
    }

    private void ensurePanelView() {
        if (panelView != null && panelParams != null) return;
        if (windowManager == null) return;

        FrameLayout card = new FrameLayout(this);
        card.setClickable(true);
        card.setFocusable(true);
        card.setFocusableInTouchMode(true);
        card.setElevation(dp(18));

        GradientDrawable bg = new GradientDrawable();
        bg.setColor(0xFFFFFFFF);
        bg.setCornerRadius(dp(18));
        bg.setStroke(dp(1), 0x22000000);
        card.setBackground(bg);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            card.setClipToOutline(true);
        }

        WebView webView = new WebView(this);
        webView.setOverScrollMode(View.OVER_SCROLL_NEVER);
        webView.setVerticalScrollBarEnabled(false);
        webView.setHorizontalScrollBarEnabled(false);
        webView.setBackgroundColor(0xFFFFFFFF);
        webView.setWebChromeClient(new WebChromeClient());
        webView.setWebViewClient(new WebViewClient());
        webView.addJavascriptInterface(new OverlayWebBridge(), "DewemojiOverlayHost");

        WebSettings ws = webView.getSettings();
        ws.setJavaScriptEnabled(true);
        ws.setDomStorageEnabled(true);
        ws.setAllowFileAccess(true);
        ws.setAllowContentAccess(true);
        ws.setMediaPlaybackRequiresUserGesture(false);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            ws.setAllowFileAccessFromFileURLs(true);
            ws.setAllowUniversalAccessFromFileURLs(true);
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            ws.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
        }

        FrameLayout.LayoutParams webLp = new FrameLayout.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT
        );
        card.addView(webView, webLp);
        panelWebView = webView;

        try {
            webView.loadUrl(buildOverlayWebViewUrl());
        } catch (Exception ex) {
            Log.w(TAG, "Failed to load overlay webview URL", ex);
        }

        panelView = card;

        int type = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
            ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
            : WindowManager.LayoutParams.TYPE_PHONE;
        panelParams = new WindowManager.LayoutParams(
            preferredPanelWidthPx(),
            maxPanelHeightPx(),
            type,
            WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
            PixelFormat.TRANSLUCENT
        );
        panelParams.gravity = Gravity.TOP | Gravity.START;
        panelParams.x = dp(12);
        panelParams.y = dp(120);
        panelParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
    }

    private void positionPanelNearBubble(boolean resetIfOffscreen) {
        if (panelParams == null) return;
        int screenW = getResources().getDisplayMetrics().widthPixels;
        int screenH = getResources().getDisplayMetrics().heightPixels;
        int topSafe = safeTopInsetPx();
        int bottomSafe = safeBottomInsetPx();
        int availableH = Math.max(dp(280), screenH - topSafe - bottomSafe);
        panelParams.width = preferredPanelWidthPx();
        panelParams.height = Math.min(panelParams.height > 0 ? panelParams.height : maxPanelHeightPx(), maxPanelHeightPx());
        panelParams.x = Math.max(safeSideMarginPx(), (screenW - panelParams.width) / 2);
        panelParams.y = topSafe + Math.max(0, (availableH - panelParams.height) / 2);
        clampPanelPosition();
    }

    private void clampPanelPosition() {
        if (panelParams == null) return;
        int screenW = getResources().getDisplayMetrics().widthPixels;
        int screenH = getResources().getDisplayMetrics().heightPixels;
        int sideMargin = safeSideMarginPx();
        int topSafe = safeTopInsetPx();
        int bottomSafe = safeBottomInsetPx();
        int panelW = panelParams.width > 0 ? panelParams.width : preferredPanelWidthPx();
        int panelH = panelParams.height > 0 ? panelParams.height : maxPanelHeightPx();

        int maxX = Math.max(sideMargin, screenW - panelW - sideMargin);
        int maxY = Math.max(topSafe, screenH - panelH - bottomSafe);
        panelParams.x = Math.max(sideMargin, Math.min(panelParams.x, maxX));
        panelParams.y = Math.max(topSafe, Math.min(panelParams.y, maxY));
    }

    private void clampBubblePosition() {
        if (bubbleParams == null) return;
        int screenW = getResources().getDisplayMetrics().widthPixels;
        int screenH = getResources().getDisplayMetrics().heightPixels;
        int sideMargin = safeSideMarginPx();
        int topSafe = safeTopInsetPx();
        int bottomSafe = safeBottomInsetPx();
        int bubbleW = bubbleParams.width > 0 ? bubbleParams.width : dp(64);
        int bubbleH = bubbleParams.height > 0 ? bubbleParams.height : dp(64);

        int maxX = Math.max(sideMargin, screenW - bubbleW - sideMargin);
        int maxY = Math.max(topSafe, screenH - bubbleH - bottomSafe);
        bubbleParams.x = Math.max(sideMargin, Math.min(bubbleParams.x, maxX));
        bubbleParams.y = Math.max(topSafe, Math.min(bubbleParams.y, maxY));
    }

    private int preferredPanelWidthPx() {
        int screenW = getResources().getDisplayMetrics().widthPixels;
        int minMargin = dp(10);
        int maxWidth = Math.max(dp(220), screenW - (minMargin * 2));
        int target = Math.round(screenW * 0.94f);
        int minWidth = Math.min(dp(280), maxWidth);
        return Math.max(minWidth, Math.min(maxWidth, target));
    }

    private int maxPanelHeightPx() {
        int screenH = getResources().getDisplayMetrics().heightPixels;
        int topSafe = safeTopInsetPx();
        int bottomSafe = safeBottomInsetPx();
        int available = Math.max(dp(360), screenH - topSafe - bottomSafe);
        int maxHeight = Math.max(dp(260), available - dp(8));
        int target = Math.round(available * 0.92f);
        int minHeight = Math.min(dp(360), maxHeight);
        return Math.max(minHeight, Math.min(maxHeight, target));
    }

    private int minPanelHeightPx() {
        int max = maxPanelHeightPx();
        return Math.min(max, dp(260));
    }

    private int safeSideMarginPx() {
        return dp(10);
    }

    private int safeTopInsetPx() {
        return readSystemDimenPx("status_bar_height") + dp(8);
    }

    private int safeBottomInsetPx() {
        return readSystemDimenPx("navigation_bar_height") + dp(8);
    }

    private int readSystemDimenPx(String name) {
        try {
            int resId = getResources().getIdentifier(name, "dimen", "android");
            if (resId > 0) {
                return getResources().getDimensionPixelSize(resId);
            }
        } catch (Exception ignored) {
        }
        return 0;
    }

    private String buildOverlayWebViewUrl() {
        return "file:///android_asset/public/index.html?mode=overlay";
    }

    private boolean copyTextToClipboardNative(String text) {
        try {
            ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
            if (clipboard == null) return false;
            clipboard.setPrimaryClip(ClipData.newPlainText("dewemoji", String.valueOf(text == null ? "" : text)));
            return true;
        } catch (Exception ex) {
            Log.w(TAG, "Native clipboard copy failed", ex);
            return false;
        }
    }

    private final class OverlayWebBridge {
        @JavascriptInterface
        public boolean copyText(String text) {
            return copyTextToClipboardNative(text);
        }

        @JavascriptInterface
        public void setContentHeight(int cssPx) {
            if (cssPx <= 0) return;
            View target = panelView;
            if (target == null) return;
            target.post(() -> applyPanelContentHeightCss(cssPx));
        }

        @JavascriptInterface
        public void closePanel() {
            hidePanelView();
        }

        @JavascriptInterface
        public void openFullApp() {
            openMainActivity();
        }
    }

    private void applyPanelContentHeightCss(int cssPx) {
        if (panelParams == null || panelView == null || windowManager == null) return;
        float density = getResources().getDisplayMetrics().density;
        int desiredPx = Math.round(cssPx * density);
        desiredPx += dp(2);
        desiredPx = Math.max(minPanelHeightPx(), Math.min(desiredPx, maxPanelHeightPx()));
        if (Math.abs(desiredPx - panelParams.height) < dp(6)) {
            return;
        }
        panelParams.height = desiredPx;

        int screenH = getResources().getDisplayMetrics().heightPixels;
        int topSafe = safeTopInsetPx();
        int bottomSafe = safeBottomInsetPx();
        int availableH = Math.max(dp(280), screenH - topSafe - bottomSafe);
        panelParams.y = topSafe + Math.max(0, (availableH - panelParams.height) / 2);
        clampPanelPosition();

        try {
            if (panelView.isAttachedToWindow()) {
                windowManager.updateViewLayout(panelView, panelParams);
            }
        } catch (Exception ex) {
            Log.w(TAG, "Failed to apply panel content height", ex);
        }
    }

    private void removeBubbleView() {
        if (windowManager == null || bubbleView == null) {
            bubbleView = null;
            return;
        }
        try {
            windowManager.removeView(bubbleView);
        } catch (Exception ignored) {
        } finally {
            bubbleView = null;
        }
    }

    private int dp(int value) {
        float density = getResources().getDisplayMetrics().density;
        return Math.round(value * density);
    }
}
