diff --git a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java index 99918cce5c0d..ff35d268a93f 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -21,11 +21,10 @@ import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; -import android.content.Intent; +import android.media.MediaScannerConnection; import android.content.OperationApplicationException; import android.database.Cursor; import android.net.Uri; -import android.os.Build; import android.os.RemoteException; import android.provider.MediaStore; import android.text.TextUtils; @@ -2015,37 +2014,28 @@ public List getSharesWithForAFile(String filePath, String accountName) } public static void triggerMediaScan(String path) { - triggerMediaScan(path, null); + triggerMediaScan(MainApp.getAppContext(), path, null); } public static void triggerMediaScan(String path, OCFile file) { + triggerMediaScan(MainApp.getAppContext(), path, file); + } + + public static void triggerMediaScan(Context context, String path, OCFile file) { if (path != null && !TextUtils.isEmpty(path)) { - ContentValues values = new ContentValues(); - ContentResolver contentResolver = MainApp.getAppContext().getContentResolver(); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) { - if (file != null) { - values.put(MediaStore.Images.Media.MIME_TYPE, file.getMimeType()); - values.put(MediaStore.Images.Media.TITLE, file.getFileName()); - values.put(MediaStore.Images.Media.DISPLAY_NAME, file.getFileName()); - } - values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000); - values.put(MediaStore.Images.Media.RELATIVE_PATH, path); - values.put(MediaStore.Images.Media.IS_PENDING, 0); - try { - contentResolver.insert(MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY), - values); - } catch (IllegalArgumentException e) { - Log_OC.e("MediaScanner", "Adding image to media scanner failed: " + e); + String mimeType = file != null ? file.getMimeType() : null; + MediaScannerConnection.scanFile( + context, + new String[]{path}, + mimeType != null ? new String[]{mimeType} : null, + (scannedPath, scannedUri) -> { + if (scannedUri != null) { + Log_OC.d(TAG, "Media scan completed for " + scannedPath); + } else { + Log_OC.w(TAG, "Media scan failed for " + scannedPath); } - } else { - Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); - intent.setData(Uri.fromFile(new File(path))); - MainApp.getAppContext().sendBroadcast(intent); } - } else { - Log_OC.d(TAG, "SDK > 29, skipping media scan"); - } + ); } } diff --git a/app/src/test/java/com/owncloud/android/datamodel/FileDataStorageManagerTriggerMediaScanTest.kt b/app/src/test/java/com/owncloud/android/datamodel/FileDataStorageManagerTriggerMediaScanTest.kt new file mode 100644 index 000000000000..04b63716af24 --- /dev/null +++ b/app/src/test/java/com/owncloud/android/datamodel/FileDataStorageManagerTriggerMediaScanTest.kt @@ -0,0 +1,98 @@ +/* + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.datamodel + +import android.content.Context +import android.media.MediaScannerConnection +import android.text.TextUtils +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.slot +import io.mockk.unmockkAll +import io.mockk.verify +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test + +class FileDataStorageManagerTriggerMediaScanTest { + + private lateinit var mockContext: Context + + @Before + fun setUp() { + mockContext = mockk(relaxed = true) + mockkStatic(TextUtils::class) + every { TextUtils.isEmpty(any()) } answers { arg(0)?.toString().isNullOrEmpty() } + mockkStatic(MediaScannerConnection::class) + every { MediaScannerConnection.scanFile(any(), any(), any(), any()) } returns Unit + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun triggerMediaScan_withValidPath_callsMediaScannerConnection() { + val path = "/storage/emulated/0/DCIM/photo.jpg" + + FileDataStorageManager.triggerMediaScan(mockContext, path, null) + + val pathsSlot = slot>() + verify(exactly = 1) { + MediaScannerConnection.scanFile( + mockContext, + capture(pathsSlot), + null, + any() + ) + } + assertEquals(path, pathsSlot.captured.single()) + } + + @Test + fun triggerMediaScan_withOCFile_passesMimeType() { + val path = "/storage/emulated/0/DCIM/photo.jpg" + val file = mockk(relaxed = true) { + every { mimeType } returns "image/jpeg" + } + + FileDataStorageManager.triggerMediaScan(mockContext, path, file) + + val pathsSlot = slot>() + val mimeTypesSlot = slot>() + verify(exactly = 1) { + MediaScannerConnection.scanFile( + mockContext, + capture(pathsSlot), + capture(mimeTypesSlot), + any() + ) + } + assertEquals(path, pathsSlot.captured.single()) + assertEquals("image/jpeg", mimeTypesSlot.captured.single()) + } + + @Test + fun triggerMediaScan_withEmptyPath_doesNotCallMediaScanner() { + FileDataStorageManager.triggerMediaScan(mockContext, "", null) + + verify(exactly = 0) { + MediaScannerConnection.scanFile(any(), any(), any(), any()) + } + } + + @Test + fun triggerMediaScan_withNullPath_doesNotCallMediaScanner() { + FileDataStorageManager.triggerMediaScan(mockContext, null, null) + + verify(exactly = 0) { + MediaScannerConnection.scanFile(any(), any(), any(), any()) + } + } +}