Skip to content

Commit 48bea3c

Browse files
Merge pull request #16726 from mykh-hailo/fix/media-scan-on-download
fix: media scan on download for Android 11+
2 parents 2abbc3e + 82478f5 commit 48bea3c

2 files changed

Lines changed: 115 additions & 27 deletions

File tree

app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,10 @@
2121
import android.content.ContentUris;
2222
import android.content.ContentValues;
2323
import android.content.Context;
24-
import android.content.Intent;
24+
import android.media.MediaScannerConnection;
2525
import android.content.OperationApplicationException;
2626
import android.database.Cursor;
2727
import android.net.Uri;
28-
import android.os.Build;
2928
import android.os.RemoteException;
3029
import android.provider.MediaStore;
3130
import android.text.TextUtils;
@@ -2015,37 +2014,28 @@ public List<OCShare> getSharesWithForAFile(String filePath, String accountName)
20152014
}
20162015

20172016
public static void triggerMediaScan(String path) {
2018-
triggerMediaScan(path, null);
2017+
triggerMediaScan(MainApp.getAppContext(), path, null);
20192018
}
20202019

20212020
public static void triggerMediaScan(String path, OCFile file) {
2021+
triggerMediaScan(MainApp.getAppContext(), path, file);
2022+
}
2023+
2024+
public static void triggerMediaScan(Context context, String path, OCFile file) {
20222025
if (path != null && !TextUtils.isEmpty(path)) {
2023-
ContentValues values = new ContentValues();
2024-
ContentResolver contentResolver = MainApp.getAppContext().getContentResolver();
2025-
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
2026-
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
2027-
if (file != null) {
2028-
values.put(MediaStore.Images.Media.MIME_TYPE, file.getMimeType());
2029-
values.put(MediaStore.Images.Media.TITLE, file.getFileName());
2030-
values.put(MediaStore.Images.Media.DISPLAY_NAME, file.getFileName());
2031-
}
2032-
values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
2033-
values.put(MediaStore.Images.Media.RELATIVE_PATH, path);
2034-
values.put(MediaStore.Images.Media.IS_PENDING, 0);
2035-
try {
2036-
contentResolver.insert(MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY),
2037-
values);
2038-
} catch (IllegalArgumentException e) {
2039-
Log_OC.e("MediaScanner", "Adding image to media scanner failed: " + e);
2026+
String mimeType = file != null ? file.getMimeType() : null;
2027+
MediaScannerConnection.scanFile(
2028+
context,
2029+
new String[]{path},
2030+
mimeType != null ? new String[]{mimeType} : null,
2031+
(scannedPath, scannedUri) -> {
2032+
if (scannedUri != null) {
2033+
Log_OC.d(TAG, "Media scan completed for " + scannedPath);
2034+
} else {
2035+
Log_OC.w(TAG, "Media scan failed for " + scannedPath);
20402036
}
2041-
} else {
2042-
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
2043-
intent.setData(Uri.fromFile(new File(path)));
2044-
MainApp.getAppContext().sendBroadcast(intent);
20452037
}
2046-
} else {
2047-
Log_OC.d(TAG, "SDK > 29, skipping media scan");
2048-
}
2038+
);
20492039
}
20502040
}
20512041

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
package com.owncloud.android.datamodel
7+
8+
import android.content.Context
9+
import android.media.MediaScannerConnection
10+
import android.text.TextUtils
11+
import io.mockk.every
12+
import io.mockk.mockk
13+
import io.mockk.mockkStatic
14+
import io.mockk.slot
15+
import io.mockk.unmockkAll
16+
import io.mockk.verify
17+
import org.junit.After
18+
import org.junit.Assert.assertEquals
19+
import org.junit.Before
20+
import org.junit.Test
21+
22+
class FileDataStorageManagerTriggerMediaScanTest {
23+
24+
private lateinit var mockContext: Context
25+
26+
@Before
27+
fun setUp() {
28+
mockContext = mockk(relaxed = true)
29+
mockkStatic(TextUtils::class)
30+
every { TextUtils.isEmpty(any()) } answers { arg<CharSequence?>(0)?.toString().isNullOrEmpty() }
31+
mockkStatic(MediaScannerConnection::class)
32+
every { MediaScannerConnection.scanFile(any(), any(), any(), any()) } returns Unit
33+
}
34+
35+
@After
36+
fun tearDown() {
37+
unmockkAll()
38+
}
39+
40+
@Test
41+
fun triggerMediaScan_withValidPath_callsMediaScannerConnection() {
42+
val path = "/storage/emulated/0/DCIM/photo.jpg"
43+
44+
FileDataStorageManager.triggerMediaScan(mockContext, path, null)
45+
46+
val pathsSlot = slot<Array<String>>()
47+
verify(exactly = 1) {
48+
MediaScannerConnection.scanFile(
49+
mockContext,
50+
capture(pathsSlot),
51+
null,
52+
any()
53+
)
54+
}
55+
assertEquals(path, pathsSlot.captured.single())
56+
}
57+
58+
@Test
59+
fun triggerMediaScan_withOCFile_passesMimeType() {
60+
val path = "/storage/emulated/0/DCIM/photo.jpg"
61+
val file = mockk<OCFile>(relaxed = true) {
62+
every { mimeType } returns "image/jpeg"
63+
}
64+
65+
FileDataStorageManager.triggerMediaScan(mockContext, path, file)
66+
67+
val pathsSlot = slot<Array<String>>()
68+
val mimeTypesSlot = slot<Array<String>>()
69+
verify(exactly = 1) {
70+
MediaScannerConnection.scanFile(
71+
mockContext,
72+
capture(pathsSlot),
73+
capture(mimeTypesSlot),
74+
any()
75+
)
76+
}
77+
assertEquals(path, pathsSlot.captured.single())
78+
assertEquals("image/jpeg", mimeTypesSlot.captured.single())
79+
}
80+
81+
@Test
82+
fun triggerMediaScan_withEmptyPath_doesNotCallMediaScanner() {
83+
FileDataStorageManager.triggerMediaScan(mockContext, "", null)
84+
85+
verify(exactly = 0) {
86+
MediaScannerConnection.scanFile(any(), any(), any(), any())
87+
}
88+
}
89+
90+
@Test
91+
fun triggerMediaScan_withNullPath_doesNotCallMediaScanner() {
92+
FileDataStorageManager.triggerMediaScan(mockContext, null, null)
93+
94+
verify(exactly = 0) {
95+
MediaScannerConnection.scanFile(any(), any(), any(), any())
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)