fix: use setRequireOriginal on SDK 29 and above (#29072)

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
shenlong 2026-08-04 20:43:51 +05:30 committed by GitHub
parent 7121e0a75c
commit 29512081db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 54 additions and 17 deletions

View file

@ -4,7 +4,6 @@ import android.annotation.SuppressLint
import android.content.ContentUris import android.content.ContentUris
import android.content.Context import android.content.Context
import android.database.Cursor import android.database.Cursor
import androidx.exifinterface.media.ExifInterface
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.ext.SdkExtensions import android.os.ext.SdkExtensions
@ -30,6 +29,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.sync.withPermit
import java.io.File import java.io.File
import java.io.IOException
import java.io.InputStream
import java.security.MessageDigest import java.security.MessageDigest
import kotlin.coroutines.cancellation.CancellationException import kotlin.coroutines.cancellation.CancellationException
@ -377,7 +378,11 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
)?.use { cursor -> cursor.count.toLong() } ?: 0L )?.use { cursor -> cursor.count.toLong() } ?: 0L
fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?, callback: (Result<List<PlatformAsset>>) -> Unit) { fun getAssetsForAlbum(
albumId: String,
updatedTimeCond: Long?,
callback: (Result<List<PlatformAsset>>) -> Unit
) {
runSync(callback) { getAssetsForAlbum(albumId, updatedTimeCond) } runSync(callback) { getAssetsForAlbum(albumId, updatedTimeCond) }
} }
@ -419,7 +424,7 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
}.awaitAll() }.awaitAll()
completeWhenActive(callback, Result.success(results)) completeWhenActive(callback, Result.success(results))
} catch (e: CancellationException) { } catch (_: CancellationException) {
completeWhenActive( completeWhenActive(
callback, Result.failure( callback, Result.failure(
FlutterError( FlutterError(
@ -437,23 +442,20 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
private suspend fun hashAsset(assetId: String): HashResult { private suspend fun hashAsset(assetId: String): HashResult {
return try { return try {
val assetUri = ContentUris.withAppendedId(
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
assetId.toLong()
)
val digest = MessageDigest.getInstance("SHA-1") val digest = MessageDigest.getInstance("SHA-1")
ctx.contentResolver.openInputStream(assetUri)?.use { inputStream -> openOriginalStream(assetId).use { inputStream ->
var bytesRead: Int
val buffer = ByteArray(HASH_BUFFER_SIZE) val buffer = ByteArray(HASH_BUFFER_SIZE)
while (inputStream.read(buffer).also { bytesRead = it } > 0) { while (true) {
val bytesRead = inputStream.read(buffer)
if (bytesRead == -1) break
currentCoroutineContext().ensureActive() currentCoroutineContext().ensureActive()
digest.update(buffer, 0, bytesRead) digest.update(buffer, 0, bytesRead)
} }
} ?: return HashResult(assetId, "Cannot open input stream for asset", null) }
val hashString = Base64.encodeToString(digest.digest(), Base64.NO_WRAP) HashResult(assetId, null, Base64.encodeToString(digest.digest(), Base64.NO_WRAP))
HashResult(assetId, null, hashString) } catch (e: CancellationException) {
throw e
} catch (e: SecurityException) { } catch (e: SecurityException) {
HashResult(assetId, "Permission denied accessing asset: ${e.message}", null) HashResult(assetId, "Permission denied accessing asset: ${e.message}", null)
} catch (e: Exception) { } catch (e: Exception) {
@ -461,6 +463,37 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
} }
} }
private fun openOriginalStream(assetId: String): InputStream {
val id = assetId.toLong()
val collection = when (getMediaType(id)) {
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
else -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
val uri = ContentUris.withAppendedId(collection, id)
val original = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.setRequireOriginal(uri)
} else {
uri
}
return ctx.contentResolver.openInputStream(original)
?: throw IOException("Cannot open original stream for asset $assetId")
}
private fun getMediaType(id: Long): Int =
getCursor(
MediaStore.VOLUME_EXTERNAL,
"${MediaStore.MediaColumns._ID} = ?",
arrayOf(id.toString()),
arrayOf(MediaStore.Files.FileColumns.MEDIA_TYPE)
)?.use { cursor ->
if (cursor.moveToFirst()) {
cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MEDIA_TYPE))
} else {
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
}
} ?: MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
fun cancelHashing() { fun cancelHashing() {
hashTask?.cancel() hashTask?.cancel()
hashTask = null hashTask = null
@ -476,8 +509,11 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa
syncJob = CoroutineScope(Dispatchers.IO).launch { syncJob = CoroutineScope(Dispatchers.IO).launch {
try { try {
completeWhenActive(callback, Result.success(work())) completeWhenActive(callback, Result.success(work()))
} catch (e: CancellationException) { } catch (_: CancellationException) {
completeWhenActive(callback, Result.failure(FlutterError(SYNC_CANCELLED_CODE, "Sync cancelled", null))) completeWhenActive(
callback,
Result.failure(FlutterError(SYNC_CANCELLED_CODE, "Sync cancelled", null))
)
} catch (e: Exception) { } catch (e: Exception) {
completeWhenActive(callback, Result.failure(e)) completeWhenActive(callback, Result.failure(e))
} }

View file

@ -66,11 +66,12 @@ class HashService {
await _hashAssets(pseudoAlbum, trashedToHash, isTrashed: true); await _hashAssets(pseudoAlbum, trashedToHash, isTrashed: true);
} }
} }
} on PlatformException catch (e) { } on PlatformException catch (e, s) {
if (e.code == _kHashCancelledCode) { if (e.code == _kHashCancelledCode) {
_log.warning("Hashing cancelled by platform"); _log.warning("Hashing cancelled by platform");
return; return;
} }
_log.severe("Native hashing failed: ${e.code}", e, s);
} catch (e, s) { } catch (e, s) {
_log.severe("Error during hashing", e, s); _log.severe("Error during hashing", e, s);
} }