How to Debug Foldable Device Issues in Mobile Apps

How to Debug Foldable Device Issues in Mobile Apps starts with understanding the unique lifecycle events and UI constraints that foldable phones introduce. Unlike traditional slab devices, foldables e

June 09, 2026 · 15 min read · Common Issues

How to Debug Foldable Device Issues in Mobile Apps starts with understanding the unique lifecycle events and UI constraints that foldable phones introduce. Unlike traditional slab devices, foldables expose multiple screen states—closed, partially folded, and fully opened—each triggering configuration changes that can break layouts, restart activities, or expose hidden bugs. This guide walks you through a reproducible workflow: from setting up, through log and analysis of UI‑state transitions, handling of multi‑window continuity, input method quirks, accessibility checks, autonomous exploration with SUSA, and finally a prevention checklist you can embed in CI. Each section contains concrete commands, code snippets, and real‑world examples that you can apply immediately.

How to Debug Foldable Device Issues in Mobile Apps: Setting Up a Reproducible Test Environment

Device matrix and emulator configuration

To reproduce foldable‑specific bugs you need a matrix that covers the three primary postures: closed (cover screen active), half‑opened (hinge at 90°), and fully opened (main display active). On real hardware, devices such as the Samsung Galaxy Z Fold 5, Google Pixel Fold, or Huawei Mate Xs 2 provide distinct hinge angles and screen ratios. For CI‑friendly runs, use the Android Emulator with the *foldable* system images shipped in Android Studio Flamingo or later.

  1. Create an AVD with a foldable profile:
  2. 
       avdmanager create avd -n foldable_pixel -k "system-images;android-34;google_apis;x86_64" \
         -d "pixel_fold" --abi google_apis/x86_64
    
  3. Enable hinge angle sensor in the emulator’s extended controls → *Sensor* → *Hinge Angle*. Set it to 0° (closed), 90° (half), or 180° (fully opened).
  4. Map posture changes to configuration changes by adding the following to your test harness:
  5. 
       @Override
       public void onConfigurationChanged(Configuration newConfig) {
           super.onConfigurationChanged(newConfig);
           int orientation = newConfig.orientation;
           int screenWidthDp = newConfig.screenWidthDp;
           int screenHeightDp = newConfig.screenHeightDp;
           Log.d("FoldableDebug", String.format(
               "ConfigChanged: orientation=%s, width=%ddp, height=%ddp",
               orientation == Configuration.ORIENTATION_LANDSCAPE ? "LAND" : "PORT",
               screenWidthDp, screenHeightDp));
       }
    

This logs every time the system recomputes resources, letting you correlate UI glitches with specific posture values.

Test matrix for automated runs

PostureHinge AngleMain Display Width (dp)Cover Display Width (dp)Typical Aspect Ratio
Closed0 (hidden)393 (cover)25:9 (approx)
Half‑opened90°600 (main)393 (cover)12:9 (main) + 25:9 (cover)
Fully opened180°810 (main)0 (hidden)22:9 (main)

Use this matrix to drive parameterized UI tests. In Espresso, a simple rule can set the hinge angle before each test:


@Rule
public ActivityTestRule<MainActivity> activityRule =
        new ActivityTestRule<>(MainActivity.class);

@Before
public void setHingeAngle() {
    // Using adb shell to inject sensor value (requires root or emulator)
    DeviceDeviceUtils.setHingeAngle(90); // 0, 90, or 180
}

With the matrix in place, you can run the same test suite three times (closed, half, opened) and assert that critical flows—login, product listing, checkout—complete without crashes or ANRs. Any divergence immediately flags a foldable‑specific regression.

How to Debug Foldable Device Issues in Mobile Apps: Capturing Logs, Traces, and Profiling Data

Logcat filters for foldable events

Android logs several tags that are useful when debugging foldables:

Create a focused logcat session:


adb logcat ActivityManager:V WindowManager:V HingeAngleSensor:V SurfaceFlinger:V *:S

Pipe the output to a file for later grep:


adb logcat -v threadtime > foldable_log.txt

Systrace for UI jank during posture changes

When the device folds or unfolds, the UI thread may be blocked while layouts are remeasured. Capture a 5‑second systrace around the event:


adb shell systrace \
    -t 5 \
    -o foldable_trace.html \
    sched gfx view wm

Open foldable_trace.html in Chrome and look for:

Profiling GPU overdraw and memory

Foldables often have higher resolution on the main display, increasing GPU load. Use GPU Overdraw (adb shell setprop debug.gpu.overdraw 1) and watch for red overdraw areas that only appear in the fully opened state. Similarly, track memory usage with adb shell dumpsys meminfo before and after each posture switch to detect leaks that are only triggered when extra resources (e.g., high‑resolution assets) are loaded.

Example: Detecting a layout‑thrash bug

Suppose a RecyclerView flickers when moving from half‑opened to fully opened. In the systrace you see:


12.345ms  ViewRootImpl.performTraversals (RecyclerView)
   -> Layout (RecyclerView) 8.2ms
   -> Measure (RecyclerView) 6.1ms

Cross‑reference with logcat:


09:12:34.567 I/ActivityManager: Config changed: screenWidthDp 600 -> 810
09:12:34.580 D/RecyclerView: Adapter notified of data set change (unexpected)

The unexpected adapter notification indicates that your code is calling notifyDataSetChanged() in onConfigurationChanged. Fix by guarding the call with a check for actual data changes or moving the update to onCreate/onStart.

How to Debug Foldable Device Issues in Mobile Apps: Diagnosing UI Layout and Resize Issues

Common layout pitfalls

Foldable devices expose two major layout challenges:

  1. Hard‑coded dimensions (dp or px) that assume a fixed screen width.
  2. Assuming portrait/landscape only, ignoring the intermediate *folded* state where width may be less than height but the device is physically rotated.

Using WindowMetrics and WindowLayoutInfo

Starting API 30, WindowMetrics provides the actual bounds of the application window, excluding system decorations. For foldables, also use WindowLayoutInfo from Jetpack WindowManager to get display feature bounds (hinge, fold, etc.).


val windowMetrics = windowManager.currentWindowMetrics
val bounds = windowMetrics.bounds // Rect of usable area
log("Window bounds: $ bounds")

val windowInfo = WindowInfoTracker.getOrCreate(context)?.windowLayoutInfo
    ?.collect { layoutInfo ->
        layoutInfo.displayFeatures.forEach { feature ->
            when (feature) {
                is FoldingFeature -> {
                    log("FoldingFeature: state=${feature.state}, orientation=${feature.orientation}, bounds=${feature.bounds}")
                }
            }
        }
    }

Debugging with Layout Inspector

Android Studio’s Layout Inspector can capture a snapshot of the view hierarchy for each posture. To automate:

  1. Run the app in the emulator.
  2. Change hinge angle via extended controls.
  3. In Android Studio → *View* → *Tool Windows* → *Layout Inspector* → *Capture snapshot*.
  4. Compare the snapshots side‑by‑side: look for views that exceed their parent bounds, missing constraints, or weight‑sum mismatches.

Example: Fixing a cut‑off toolbar

A common issue is a toolbar that uses match_parent for width but is placed inside a LinearLayout with weightSum that assumes a minimum width of 360dp. On the cover screen (width ≈ 393dp) the toolbar renders fine; on the main screen (width ≈ 810dp) the weight distribution leaves a large empty space on the right, making the toolbar appear left‑aligned.

Fix: Replace the weight‑based layout with a ConstraintLayout that anchors the toolbar to the parent’s start and end, or use and end, or use android:layout_width="0dp" with layout_constraintWidth_default="spread" to let it expand fully.


<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="0dp"
        android:layout_height="?attr/actionBarSize"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

After applying the change, re‑run the test matrix; the toolbar now spans the full width in all three postures.

How to Debug Foldable Device Issues in Mobile Apps: Handling Multi‑Window and App Continuity

Understanding multi‑window modes

Foldables support split‑screen, free‑form, and app continuity (the system automatically resizes the activity when the hinge angle changes). App continuity can cause an activity to be destroyed and recreated if the system decides that the new configuration cannot be satisfied without a restart (e.g., switching from single‑pane to multi‑pane layout).

Detecting unwanted restarts

Add a flag in your Application class to log when an activity is created due to a configuration change:


public class FoldableApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                boolean isConfigChange = savedInstanceState != null;
                Log.i("FoldableApp", "Activity ${activity.localClassName} created"
                        + (isConfigChange ? " after config change" : ""));
            }
            // … other callbacks omitted for brevity …
        });
    }
}

If you see “after config change” logs for every hinge adjustment, your app is likely undergoing unnecessary restarts.

Preventing restarts with android:configChanges

Declare the configuration changes you want to handle yourself in the manifest:


<activity
    android:name=".MainActivity"
    android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
    android:windowSoftInputMode="adjustResize"/>

When you handle these changes, you must also update UI components manually in onConfigurationChanged. Forgetting to adjust a fragment transaction, for example, can lead to overlapping fragments.

Example: Avoiding fragment duplication

Suppose you have a MainActivity that loads a ListFragment in onCreate. In onConfigurationChanged you also call getSupportFragmentManager().beginTransaction().replace(R.id.container, new ListFragment()).commit(). Without checking if the fragment already exists, each configuration change stacks a new instance.

Fix: Use a tag and findFragmentById before committing:


override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)
    val frag = supportFragmentManager.findFragmentById(R.id.container)
    if (frag == null || !(frag is ListFragment)) {
        supportFragmentManager.beginTransaction()
            .replace(R.id.container, ListFragment())
            .commit()
    }
}

Now the fragment survives across posture changes, preserving scroll position and view state.

Testing app continuity with adb

You can force a continuity event without physically folding the device using the shell command:


adb shell cmd window set-app-continuity <package> <enabled|disabled>

Set it to disabled, change the hinge angle, then re‑enable to observe the system’s resize behavior. Combine this with the activity‑creation log above to verify that your app handles the transition gracefully.

How to Debug Foldable Device Issues in Mobile Apps: Dealing with Input Method and Keyboard Changes

Why the IME behaves differently on foldables

When the cover screen is active, the system may show a reduced‑height keyboard because the available vertical space shrinks. Conversely, on the main screen the keyboard may occupy a larger portion, especially if the device is in a laptop‑like mode with an external keyboard attached.

Logging IME visibility

Use ViewTreeObserver.OnGlobalLayoutListener to detect when the window layout changes due to the IME:


view.viewTreeObserver.addOnGlobalLayoutListener {
    val rect = Rect()
    view.getWindowVisibleDisplayFrame(rect)
    val screenHeight = view.rootWindowHeight
    val imeHeight = screenHeight - rect.bottom
    Log.i("IME", "Visible height: $screenHeight, IME height: $imeHeight")
}

Log the imeHeight for each posture; a sudden jump from 0 to 250dp when moving from closed to half‑opened indicates the IME appeared.

Handling adjustResize vs adjustPan

If your layout uses android:windowSoftInputMode="adjustResize", the activity’s content will be resized to make room for the IME. On a narrow cover screen this can push essential UI off‑screen. Switch to adjustPan for screens where you cannot afford to shrink the layout, or provide a separate layout resource for the cover screen (layout-cover).

Example: Fixing a login form that gets hidden

A login screen contains a ScrollView with fields and a button at the bottom. On the cover screen (height ≈ 800dp) the IME height (~260dp) reduces the visible area to ~540dp, causing the button to be hidden despite the ScrollView.

Solution: Add a paddingBottom to the ScrollView equal to the expected IME height when in cover mode, using a resource qualifier:


<!-- res/layout/login.xml -->
<ScrollView
    android:id="@+id/login_scroll"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="0dp">
    <!-- fields -->
</ScrollView>

<!-- res/layout-cover/login.xml -->
<ScrollView
    android:id="@+id/login_scroll"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="260dp">
    <!-- fields -->
</ScrollView>

Alternatively, detect IME height at runtime and set the padding dynamically:


view.viewTreeObserver.addOnGlobalLayoutListener {
    val imeHeight = computeImeHeight()
    login_scroll.setPadding(0, 0, 0, imeHeight)
}

After applying the fix, run the test matrix and verify that the login button remains visible in all three postures.

How to Debug Foldable Device Issues in Mobile Apps: Testing Accessibility and WCAG on Foldables

Accessibility quirks unique to foldables

Automated accessibility checks with Espresso + Accessibility Test Framework

Add the following dependency:


androidTestImplementation 'com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:1.4.0'

Then in a test:


@Rule
public ActivityTestRule<MainActivity> activityRule =
        new ActivityTestRule<>(MainActivity.class);

@Test
public void checkAccessibility() {
    onView(withId(R.id.root))
        .check(matches(isDisplayed()))
        .check(matches(compliesWithAccessibilityRules()));
}

Run this test for each posture (closed, half, opened) by setting the hinge angle before the test. The framework will flag violations such as insufficient contrast, missing content descriptions, or touch targets smaller than 48 dp.

Manual testing with TalkBack

  1. Enable TalkBack (Settings → Accessibility → TalkBack).
  2. Fold/unfold the device while listening to spoken feedback.
  3. Note any moments where TalkBack skips a element, repeats it, or announces the wrong state (e.g., “button disabled” when it is enabled).

Example: Fixing a low‑contrast button on the cover screen

A primary button uses #4A90E2 on a white background. Contrast ratio is 4.5:1, which passes AA for normal text but fails for large text (≥18pt) on the cover screen where the button appears larger due to scaling. The WCAG 2.1 AA requirement for large text is 3:1, but many users with low vision need stricter contrast.

Fix: Adjust the color to #0066CC (contrast 5.3:1) or add a subtle dark overlay (#00000020) behind the text. Update the color in values-cover/colors.xml:


<!-- res/values-cover/colors.xml -->
<color name="button_background">#0066CC</color>

Re‑run the accessibility test; the contrast violation disappears.

How to Debug Foldable Device Issues in Mobile Apps: Using SUSA for Autonomous Exploration

How SUSA discovers foldable‑specific issues

SUSA (the autonomous QA platform from SUSATest) treats each foldable posture as a distinct *environment* and explores the app using a set of persona‑driven agents (curious, impatient, novice, adversarial, elderly, accessibility, power user). Because the platform injects realistic input events—taps, scrolls, long presses, voice commands—it naturally triggers configuration changes as it navigates between screens.

When you point SUSA at an APK or a web URL and enable the *foldable* device profile, it:

  1. Enumerates postures: It starts with closed, then randomly varies hinge angle between 0° and 180° in 15° increments while executing actions.
  2. Logs window metrics: Each action is accompanied by a snapshot of WindowMetrics and WindowLayoutInfo, allowing correlation between a specific hinge angle and any observed crash, ANR, or UI glitch.
  3. Applies persona variations: For example, the *elderly* persona uses slower gestures and larger tap targets, which can reveal issues where UI elements become unreachable after a resize.
  4. Generates regression scripts: After a run, SUSA outputs Appium (Android) scripts that reproduce the exact sequence of gestures and hinge‑angle changes that led to a failure, making it trivial to add the script to your CI pipeline.

Running SUSA locally

Install the agent:


pip install susatest-agent

Assuming you have an APK built (app-debug.apk) and want to test on a Samsung Galaxy Z Fold 5 emulator:


susatest run \
    --apk app-debug.apk \
    --device pixel_fold \
    --postures closed,half,opened \
    --personas curious,elderly,accessibility \
    --output susa_report.json

The --postures flag tells SUSA to cycle through the three hinge angles; you can also supply a custom list like 0,30,60,90,120,150,180 for finer granularity.

Interpreting the report

The JSON report contains sections such as:


{
  "failures": [
    {
      "type": "crash",
      "stacktrace": "...",
      "hingeAngle": 90,
      "persona": "elderly",
      "steps": [
        {"action": "tap", "target": "id/login_button"},
        {"action": "set_hinge_angle", "value": 90},
        {"action": "swipe", "direction": "UP", "distance": 200}
      ]
    },
    {
      "type": "accessibility_violation",
      "rule": "touch_target_size",
      "details": {
        "viewId": "id/next_button",
        "currentSize": "36dp",
        "requiredSize": "48dp",
        "hingeAngle": 0
      }
    }
  ]
}

From this you can see that a crash occurred at a 90° hinge angle when the elderly persona tapped the login button after a swipe, indicating a possible race condition in the login flow triggered by a resize. The accessibility violation shows that the next button is too small on the closed screen.

Integrating SUSA into CI

Add a step in your CI configuration (GitHub Actions example):


- name: Run SUSA foldable test
  run: |
    pip install susatest-agent
    susatest run --apk app/build/outputs/apk/debug/app-debug.apk \
        --device pixel_fold \
        --postures 0,90,180 \
        --personas curious,accessibility \
        --output susa_report.json
- name: Upload SUSA report
  uses: actions/upload-artifact@v3
  with:
    name: susa-report
    path: susa_report.json

If the report contains any failures, the job can be marked as failed, giving you early detection of foldable‑specific regressions before they reach production.

How to Debug Foldable Device Issues in Mobile Apps: Building a Prevention Checklist and CI Integration

Checklist for each release

CategoryItemVerification Method
Manifestandroid:configChanges includes `orientationscreenSizesmallestScreenSizescreenLayout` for activities that must survive posture changesgrep -R "configChanges" in AndroidManifest.xml
ResourcesProvide alternate layouts for layout-cover (width < 600dp) and layout-main (width ≥ 600dp)ls res/layout*
UINo hard‑coded dp widths > 90% of smallest screen widthLayout Inspector + custom lint rule
NavigationFragment transactions use findFragmentById/findFragmentByTag before replaceCode review / SpotBugs
IMEAdjust padding or use adjustPan when imeHeight > 0Espresso test that logs IME height
AccessibilityTouch targets ≥48dp, contrast ≥4.5:1 (normal) / ≥3:1 (large)Run androidx.test.espresso.accessibility.AccessibilityChecks on each posture
State PersistenceViewModel survives configuration changes (SavedStateHandle or ViewModelProvider.Factory)Unit test that rotates configuration and checks state
PerformanceNo UI thread blocks >16ms during hinge angle change (measured via Systrace)CI step that runs a 5‑second systrace and asserts max frame time
Crash/ANRNo crashes or ANRs in any posture after 10 min of random monkey testingadb shell monkey -p -v 5000 per posture
SUSAAutonomous exploration returns zero failures for the defined persona setRun susatest as part of nightly build

Automating the checklist

Create a Gradle task that runs the most critical checks:


task foldableCheck {
    doLast {
        exec {
            commandLine 'adb', 'shell', 'cmd', 'window', 'set-app-continuity', applicationId, 'enabled'
        }
        exec {
            commandLine 'adb', 'shell', 'systrace', '-t', '5', '-o', 'foldable_trace.html', 'sched', 'gfx', 'view', 'wm'
        }
        // run lint with custom rules
        exec {
            commandLine './gradlew', 'lintDebug', '-Pandroid.experimental.lint.testMode=true'
        }
        // run accessibility tests
        exec {
            commandLine './gradlew', 'connectedAndroidTest', '-Pandroid.testInstrumentationRunnerArguments.annotation=androidx.test.espresso.accessibility.AccessibilityChecks'
        }
    }
}

Add foldableCheck to your CI pipeline (e.g., after the unit test stage). If any sub‑step returns a non‑zero exit code, the build fails and you get a clear indication of which category needs attention.

Using the checklist to triage production issues

When a user reports a foldable‑specific bug, refer to the table:

SymptomLikely checklist itemQuick verification
UI elements clipped on cover screenLayout‑cover missing or hard‑coded widthsInspect res/layout-cover/*
App restarts when folding/unfoldingMissing configChanges or improper fragment handlingCheck manifest and onConfigurationChanged
Keyboard hides input fieldwindowSoftInputMode not set to adjustPan or missing IME paddingLog IME height in runtime
TalkBack skips elements after rotationUnstable view IDS or duplicate fragmentsRun accessibility test on each posture
Sporadic ANR during hinge changeHeavy work on main thread during onConfigurationChangedSystrace trace for long UI thread blocks

By mapping the symptom back to the checklist, you can prioritize the fix and add a regression test to prevent recurrence.

How to Debug Foldable Device Issues in Mobile Apps: Real‑World Case Studies and Lessons Learned

Case study 1: Banking app balance overlay

A major bank’s Android app displayed a semi‑transparent overlay showing the account balance on the home screen. On foldable devices, the overlay would appear misaligned—half of it on the cover screen, half on the main screen—when the device was half‑opened. The root cause was that the overlay used WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY with hard‑coded x and y offsets based on the primary display’s metrics obtained at app start.

Fix: Switch to using WindowMetrics#getBounds() to compute the offset each time the overlay is shown, and listen to WindowManager.LayoutChangedCallback for updates. After the fix, the overlay remained anchored to the top‑right corner of the visible window regardless of posture.

Lesson: Never cache display metrics at startup on foldables; always query the current window metrics before laying out overlays or dialogs.

Case study 2: Game rendering surface tearing

A 3D game used a GLSurfaceView that was recreated in onSurfaceChanged. When the device switched from closed to fully opened, the surface width changed from 393 dp to 810 dp, but the game’s projection matrix continued to use the old aspect ratio, causing horizontal stretching and occasional tearing.

Fix: In onSurfaceChanged(GL10 gl, int width, int height), recompute the projection matrix using the actual width and height parameters. Additionally, add a listener to SurfaceHolder.OnRedrawNeeded to trigger a re‑render when the surface size changes.

Lesson: Any custom rendering that depends on window size must read the size from the callback parameters, not from cached values.

Case study 3: Accessibility talkback loop

An e‑commerce app had a floating action button (FAB) that expanded into a speed‑dial menu. On the cover screen, the FAB’s expanded state caused TalkBack to announce the same item repeatedly because the menu’s views were being added and removed on each layout pass, confusing the accessibility focus manager.

Fix: Wrap the speed‑dial menu in a FrameLayout with android:visibility="gone" when collapsed, and only change visibility (not add/remove views) when toggling. This kept the view hierarchy stable, eliminating the loop.

Lesson: Dynamic view insertion/removal can break accessibility services; prefer toggling visibility or using ViewStub for infrequently used UI.

Takeaways from the cases

How to Debug Foldable Device Issues in Mobile Apps: Quick Reference Triage Table

SymptomPrimary CauseDiagnostic StepFix Pattern
UI clipped or overlapped on one posture

Test Your App Autonomously

Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.

Try SUSA Free