2121import android .os .IBinder ;
2222import android .os .Looper ;
2323import android .util .Log ;
24+ import android .view .Choreographer ;
2425import android .view .Gravity ;
2526import android .view .MotionEvent ;
2627import android .view .ScaleGestureDetector ;
@@ -95,7 +96,19 @@ public class OverlayService extends Service implements TextureView.SurfaceTextur
9596 private int overlayMode = OVERLAY_MODE_SAIC ;
9697
9798 private ScaleGestureDetector scaleGestureDetector ;
99+ private float resizeTargetWidthPx = DEFAULT_OVERLAY_WIDTH_PX ;
100+ private boolean resizePending ;
101+ private boolean resizeFrameScheduled ;
102+ private boolean saveLayoutAfterResizeFrame ;
98103 private final Handler mainHandler = new Handler (Looper .getMainLooper ());
104+ private final Choreographer .FrameCallback resizeFrameCallback = frameTimeNanos -> {
105+ resizeFrameScheduled = false ;
106+ applyPendingResize ();
107+ if (saveLayoutAfterResizeFrame ) {
108+ saveLayoutAfterResizeFrame = false ;
109+ saveOverlayLayoutPrefs ();
110+ }
111+ };
99112 private final Runnable foregroundModePollRunnable = new Runnable () {
100113 @ Override
101114 public void run () {
@@ -121,6 +134,9 @@ public void run() {
121134 updateOverlayPresentation (true );
122135 } else if (UiPrefs .KEY_DEV_FOREGROUND_MODE_POLL_MS .equals (key )) {
123136 restartForegroundModePolling ();
137+ } else if (UiPrefs .KEY_OVERLAY_RESIZE_LOCKED .equals (key )
138+ && UiPrefs .isOverlayResizeLocked (sharedPreferences )) {
139+ cancelPendingResize ();
124140 } else if (UiPrefs .KEY_SMOOTH_ENTRY .equals (key )
125141 && !UiPrefs .isSmoothEntryEnabled (sharedPreferences )
126142 && overlayView != null ) {
@@ -277,25 +293,43 @@ private void showFloatingWindow() {
277293
278294 scaleGestureDetector = new ScaleGestureDetector (this ,
279295 new ScaleGestureDetector .SimpleOnScaleGestureListener () {
296+ @ Override
297+ public boolean onScaleBegin (ScaleGestureDetector detector ) {
298+ if (uiPrefs != null && UiPrefs .isOverlayResizeLocked (uiPrefs )) {
299+ return false ;
300+ }
301+ if (!resizePending ) {
302+ resizeTargetWidthPx = overlayWidthPx ;
303+ }
304+ saveLayoutAfterResizeFrame = false ;
305+ return true ;
306+ }
307+
280308 @ Override
281309 public boolean onScale (ScaleGestureDetector detector ) {
310+ if (uiPrefs != null && UiPrefs .isOverlayResizeLocked (uiPrefs )) {
311+ return true ;
312+ }
282313 float factor = detector .getScaleFactor ();
283- if (factor <= 0f || Float .isNaN (factor )) return true ;
284- int newW = Math .round (overlayWidthPx * factor );
285- int [] wh = clampOverlaySize (newW );
286- overlayWidthPx = wh [0 ];
287- overlayHeightPx = wh [1 ];
288- overlayParams .width = overlayWidthPx ;
289- overlayParams .height = overlayHeightPx ;
290- clampOverlayPositionToScreen ();
291- windowManager .updateViewLayout (overlayView , overlayParams );
292- applyPreviewTransform ();
314+ int minWidth = clampOverlaySize (OVERLAY_MIN_WIDTH_PX )[0 ];
315+ int maxWidth = clampOverlaySize (OVERLAY_MAX_WIDTH_PX )[0 ];
316+ float nextTargetWidth = OverlayResizeMath .updateTargetWidth (
317+ resizeTargetWidthPx ,
318+ factor ,
319+ minWidth ,
320+ maxWidth
321+ );
322+ if (Float .compare (nextTargetWidth , resizeTargetWidthPx ) != 0 ) {
323+ resizeTargetWidthPx = nextTargetWidth ;
324+ resizePending = true ;
325+ scheduleResizeFrame ();
326+ }
293327 return true ;
294328 }
295329
296330 @ Override
297331 public void onScaleEnd (ScaleGestureDetector detector ) {
298- saveOverlayLayoutPrefs ();
332+ finishResizeGesture ();
299333 }
300334 });
301335
@@ -315,12 +349,12 @@ public void onScaleEnd(ScaleGestureDetector detector) {
315349 int action = event .getActionMasked ();
316350 int pointerCount = event .getPointerCount ();
317351
352+ if (action == MotionEvent .ACTION_CANCEL ) {
353+ finishResizeGesture ();
354+ return true ;
355+ }
356+
318357 if (pointerCount >= 2 ) {
319- if (action == MotionEvent .ACTION_POINTER_UP
320- || action == MotionEvent .ACTION_CANCEL
321- || action == MotionEvent .ACTION_UP ) {
322- saveOverlayLayoutPrefs ();
323- }
324358 return true ;
325359 }
326360
@@ -335,6 +369,9 @@ public void onScaleEnd(ScaleGestureDetector detector) {
335369 if (scaleGestureDetector .isInProgress ()) {
336370 return true ;
337371 }
372+ if (uiPrefs != null && UiPrefs .isOverlayMoveLocked (uiPrefs )) {
373+ return true ;
374+ }
338375 float dx = event .getRawX () - initialTouchX ;
339376 float dy = event .getRawY () - initialTouchY ;
340377 int newX = (int ) (initialX + dx );
@@ -345,7 +382,7 @@ public void onScaleEnd(ScaleGestureDetector detector) {
345382 windowManager .updateViewLayout (overlayView , overlayParams );
346383 return true ;
347384 case MotionEvent .ACTION_UP :
348- saveOverlayLayoutPrefs ();
385+ finishResizeGesture ();
349386 float tapDx = event .getRawX () - initialTouchX ;
350387 float tapDy = event .getRawY () - initialTouchY ;
351388 boolean isTap = (tapDx * tapDx + tapDy * tapDy ) < (float ) (dpToPx (12 ) * dpToPx (12 ));
@@ -366,6 +403,62 @@ public void onScaleEnd(ScaleGestureDetector detector) {
366403 });
367404 }
368405
406+ private void scheduleResizeFrame () {
407+ if (resizeFrameScheduled ) return ;
408+ resizeFrameScheduled = true ;
409+ Choreographer .getInstance ().postFrameCallback (resizeFrameCallback );
410+ }
411+
412+ private void finishResizeGesture () {
413+ if (!resizePending ) {
414+ if (!resizeFrameScheduled ) {
415+ saveOverlayLayoutPrefs ();
416+ } else {
417+ saveLayoutAfterResizeFrame = true ;
418+ }
419+ return ;
420+ }
421+ saveLayoutAfterResizeFrame = true ;
422+ scheduleResizeFrame ();
423+ }
424+
425+ private void applyPendingResize () {
426+ if (!resizePending || overlayParams == null || overlayView == null || windowManager == null ) {
427+ return ;
428+ }
429+ resizePending = false ;
430+
431+ int requestedWidth = Math .round (resizeTargetWidthPx );
432+ int [] wh = clampOverlaySize (requestedWidth );
433+ if (wh [0 ] != requestedWidth ) {
434+ resizeTargetWidthPx = wh [0 ];
435+ }
436+ if (overlayWidthPx == wh [0 ] && overlayHeightPx == wh [1 ]) {
437+ return ;
438+ }
439+
440+ overlayWidthPx = wh [0 ];
441+ overlayHeightPx = wh [1 ];
442+ overlayParams .width = overlayWidthPx ;
443+ overlayParams .height = overlayHeightPx ;
444+ clampOverlayPositionToScreen ();
445+ windowManager .updateViewLayout (overlayView , overlayParams );
446+ applyPreviewTransform ();
447+ }
448+
449+ private void cancelResizeFrame () {
450+ if (!resizeFrameScheduled ) return ;
451+ Choreographer .getInstance ().removeFrameCallback (resizeFrameCallback );
452+ resizeFrameScheduled = false ;
453+ }
454+
455+ private void cancelPendingResize () {
456+ cancelResizeFrame ();
457+ resizePending = false ;
458+ saveLayoutAfterResizeFrame = false ;
459+ resizeTargetWidthPx = overlayWidthPx ;
460+ }
461+
369462 private View createOverlayCard () {
370463 FrameLayout card = new FrameLayout (this );
371464 card .setLayoutParams (new FrameLayout .LayoutParams (
@@ -560,26 +653,33 @@ private void updateOverlayPresentation(boolean persist) {
560653 private void applyPreviewTransform () {
561654 final TextureView tv = textureView ;
562655 if (tv == null ) return ;
563- tv .post (() -> {
564- FrameLayout .LayoutParams params ;
565- if (shouldRotatePreviewToDrivingDirection ()) {
566- params = new FrameLayout .LayoutParams (
567- overlayHeightPx ,
568- overlayWidthPx ,
569- Gravity .CENTER
570- );
571- } else {
572- params = new FrameLayout .LayoutParams (
573- ViewGroup .LayoutParams .MATCH_PARENT ,
574- ViewGroup .LayoutParams .MATCH_PARENT ,
575- Gravity .CENTER
576- );
577- }
578- tv .setLayoutParams (params );
579- tv .setScaleX (1f );
580- tv .setScaleY (1f );
581- tv .setRotation (getPreviewRotationDegrees ());
582- });
656+ if (Looper .myLooper () != Looper .getMainLooper ()) {
657+ tv .post (() -> applyPreviewTransformTo (tv ));
658+ return ;
659+ }
660+ applyPreviewTransformTo (tv );
661+ }
662+
663+ private void applyPreviewTransformTo (TextureView tv ) {
664+ boolean rotated = shouldRotatePreviewToDrivingDirection ();
665+ int desiredWidth = rotated ? overlayHeightPx : ViewGroup .LayoutParams .MATCH_PARENT ;
666+ int desiredHeight = rotated ? overlayWidthPx : ViewGroup .LayoutParams .MATCH_PARENT ;
667+ float desiredRotation = getPreviewRotationDegrees ();
668+
669+ ViewGroup .LayoutParams currentParams = tv .getLayoutParams ();
670+ if (!(currentParams instanceof FrameLayout .LayoutParams )
671+ || currentParams .width != desiredWidth
672+ || currentParams .height != desiredHeight
673+ || ((FrameLayout .LayoutParams ) currentParams ).gravity != Gravity .CENTER ) {
674+ tv .setLayoutParams (new FrameLayout .LayoutParams (
675+ desiredWidth ,
676+ desiredHeight ,
677+ Gravity .CENTER
678+ ));
679+ }
680+ if (tv .getScaleX () != 1f ) tv .setScaleX (1f );
681+ if (tv .getScaleY () != 1f ) tv .setScaleY (1f );
682+ if (tv .getRotation () != desiredRotation ) tv .setRotation (desiredRotation );
583683 }
584684
585685 private void clampOverlayPositionToScreen () {
@@ -710,6 +810,12 @@ public void onDestroy() {
710810 uiPrefs .unregisterOnSharedPreferenceChangeListener (prefListener );
711811 }
712812 mainHandler .removeCallbacks (foregroundModePollRunnable );
813+ cancelResizeFrame ();
814+ if (resizePending ) {
815+ applyPendingResize ();
816+ saveOverlayLayoutPrefs ();
817+ }
818+ saveLayoutAfterResizeFrame = false ;
713819 fadeOutPending = false ;
714820 if (overlayView != null ) {
715821 overlayView .animate ().cancel ();
0 commit comments