From 5d1febf7def124ef84e2224923d2b26fcdb81236 Mon Sep 17 00:00:00 2001 From: JatinDream11 Date: Tue, 22 Apr 2025 05:46:44 -0700 Subject: [PATCH] refactor: migrate BlobProvider.java to Kotlin (#50756) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../react/modules/blob/BlobProvider.java | 131 ------------------ .../react/modules/blob/BlobProvider.kt | 113 +++++++++++++++ 2 files changed, 113 insertions(+), 131 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobProvider.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobProvider.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobProvider.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobProvider.java deleted file mode 100644 index 52e67fbcd85..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobProvider.java +++ /dev/null @@ -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; - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobProvider.kt new file mode 100644 index 00000000000..c7bd8aa2332 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobProvider.kt @@ -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?, + selection: String?, + selectionArgs: Array?, + 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?): Int = 0 + + override fun update( + uri: Uri, + values: ContentValues?, + selection: String?, + selectionArgs: Array? + ): 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 + 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 + } +}