From 24164bbe7d5f408c20ee03f8ac80e3b5e5273bec Mon Sep 17 00:00:00 2001 From: Hemant72 Date: Mon, 20 Apr 2026 16:36:55 +0530 Subject: [PATCH] feat(android): enable two-axis draggable overlay window --- .../services/WindowServiceNew.java | 59 ++++++++++++------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/android/src/main/java/in/jvapps/system_alert_window/services/WindowServiceNew.java b/android/src/main/java/in/jvapps/system_alert_window/services/WindowServiceNew.java index 4761db5..8b0b910 100644 --- a/android/src/main/java/in/jvapps/system_alert_window/services/WindowServiceNew.java +++ b/android/src/main/java/in/jvapps/system_alert_window/services/WindowServiceNew.java @@ -14,6 +14,7 @@ import android.graphics.PixelFormat; import android.os.Build; import android.os.IBinder; +import android.util.DisplayMetrics; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; @@ -36,7 +37,6 @@ import io.flutter.embedding.android.FlutterView; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngineCache; - public class WindowServiceNew extends Service implements View.OnTouchListener { private static final String TAG = WindowServiceNew.class.getSimpleName(); @@ -45,18 +45,23 @@ public class WindowServiceNew extends Service implements View.OnTouchListener { public static final String INTENT_EXTRA_IS_UPDATE_WINDOW = "IsUpdateWindow"; public static final String INTENT_EXTRA_IS_CLOSE_WINDOW = "IsCloseWindow"; - private WindowManager windowManager; private String windowGravity; private int windowWidth; private int windowHeight; - private FlutterView flutterView; - private int paramFlags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED : WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; + private int paramFlags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + ? WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + : WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; private int paramsFromDart; - private final int paramType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; + private final int paramType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY + : WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; + + // Track both axes for free drag + private float offsetX; private float offsetY; private boolean moving; @@ -66,9 +71,8 @@ public void onCreate() { createNotificationChannel(); Intent notificationIntent = new Intent(this, SystemAlertWindowPlugin.class); PendingIntent pendingIntent; - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { - pendingIntent = PendingIntent.getActivity(this, - 0, notificationIntent, PendingIntent.FLAG_MUTABLE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_MUTABLE); } else { pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); } @@ -86,7 +90,6 @@ public void onCreate() { @Override public int onStartCommand(Intent intent, int flags, int startId) { - //Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show(); if (null != intent && intent.getExtras() != null) { ContextHolder.setApplicationContext(this); @SuppressWarnings("unchecked") @@ -138,10 +141,11 @@ private void setWindowLayoutFromMap(HashMap paramsMap) { } private WindowManager.LayoutParams getLayoutParams() { - final WindowManager.LayoutParams params; - params = new WindowManager.LayoutParams(); - params.width = (windowWidth == 0) ? WindowManager.LayoutParams.MATCH_PARENT : Commons.getPixelsFromDp(this, windowWidth); - params.height = (windowHeight == 0) ? WindowManager.LayoutParams.WRAP_CONTENT : Commons.getPixelsFromDp(this, windowHeight); + final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + int widthPx = (windowWidth == 0) ? WindowManager.LayoutParams.MATCH_PARENT : Commons.getPixelsFromDp(this, windowWidth); + int heightPx = (windowHeight == 0) ? WindowManager.LayoutParams.WRAP_CONTENT : Commons.getPixelsFromDp(this, windowHeight); + params.width = widthPx; + params.height = heightPx; params.format = PixelFormat.TRANSLUCENT; params.type = paramType; params.flags |= paramFlags; @@ -149,7 +153,16 @@ private WindowManager.LayoutParams getLayoutParams() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && Commons.isClickDisabled) { params.alpha = 0.8f; } - params.gravity = Commons.getGravity(windowGravity, Gravity.TOP); + // Use TOP|START so both params.x and params.y are measured from the + // top-left corner of the screen, enabling free drag in all directions. + params.gravity = Gravity.TOP | Gravity.START; + + // Center the overlay horizontally on first display + if (windowWidth > 0) { + DisplayMetrics metrics = getResources().getDisplayMetrics(); + params.x = (metrics.widthPixels - widthPx) / 2; + } + params.y = Commons.getPixelsFromDp(this, 100); // default 100dp from top return params; } @@ -165,6 +178,7 @@ private void createWindow(HashMap paramsMap) { throw new IllegalStateException("FlutterEngine not available"); } engine.getLifecycleChannel().appIsResumed(); + flutterView = new FlutterView(getApplicationContext(), new FlutterTextureView(getApplicationContext())); flutterView.attachToFlutterEngine(Objects.requireNonNull(FlutterEngineCache.getInstance().get(Constants.FLUTTER_CACHE_ENGINE))); flutterView.setFitsSystemWindows(true); @@ -224,10 +238,10 @@ private void closeWindow(boolean isStopService) { LogUtils.getInstance().i(TAG, "Closing the overlay window"); try { if (windowManager != null && flutterView != null) { - windowManager.removeView(flutterView); - windowManager = null; - flutterView.detachFromFlutterEngine(); - LogUtils.getInstance().i(TAG, "Successfully closed overlay window"); + windowManager.removeView(flutterView); + windowManager = null; + flutterView.detachFromFlutterEngine(); + LogUtils.getInstance().i(TAG, "Successfully closed overlay window"); } } catch (IllegalArgumentException e) { LogUtils.getInstance().e(TAG, "view not found"); @@ -244,15 +258,20 @@ public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: moving = false; + offsetX = event.getRawX(); offsetY = event.getRawY(); break; case MotionEvent.ACTION_MOVE: + float dx = event.getRawX() - offsetX; float dy = event.getRawY() - offsetY; - if (!moving && Math.abs(dy) > 25) { + // Start moving if distance in either axis exceeds threshold + if (!moving && (Math.abs(dx) > 10 || Math.abs(dy) > 10)) { moving = true; } if (moving) { + offsetX = event.getRawX(); offsetY = event.getRawY(); + params.x = params.x + (int) dx; params.y = params.y + (int) dy; windowManager.updateViewLayout(flutterView, params); } @@ -286,4 +305,4 @@ public void onDestroy() { public IBinder onBind(Intent intent) { return null; } -} +} \ No newline at end of file