Skip to content

fix(playintegrity): complete unsupported requestAndShowDialog requests - #3829

Open
ysard wants to merge 2 commits into
microg:masterfrom
ysard:ysard-fix-requestAndShowDialog-hanging-clients-indefinitely
Open

ysard wants to merge 2 commits into
microg:masterfrom
ysard:ysard-fix-requestAndShowDialog-hanging-clients-indefinitely

Conversation

@ysard

@ysard ysard commented Sep 23, 2026

Copy link
Copy Markdown

Description

IntegrityService.requestAndShowDialog() currently does not send any response to the caller.

As a result, Play Integrity clients waiting for the corresponding Task remain pending indefinitely.

This was observed with Boursobank, which calls:

requestAndShowDialog(com.boursorama.android.clients, 1)

where dialog type 1 is GET_LICENSED (see).
GET_LICENSED should trigger a dialog prompting users to acquire or purchase a license for the app on Google Play (see)

Problem

The current implementation of requestAndShowDialog() is empty and returns without invoking IRequestDialogCallback:

override fun requestAndShowDialog(bundle: Bundle?, callback: IRequestDialogCallback?) {
Log.d(TAG, "Not yet implemented: requestAndShowDialog")
}

This leaves the Play Core request unresolved.

The issue is particularly visible after #3725, which fixed the Binder transaction ID of IRequestDialogCallback.onRequestDialog(). Once that transaction ID is correct, clients can reach requestAndShowDialog(), but the service still does not complete the request.

Observed behavior

On Boursobank app:

PlayCore: IntegrityService : requestAndShowDialog(com.boursorama.android.clients, 1)
IntegrityService: requestAndShowDialog(...)

The application remains stuck on its loading screen indefinitely.

Now

The app receives a DIALOG_UNAVAILABLE result and proceeds to its own handling of the unavailable remediation dialog, displaying a message that the application must be installed from Google Play.

This confirms that the missing callback is the cause of the indefinite wait.

Reversed app

The Play Core client expects the IRequestDialogCallback response to contain either:

error or dialog.intent

int error = bundle.getInt("error");

if (error != 0) {
    ...
    return;
}

PendingIntent pendingIntent =
    (PendingIntent) bundle.getParcelable("dialog.intent");

if (pendingIntent == null) {
    iVar.d(0); // DIALOG_UNAVAILABLE
    return;
}

Quick workflow:

              Boursobank
                  │
                  │ requestAndShowDialog()
                  ▼
              microG IntegrityService
                  │
        ┌─────────┴─────────┐─────────────────────────────────┐
        │                   │                                 |
Bundle "error"        Bundle "dialog.intent"           Empty Bundle
        │                   │                                 │
        ▼                   ▼                                 ▼
Task error       PlayCoreDialogWrapperActivity      Show message/handle the error
                            │                        (DIALOG_UNAVAILABLE)
                            ▼
                        PendingIntent

When neither is present, the Play Core implementation treats the response as an unavailable dialog and completes the corresponding task with DIALOG_UNAVAILABLE.

Therefore, returning an empty Bundle is preferable to silently leaving the request pending.

This PR does not claim to implement Play Integrity remediation dialogs/flow. It only ensures that unsupported remediation requests complete.

In particular, GET_LICENSED may still requires a proper remediation PendingIntent if full Play Integrity remediation support is to be implemented...

Related

#3738
#3725

Before After
Boursobank stuck on loading screen Error shown with a Try again button
The app is stuck on the integrity check screen The callback is executed

jonathanklee and others added 2 commits August 19, 2026 16:31
Play Core dispatches onRequestDialog on binder code 2 while the implicit AIDL id resolved to code 1, so the integrity dialog callback was silently dropped and apps waiting on it stayed stuck on their integrity check screen forever.
requestAndShowDialog() currently does not send any response to the caller.
As a result, Play Integrity clients waiting for the corresponding Task remain pending indefinitely.
@ale5000-git

Copy link
Copy Markdown
Member

Is all that code really needed now?

In my opinion this is enough (for an unimplemented code):

    override fun requestAndShowDialog(bundle: Bundle?, callback: IRequestDialogCallback?) {
        Log.d(TAG, "Not yet implemented: requestAndShowDialog")

        try {
            /* A proper remediation implementation should return a Bundle
             * containing "dialog.intent".
             */
            callback?.onRequestDialog(Bundle())
        } catch (e: Exception) {
            Log.d(TAG, "requestAndShowDialog: callback failed")
        }
    }

Once it is fully implemented then the log can be improved but now it is just wasting time.

@ysard

ysard commented Sep 24, 2026

Copy link
Copy Markdown
Author

Thanks for the review.
As you wish, I can edit, force, or squash the commit.
I’ve tended to add a lot of logs after spending hours pulling my hair out, following a ROM reinstall...

That said, I’d like to point out that knowing the bundle’s attributes (especially dialog.intent.type) can be very helpful for tracking the progress of a process that’s often completely opaque or poorly reported to the user by the apps.

Here, I was able to figure out what was causing my issues as I applied fixes to the ROM:

  • app recognition verdict
  • app licensing verdict
  • and now, device recognition verdict

@ale5000-git

Copy link
Copy Markdown
Member

The choice is on @mar-v-in to whether the binder check is worth in an unimplemented function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants