Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gleap/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<application>
<activity
android:name="io.gleap.GleapMainActivity"
android:configChanges="orientation|screenSize"
android:configChanges="orientation|screenSize|keyboard|keyboardHidden|screenLayout|smallestScreenSize|uiMode|navigation|density"

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The activity now claims to handle a very broad set of configChanges (including density and uiMode). When these are listed, Android will not recreate the Activity and will also not automatically re-apply updated resources for those configuration changes, which can cause incorrect theming/scaling after dark-mode changes or display-size/density changes. Consider narrowing this list to only the minimum needed to prevent the keyboard-related recreation (and leave other changes to the default recreation behavior).

Suggested change
android:configChanges="orientation|screenSize|keyboard|keyboardHidden|screenLayout|smallestScreenSize|uiMode|navigation|density"
android:configChanges="orientation|screenSize|keyboard|keyboardHidden"

Copilot uses AI. Check for mistakes.
android:theme="@style/AppTheme.TranslucentBG" />
</application>

Expand Down
50 changes: 43 additions & 7 deletions gleap/src/main/java/io/gleap/GleapMainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
Expand Down Expand Up @@ -197,7 +198,12 @@ public void handleOnBackPressed() {

this.requestWindowFeature(Window.FEATURE_NO_TITLE);
try {
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
// SOFT_INPUT_ADJUST_RESIZE is deprecated on API 30+ and ignored on API 35+.
// On API 30+ we rely solely on the WindowInsetsCompat listener for keyboard
// handling; on older versions we keep the legacy flag for compatibility.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
}
if (getSupportActionBar() != null) {
getSupportActionBar().hide();
}
Expand Down Expand Up @@ -285,9 +291,19 @@ public void invoke() {
@Override
public void run() {
url += GleapURLGenerator.generateURL();
initBrowser();
if (savedInstanceState != null) {
webView.restoreState(savedInstanceState);
// Activity was recreated (e.g. process killed in background).
// Set up WebView clients/settings but skip loading a new URL;
// restoreState will navigate back to the previous page.
initBrowserSettings();
android.webkit.WebBackForwardList restored = webView.restoreState(savedInstanceState);
if (restored == null) {
// restoreState failed — fall back to a full reload.
webView.loadUrl(url);
Comment on lines +299 to +301

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the savedInstanceState != null path you call initBrowserSettings() (which sets the WebView INVISIBLE) and then webView.restoreState(...), but if restoreState succeeds there is no native code path that hides R.id.loader / shows the WebView (the only hide/show is triggered by the JS "ping" message). If the restored page does not re-emit that ping, the UI can remain stuck in an infinite loader/blank state after process recreation. Consider explicitly showing the WebView / hiding the loader after a successful restore, or triggering a reload/timeout fallback when the ping isn’t received.

Suggested change
if (restored == null) {
// restoreState failed — fall back to a full reload.
webView.loadUrl(url);
if (restored == null || restored.getSize() == 0) {
// restoreState failed — fall back to a full reload.
webView.loadUrl(url);
} else {
// Successfully restored a previous page. Ensure that the
// WebView is visible and the loader is hidden, since the
// restored content might not re-emit the JS "ping".
webView.setVisibility(View.VISIBLE);
View loader = findViewById(R.id.loader);
if (loader != null) {
loader.setVisibility(View.GONE);
}

Copilot uses AI. Check for mistakes.
}
} else {
// Fresh start — initialise everything and load the URL.
initBrowser();
}
}
});
Expand All @@ -308,6 +324,15 @@ protected void onRestoreInstanceState(Bundle savedInstanceState) {
webView.restoreState(savedInstanceState);

Copilot AI Mar 3, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

webView.restoreState(savedInstanceState) is invoked in the webView.post(...) runnable (saved-state branch) and again in onRestoreInstanceState(). Restoring twice can lead to redundant work and can overwrite state after clients/settings have been attached, making behavior harder to reason about. Consider choosing a single restore location (either rely on onRestoreInstanceState or gate the second call with a flag).

Suggested change
webView.restoreState(savedInstanceState);
// WebView state is restored in onCreate via the savedInstanceState branch.

Copilot uses AI. Check for mistakes.
}

@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Intentionally empty — we declared the relevant configChanges in the
// manifest so the system delivers them here instead of recreating the
// activity. This keeps the WebView (and its chat state) alive when the
// soft keyboard appears/disappears or the screen layout changes.
}

@Override
protected void onDestroy() {
try {
Expand Down Expand Up @@ -359,7 +384,12 @@ protected void onDestroy() {
super.onDestroy();
}

private void initBrowser() {
/**
* Configure WebView settings, clients and JS bridge without loading a URL.
* Call this when restoring from saved state so the WebView is properly
* wired up before {@code restoreState()} navigates to the previous page.
*/
private void initBrowserSettings() {
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setDomStorageEnabled(true);
Expand Down Expand Up @@ -443,10 +473,16 @@ public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathC
}
}
});
webView.loadUrl(url);
webView.setVisibility(View.INVISIBLE);
settings.setUseWideViewPort(true);
settings.setLoadWithOverviewMode(true);
}

/**
* Full browser initialisation: configure settings, clients and load the URL.
* Used on a fresh start (no saved instance state).
*/
private void initBrowser() {
initBrowserSettings();
webView.loadUrl(url);
}

public void askForPermission(String origin, String androidPermission, String webkitPermission, int requestCode) {
Expand Down