From 2dd0acbc6439c17e14d033cfe7fbc00bcdc1abed Mon Sep 17 00:00:00 2001 From: mykh-hailo Date: Fri, 20 Mar 2026 18:27:53 -0300 Subject: [PATCH 1/2] fix: Scan media on download Signed-off-by: mykh-hailo --- .../android/datamodel/FileDataStorageManager.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) 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..8cce73ea8b6b 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -22,6 +22,7 @@ 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; @@ -2044,7 +2045,19 @@ public static void triggerMediaScan(String path, OCFile file) { MainApp.getAppContext().sendBroadcast(intent); } } else { - Log_OC.d(TAG, "SDK > 29, skipping media scan"); + String mimeType = file != null ? file.getMimeType() : null; + MediaScannerConnection.scanFile( + MainApp.getAppContext(), + 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); + } + } + ); } } } From 82478f5fb562f430e239a8b13ad768d76385f9da Mon Sep 17 00:00:00 2001 From: mykh-hailo Date: Tue, 24 Mar 2026 19:19:43 -0300 Subject: [PATCH 2/2] chore: add test update Signed-off-by: mykh-hailo --- .../datamodel/FileDataStorageManager.java | 55 +++-------- ...eDataStorageManagerTriggerMediaScanTest.kt | 98 +++++++++++++++++++ 2 files changed, 114 insertions(+), 39 deletions(-) create mode 100644 app/src/test/java/com/owncloud/android/datamodel/FileDataStorageManagerTriggerMediaScanTest.kt 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 8cce73ea8b6b..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,12 +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; @@ -2016,49 +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 { - String mimeType = file != null ? file.getMimeType() : null; - MediaScannerConnection.scanFile( - MainApp.getAppContext(), - 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); - } - } - ); - } + ); } } 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()) + } + } +}