<NME> LocalStorage.java
<BEF> package cgeo.geocaching.storage;

import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.R;
import cgeo.geocaching.activity.Progress;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.settings.SettingsActivity;
import cgeo.geocaching.ui.TextParam;
import cgeo.geocaching.ui.dialog.SimpleDialog;
import cgeo.geocaching.utils.AndroidRxUtils;
import cgeo.geocaching.utils.AsyncTaskWithProgressText;
import cgeo.geocaching.utils.ContextLogger;
import cgeo.geocaching.utils.EnvironmentUtils;
import cgeo.geocaching.utils.FileUtils;
import cgeo.geocaching.utils.ImageUtils;
import cgeo.geocaching.utils.Log;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Environment;

import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.core.os.EnvironmentCompat;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

/**
 * Handle local storage issues on phone and SD card.
 */
public final class LocalStorage {

    public static final Pattern GEOCACHE_FILE_PATTERN = Pattern.compile("^(GC|TB|TC|CC|LC|EC|GK|MV|TR|VI|MS|EV|CT|GE|GA|WM|O)[A-Z0-9]{2,7}$");

    private static final String FILE_SYSTEM_TABLE_PATH = "/system/etc/vold.fstab";
    private static final String DATABASES_DIRNAME = "databases";
    private static final String GEOCACHE_DATA_DIR_NAME = "GeocacheData";
    private static final String OFFLINE_LOG_IMAGES_DIR_NAME = "OfflineLogImages";
    private static final String MAP_THEME_INTERNAL_DIR_NAME = "MapThemeData";
    private static final String MAPSFORGE_SVG_CACHE_DIR_NAME = "mapsforge-svg-cache";
    private static final String TRACKFILE_CACHE_DIR_NAME = "trackfiles";
    private static final long LOW_DISKSPACE_THRESHOLD = 1024 * 1024 * 100; // 100 MB in bytes

    //Legacy directory names which should NO LONGER BE OF USE
    private static final String CGEO_DIRNAME = "cgeo"; //legacy
    private static final String BACKUP_DIR_NAME = "backup"; //legacy
     *
     * @return the root of the cache directory
     */
    public static File getStorage() {
        return getStorageSpecific(false);
    }
    private static File externalPublicCgeoDirectory;

    private static final int LOCALSTORAGE_VERSION = 3;
     *
     * @return the root of the cache directory
     */
    public static File getStorageSec() {
        return getStorageSpecific(true);
    }

    private static File getStorageSpecific(final boolean secondary) {
        return EnvironmentUtils.isExternalStorageAvailable() ^ secondary ?
                getExternalStorageBase() :
                new File(getInternalStorageBase(), CACHE_DIRNAME);
    }

    public static File getExternalDbDirectory() {
        return getExternalStorageBase();
    }

    public static File getInternalDbDirectory() {
        return new File(getInternalStorageBase(), "databases");
    }

    private static File getExternalStorageBase() {
        return new File(Environment.getExternalStorageDirectory(), CACHE_DIRNAME);
    }
    public static List<File> getAvailableExternalPrivateCgeoDirectories() {
        final List<File> availableExtDirs = new ArrayList<>();
        final File[] externalFilesDirs = ContextCompat.getExternalFilesDirs(CgeoApplication.getInstance(), null);
        for (final File dir : externalFilesDirs) {
            if (dir != null && EnvironmentCompat.getStorageState(dir).equals(Environment.MEDIA_MOUNTED)) {
                availableExtDirs.add(dir);
                Log.i("Added '" + dir + "' as available external dir");
            } else {
                Log.w("'" + dir + "' is NOT available as external dir");
            }
        }
        return availableExtDirs;
    }
     *            the relative or absolute URL
     * @return the file extension, including the leading dot, or the empty string if none could be determined
     */
    static String getExtension(final String url) {
        final String urlExt;
        if (url.startsWith("data:")) {
            // "data:image/png;base64,i53…" -> ".png"
            // find the one selected in preferences
            final String prefDirectory = Settings.getExternalPrivateCgeoDirectory();
            for (final File dir : getAvailableExternalPrivateCgeoDirectories()) {
                if (dir.getAbsolutePath().equals(prefDirectory)) {
                    externalPrivateCgeoDirectory = dir;
                    break;
                }
            }

            // fallback to default external files dir
            if (externalPrivateCgeoDirectory == null) {
                Log.w("Chosen extCgeoDir " + prefDirectory + " is not an available external dir, falling back to default extCgeoDir");
                externalPrivateCgeoDirectory = getFirstExternalPrivateCgeoDirectory();
     *            the geocode
     * @return the cache directory
     */
    public static File getStorageDir(@NonNull final String geocode) {
        return storageDir(getStorage(), geocode);
    }
    }

    /**
     * Uses {@link android.content.Context#getExternalFilesDir(String)} with "null".
     * This is usually the emulated external storage.
     * It falls back to {@link LocalStorage#getInternalCgeoDirectory()}.
     *            the geocode
     * @return the cache directory
     */
    private static File getStorageSecDir(@NonNull final String geocode) {
        return storageDir(getStorageSec(), geocode);
    }

    private static File storageDir(final File base, @NonNull final String geocode) {
        return new File(base, geocode);
    }
    }

    @NonNull
    public static File getExternalDbDirectory() {
        return new File(getFirstExternalPrivateCgeoDirectory(), DATABASES_DIRNAME);
    }

    @NonNull
    public static File getInternalDbDirectory() {
        return new File(getInternalCgeoDirectory(), DATABASES_DIRNAME);
    }
     *            true if an url was given, false if a file name was given
     * @return the file
     */
    public static File getStorageFile(@NonNull final String geocode, final String fileNameOrUrl, final boolean isUrl, final boolean createDirs) {
        return buildFile(getStorageDir(geocode), fileNameOrUrl, isUrl, createDirs);
    }

    @NonNull
    public static File getGeocacheDataDirectory(@NonNull final String geocode) {
        return new File(getGeocacheDataDirectory(), geocode);
    }

    /**
     * Get the internal directory to store offline log images (c:geo-copies) while they are not
     * sent to the server
     *
     * @return the offline log images directory
     *            true if an url was given, false if a file name was given
     * @return the file
     */
    public static File getStorageSecFile(final String geocode, final String fileNameOrUrl, final boolean isUrl) {
        return buildFile(getStorageSecDir(geocode), fileNameOrUrl, isUrl, false);
    }

    private static File buildFile(final File base, final String fileName, final boolean isUrl, final boolean createDirs) {
        if (createDirs) {
            FileUtils.mkdirs(base);
        }
    }

    @NonNull
    public static File getMapsforgeSvgCacheDir() {
        final File dir = new File(getInternalCgeoDirectory(), MAPSFORGE_SVG_CACHE_DIR_NAME);
        dir.mkdirs();
        return dir;
    }

     *            the target file, which will be created if necessary
     * @return true if the operation was successful, false otherwise, in which case the file will not exist
     */
    public static boolean saveEntityToFile(final Response response, final File targetFile) {
        try {
            final boolean saved = saveToFile(response.body().byteStream(), targetFile);
            if (saved) {
    /**
     * Get the primary file corresponding to a geocode and a file name or an url. If it is an url, an appropriate
     * filename will be built by hashing it. The directory structure will be created if needed.
     * A null or empty geocode will be replaced by a default value.
     *
     * @param geocode       the geocode
     * @param fileNameOrUrl the file name or url
     * @param isUrl         true if an url was given, false if a file name was given
        return false;
    }

    private static void saveHeader(final String name, final Response response, final File baseFile) {
        final String header = response.header(name);
        final File file = filenameForHeader(baseFile, name);
        if (header == null) {
    /**
     * Check if an external media (SD card) is available for use.
     *
     * @return true if the external media is properly mounted
     */
    public static boolean isExternalStorageAvailable() {
        return EnvironmentUtils.isExternalStorageAvailable();
    }

        }
    }

    private static File filenameForHeader(final File baseFile, final String name) {
        return new File(baseFile.getAbsolutePath() + "-" + name);
    }

    }

    /**
     * Get all storages available on the device.
     * Will include paths like /mnt/sdcard /mnt/usbdisk /mnt/ext_card /mnt/sdcard/ext_card
     */
    @NonNull
     * @return the cached value, or <tt>null</tt> if none has been cached
     */
    @Nullable
    public static String getSavedHeader(final File baseFile, final String name) {
        try {
            final File file = filenameForHeader(baseFile, name);
            final Reader reader = new InputStreamReader(new FileInputStream(file), CharEncoding.UTF_8);
            try {
                for (final String str : org.apache.commons.io.FileUtils.readLines(file, StandardCharsets.UTF_8.name())) {
                    if (str.startsWith("dev_mount")) {
                        final String[] tokens = StringUtils.split(str);
                        if (tokens.length >= 3) {
                            final String path = tokens[2]; // mountpoint
                            if (!extStorage.equals(path)) {
                                final File directory = new File(path);
                                if (directory.exists() && directory.isDirectory()) {
                                    storages.add(directory);
                                }
                            }
                        }
                    }
                }
            } catch (final IOException e) {
                Log.e("Could not get additional mount points for user content. " +
                        "Proceeding with external storage only (" + extStorage + ")", e);
            }
        }
        return storages;
    }

    /**
     * Returns the external public cgeo directory, something like <pre>/sdcard/cgeo</pre>.
     *            the target file, which will be created if necessary
     * @return true if the operation was successful, false otherwise
     */
    public static boolean saveToFile(final InputStream inputStream, final File targetFile) {
        if (inputStream == null) {
            return false;
        }
            FileUtils.mkdirs(externalPublicCgeoDirectory);
            if (!externalPublicCgeoDirectory.exists() || !externalPublicCgeoDirectory.canWrite()) {
                Log.w("External public cgeo directory '" + externalPublicCgeoDirectory + "' not available");
                externalPublicCgeoDirectory = getInternalCgeoDirectory();
                Log.i("Fallback to internal storage: " + externalPublicCgeoDirectory);
            }
        }
        return externalPublicCgeoDirectory;
    }

    @Deprecated // use ContentStorage to access public dirs
    public static void resetExternalPublicCgeoDirectory() {
        externalPublicCgeoDirectory = null;
    }

    @NonNull
    @Deprecated // Use PersistableFolder.FIELD_NOTES instead
    public static File getFieldNotesDirectory() {
        return new File(getExternalPublicCgeoDirectory(), FIELD_NOTES_DIR_NAME);
    }

    @NonNull
    @Deprecated // Use PersistableFolder.FIELD_NOTES instead
    public static File getLegacyFieldNotesDirectory() {
        return new File(Environment.getExternalStorageDirectory(), FIELD_NOTES_DIR_NAME);
    }

    @NonNull
    @Deprecated // Use PersistableFolder.GPX instead
     *            the target file
     * @return true if the copy happened without error, false otherwise
     */
    public static boolean copy(final File source, final File destination) {
        FileUtils.mkdirs(destination.getParentFile());

        InputStream input = null;
        return new File(Environment.getExternalStorageDirectory(), GPX_DIR_NAME);
    }

    @NonNull
    @Deprecated // Use external dirs through ContentStorage
    public static File getLegacyExternalCgeoDirectory() {
        return new File(Environment.getExternalStorageDirectory(), LEGACY_CGEO_DIR_NAME);
    }

    @NonNull
    @Deprecated // Use PersistableFolder.Backup instead
    public static File getBackupRootDirectory() {
        return new File(getExternalPublicCgeoDirectory(), BACKUP_DIR_NAME);
    }

    @NonNull
    public static File getGeocacheDataDirectory() {
        return new File(getExternalPrivateCgeoDirectory(), GEOCACHE_DATA_DIR_NAME);
    }

    public static void changeExternalPrivateCgeoDir(final SettingsActivity fromActivity, final String newExtDir) {
        final Progress progress = new Progress();
        return copyDone;
    }

    public static boolean copy(final InputStream input, final OutputStream output) {
        try {
            int length;
            final byte[] buffer = new byte[4096];
            if (ArrayUtils.isNotEmpty(files)) {
                progress.setMaxProgressAndReset(files.length);
                progress.setProgress(0);
                for (final File geocacheDataDir : files) {
                    success &= FileUtils.moveTo(geocacheDataDir, newDataDir);
                    progress.incrementProgressBy(1);
                }
            }

            Settings.setExternalPrivateCgeoDirectory(newExtDir);
            Log.i("Ext private c:geo dir was moved to " + newExtDir);

            externalPrivateCgeoDirectory = new File(newExtDir);
            return Observable.just(success);
        }).subscribeOn(Schedulers.io())).subscribe(success -> {
            progress.dismiss();
            final String message = success ? fromActivity.getString(R.string.init_datadirmove_success) : fromActivity.getString(R.string.init_datadirmove_failed);
            SimpleDialog.of(fromActivity).setTitle(R.string.init_datadirmove_datadirmove).setMessage(TextParam.text(message)).show();
        });
    }

    public static int getCurrentVersion() {
        return Settings.getLocalStorageVersion();
    }

    public static int getExpectedVersion() {
        return LOCALSTORAGE_VERSION;
     * @param prefix
     *            The filename prefix
     */
    public static void deleteFilesWithPrefix(final String geocode, final String prefix) {
        final File[] filesToDelete = getFilesWithPrefix(geocode, prefix);
        if (filesToDelete == null) {
            return;
        if (currentVersion >= LOCALSTORAGE_VERSION) {
            //nothing to migrate
            return;
        }

        new MigrateTask(activity, currentVersion, LOCALSTORAGE_VERSION).execute();
    }

    public static boolean isRunningLowOnDiskSpace() {
        return FileUtils.getFreeDiskSpace(getExternalPrivateCgeoDirectory()) < LOW_DISKSPACE_THRESHOLD;
    }

    public static void initGeocacheDataDir() {
        final File nomedia = new File(getGeocacheDataDirectory(), ".nomedia");
        if (!nomedia.exists()) {
            try {
                FileUtils.mkdirs(nomedia.getParentFile());
                nomedia.createNewFile();
            } catch (final IOException e) {
     *            The prefix of the files
     * @return File[] the array of files starting with filenamePrefix in geocode directory
     */
    public static File[] getFilesWithPrefix(final String geocode, final String filenamePrefix) {
        final FilenameFilter filter = new FilenameFilter() {
            @Override
            public boolean accept(final File dir, final String filename) {
                return filename.startsWith(filenamePrefix);
            }
        };
        private int currentMigrateVersion;
        private String currentMigrateVersionTitle;

        MigrateTask(@NonNull final Activity activity, final int currentVersion, final int finalVersion) {
     * Get all storages available on the device.
     * Will include paths like /mnt/sdcard /mnt/usbdisk /mnt/ext_card /mnt/sdcard/ext_card
     */
    public static List<File> getStorages() {

        final String extStorage = Environment.getExternalStorageDirectory().getAbsolutePath();

        private void setMigratedVersion(final int version, final String currentMigrateVersionTitle) {
            this.currentMigrateVersion = version;
            this.currentMigrateVersionTitle = currentMigrateVersionTitle;
            Log.d("MigrateLocalStorage to " + version + ": " + currentMigrateVersionTitle);
            displayProgress(null);
        }

        private void displayProgress(final String minorStatus) {
            publishProgress(
                    activity.getString(R.string.localstorage_migrate_status_major, this.currentMigrateVersion) +
                            (this.currentMigrateVersionTitle == null ? "" : ": " + currentMigrateVersionTitle) +
                            (minorStatus == null ? "" : "\n" + minorStatus));
        }

        @Override
        protected Integer doInBackgroundInternal(final Void[] params) {
            try (ContextLogger ignore = new ContextLogger(true, "LocalStorage: perform Migration from " + currentMigrateVersion + " to " + finalVersion)) {
                return doMigration();
            } catch (RuntimeException re) {
                Log.e("LocalStorage: Exception during Migration to v" + finalVersion + ", stays in v" + currentMigrateVersion, re);
                return currentMigrateVersion;
            }

        }

        private int doMigration() {

            //move Offline Log Images from legacy directory to GeocacheData directories
            if (currentVersion < 1) {
                setMigratedVersion(1, "OfflineLogImages");

                //legacy cgeo offline log images dir
                final File legacyOfflineLogImagesDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), CGEO_DIRNAME);

                //migrate existing Offline Log Image paths
                if (legacyOfflineLogImagesDir.isDirectory()) {
                    final File[] files = legacyOfflineLogImagesDir.listFiles();
                    if (files != null) {
                        for (File offlineImage : files) {
                            if (offlineImage.isFile()) {
                                displayProgress(offlineImage.getName());
                                FileUtils.copy(offlineImage, ImageUtils.getFileForOfflineLogImage(offlineImage.getName()));
                            }
                        }
                    }
                }
            }

            //delete theme files from outdated internal theme file dir
            if (currentVersion < 2) {
                setMigratedVersion(2, "InternalThemeDirMigration");
                FileUtils.deleteDirectory(new File(getGeocacheDataDirectory("shared"), MAP_THEME_INTERNAL_DIR_NAME));
            }

            //delete theme files from outdated internal theme file dir
            if (currentVersion < 3) {
                setMigratedVersion(3, "Move Mapsforge SVG Cache Dir");
                //emergency fix for #12340: SKIP actual migration
                //FileUtils.deleteFilesWithPrefix(getInternalCgeoDirectory(), "svg-");
                //FileUtils.deleteFilesWithPrefix(new File(getInternalCgeoDirectory(), "files"), "svg-");
            }

            return finalVersion;
        }

        protected void onPostExecuteInternal(final Integer result) {
            Log.iForce("LocalStorage: migrated to v" + result);
            Settings.setLocalStorageVersion(result);
        }

    }

}

<MSG> Add nullness annotations to LocalStorage

<DFF> @@ -57,6 +57,7 @@ public final class LocalStorage {
      *
      * @return the root of the cache directory
      */
+    @NonNull
     public static File getStorage() {
         return getStorageSpecific(false);
     }
@@ -66,24 +67,29 @@ public final class LocalStorage {
      *
      * @return the root of the cache directory
      */
+    @NonNull
     public static File getStorageSec() {
         return getStorageSpecific(true);
     }
 
+    @NonNull
     private static File getStorageSpecific(final boolean secondary) {
         return EnvironmentUtils.isExternalStorageAvailable() ^ secondary ?
                 getExternalStorageBase() :
                 new File(getInternalStorageBase(), CACHE_DIRNAME);
     }
 
+    @NonNull
     public static File getExternalDbDirectory() {
         return getExternalStorageBase();
     }
 
+    @NonNull
     public static File getInternalDbDirectory() {
         return new File(getInternalStorageBase(), "databases");
     }
 
+    @NonNull
     private static File getExternalStorageBase() {
         return new File(Environment.getExternalStorageDirectory(), CACHE_DIRNAME);
     }
@@ -103,7 +109,8 @@ public final class LocalStorage {
      *            the relative or absolute URL
      * @return the file extension, including the leading dot, or the empty string if none could be determined
      */
-    static String getExtension(final String url) {
+    @NonNull
+    static String getExtension(@NonNull final String url) {
         final String urlExt;
         if (url.startsWith("data:")) {
             // "data:image/png;base64,i53…" -> ".png"
@@ -123,6 +130,7 @@ public final class LocalStorage {
      *            the geocode
      * @return the cache directory
      */
+    @NonNull
     public static File getStorageDir(@NonNull final String geocode) {
         return storageDir(getStorage(), geocode);
     }
@@ -135,10 +143,12 @@ public final class LocalStorage {
      *            the geocode
      * @return the cache directory
      */
+    @NonNull
     private static File getStorageSecDir(@NonNull final String geocode) {
         return storageDir(getStorageSec(), geocode);
     }
 
+    @NonNull
     private static File storageDir(final File base, @NonNull final String geocode) {
         return new File(base, geocode);
     }
@@ -156,7 +166,8 @@ public final class LocalStorage {
      *            true if an url was given, false if a file name was given
      * @return the file
      */
-    public static File getStorageFile(@NonNull final String geocode, final String fileNameOrUrl, final boolean isUrl, final boolean createDirs) {
+    @NonNull
+    public static File getStorageFile(@NonNull final String geocode, @NonNull final String fileNameOrUrl, final boolean isUrl, final boolean createDirs) {
         return buildFile(getStorageDir(geocode), fileNameOrUrl, isUrl, createDirs);
     }
 
@@ -173,11 +184,13 @@ public final class LocalStorage {
      *            true if an url was given, false if a file name was given
      * @return the file
      */
-    public static File getStorageSecFile(final String geocode, final String fileNameOrUrl, final boolean isUrl) {
+    @NonNull
+    public static File getStorageSecFile(@NonNull final String geocode, @NonNull final String fileNameOrUrl, final boolean isUrl) {
         return buildFile(getStorageSecDir(geocode), fileNameOrUrl, isUrl, false);
     }
 
-    private static File buildFile(final File base, final String fileName, final boolean isUrl, final boolean createDirs) {
+    @NonNull
+    private static File buildFile(final File base, @NonNull final String fileName, final boolean isUrl, final boolean createDirs) {
         if (createDirs) {
             FileUtils.mkdirs(base);
         }
@@ -193,7 +206,7 @@ public final class LocalStorage {
      *            the target file, which will be created if necessary
      * @return true if the operation was successful, false otherwise, in which case the file will not exist
      */
-    public static boolean saveEntityToFile(final Response response, final File targetFile) {
+    public static boolean saveEntityToFile(@NonNull final Response response, @NonNull final File targetFile) {
         try {
             final boolean saved = saveToFile(response.body().byteStream(), targetFile);
             if (saved) {
@@ -208,7 +221,7 @@ public final class LocalStorage {
         return false;
     }
 
-    private static void saveHeader(final String name, final Response response, final File baseFile) {
+    private static void saveHeader(final String name, @NonNull final Response response, @NonNull final File baseFile) {
         final String header = response.header(name);
         final File file = filenameForHeader(baseFile, name);
         if (header == null) {
@@ -224,7 +237,8 @@ public final class LocalStorage {
         }
     }
 
-    private static File filenameForHeader(final File baseFile, final String name) {
+    @NonNull
+    private static File filenameForHeader(@NonNull final File baseFile, final String name) {
         return new File(baseFile.getAbsolutePath() + "-" + name);
     }
 
@@ -238,7 +252,7 @@ public final class LocalStorage {
      * @return the cached value, or <tt>null</tt> if none has been cached
      */
     @Nullable
-    public static String getSavedHeader(final File baseFile, final String name) {
+    public static String getSavedHeader(@NonNull final File baseFile, final String name) {
         try {
             final File file = filenameForHeader(baseFile, name);
             final Reader reader = new InputStreamReader(new FileInputStream(file), CharEncoding.UTF_8);
@@ -270,7 +284,7 @@ public final class LocalStorage {
      *            the target file, which will be created if necessary
      * @return true if the operation was successful, false otherwise
      */
-    public static boolean saveToFile(final InputStream inputStream, final File targetFile) {
+    public static boolean saveToFile(@Nullable final InputStream inputStream, @NonNull final File targetFile) {
         if (inputStream == null) {
             return false;
         }
@@ -306,7 +320,7 @@ public final class LocalStorage {
      *            the target file
      * @return true if the copy happened without error, false otherwise
      */
-    public static boolean copy(final File source, final File destination) {
+    public static boolean copy(@NonNull final File source, @NonNull final File destination) {
         FileUtils.mkdirs(destination.getParentFile());
 
         InputStream input = null;
@@ -335,7 +349,7 @@ public final class LocalStorage {
         return copyDone;
     }
 
-    public static boolean copy(final InputStream input, final OutputStream output) {
+    public static boolean copy(@NonNull final InputStream input, @NonNull final OutputStream output) {
         try {
             int length;
             final byte[] buffer = new byte[4096];
@@ -369,7 +383,7 @@ public final class LocalStorage {
      * @param prefix
      *            The filename prefix
      */
-    public static void deleteFilesWithPrefix(final String geocode, final String prefix) {
+    public static void deleteFilesWithPrefix(@NonNull final String geocode, @NonNull final String prefix) {
         final File[] filesToDelete = getFilesWithPrefix(geocode, prefix);
         if (filesToDelete == null) {
             return;
@@ -395,10 +409,10 @@ public final class LocalStorage {
      *            The prefix of the files
      * @return File[] the array of files starting with filenamePrefix in geocode directory
      */
-    public static File[] getFilesWithPrefix(final String geocode, final String filenamePrefix) {
+    public static File[] getFilesWithPrefix(@NonNull final String geocode, @NonNull final String filenamePrefix) {
         final FilenameFilter filter = new FilenameFilter() {
             @Override
-            public boolean accept(final File dir, final String filename) {
+            public boolean accept(final File dir, @NonNull final String filename) {
                 return filename.startsWith(filenamePrefix);
             }
         };
@@ -409,6 +423,7 @@ public final class LocalStorage {
      * Get all storages available on the device.
      * Will include paths like /mnt/sdcard /mnt/usbdisk /mnt/ext_card /mnt/sdcard/ext_card
      */
+    @NonNull
     public static List<File> getStorages() {
 
         final String extStorage = Environment.getExternalStorageDirectory().getAbsolutePath();

Add nullness annotations to LocalStorage