package org.osmdroid.tileprovider.util; import android.content.Context; import android.os.Build; import android.os.Environment; import android.util.Log; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Scanner; import java.util.Set; import java.util.StringTokenizer; import java.util.UUID; /** * Based on some of the responses from this question * http://stackoverflow.com/questions/5694933/find-an-external-sd-card-location/15612964#15612964 * Returns the first storage mount point with the most space that is writable for use as the default * location for the osmdroid cache. If an external mount point is not available, application private * storage will be used * Created by alex on 10/19/16. */ public class StorageUtils { public static final String SD_CARD = "sdCard"; public static final String EXTERNAL_SD_CARD = "externalSdCard"; private static final String TAG = "StorageUtils"; public static class StorageInfo { public final String path; public final boolean internal; public boolean readonly; public final int display_number; public long freeSpace = 0; String displayName=""; public StorageInfo(String path, boolean internal, boolean readonly, int display_number) { this.path = path; this.internal = internal; this.display_number = display_number; if (Build.VERSION.SDK_INT >= 9) { this.freeSpace = new File(path).getFreeSpace(); } if (!readonly) { //confirm it's writable File f = new File(path + File.separator + UUID.randomUUID().toString()); try { f.createNewFile(); f.delete(); this.readonly = false; } catch (Throwable e) { this.readonly = true; } } else { this.readonly = readonly; } StringBuilder res = new StringBuilder(); if (internal) { res.append("Internal SD card"); } else if (display_number > 1) { res.append("SD card " + display_number); } else { res.append("SD card"); } if (readonly) { res.append(" (Read only)"); } displayName= res.toString(); } public String getDisplayName() { return displayName; } public void setDisplayName(String val){ displayName=val; } } /** * returns all storage paths, writable or not * @return */ public static List<StorageInfo> getStorageList() { List<StorageInfo> list = new ArrayList<StorageInfo>(); String def_path = ""; boolean def_path_internal = false; String def_path_state = ""; boolean def_path_readonly = true; boolean def_path_available = false; try { if (Environment.getExternalStorageDirectory() != null) { def_path = Environment.getExternalStorageDirectory().getPath(); } } catch (Throwable ex) { //trap for android studio layout editor and some for certain devices //see https://github.com/osmdroid/osmdroid/issues/508 ex.printStackTrace(); } try { def_path_internal = (Build.VERSION.SDK_INT >= 9) && !Environment.isExternalStorageRemovable(); } catch (Throwable ex) { //trap for android studio layout editor and some for certain devices //see https://github.com/osmdroid/osmdroid/issues/508 ex.printStackTrace(); } try { def_path_state = Environment.getExternalStorageState(); } catch (Throwable ex) { //trap for android studio layout editor and some for certain devices //see https://github.com/osmdroid/osmdroid/issues/508 ex.printStackTrace(); } try { def_path_available = def_path_state.equals(Environment.MEDIA_MOUNTED) || def_path_state.equals(Environment.MEDIA_MOUNTED_READ_ONLY); } catch (Throwable ex) { //trap for android studio layout editor and some for certain devices //see https://github.com/osmdroid/osmdroid/issues/508 ex.printStackTrace(); } try { def_path_readonly = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY); } catch (Throwable ex) { //trap for android studio layout editor and some for certain devices //see https://github.com/osmdroid/osmdroid/issues/508 ex.printStackTrace(); } BufferedReader buf_reader = null; try { HashSet<String> paths = new HashSet<String>(); buf_reader = new BufferedReader(new FileReader("/proc/mounts")); String line; int cur_display_number = 1; Log.d(TAG, "/proc/mounts"); while ((line = buf_reader.readLine()) != null) { Log.d(TAG, line); if (line.contains("vfat") || line.contains("/mnt")) { StringTokenizer tokens = new StringTokenizer(line, " "); String unused = tokens.nextToken(); //device String mount_point = tokens.nextToken(); //mount point if (paths.contains(mount_point)) { continue; } unused = tokens.nextToken(); //file system List<String> flags = Arrays.asList(tokens.nextToken().split(",")); //flags boolean readonly = flags.contains("ro"); if (mount_point.equals(def_path)) { paths.add(def_path); list.add(0, new StorageInfo(def_path, def_path_internal, readonly, -1)); } else if (line.contains("/dev/block/vold")) { if (!line.contains("/mnt/secure") && !line.contains("/mnt/asec") && !line.contains("/mnt/obb") && !line.contains("/dev/mapper") && !line.contains("tmpfs")) { paths.add(mount_point); // if (isWritable(new File(mount_point+ File.separator))) list.add(new StorageInfo(mount_point, false, readonly, cur_display_number++)); } } } } if (!paths.contains(def_path) && def_path_available && def_path.length() > 0) { list.add(0, new StorageInfo(def_path, def_path_internal, def_path_readonly, -1)); } } catch (FileNotFoundException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } finally { if (buf_reader != null) { try { buf_reader.close(); } catch (IOException ex) { } } } Set<File> allStorageLocationsRevised = getAllStorageLocationsRevised(); Iterator<File> iterator = allStorageLocationsRevised.iterator(); while (iterator.hasNext()) { File next = iterator.next(); boolean found=false; for (int i=0; i < list.size(); i++){ if (list.get(i).path.equals(next.getAbsolutePath())){ found=true; break; } } if (!found){ list.add(new StorageInfo(next.getAbsolutePath(), false, false, -1)); } } return list; } /** * gets the best possible storage location by freespace * * @return */ public static File getStorage() { //Map<String, File> allStorageLocations = getAllStorageLocations(); //if (allStorageLocations.isEmpty()){ //use the current app's storage // return Environment.getDataDirectory(); //} StorageInfo ptr = null; List<StorageInfo> storageList = getStorageList(); for (int i = 0; i < storageList.size(); i++) { StorageInfo storageInfo = storageList.get(i); if (!storageInfo.readonly && isWritable(new File(storageInfo.path))) { if (ptr != null) { //compare free space if (ptr.freeSpace < storageInfo.freeSpace) { ptr = storageInfo; } } else { ptr = storageInfo; } } } if (ptr != null) { return new File(ptr.path); } //http://stackoverflow.com/questions/21230629/getfilesdir-vs-environment-getdatadirectory try { return Environment.getExternalStorageDirectory(); }catch (Exception ex) { //trap for android studio layout editor and some for certain devices //see https://github.com/osmdroid/osmdroid/issues/508 return null; } } /** * gets the best possible storage location by freespace * * @return */ public static File getStorage(final Context ctx) { //Map<String, File> allStorageLocations = getAllStorageLocations(); //if (allStorageLocations.isEmpty()){ //use the current app's storage // return Environment.getDataDirectory(); //} StorageInfo ptr = null; List<StorageInfo> storageList = getStorageList(); for (int i = 0; i < storageList.size(); i++) { StorageInfo storageInfo = storageList.get(i); if (!storageInfo.readonly && isWritable(new File(storageInfo.path))) { if (ptr != null) { //compare free space if (ptr.freeSpace < storageInfo.freeSpace) { ptr = storageInfo; } } else { ptr = storageInfo; } } } if (ptr != null) { return new File(ptr.path); } //http://stackoverflow.com/questions/21230629/getfilesdir-vs-environment-getdatadirectory return new File(ctx.getDatabasePath("temp.sqlite").getAbsolutePath().replace("temp.sqlite", "")); } /** * @return True if the external storage is available. False otherwise. */ public static boolean isAvailable() { String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { return true; } return false; } public static String getSdCardPath() { return Environment.getExternalStorageDirectory().getPath() + "/"; } /** * @return True if the external storage is writable. False otherwise. */ public static boolean isWritable() { String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state)) { return true; } return false; } public static boolean isWritable(File path) { //if (path.exists()) { //if (path.canWrite()) { //try to create a new file, save it, then delete it try { File tmp = new File(path.getAbsolutePath() + File.separator + "osm.tmp"); FileOutputStream fos = new FileOutputStream(tmp); fos.write("hi".getBytes()); fos.close(); Log.i(TAG, path.getAbsolutePath() + " is writable"); tmp.delete(); return true; } catch (Throwable ex) { Log.i(TAG, path.getAbsolutePath() + " is NOT writable"); return false; } } } //return false; } /** * @return A map of all storage locations available */ public static Map<String, File> getAllStorageLocations() { Map<String, File> map = new HashMap<String, File>(10); List<String> mMounts = new ArrayList<String>(10); List<String> mVold = new ArrayList<String>(10); mMounts.add("/mnt/sdcard"); mVold.add("/mnt/sdcard"); try { File mountFile = new File("/proc/mounts"); if (mountFile.exists()) { Scanner scanner = new Scanner(mountFile); while (scanner.hasNext()) { String line = scanner.nextLine(); if (line.startsWith("/dev/block/vold/")) { String[] lineElements = line.split(" "); String element = lineElements[1]; // don't add the default mount path // it's already in the list. if (!element.equals("/mnt/sdcard")) mMounts.add(element); } } } } catch (Exception e) { e.printStackTrace(); } try { File voldFile = new File("/system/etc/vold.fstab"); if (voldFile.exists()) { Scanner scanner = new Scanner(voldFile); while (scanner.hasNext()) { String line = scanner.nextLine(); if (line.startsWith("dev_mount")) { String[] lineElements = line.split(" "); String element = lineElements[2]; if (element.contains(":")) element = element.substring(0, element.indexOf(":")); if (!element.equals("/mnt/sdcard")) mVold.add(element); } } } } catch (Exception e) { e.printStackTrace(); } for (int i = 0; i < mMounts.size(); i++) { String mount = mMounts.get(i); if (!mVold.contains(mount)) mMounts.remove(i--); } mVold.clear(); List<String> mountHash = new ArrayList<String>(10); for (String mount : mMounts) { File root = new File(mount); if (root.exists() && root.isDirectory() && root.canWrite()) { File[] list = root.listFiles(); String hash = "["; if (list != null) { for (File f : list) { hash += f.getName().hashCode() + ":" + f.length() + ", "; } } hash += "]"; if (!mountHash.contains(hash)) { String key = SD_CARD + "_" + map.size(); if (map.size() == 0) { key = SD_CARD; } else if (map.size() == 1) { key = EXTERNAL_SD_CARD; } mountHash.add(hash); map.put(key, root); } } } mMounts.clear(); if (map.isEmpty()) { map.put(SD_CARD, Environment.getExternalStorageDirectory()); } //ok now that we've done the dirty linux work, let's pull in the android bits if (!map.containsValue(Environment.getExternalStorageDirectory())) map.put(SD_CARD, Environment.getExternalStorageDirectory()); String primary_sd = System.getenv("EXTERNAL_STORAGE"); if (primary_sd != null) { File t = new File(primary_sd); if (t.exists() && !map.containsValue(t)) map.put(SD_CARD, t); } String secondary_sd = System.getenv("SECONDARY_STORAGE"); if (secondary_sd != null) { String[] split = secondary_sd.split(File.pathSeparator); for (int i = 0; i < split.length; i++) { File t = new File(split[i]); if (t.exists() && !map.containsValue(t)) map.put(SD_CARD, t); } } return map; } /** * @return A map of all storage locations available */ private static Set<File> getAllStorageLocationsRevised() { Set<File> map = new HashSet<>(); String primary_sd = System.getenv("EXTERNAL_STORAGE"); if (primary_sd != null) { File t = new File(primary_sd+ File.separator); if (isWritable(t)) { map.add(t); } } String secondary_sd = System.getenv("SECONDARY_STORAGE"); if (secondary_sd != null) { String[] split = secondary_sd.split(File.pathSeparator); for (int i = 0; i < split.length; i++) { File t = new File(split[i] + File.separator); if (isWritable(t)) { map.add(t); } } } if (Environment.getExternalStorageDirectory()!=null) { File t = Environment.getExternalStorageDirectory(); if (isWritable(t)) { map.add(t); } } List<String> mMounts = new ArrayList<String>(10); List<String> mVold = new ArrayList<String>(10); mMounts.add("/mnt/sdcard"); mVold.add("/mnt/sdcard"); try { File mountFile = new File("/proc/mounts"); if (mountFile.exists()) { Scanner scanner = new Scanner(mountFile); while (scanner.hasNext()) { String line = scanner.nextLine(); if (line.startsWith("/dev/block/vold/")) { String[] lineElements = line.split(" "); String element = lineElements[1]; // don't add the default mount path // it's already in the list. if (!element.equals("/mnt/sdcard")) mMounts.add(element); } } } } catch (Exception e) { e.printStackTrace(); } try { File voldFile = new File("/system/etc/vold.fstab"); if (voldFile.exists()) { Scanner scanner = new Scanner(voldFile); while (scanner.hasNext()) { String line = scanner.nextLine(); if (line.startsWith("dev_mount")) { String[] lineElements = line.split(" "); String element = lineElements[2]; if (element.contains(":")) element = element.substring(0, element.indexOf(":")); if (!element.equals("/mnt/sdcard")) mVold.add(element); } } } } catch (Exception e) { e.printStackTrace(); } for (int i = 0; i < mMounts.size(); i++) { String mount = mMounts.get(i); if (!mVold.contains(mount)) mMounts.remove(i--); } mVold.clear(); List<String> mountHash = new ArrayList<String>(10); for (String mount : mMounts) { File root = new File(mount); if (root.exists() && root.isDirectory() && root.canWrite()) { File[] list = root.listFiles(); String hash = "["; if (list != null) { for (File f : list) { hash += f.getName().hashCode() + ":" + f.length() + ", "; } } hash += "]"; if (!mountHash.contains(hash)) { String key = SD_CARD + "_" + map.size(); if (map.size() == 0) { key = SD_CARD; } else if (map.size() == 1) { key = EXTERNAL_SD_CARD; } mountHash.add(hash); if (isWritable(root)) { map.add(root); } } } } mMounts.clear(); return map; } }