1515import android .util .Log ;
1616import android .view .Gravity ;
1717import android .view .KeyEvent ;
18+ import android .view .MotionEvent ;
1819import android .view .View ;
1920import android .view .ViewGroup ;
2021import android .view .ViewTreeObserver ;
@@ -196,6 +197,50 @@ public boolean dispatchKeyEvent(KeyEvent event) {
196197 return super .dispatchKeyEvent (event );
197198 }
198199
200+ @ Override
201+ public boolean dispatchTouchEvent (MotionEvent ev ) {
202+ // Dynamically transfer IME focus between the overlay and the host activity:
203+ // - ACTION_OUTSIDE (delivered because of FLAG_WATCH_OUTSIDE_TOUCH): user
204+ // touched outside the overlay's bounds; the touch already passed through
205+ // to the activity via FLAG_NOT_TOUCH_MODAL, but we additionally restore
206+ // FLAG_NOT_FOCUSABLE so the activity's EditText can claim IME focus.
207+ // - ACTION_DOWN (touch inside the overlay's bounds): clear FLAG_NOT_FOCUSABLE
208+ // so the WebView's form fields can bring up the keyboard.
209+ // Consumes nothing; existing touch handling (WebView, etc.) continues normally.
210+ if (ev .getAction () == MotionEvent .ACTION_OUTSIDE ) {
211+ setWindowFocusable (false );
212+ return true ;
213+ }
214+ if (ev .getAction () == MotionEvent .ACTION_DOWN ) {
215+ setWindowFocusable (true );
216+ }
217+ return super .dispatchTouchEvent (ev );
218+ }
219+
220+ private void setWindowFocusable (boolean focusable ) {
221+ if (!isAddedToWindow || windowManager == null ) {
222+ return ;
223+ }
224+ try {
225+ WindowManager .LayoutParams lp = (WindowManager .LayoutParams ) getLayoutParams ();
226+ if (lp == null ) {
227+ return ;
228+ }
229+ boolean alreadyFocusable = (lp .flags & WindowManager .LayoutParams .FLAG_NOT_FOCUSABLE ) == 0 ;
230+ if (alreadyFocusable == focusable ) {
231+ return ;
232+ }
233+ if (focusable ) {
234+ lp .flags &= ~WindowManager .LayoutParams .FLAG_NOT_FOCUSABLE ;
235+ } else {
236+ lp .flags |= WindowManager .LayoutParams .FLAG_NOT_FOCUSABLE ;
237+ }
238+ windowManager .updateViewLayout (this , lp );
239+ } catch (Exception e ) {
240+ Log .w (Countly .TAG , "[ContentOverlayView] setWindowFocusable, failed to update flags" , e );
241+ }
242+ }
243+
199244 private TransparentActivityConfig getCurrentConfig () {
200245 if (currentOrientation == Configuration .ORIENTATION_LANDSCAPE ) {
201246 return configLandscape ;
@@ -219,9 +264,19 @@ private WindowManager.LayoutParams createWindowParams(@NonNull Activity activity
219264 }
220265 }
221266
267+ // FLAG_NOT_FOCUSABLE: overlay does NOT grab IME focus by default, so the
268+ // underlying Activity's EditText can receive keyboard input even while the
269+ // overlay is shown. Toggled off in dispatchTouchEvent on inside touches so
270+ // the WebView's <input>/<textarea> can still bring up the keyboard.
271+ // FLAG_WATCH_OUTSIDE_TOUCH: deliver an ACTION_OUTSIDE event to the overlay
272+ // when the user touches outside its bounds, so we can return focus to the
273+ // non-focusable state. The actual touch still passes through to the host
274+ // activity via FLAG_NOT_TOUCH_MODAL.
222275 int flags = WindowManager .LayoutParams .FLAG_LAYOUT_IN_SCREEN
223276 | WindowManager .LayoutParams .FLAG_LAYOUT_INSET_DECOR
224- | WindowManager .LayoutParams .FLAG_NOT_TOUCH_MODAL ;
277+ | WindowManager .LayoutParams .FLAG_NOT_TOUCH_MODAL
278+ | WindowManager .LayoutParams .FLAG_NOT_FOCUSABLE
279+ | WindowManager .LayoutParams .FLAG_WATCH_OUTSIDE_TOUCH ;
225280
226281 if (!isContentLoaded ) {
227282 flags |= WindowManager .LayoutParams .FLAG_NOT_TOUCHABLE ;
@@ -367,6 +422,25 @@ private void enableTouchInteraction() {
367422
368423 private void removeFromWindow () {
369424 if (isAddedToWindow && windowManager != null ) {
425+ // Expedite any pending scrollbar-fade Runnables: scrolling inside the WebView
426+ // schedules a ScrollabilityCache fade Runnable on the main MessageQueue. If
427+ // the View is detached before that Runnable fires, the pending Message keeps
428+ // ViewRootImpl alive (and through it, this overlay) for ~550ms — visible as
429+ // a transient leak under repeated show/close cycles. Calling awakenScrollBars(0)
430+ // re-schedules the existing fade with zero delay, so the Message drains on the
431+ // next message-loop iteration (~16ms) and the View becomes GC-eligible promptly.
432+ // No-op if mScrollCache wasn't created (no scrolling occurred).
433+ try {
434+ // CountlyWebView#expediteScrollbarFade exposes protected View#awakenScrollBars(int)
435+ // (only callable through inheritance, hence the wrapper on the subclass).
436+ if (webView instanceof CountlyWebView ) {
437+ ((CountlyWebView ) webView ).expediteScrollbarFade ();
438+ }
439+ awakenScrollBars (0 );
440+ } catch (Exception ignored ) {
441+ // Public API, but defensive against any edge-case throws during teardown.
442+ }
443+
370444 try {
371445 // Use removeViewImmediate for synchronous removal to prevent WindowLeaked.
372446 // WindowManager.removeView() is async (posts MSG_DIE), so the view may still
0 commit comments