diff --git a/services_app/src/androidTest/java/org/opendatakit/utilities/ODKFileUtilsTest.java b/services_app/src/androidTest/java/org/opendatakit/utilities/ODKFileUtilsTest.java new file mode 100644 index 000000000..6f5f476e5 --- /dev/null +++ b/services_app/src/androidTest/java/org/opendatakit/utilities/ODKFileUtilsTest.java @@ -0,0 +1,106 @@ +package org.opendatakit.utilities; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Environment; +import android.provider.MediaStore; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.rule.GrantPermissionRule; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.opendatakit.services.utilities.FileSystemHelper; + +@RunWith(AndroidJUnit4.class) +public class ODKFileUtilsTest { + + private static final String ODK_FOLDER_NAME = "opendatakit"; + private FileSystemHelper fileSystemHelper; + private Context context; + + @Rule + public GrantPermissionRule permissionRule = GrantPermissionRule.grant(android.Manifest.permission.WRITE_EXTERNAL_STORAGE); + + @Before + public void setUp() { + context = ApplicationProvider.getApplicationContext(); + fileSystemHelper = new FileSystemHelper(context); + } + + @Test + public void testCreateDirectory() { + String folderName = "testFolder"; + String relativePath = Environment.DIRECTORY_DOCUMENTS + "/" + ODK_FOLDER_NAME + "/" + folderName; + + Uri directoryUri = fileSystemHelper.getFolderUri(relativePath); + if (directoryUri == null) { + directoryUri = fileSystemHelper.createDirectory(relativePath); + } + assertNotNull("Directory URI should not be null after creation", directoryUri); + + Uri externalUri = MediaStore.Files.getContentUri("external"); + String[] projection = {MediaStore.Files.FileColumns.RELATIVE_PATH}; + String selection = MediaStore.Files.FileColumns.RELATIVE_PATH + "=?"; + String[] selectionArgs = {relativePath + "/"}; + try (Cursor cursor = context.getContentResolver().query(externalUri, projection, selection, selectionArgs, null)) { + boolean directoryExists = cursor != null && cursor.getCount() > 0; + assertTrue("Directory should exist after creation", directoryExists); + } + } + + @Test + public void testGetOdkFolderUri() { + Uri resultUri = fileSystemHelper.getOdkFolder(); + assertNotNull(resultUri); + String[] projection = {MediaStore.MediaColumns.RELATIVE_PATH}; + String selection = MediaStore.MediaColumns.RELATIVE_PATH + "=?"; + String[] selectionArgs = {Environment.DIRECTORY_DOCUMENTS + "/" + ODK_FOLDER_NAME + "/"}; + + Cursor cursor = context.getContentResolver().query( + MediaStore.Files.getContentUri("external"), + projection, + selection, + selectionArgs, + null + ); + assertNotNull(cursor); + assertTrue(cursor.getCount() > 0); + cursor.close(); + } + + + @Test + public void testAssertDirectoryStructure() { + String appName = ODK_FOLDER_NAME; + + try { + fileSystemHelper.assertDirectoryStructure(appName); + } catch (Exception e) { + fail("Exception thrown while asserting directory structure: " + e.getMessage()); + } + + String[] folderNames = {"config", "data", "output", "system", "permanent"}; + for (String folderName : folderNames) { + String relativePath = Environment.DIRECTORY_DOCUMENTS + "/" + appName + "/" + folderName; + + Uri folderUri = fileSystemHelper.getFolderUri(relativePath); + assertNotNull("Folder " + folderName + " does not exist", folderUri); + +// Uri nomediaUri = fileSystemHelper.getFileUri(relativePath, ".nomedia"); +// assertNotNull(".nomedia file does not exist in folder " + folderName, nomediaUri); + } + } + + +} + + diff --git a/services_app/src/main/java/org/opendatakit/services/utilities/FileSystemHelper.java b/services_app/src/main/java/org/opendatakit/services/utilities/FileSystemHelper.java new file mode 100644 index 000000000..dd7a9f151 --- /dev/null +++ b/services_app/src/main/java/org/opendatakit/services/utilities/FileSystemHelper.java @@ -0,0 +1,107 @@ +package org.opendatakit.services.utilities; + +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; + +public class FileSystemHelper { + private static final String ODK_FOLDER_NAME = "opendatakit"; + private final Context context; + + public FileSystemHelper(Context context) { + this.context = context; + } + + public Uri getOdkFolder() { + ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOCUMENTS + "/" + ODK_FOLDER_NAME); + Uri folderUri = context.getContentResolver().insert(MediaStore.Files.getContentUri("external"), values); + return folderUri; + } + + public void assertDirectoryStructure(String appName) { + String[] folderNames = {"config", "data", "output", "system", "permanent"}; + + // Create each directory and check for existence + for (String folderName : folderNames) { + String relativePath = Environment.DIRECTORY_DOCUMENTS + "/" + appName + "/" + folderName; + if (getFolderUri(relativePath) == null) { + createDirectory(relativePath); + } + + // Ensure .nomedia file is created inside each folder + String nomediaFilePath = relativePath; + if (getFileUri(nomediaFilePath, ".nomedia") == null) { + createNomediaFile(nomediaFilePath); + } + } + } + + // Helper method to create a .nomedia file inside a specific directory + private void createNomediaFile(String relativePath) { + ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.DISPLAY_NAME, ".nomedia"); + values.put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath + "/"); + values.put(MediaStore.MediaColumns.MIME_TYPE, "application/octet-stream"); // MIME type for generic binary data + + context.getContentResolver().insert(MediaStore.Files.getContentUri("external"), values); + } + + // Helper method to create a directory using MediaStore + public Uri createDirectory(String relativePath) { + ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.DISPLAY_NAME, relativePath.substring(relativePath.lastIndexOf("/") + 1)); // Directory name + values.put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath); + values.put(MediaStore.MediaColumns.MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR); // MIME type for a directory + + return context.getContentResolver().insert(MediaStore.Files.getContentUri("external"), values); + } + + // Helper method to get the folder Uri using MediaStore + public Uri getFolderUri(String relativePath) { + String[] projection = {MediaStore.MediaColumns._ID}; + String selection = MediaStore.MediaColumns.RELATIVE_PATH + "=?"; + String[] selectionArgs = {relativePath + "/"}; + + try (Cursor cursor = context.getContentResolver().query( + MediaStore.Files.getContentUri("external"), + projection, + selection, + selectionArgs, + null + )) { + if (cursor != null && cursor.moveToFirst()) { + long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)); + return ContentUris.withAppendedId(MediaStore.Files.getContentUri("external"), id); + } + } + return null; + } + + // Helper method to get the file Uri using MediaStore + public Uri getFileUri(String relativePath, String fileName) { + String[] projection = {MediaStore.MediaColumns._ID}; + String selection = MediaStore.MediaColumns.RELATIVE_PATH + "=? AND " + MediaStore.MediaColumns.DISPLAY_NAME + "=?"; + String[] selectionArgs = {relativePath + "/", fileName}; + + try (Cursor cursor = context.getContentResolver().query( + MediaStore.Files.getContentUri("external"), + projection, + selection, + selectionArgs, + null + )) { + if (cursor != null && cursor.moveToFirst()) { + long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)); + return ContentUris.withAppendedId(MediaStore.Files.getContentUri("external"), id); + } + } + return null; + } + +}