Skip to content

Commit 00d34fb

Browse files
Merge pull request #561 from Countly/fix_background_inputs_flow_contents
fix: direct key events to background view for contents
2 parents 392a298 + c1ab0a2 commit 00d34fb

3 files changed

Lines changed: 87 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
* Added gradle configuration cache support to upload symbols plugin.
33
* Improved user properties auto-save conditions to flush event queue with every user property call.
44

5+
* Mitigated an issue where content overlays and feedback widgets prevented keyboard input on the underlying activity's text fields while displayed.
6+
* Mitigated a memory retention issue where content overlays and feedback widgets could be briefly held in memory after closing, surfacing under repeated open/close cycles.
57
* Mitigated a memory leak where the content overlay retained the activity it was first opened in across subsequent activity transitions.
68

79
## 26.1.2

sdk/src/main/java/ly/count/android/sdk/ContentOverlayView.java

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import android.util.Log;
1616
import android.view.Gravity;
1717
import android.view.KeyEvent;
18+
import android.view.MotionEvent;
1819
import android.view.View;
1920
import android.view.ViewGroup;
2021
import 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

sdk/src/main/java/ly/count/android/sdk/CountlyWebView.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,14 @@ public CountlyWebView(Context context) {
1515
public boolean onCheckIsTextEditor() {
1616
return true;
1717
}
18+
19+
/**
20+
* Expedites any pending scrollbar-fade Runnable by re-scheduling it with zero delay.
21+
* Used by ContentOverlayView before detach to drain MessageQueue entries that would
22+
* otherwise hold the ViewRootImpl alive for ~550ms (transient leak under stress).
23+
* No-op if no scroll cache exists. Calls protected View#awakenScrollBars(int).
24+
*/
25+
void expediteScrollbarFade() {
26+
awakenScrollBars(0);
27+
}
1828
}

0 commit comments

Comments
 (0)