Samsung file io - can write file but not read back in - android

For several weeks, we've been fighting an issue that's been discussed on SO before, but without a working answer in the specific case we've encountered. So after re-reading dozens of threads and trying all the code folks offered, I'm asking for your assistance, please. (BTW, forget asking Samsung: they proved way less than helpful. )
OS : 4.0.x through 4.2.x (API lvls 14 - 17)
Devices : Samsung S3 which have /storage/sdcard0 (all we tested do.)
... as opposed to the older /mnt/sdcard or /storage/sdcard (note lack of trailing zero).
Use any flavor of
Environment.getExternalStorageDirectory().getAbsolutePath();
// e.g .getPath() .getName() etc.
// or
Environment.getExternalStoragePublicDirectory() ...
or use a media URI to inform system of file location. (Astounding that THAT fails too.)
Now save a file - you'll see it show up in the DDMS file explorer. And you can pull it via ADB to verify. But just try reading that same file back in - using the same app that you wrote it with in the first place ! ENOENT - file not found. Hardcode the path, append '/external_sd/' to the path the os call gives above (Sumsung says this is needed - but it doesn't do squat.)
Change 'sdcard0' to 'sdcard' as some have suggested ... tried all that too. ZILCH.
Permissions, etc are all correct of course ( since when could a process write a file but not read it !?) .
USB cable connected or not, debug mode or not, "real app" vs developer app (untrusted app) - results all the same: ENOENT )
Any ideas or suggestions on how to proceed here?
(With a sledge hammer in hand, staring intently at a new SG3 ... And, SAMSUNG, if you are reading this: "/storage/sdcard0/external_sd/myFileFoo.txt" does NOT work.)
/**
*
* [Edit - added sample of failing code, as requested]
*/
public void testFile () {
ImageView image ;
String m_Path = "/SamsuxS3/" ; // more fun than a barrel of NULLs
String m_MyFile = "myFileFoo.jpg" ;
image = (ImageView) findViewById ( R.id.imageView1 ) ;
//// Test 0:
m_Path = Environment.getExternalStorageDirectory().getAbsolutePath() ;
//// Test 1:
// String getPath = Environment.getExternalStorageDirectory().getPath(); // fails
// m_Path = getPath ;
//// Test 2:
// String getName = Environment.getExternalStorageDirectory().getName() ; //fails
// m_Path = getName ;
//// Test 3:
// String defPics = Environment.DIRECTORY_PICTURES; // fails
// m_Path = m_path + "/" + defPics + "/" ;
//// Test 4:
// m_Path = "/storage/sdcard0/" ; // fails
//// Test 5:
// m_Path = "/storage/sdcard0/external_sd/" // Samsung says so, but it fails too.
//// Test 6: now we're really hacking ...
// m_Path = "/storage/sdcard/" // Fails (although sdcard is mounted as sdcard0 - hmmm)
InputStream fIn = null;
File fileIn = new File(m_Path, m_MyFile);
try { //// This is only one way many attempts...
//// 1) just grab an image from a known resource,
//// 2) try to save it,
//// 3) then read it back into an ImageView.
//// External storage must be mounted or this fails.
InputStream is = getResources().openRawResource(R.drawable.somepicture) ; // works
OutputStream os = new FileOutputStream(fileIn); // OK
byte[] data = new byte[is.available()]; // OK
is.read(data); // OK
os.write(data); // OK - DDMS file explorer verfied
is.close();
os.close();
} catch (IOException e) {
Log.d("Error writing to " + m_Path, e.toString()); // never happened yet
}
//// now we step into the SamDung
////
InputStream fIn2 = null; //// Well, it's redundant but ...
File fileIn2 = new File(m_Path, m_MyFile);
try {
fIn2 = new FileInputStream (fileIn2) ;
//
// Here be the Dragons...
//
// Next line WORKS on every device EXCEPT a Samsung - blows up w/ ENOENT !
//
image.setImageBitmap ( BitmapFactory.decodeStream(fIn2) );
fIn2.close ();
} catch (Exception IOError) {
Log.d("WTF? I'm not moving to Korea: ", IOError.toString()) ;
}
}

The proper way to write a file on Android for Android 3.0+, where you intend to use the file again quickly, is:
os.flush();
os.getFD().sync();
os.close();
for a FileOutputStream named os.
Using this change, your example code works with Environment.getExternalStorageDirectory(), on a Samsung Galaxy S3, running Android 4.1.2.

Related

Unable to make new folder in Apps cache folder

I have written a caching system. It saves data to the cache folder of the App using platform / Android methods to find the folder. I have an issue that I cannot create a new folder in the Apps cache folder.
This error is reporting in Crashlytics (via non-fatal error reporting) for some devices and not for the majority.
The method that contains the issue is my getCacheFolder() method.
An example folder path that does not work on these devices: /storage/emulated/0/Android/data/app.package.name/cache/stream-cache.
Reported versions affected: 4.4.2; 5.0.1; 6.0, 6.0.1.
Reported devices affected: LENOVO YOGA Tablet 2-1050L, MOTOROLA MotoE2(4G-LTE), Samsung SM-N910F (accounts for 98% of faults reported).
My suspicions are that there might be an issue relating to removable sdcards.
#NonNull
public File getCacheFolder() {
// If this is the first run of this library function, we should get our cache folder
if (mCacheFolder == null || !mCacheFolder.exists()) {
File dir = null;
// Prioritise the external sdcard to store cached files
dir = mContext.get().getExternalCacheDir();
if(dir == null) {
// There was no external sdcard - instead use internal storage
dir = mContext.get().getCacheDir();
}
// Still couldn't get a cache location, go to fallback plan and throw exception
if(dir == null) {
throw new IllegalStateException("Could not create a location to cache. " +
"This leaves the caching library in a bad state.");
}
// Point to our specific caching folder for this system
mCacheFolder = new File(dir, "stream-cache");
}
// If it doesn't exist, we will need to make it
if (!mCacheFolder.exists()) {
// If we cannot make the directory, go to fallback plan and throw an exception
if (!mCacheFolder.mkdirs()) {
// HERE IS WHERE THE FAULT IS REPORTED
throw new IllegalStateException("Could not create a location to cache. " +
"This leaves the caching library in a bad state.: "+mCacheFolder);
}
}
return mCacheFolder;
}
Update 1
I updated the code to write out various information about the state:
#NonNull
public File getCacheFolder() {
if (mCacheFolder == null || !mCacheFolder.exists()) {
File dir = mContext.get().getExternalCacheDir();
if(dir == null || !Utils.isExternalStorageWritable()) {
dir = mContext.get().getCacheDir();
}
mCacheFolder = new File(dir, "stream-cache");
}
if (!mCacheFolder.exists()) {
if (!mCacheFolder.mkdirs()) {
// Problems writing to cache - test writing to file path.
File filePath = mContext.get().getExternalFilesDir(null);
File cachePath = new File(filePath, "stream-cache");
throw new IllegalStateException("Could not create a location to cache. " +
"This leaves the caching library in a bad state.: "+mCacheFolder+ ". " +
"filePath="+filePath+". " +
"cachePath="+cachePath+". " +
"cachePath-exists="+cachePath.exists()+". " +
"cachePath-mkdirs="+cachePath.mkdirs());
}
}
return mCacheFolder;
}
The important results from this:
filePath=null - Context.getExternalFilesDir(null); returns a null. Breaking the rest of the tests.
Instead of using
// Point to our specific caching folder for this system
mCacheFolder = new File(dir, "stream-cache");
try using this:
// Point to our specific caching folder for this system
mCacheFolder = new File(dir + "/stream-cache");
in your first code snippets.

MtpDevice.importFile and DocumentFile

MtpDevice.importFile(int objectHandle, String destPath) fails on 4.4 and above. Is there a way to import from an MtpDevice with the SD card write lock?
For the time being this is what I'm doing, though I'd love to avoid the double transfer:
// KitKat and higher require the extra step of importing to the cache then moving
if (Util.hasKitkat())
{
File tmp = new File(getExternalCacheDir(), name);
mMtpDevice.importFile(objectHandle, tmp.getPath());
success = FileUtil.moveFile(CameraImportActivity.this, tmp, endFile);
}
else
{
success = mMtpDevice.importFile(objectHandle, endFile.getPath());
}

Android service file observer strange behavior

I need to implement a service in android that must be able to monitor a folder to detect a certain file and read what it contains. I'm having a strange behavior with my code and I can't find the reason. This is my relevant code.
public void onCreate(){
lectorFichCSV = new LectorFichCSV(); //object to read CSV files
ftpFileObserver = new FileObserver(filePath.getAbsolutePath()){
public void onEvent(int event, String file) {
if((FileObserver.CREATE & event) != 0){
Log.i("INFO: ", filePath.getAbsolutePath() + "/" + file + " is created");
if(file.substring(0,3).equals("RVE")){ //If file is created and the one I expect
try{
Log.i("INFO: ", "We have a RVE answer");
is = new FileInputStream(filePath + "/" + file);
lineaVent = lectorFichCSV.parseCSVFileAsList(is); //Get information in a list
//Get dao from ORMLite
dao = getHelper().getLineaVentDao();
Iterator<String[]> iterator = lineaVent.iterator();
if(iterator.hasNext()){
String[] aux = iterator.next();
Log.i("INFO:", "CodLineaVent "+aux[0]);
if(aux[2].equals("S")){
//Update DB information accordin to my file
UpdateBuilder<LineaVent, Integer> updateBuilder = dao.updateBuilder();
updateBuilder.where().eq("_id", aux[0]);
updateBuilder.updateColumnValue("valido", true);
updateBuilder.updateColumnValue("saldo", true);
updateBuilder.update();
lineaVent.clear();
}else if(aux[2].equals("N")){
UpdateBuilder<LineaVent, Integer> updateBuilder = dao.updateBuilder();
updateBuilder.where().eq("_id", aux[0]);
updateBuilder.updateColumnValue("saldo", false);
updateBuilder.update();
lineaVent.clear();
}
File fileToDel = new File(filePath + "/" + file);
fileToDel.delete();
}
}catch(FileNotFoundException e){
e.printStackTrace();
}catch(SQLException e){
e.printStackTrace();
}
}
I debugged the code and sometimes is working and sometimes I get lineaVent.size() == 0. I'm going crazy with this, I'm thinking, is it possible that events occurs faster than the creation of my file? that would be the reason when I tried to parse my CSV file into my List object is size = 0? In that case I'm not getting any FileNotFoundException.
Any help will be appreciate. Thank you.
I am not an expert with the inotify POSIX API that, IIRC, underlies FileObserver. However, given that there are separate events for CREATE, MODIFY, and CLOSE_WRITE, it stands to reason that the CREATE event is solely for file creation -- in other words, allocating a new entry in the filesystem for the file. That would either create an empty file, or perhaps a file with some initial load of bytes, but where other MODIFY calls might be needed to write out the full contents. CLOSE_WRITE would then be called to indicate that whoever was writing to the file has now closed their file handle.
Hence, if you are watching for some file to be created, to read it in, watch for CREATE, then watch for CLOSE_WRITE on that same file, and then try to read it, and see if that works better.

Accessing Storage on the Nook

The closest thing to documentation I can find having to do with file storage is this post (see below if you can't access it), but it leaves me with several questions.
I would really, really, really like a knowledgeable explanation of what paths map to what storage here, seeing as how we're supposed to hard-code them, and how precisely we're expected to access them. An actual code sample would be superlative. My best guess from messing around with this is that:
/sdcard-> maps to the internal eMMC slot, and access is restricted.
Environment.getExternalStorageDirectory(); ... still returns this.
/media -> maps to the internal 8GB memory (I can write to this)
/data -> ?
? -> maps to the optional microSD card
How can we access the external (optional, additional, the one you can pop out) sdcard, if /sdcard maps to restricted storage instead?
Now to quote the Nook developer docs:
Background There are two different partition schemes for the NOOK
Color devices in market today, one with only 250MB available to
applications on the /data partition and one with 4GB available to
applications on the /data partition. As a result, it is imperative
that applications are designed and developed in such a way as to
manage space effectively. Applications which fail to do so will not be
accepted for distribution via the Shop.
Area Associated Technical Recommendation or Solution if your
application requires large amount of data (including but not limited
to images, audio or video content), you should download those
resources at runtime and store them in the larger partition of the
device. If your application is going to request and store more than
100MB of data or resource you MUST abide by the the following
restrictions:
Your application must clearly and explicitly state in the description
provided that a large amount of data is used/delivered by the
application. You MUST write your resources and data onto appropriate
partition. You can detect if the device has an enlarged /data
partition as follows :
StatFs stat = new StatFs("/data");
long bytesAvailable = (long)stat.getBlockSize() *(long)stat.getBlockCount();
long megAvailable = bytesAvailable / 1048576;
if (megAvailable > 1000){
... write your resources in /data
} else {
... write your resources on /mnt/media ...
}
To write data into your application's private space on /data
FileOutputStream fos = openFileOutput(FILENAME, Context.MODE_WORLD_READABLE);
Your application should NOT assume the
presence of an sdcard on device, but you can test for one via a call
to
Environment.getExternalStorageState(); If an SD Card is not found,
your application MUST exit gracefully with a notification to the user
as to the reason for the exit.
Remember, that to access the /media partition, as well as
ExternalStorage you need to declare in your manifest:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE">
</uses-permission>
Okay, here's what I've learned in the past couple of weeks.
If you want to write to the internal SDcard, use Context.getFilesDir(). It'll return the private directory for your application. You can not invent your own directories on the internal flash storage (aka "/data"). You don't have permission to write anywhere other than the folder your application gets assigned. Supposedly there are two internal partitions, "/data" and "/media", but I can't get at "/media" to save my life.
You can use the external flash memory, "/sdcard", when one is available. This is the card you can pop out of the device. There are two ways to go about this:
Store things in the folder assigned to your app (so it'll get deleted
when your application is uninstalled). You can find that folder with
Context.getExternalFilesDir().
Store things wherever, either in some hard-coded path under "/sdcard/foo/bar" or in
Environment.getExternalStorageDirectory() / whatever.
This post by a B&N rep (which I referenced in my question) turned out to be a bit of a red herring, "/sdcard" doesn't map to the eMMC slot, and I have no idea what "we mapped the SD card to our internal eMMC" means.
This B&N post says that "/media" is internal, but I can't write to it even though I have the proper manifest permissions... so go figure.
This is a screencap of my test device, showing what is and isn't accessible:
The code for that (note that FileUtils isn't included in the sdk by default,it's from the org.apache.commons.io lib):
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
TextView dataView = (TextView) findViewById(R.id.data);
dataView.setText(testIt("/data"));
TextView mediaView = (TextView) findViewById(R.id.media);
mediaView.setText(testIt("/media"));
TextView mntMediaView = (TextView) findViewById(R.id.mntMedia);
mntMediaView.setText(testIt("/mnt/media"));
try {
File fd = this.getFilesDir();
if(fd != null) {
TextView fdView = (TextView) findViewById(R.id.filesDir);
fdView.setText("getFilesDir(): " + testIt(fd.toString()));
}
} catch (Exception e) {
e.printStackTrace();
}
try {
File efd = this.getExternalFilesDir(null);
if(efd != null) {
TextView efdView = (TextView) findViewById(R.id.externalFilesDir);
efdView.setText("getExternalFilesDir(): " + testIt(efd.toString()));
}
} catch (Exception e) {
e.printStackTrace();
}
try {
File esd = Environment.getExternalStorageDirectory();
if(esd != null) {
TextView esdView = (TextView) findViewById(R.id.externalStorageDirectory);
esdView.setText("getExternalStorageDirectory(): " + testIt(esd.toString()));
}
} catch (Exception e) {
e.printStackTrace();
}
try {
File espd = Environment.getExternalStoragePublicDirectory(null);
if(espd != null) {
TextView espdView = (TextView) findViewById(R.id.externalStoragePublicDirectory);
espdView.setText("getExternalStoragePublicDirectory(): " + testIt(espd.toString()));
}
} catch (Exception e) {
e.printStackTrace();
}
}
public String testIt(String dir){
StatFs stat = new StatFs(dir);
long bytesAvailable = (long) stat.getBlockSize() * (long) stat.getBlockCount();
long megAvailable = bytesAvailable / FileUtils.ONE_MB;
File dirFile = new File(dir + "/test/");
dirFile.mkdir();
return dir + "/test \n canRead() " + dirFile.canRead() + ", \n canWrite() " + dirFile.canWrite() + " with " + megAvailable + "MB available";
}
First of all try the following methods:
Context.getExternalFilesDir
Context.getExternalCacheDir
Environment.getExternalStoragePublicDirectory
If neither of those return you a directory where you can write to, check the following google-groups thread, and use the code provided in the last answer, which enumerates all current mount-points:
I have the same issue with Galaxy S. Until now all Android devices I
got have "mount" command available. I build a list of available volume
parsing "mount" response. This is not perfect but cleaver than Android
storage API.
String cmd = "/system/bin/mount";
try {
Runtime rt = Runtime.getRuntime();
Process ps = rt.exec(cmd);
BufferedReader rd = new BufferedReader( new InputStreamReader(ps.getInputStream()) );
String rs;
while ((rs = rd.readLine()) != null)
{
//check what you need!
Log.i("MOUNT_CMD", rs);
}
rd.close();
ps.waitFor();
} catch(Exception e) {
//...
}
If it is possible to insert the microSD card in the Nook device, while it is running, you could also try the following:
mVolumeManagerReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
Log.i("MediaMounter", "Storage: " + intent.getData());
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_MEDIA_MOUNTED);
filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
filter.addAction(Intent.ACTION_MEDIA_REMOVED);
filter.addDataScheme("file");
context.registerReceiver(mVolumeManagerReceiver, filter);

Copy the shared preferences XML file from /data on Samsung device failed

There's an exporting feature in my application. It's just a copy operation since all my settings are store in shared preference.
I just copy the xml file from /data/data/package.name/shared_prefs/settings.xml to SD card. It works fine on my HTC desire. However, it might not work on Samsung devices, and i got the following error while I try to copy the file.
I/System.out( 3166): /data/data/package.name/shared_prefs/settings.xml (No such file or directory)
in the directory.
Anyone know how to fix it, or is there another simple way to store the shared preference ?
Thanks.
Never never never never never never never never never hardwire paths.
Unfortunately, there's no getSharedPreferenceDir() anywhere that I can think of. The best solution I can think of will be:
new File(getFilesDir(), "../shared_prefs")
This way if a device manufacturer elects to change partition names, you are covered.
Try this and see if it helps.
CommonsWare's suggestion would a be clever hack, but unfortunately it won't work.
Samsung does not always put the shared_prefs directory in the same parent directory as the getFilesDir().
I'd recommend testing for the existence of (hardcode it, except for package name):
/dbdata/databases/<package_name>/shared_prefs/package.name_preferences.xml and if it exists use it, otherwise fall back to either CommonsWare's suggestion of new File(getFilesDir(), "../shared_prefs") or just /data/data/<package_name>/shared_prefs/package.name_preferences.xml.
A warning though that this method could potentially have problems if a user switched from a Samsung rom to a custom rom without wiping, as the /dbdata/databases file might be unused but still exist.
More details
On some Samsung devices, such as the Galaxy S series running froyo, the setup is this:
/data/data/<package_name>/(lib|files|databases)
Sometimes there's a shared_prefs there too, but it's just Samsung's attempt to confuse you! Don't trust it! (I think it can happen as a left over from a 2.1 upgrade to 2.2, but it might be a left over from users switching roms. I don't really know, I just have both included in my app's bug report interface and sometimes see both files).
And:
/dbdata/databases/<package_name>/shared_prefs
That's the real shared_prefs directory.
However on the Galaxy Tab on Froyo, it's weird. Generally you have: /data/data/<package_name>/(lib|shared_prefs|files|databases)
With no /dbdata/databases/<package_name> directory, but it seems the system apps do have:
/dbdata/databases/<package_name>/yourdatabase.db
And added bonus is that /dbdata/databases/<package_name> is not removed when your app is uninstalled. Good luck using SharedPreferences if the user ever reinstalls your app!
Try using
context.getFilesDir().getParentFile().getAbsolutePath()
Best way to get valid path on all devices - run method Context.getSharedPrefsFile defined as:
/**
* {#hide}
* Return the full path to the shared prefs file for the given prefs group name.
*
* <p>Note: this is not generally useful for applications, since they should
* not be directly accessing the file system.
*/
public abstract File getSharedPrefsFile(String name);
Because of it hidden need use reflection and use fallback on fail:
private File getSharedPrefsFile(String name) {
Context context = ...;
File file = null;
try {
if (Build.VERSION.SDK_INT >= 24) {
try {
Method m = context.getClass().getMethod("getSharedPreferencesPath", new Class[] {String.class});
file = (File)m.invoke(context, new Object[]{name});
} catch (Throwable e) {
Log.w("App TAG", "Failed call getSharedPreferencesPath", e);
}
}
if (file == null) {
Method m = context.getClass().getMethod("getSharedPrefsFile", new Class[] {String.class});
file = (File)m.invoke(context, new Object[]{name});
}
} catch (Throwable e) {
Log.w("App TAG", "Failed call getSharedPrefsFile", e);
file = new File(context.getFilesDir(), "../shared_prefs/" + name + ".xml");
}
return file;
}
On some Samsungs implements like this:
public File getSharedPrefsFile(String paramString) {
return makeFilename(getPreferencesDir(), paramString + ".xml");
}
private File getPreferencesDir() {
synchronized (this.mSync) {
if (this.mPreferencesDir == null) {
this.mPreferencesDir = new File("/dbdata/databases/" + getPackageName() + "/", "shared_prefs");
}
File localFile = this.mPreferencesDir;
return localFile;
}
}
On other Android like this:
public File getSharedPrefsFile(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
private File getPreferencesDir() {
synchronized (mSync) {
if (mPreferencesDir == null) {
mPreferencesDir = new File(getDataDirFile(), "shared_prefs");
}
return mPreferencesDir;
}
}
private File getDataDirFile() {
if (mPackageInfo != null) {
return mPackageInfo.getDataDirFile();
}
throw new RuntimeException("Not supported in system context");
}
After while Google change API for level 24 and later:
https://android.googlesource.com/platform/frameworks/base/+/6a6cdafaec56fcd793214678c7fcc52f0b860cfc%5E%21/core/java/android/app/ContextImpl.java
I've tested in Samsung P1010 with:
//I'm in a IntentService class
File file = this.getDir("shared_prefs", MODE_PRIVATE);
I got:
"/data/data/package.name/app_shared_prefs"
It works fine to me. I can run ffmpeg in this folder.
Look:
Context.getDir
You have to create the shared_prefs directory:
try{
String dir="/data/data/package.name/shared_prefs";
// Create one directory
boolean success = (new File(dir)).mkdirs();
if (success) {
// now copy the file
}
}catch (Exception e){//Catch exception if any
System.err.println("Error: " + e.getMessage());
}
Also... the package of your app is package.name? Make sure you are referring to the right package.

Categories

Resources