Skip to content

MASTG-KNOW-0138: URI Schemes in Android Intent Results

When an activity requests content from another app and receives a result, the responding activity returns data via setResult. Legacy code commonly receives that result through onActivityResult; newer code uses the Activity Result APIs. The result can carry a URI in Intent.getData() that the caller uses to access the content. The URI scheme determines how Android routes that access.

URI Schemes

Android supports several URI schemes. The two most relevant in intent result handling are:

Scheme Route Access control
content:// Routes through a ContentProvider Governed by provider permissions, URI grants, and provider export state
file:// Accesses the filesystem path directly Governed by filesystem permissions for the calling process

A content:// URI identifies content managed by a specific ContentProvider on the device. The caller uses ContentResolver.openInputStream to open a stream, and the system routes the request through provider methods such as ContentProvider.openFile. Provider access depends on manifest permissions, URI permissions, and the provider implementation. See Android ContentProvider for general ContentProvider behavior.

A file:// URI references a filesystem path directly. When the calling app opens a stream for a file:// URI, the system reads from that path using the calling app's process identity and filesystem permissions rather than routing access through a ContentProvider.

How Callers Process Returned URIs

A common legacy pattern in the caller's onActivityResult is to read data.data and open it with ContentResolver:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (resultCode == RESULT_OK) {
        val uri = data?.data ?: return
        val inputStream = contentResolver.openInputStream(uri)
        // copy or process the stream
    }
}

file:// URI Handling

A responding app controls the URI value it returns in the result intent. If that value uses the file:// scheme, the path is interpreted from the caller's process context. For example, file:///data/data/com.example.app/shared_prefs/session.xml denotes a filesystem path under the app-private data directory of com.example.app.

If the caller copies content from a returned URI to another location, the destination's storage area and permissions determine who can read the copied data. For example, Context.getExternalCacheDir returns an app-specific external cache directory.

ContentProvider Metadata

When the responding app returns a content:// URI, the calling app can query OpenableColumns.DISPLAY_NAME to get a human-readable filename:

fun getDisplayName(uri: Uri): String? {
    contentResolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { cursor ->
        if (cursor.moveToFirst()) {
            return cursor.getString(0)
        }
    }
    return null
}

The provider controls the value returned for that column. If the value contains path separators, constructing a path with File(dir, name) resolves it according to normal filesystem path rules:

val name = getDisplayName(uri) ?: "download"
val target = File(context.filesDir, name) // resolves to ../lib-main/lib.so

When the caller opens a content:// URI, the provider implementation returns a ParcelFileDescriptor or stream for the requested content. The file descriptor returned by the provider, the metadata returned by query, and the URI path are related by provider logic rather than by a universal Android guarantee.