Welcome to XDA

Search to go directly to your device's forum

Register an account

Unlock full posting privileges

Ask a question

No registration required
Post Reply

[Q][SOLVED]Get path and size of SDcard in Android 4.4 KitKat

OP MattMatt0240

1st April 2014, 04:06 AM   |  #1  
MattMatt0240's Avatar
OP Senior Member
Thanks Meter: 113
 
282 posts
Join Date:Joined: Sep 2011
Donate to Me
More
Hello everyone, I have an app on Google Play that shows the end user information about their device. Within this information, a Memory/Storage category is shown. Everything was good and fine until mean ol` KitKat wanted to deny access to the SDcard... Although I can understand Google's move on that subject, it can not go un-noticed that it may break many app's functionality (like my own). Anyhow, below is my class that scans for mount points, and stores them in an ArrayList. I used some code from StackOverflow somewhere, but I do not have the link.

StorageUtils :
Code:
public class StorageUtils {

	private static final String TAG = "StorageUtils";

	public static class StorageInfo {

		public final String path;
		public final boolean internal;
		public final boolean readonly;
		public final int display_number;

		StorageInfo(String path, boolean internal, boolean readonly,
				int display_number) {
			this.path = path;
			this.internal = internal;
			this.readonly = readonly;
			this.display_number = display_number;
		}
	}

	public static ArrayList<StorageInfo> getStorageList() {

		ArrayList<StorageInfo> list = new ArrayList<StorageInfo>();
		String def_path = Environment.getExternalStorageDirectory().getPath();
		boolean def_path_internal = !Environment.isExternalStorageRemovable();
		String def_path_state = Environment.getExternalStorageState();
		boolean def_path_available = def_path_state
				.equals(Environment.MEDIA_MOUNTED)
				|| def_path_state.equals(Environment.MEDIA_MOUNTED_READ_ONLY);
		boolean def_path_readonly = Environment.getExternalStorageState()
				.equals(Environment.MEDIA_MOUNTED_READ_ONLY);
		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(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);
							list.add(new StorageInfo(mount_point, false,
									readonly, cur_display_number++));
						}
					}
				}
			}

			if (!paths.contains(def_path) && def_path_available) {
				list.add(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) {
				}
			}
		}
		return list;
	}

	public static String getReadableFileSize(long bytes, boolean si) {
		int unit = si ? 1000 : 1024;
		if (bytes < unit)
			return bytes + " B";
		int exp = (int) (Math.log(bytes) / Math.log(unit));
		String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1)
				+ (si ? "" : "i");
		return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
	}

	@SuppressLint("NewApi")
	public static long getFreeSpace(String path) {
		StatFs statFs = new StatFs(path);
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
			long sdAvailSize = statFs.getFreeBlocksLong()
					* statFs.getBlockSizeLong();
			return sdAvailSize;
		} else {
			@SuppressWarnings("deprecation")
			double sdAvailSize = (double) statFs.getFreeBlocks()
					* (double) statFs.getBlockSize();

			return (long) sdAvailSize;
		}
	}

	public static long getUsedSpace(String path) {
		return getTotalSpace(path) - getFreeSpace(path);
	}

	@SuppressLint("NewApi")
	public static long getTotalSpace(String path) {
		StatFs statFs = new StatFs(path);
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
			long sdTotalSize = statFs.getBlockCountLong()
					* statFs.getBlockSizeLong();
			return sdTotalSize;
		} else {
			@SuppressWarnings("deprecation")
			double sdTotalSize = (double) statFs.getBlockCount()
					* statFs.getBlockSize();

			return (long) sdTotalSize;
		}
	}

	/**
	 * getSize()[0] is /mnt/sdcard. getSize()[1] is size of sd (example 12.0G),
	 * getSize()[2] is used, [3] is free, [4] is blksize
	 * 
	 * @return
	 * @throws IOException
	 */
	public static String[] getSize() throws IOException {
		String memory = "";
		Process p = Runtime.getRuntime().exec("df /mnt/sdcard");
		InputStream is = p.getInputStream();
		int by = -1;
		while ((by = is.read()) != -1) {
			memory += new String(new byte[] { (byte) by });
		}
		for (String df : memory.split("/n")) {
			if (df.startsWith("/mnt/sdcard")) {
				String[] par = df.split(" ");
				List<String> pp = new ArrayList<String>();
				for (String pa : par) {
					if (!pa.isEmpty()) {
						pp.add(pa);
					}
				}
				return pp.toArray(new String[pp.size()]);

			}
		}
		return null;
	}

}
Next, I retrieve the used, free, and total space of each mount point. This is where KitKat breaks my app.

CpuMemFragment :
Code:
public class CpuMemFragment extends Fragment {
	// CPU
	String devCpuInfo;
	TextView tvCpuInfo;

	// RAM
	String devRamInfo;
	TextView tvRamInfo;

	// Storage
	String devStorageA, devStorageB;
	TextView tvStorageAName, tvStorageA, tvStorageB, tvStorageBName;

	AdView adView;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {

		View rootView = inflater.inflate(R.layout.cpu_mem, container, false);

		return rootView;
	}

	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onActivityCreated(savedInstanceState);

		// *** CPU ***
		//
		devCpuInfo = readCpuInfo();
		//
		// #################################

		// *** RAM ***
		//
		devRamInfo = readTotalRam();
		//
		// #################################

		// *** STORAGE ***
		//

		ArrayList<StorageInfo> storageInfoList = StorageUtils.getStorageList();

		tvStorageAName = (TextView) getView().findViewById(R.id.tvStorageAName);

		tvStorageBName = (TextView) getView().findViewById(R.id.tvStorageBName);

		if (storageInfoList.size() > 0) {

			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
					&& !storageInfoList.get(0).internal) {
				kitKatWorkaround(0);
			}
			tvStorageAName.setText(storageInfoList.get(0).path);

			devStorageA = StorageUtils.getReadableFileSize(
					(StorageUtils.getUsedSpace(storageInfoList.get(0).path)),
					true)
					+ "/"
					+ StorageUtils.getReadableFileSize((StorageUtils
							.getTotalSpace(storageInfoList.get(0).path)), true);

			if (storageInfoList.size() > 1) {
				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
						&& !storageInfoList.get(0).internal) {
					kitKatWorkaround(1);
				}
				tvStorageBName.setText(storageInfoList.get(1).path);

				devStorageB = StorageUtils.getReadableFileSize(
						StorageUtils.getUsedSpace(storageInfoList.get(1).path)
								+ (StorageUtils.getUsedSpace("system/")), true)
						+ "/"
						+ StorageUtils.getReadableFileSize((StorageUtils
								.getTotalSpace(storageInfoList.get(1).path)),
								true);
			} else {
				devStorageB = "N/A";
			}
		} else {
			devStorageA = "N/A";
			devStorageB = "N/A";
		}
		//
		// #################################

		// CPU
		tvCpuInfo = (TextView) getView().findViewById(R.id.tvCpuInfo);
		tvCpuInfo.setText(devCpuInfo);
		//
		// #################################

		// RAM
		tvRamInfo = (TextView) getView().findViewById(R.id.tvRamInfo);
		tvRamInfo.setText(devRamInfo);
		//
		// #################################

		// STORAGE
		tvStorageA = (TextView) getView().findViewById(R.id.tvStorageA);
		tvStorageA.setText(devStorageA);

		tvStorageB = (TextView) getView().findViewById(R.id.tvStorageB);
		tvStorageB.setText(devStorageB);
		//
		// #################################

		// Look up the AdView as a resource and load a request.
		adView = (AdView) getActivity().findViewById(R.id.adCpuMemBanner);
		AdRequest adRequest = new AdRequest.Builder().build();
		adView.loadAd(adRequest);
	}

	@Override
	public void onPause() {
		if (adView != null) {
			adView.pause();
		}
		super.onPause();
	}

	@Override
	public void onResume() {
		super.onResume();
		if (adView != null) {
			adView.resume();
		}
	}

	@Override
	public void onDestroy() {
		if (adView != null) {
			adView.destroy();
		}
		super.onDestroy();
	}

	private static synchronized String readCpuInfo() {
		ProcessBuilder cmd;
		String result = "";

		try {
			String[] args = { "/system/bin/cat", "/proc/cpuinfo" };
			cmd = new ProcessBuilder(args);

			Process process = cmd.start();
			InputStream in = process.getInputStream();
			byte[] re = new byte[1024];
			while (in.read(re) != -1) {
				System.out.println(new String(re));
				result = result + new String(re);
			}
			in.close();
		} catch (IOException ex) {
			ex.printStackTrace();
		}
		return result;
	}

	public static synchronized String readTotalRam() {
		String load = "";
		try {
			RandomAccessFile reader = new RandomAccessFile("/proc/meminfo", "r");
			load = reader.readLine();
			reader.close();
		} catch (IOException ex) {
			ex.printStackTrace();
		}
		return load;
	}

// An attempt to workaround KitKat changes according to android documentation
	public void kitKatWorkaround(int index) {
		String path1 = Environment.getExternalStorageDirectory().getPath();

		if (index == 0) {

			tvStorageAName.setText(path1);

			devStorageA = StorageUtils.getReadableFileSize(
					(StorageUtils.getUsedSpace(path1)), true)
					+ "/"
					+ StorageUtils.getReadableFileSize(
							(StorageUtils.getTotalSpace(path1)), true);
		}
		if (index == 1) {
			tvStorageBName.setText(path1);

			devStorageB = StorageUtils.getReadableFileSize(
					StorageUtils.getUsedSpace(path1)
							+ (StorageUtils.getUsedSpace("system/")), true)
					+ "/"
					+ StorageUtils.getReadableFileSize(
							(StorageUtils.getTotalSpace(path1)), true);

		}
	}
}
The suspected error is right here in the logcat:

Quote:

Caused by: libcore.io.ErrnoException: statvfs failed: EACCES (Permission denied)

is there any alternative to retrieving sdCards sizes, or should I simply display a dialog stating that I have no control over Google's decision in Android 4.4, and apologize for the inconvenince?
Also, while I'm at it: On some models of Android devices, the sizes of the SDcard are all out of whack. Some showing more used space than total, and innacurate readings. This seems to only happen with internal/emulated storage. What is the most accurate way of getting sizes of all SDcard locations?

Thank you kindly for your time, Happy Coding!
Last edited by MattMatt0240; 5th April 2014 at 04:00 AM.
The Following User Says Thank You to MattMatt0240 For This Useful Post: [ View ]
5th April 2014, 03:57 AM   |  #2  
MattMatt0240's Avatar
OP Senior Member
Thanks Meter: 113
 
282 posts
Join Date:Joined: Sep 2011
Donate to Me
More
Thumbs up Fully Functional
Got it working after further looking into android documentation, and implementing new methods:

Within analyzing storage method:
Code:
...
ArrayList<StorageInfo> storageInfoList = StorageUtils.getStorageList();

		tvStorageAName = (TextView) getView().findViewById(R.id.tvStorageAName);

		tvStorageBName = (TextView) getView().findViewById(R.id.tvStorageBName);

		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
			kitKatWorkaround();
		} else if (storageInfoList.size() > 0) {

			tvStorageAName.setText(storageInfoList.get(0).path);

			devStorageA = StorageUtils.getReadableFileSize(
					(StorageUtils.getUsedSpace(storageInfoList.get(0).path)),
					true)
					+ "/"
					+ StorageUtils.getReadableFileSize((StorageUtils
							.getTotalSpace(storageInfoList.get(0).path)), true);

			if (storageInfoList.size() > 1) {
				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
						&& !storageInfoList.get(0).internal) {
					kitKatWorkaround();
				}
				tvStorageBName.setText(storageInfoList.get(1).path);

				devStorageB = StorageUtils.getReadableFileSize(
						StorageUtils.getUsedSpace(storageInfoList.get(1).path)
								+ (StorageUtils.getUsedSpace("system/")), true)
						+ "/"
						+ StorageUtils.getReadableFileSize((StorageUtils
								.getTotalSpace(storageInfoList.get(1).path)),
								true);
			} else {
				devStorageB = "N/A";
			}
		} else {
			devStorageA = "N/A";
			devStorageB = "N/A";
		}
...
kitKatWorkaround();
Code:
@SuppressLint("NewApi")
	public void kitKatWorkaround() {

		tvStorageA = (TextView) getView().findViewById(R.id.tvStorageA);
		tvStorageB = (TextView) getView().findViewById(R.id.tvStorageB);

		File[] sdCards = getActivity().getApplicationContext()
				.getExternalCacheDirs();

		if (sdCards.length > 0) {

			File sdCard1 = sdCards[0];

			tvStorageAName.setText(sdCard1.getAbsolutePath()
					.replace(
							"Android/data/" + getActivity().getPackageName()
									+ "/cache", ""));

			devStorageA = StorageUtils.getReadableFileSize(
					(StorageUtils.getUsedSpace(sdCard1.getAbsolutePath())),
					true)
					+ "/"
					+ StorageUtils.getReadableFileSize((StorageUtils
							.getTotalSpace(sdCard1.getAbsolutePath())), true);

			if (sdCards.length > 1) {
				File sdCard2 = sdCards[1];

				tvStorageBName.setText(sdCard2.getAbsolutePath().replace(
						"Android/data/" + getActivity().getPackageName()
								+ "/cache", ""));

				devStorageB = StorageUtils.getReadableFileSize(
						(StorageUtils.getUsedSpace(sdCard2.getAbsolutePath())),
						true)
						+ "/"
						+ StorageUtils.getReadableFileSize((StorageUtils
								.getTotalSpace(sdCard2.getAbsolutePath())),
								true);
			} else {
				devStorageB = "N/A";
			}
		} else {
			devStorageA = "N/A";
			devStorageB = "N/A";
		}

		tvStorageA.setText(devStorageA);
		tvStorageB.setText(devStorageB);
	}
Just going to leave this out there, it works on at least Android 3.0+ that I have tested. Using StorageUtils retrieve all available mount points, and use code above to setText() path and size

Feel free to use this if it helps (don't forget the thanks button)
The Following 3 Users Say Thank You to MattMatt0240 For This Useful Post: [ View ]
Post Reply Subscribe to Thread

Tags
android 4.4 kitkat, android app development, android sd card
Previous Thread Next Thread
Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes