refactor: migrate BlobProvider.java to Kotlin (#50756)

Summary:
This PR aims to migrate BlobProvider from Java to kotlin as part of [50513](https://github.com/facebook/react-native/issues/50513)

## Changelog:
[ANDROID][CHANGED] – Migrate BlobProvider to Kotlin

Pull Request resolved: https://github.com/facebook/react-native/pull/50756

Test Plan: Tested on RN tester with both new and old arch

Reviewed By: rshest

Differential Revision: D73420896

Pulled By: cortinico

fbshipit-source-id: 4c9a26452a7e45a78c9698f699822b4ac855628c
This commit is contained in:
JatinDream11
2025-04-22 05:46:44 -07:00
committed by Facebook GitHub Bot
parent 1fb04e2134
commit 5d1febf7de
2 changed files with 113 additions and 131 deletions
@@ -1,131 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.modules.blob;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import androidx.annotation.Nullable;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.bridge.ReactContext;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Nullsafe(Nullsafe.Mode.LOCAL)
public final class BlobProvider extends ContentProvider {
private static final int PIPE_CAPACITY = 65536;
private final ExecutorService executor = Executors.newSingleThreadExecutor();
@Override
public boolean onCreate() {
return true;
}
@Override
public @Nullable Cursor query(
Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public @Nullable String getType(Uri uri) {
return null;
}
@Override
public @Nullable Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
@Override
public @Nullable ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
if (!mode.equals("r")) {
throw new FileNotFoundException("Cannot open " + uri.toString() + " in mode '" + mode + "'");
}
BlobModule blobModule = null;
Context context = getContext().getApplicationContext();
if (context instanceof ReactApplication) {
ReactNativeHost host = ((ReactApplication) context).getReactNativeHost();
ReactContext reactContext = host.getReactInstanceManager().getCurrentReactContext();
if (reactContext == null) {
throw new RuntimeException("No ReactContext associated with BlobProvider");
}
blobModule = reactContext.getNativeModule(BlobModule.class);
}
if (blobModule == null) {
throw new RuntimeException("No blob module associated with BlobProvider");
}
final byte[] data = blobModule.resolve(uri);
if (data == null) {
throw new FileNotFoundException("Cannot open " + uri + ", blob not found.");
}
ParcelFileDescriptor[] pipe;
try {
pipe = ParcelFileDescriptor.createPipe();
} catch (IOException exception) {
return null;
}
ParcelFileDescriptor readSide = pipe[0];
final ParcelFileDescriptor writeSide = pipe[1];
if (data.length <= PIPE_CAPACITY) {
// If the blob length is less than or equal to pipe capacity (64 KB),
// we can write the data synchronously to the pipe buffer.
try (OutputStream outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(writeSide)) {
outputStream.write(data);
} catch (IOException exception) {
return null;
}
} else {
// For blobs larger than 64 KB, a synchronous write would fill up the whole buffer
// and block forever, because there are no readers to empty the buffer.
// Writing from a separate thread allows us to return the read side descriptor
// immediately so that both writer and reader can work concurrently.
// Reading from the pipe empties the buffer and allows the next chunks to be written.
Runnable writer =
new Runnable() {
public void run() {
try (OutputStream outputStream =
new ParcelFileDescriptor.AutoCloseOutputStream(writeSide)) {
outputStream.write(data);
} catch (IOException exception) {
// no-op
}
}
};
executor.submit(writer);
}
return readSide;
}
}
@@ -0,0 +1,113 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.modules.blob
import android.content.ContentProvider
import android.content.ContentValues
import android.database.Cursor
import android.net.Uri
import android.os.ParcelFileDescriptor
import com.facebook.react.ReactApplication
import java.io.FileNotFoundException
import java.io.IOException
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
public final class BlobProvider : ContentProvider() {
private val executor: ExecutorService = Executors.newSingleThreadExecutor()
override fun onCreate(): Boolean = true
override fun query(
uri: Uri,
projection: Array<String>?,
selection: String?,
selectionArgs: Array<String>?,
sortOrder: String?
): Cursor? = null
override fun getType(uri: Uri): String? = null
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int = 0
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<String>?
): Int = 0
@Throws(FileNotFoundException::class)
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
if (mode != "r") {
throw FileNotFoundException("Cannot open $uri in mode '$mode'")
}
var blobModule: BlobModule? = null
val context = context?.applicationContext
if (context is ReactApplication) {
val host = (context as ReactApplication).reactNativeHost
val reactContext =
host.reactInstanceManager.currentReactContext
?: throw RuntimeException("No ReactContext associated with BlobProvider")
blobModule = reactContext.getNativeModule(BlobModule::class.java)
}
if (blobModule == null) {
throw RuntimeException("No blob module associated with BlobProvider")
}
val data =
blobModule.resolve(uri) ?: throw FileNotFoundException("Cannot open $uri, blob not found.")
val pipe: Array<ParcelFileDescriptor>
try {
pipe = ParcelFileDescriptor.createPipe()
} catch (exception: IOException) {
return null
}
val readSide = pipe[0]
val writeSide = pipe[1]
if (data.size <= PIPE_CAPACITY) {
// If the blob length is less than or equal to pipe capacity (64 KB),
// we can write the data synchronously to the pipe buffer.
try {
ParcelFileDescriptor.AutoCloseOutputStream(writeSide).use { outputStream ->
outputStream.write(data)
}
} catch (exception: IOException) {
return null
}
} else {
// For blobs larger than 64 KB, a synchronous write would fill up the whole buffer
// and block forever, because there are no readers to empty the buffer.
// Writing from a separate thread allows us to return the read side descriptor
// immediately so that both writer and reader can work concurrently.
// Reading from the pipe empties the buffer and allows the next chunks to be written.
val writer = Runnable {
try {
ParcelFileDescriptor.AutoCloseOutputStream(writeSide).use { outputStream ->
outputStream.write(data)
}
} catch (exception: IOException) {
// no-op
}
}
executor.submit(writer)
}
return readSide
}
private companion object {
private const val PIPE_CAPACITY = 65536
}
}