Skip to content

Potential main-thread ContentResolver query while importing selected images #21275

Description

@venkyqz

Checked for duplicates?

  • This issue is not a duplicate

What are the steps to reproduce this bug?

Hi AnkiDroid Team,

I’m a PhD student researching Android performance issues. My research group recently ran a static analysis scan for thread-affinity and main-thread blocking bugs in real-world Android apps, and our prototype flagged a potential issue in AnkiDroid.

This report has been strictly re-audited against the latest default branch source listed below, rather than only relying on the older commit URL from the initial static-analysis export. I did not dynamically reproduce an ANR/crash, so this should be treated as a source-confirmed main-thread blocking risk rather than a reproduced runtime failure.

App version

Current default branch: ankidroid/Anki-Android@main

Checked target

  • Repository: ankidroid/Anki-Android
  • Corrected caller: MultimediaImageFragment#handleImageUri/internalizeUri -> MultimediaUtils#getImageNameFromUri
  • Detected API / pattern: ContentResolverCompat#query(...) and Cursor#moveToFirst()
  • Underlying platform APIs: ContentResolver#query(...), Cursor#moveToFirst()
  • Observed context: multimedia image selection/import UI path
  • Expected context: worker/background thread before content-provider metadata lookup

What I found

The latest multimedia image flow still resolves a selected content URI’s display name synchronously. MultimediaImageFragment calls internalizeUri(...) from UI/lifecycle paths, and internalizeUri(...) calls MultimediaUtils.getImageNameFromUri(...). For content/document URIs, that function queries the content resolver and moves the cursor to the first row.

Verified source trace

User selects or passes an image into MultimediaImageFragment
  -> MultimediaImageFragment.handleImageUri()
  -> processExternalImage(uri)
  -> internalizeUri(uri)
  -> MultimediaUtils.getImageNameFromUri(requireContext(), uri)
  -> getImageNameFromContentResolver(...)
  -> ContentResolverCompat.query(...)
  -> cursor.moveToFirst()
  -> display name is used to create cached/internal file

Why this matters

Image import often involves external document providers, downloads, gallery providers, or cloud-backed providers. Resolving display names synchronously can block the multimedia editor while the user is waiting for the selected image to appear.

Android’s ANR guidance lists slow I/O, long calculations, and synchronous Binder calls on the main thread as common ANR patterns. In this case, the risky operation is user-visible because it runs from an Activity/Fragment/View/service lifecycle path or a direct UI action path.

Possible fix

Move display-name resolution and file internalization into Dispatchers.IO. The fragment should show loading/progress state and update preview only after the URI has been copied/resolved.

A typical Android pattern is to move the blocking work to an IO/background dispatcher or executor, then publish only the final UI state on the main thread, for example:

lifecycleScope.launch {
    val result = withContext(Dispatchers.IO) {
        // perform database/content-provider/file/media work here
    }

    // update UI here on the main thread
}

For non-UI classes, a dedicated executor, repository-level coroutine scope, or existing background worker would also work. The important point is to avoid performing the blocking operation synchronously on the main thread.

Reference

Android ANR guidance:

https://developer.android.com/topic/performance/vitals/anr

Source references

Research

  • I have checked the manual and the FAQ and could not find a solution to my issue
  • (Optional) I have confirmed the issue is not resolved in the latest alpha release (instructions)

Metadata

Metadata

Assignees

No one assigned

    Type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions