First off, thanks in advance for any help you can provide.
My issue is, hopefully, one that can be resolved. I have an app that, basically, allows a user to input data and then sends that data via email as an attachment. What I would like to do, is if the user is connected to their network via wifi, that instead of sending the file via email, it would transfer the file to a network share. I have been searching for an answer for quite a while but unfortunately have found no way of doing this.
So I guess my real question is if this is even possible, and if so, how to go about doing this.
You would need to copy the file accordingly as noted below and in your instance I would presume the dest File would get set up as such...
new File("\\\\server\\path\\to\\file.txt")
class FileUtils {
public static boolean copyFile(File source, File dest) {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new BufferedInputStream(new FileInputStream(source));
bos = new BufferedOutputStream(new FileOutputStream(dest, false));
byte[] buf = new byte[1024];
bis.read(buf);
do {
bos.write(buf);
} while(bis.read(buf) != -1);
} catch (IOException e) {
return false;
} finally {
try {
if (bis != null) bis.close();
if (bos != null) bos.close();
} catch (IOException e) {
return false;
}
}
return true;
}
// WARNING ! Inefficient if source and dest are on the same filesystem !
public static boolean moveFile(File source, File dest) {
return copyFile(source, dest) && source.delete();
}
// Returns true if the sdcard is mounted rw
public static boolean isSDMounted() {
return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
}
}
Related
I previously used external storage to store specific data that I would like to share between my applications (without having any contentprovider "host")
File folder = new File(Environment.getExternalStorageDirectory(), "FOLDER_NAME");
File file = new File(folder, "FILE_NAME.dat");
FileOutputStream outputStream = new FileOutputStream(file);
That is why I am trying to use BlobStoreManager, as suggested in google's recommendation for targeting 30 (https://developer.android.com/training/data-storage/shared/datasets)
The read & write are based on a BlobHandle with 4 parameters, one being MessageDigest based on a "content". BlobHandle must use the same 4 parameters, or read will fail (SecurityException).
I managed to write data, and to read it, but it makes no sense:
It seems that in order to write, I need to use the data I want to write to generate the BlobHandle.
Then, to read, as BlobHandle must use the same 4 parameters, I also need the data I wrote to be able to read.
Totally illogic, as I wanted to read this data, I don't have it!
I must miss something or just do not understand how it work. If someone can help :)
Here are my sample:
If I set the following:
createBlobHandle: content = "mydata"
write: data = "mydata"
Then write will success, and read will success too. But it I can not know the value before reading it in a normal usecase :(
If I set the following (which would be logic, at least to me):
createBlobHandle: content = "somekey"
write: data = "mydata"
Then write will fail :(
#RequiresApi(api = Build.VERSION_CODES.R)
private BlobHandle createBlobHandle() {
//Transfer object
String content = "SomeContentToWrite";
String label = "label123";
String tag = "test";
//Sha256 summary of the transmission object
try {
byte[] contentByte = content.getBytes("utf-8");
MessageDigest md = MessageDigest.getInstance("sha256");
byte[] contentHash = md.digest(contentByte);
return BlobHandle.createWithSha256(contentHash, label,0, tag);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
private void write() {
String data = "SomeContentToWrite";
#SuppressLint("WrongConstant") final BlobStoreManager blobStoreManager = ((BlobStoreManager) applicationContext.getSystemService(Context.BLOB_STORE_SERVICE));
//Generate the session of this operation
try {
BlobHandle blobHandle = createBlobHandle();
if (blobHandle == null)
return;
long sessionId = blobStoreManager.createSession(blobHandle);
try (BlobStoreManager.Session session = blobStoreManager.openSession(sessionId)) {
try (OutputStream pfd = new ParcelFileDescriptor.AutoCloseOutputStream(session.openWrite(0, data.getBytes().length))) {
//The abstract of the written object must be consistent with the above, otherwise it will report SecurityException
Log.d(TAG, "writeFile: >>>>>>>>>>text = " + data);
pfd.write(data.getBytes());
pfd.flush();
//Allow public access
session.allowPublicAccess();
session.commit(applicationContext.getMainExecutor(), new Consumer<Integer>() {
#Override
public void accept(Integer integer) {
//0 success 1 failure
Log.d(TAG, "accept: >>>>>>>>" + integer);
}
});
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private String read() {
String data = "";
#SuppressLint("WrongConstant") final BlobStoreManager blobStoreManager = ((BlobStoreManager) applicationContext.getSystemService(Context.BLOB_STORE_SERVICE));
BlobHandle blobHandle = createBlobHandle();
if (blobHandle != null) {
try (InputStream pfd = new ParcelFileDescriptor.AutoCloseInputStream(blobStoreManager.openBlob(createBlobHandle()))) {
//Read data
byte[] buffer = new byte[pfd.available()];
pfd.read(buffer);
String text = new String(buffer, Charset.forName("UTF-8"));
Log.d(TAG, "readFile: >>>>>>>>>>>>>>>>>>>>" + text);
} catch (IOException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
}
return data;
}
According to the official training documentation linked in the question, the missing piece of information, at the time of the question having been asked, is that the four pieces of data contained in the BlobHandler need to be uploaded to a server owned by the client application then subsequently downloaded by which ever other application wants to access the blob via the BlobStorageManager.
So it would seem that on-device blob discovery is not supported. There could also be a solution possible using a Content Provider which could offer up the four required pieces of data, thus circumventing the need for the server infrastructure.
in the app when receiving an intent which was created from other app and has a file path, it can access the file's content using the file path.
the question is if that path (call it as 'link-path') is a 'hard link' to the original file, is it possible to find the original file through this 'link-path'?
Searched and find some post like:
https://unix.stackexchange.com/questions/122333/how-to-tell-which-file-is-original-if-hard-link-is-created
they show some unix shell command. Not sure if there is some android file system support for this, anyone having suggestion?
You can use this code I made, based on this post. It will return the target path of any path. If path is not a symbolic link, it will return itself. If path doesn't exist it returns null.
public static String findLinkTarget(String path) {
try {
Process findTarget = Runtime.getRuntime().exec("readlink -f " + path);
BufferedReader br = new BufferedReader(new InputStreamReader(findTarget.getInputStream()));
return br.readLine();
} catch (IOException e) {
Log.w(TAG, "Couldn't find target file for link: " + path, e);
}
}
The code wasn't tested, but I tested the command on Termux and it worked.
EDIT: Try calling getCanonicalPath() on your file, I think it resolves the symlink.
find a way by comparing the inode, in api >21 android has Os to get it, otherwise using the command "ls -i" to get the inode. One issue though, tested on api<=18 the "ls -i" does not return any thing (tested on emulator), in that case maybe fallback to compare the file's size and timestamp.
static String getFileInode(File file) {
String inode = "-1";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
StructStat st = null;
try {
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file,
ParcelFileDescriptor.parseMode("r"));
st = Os.fstat (pfd.getFileDescriptor());
if (st != null) {
inode = ""+st.st_ino;
}
} catch (Exception e) {
Log.e(TAG, "fstat() failed”+ e.getMessage());
}
} else {
BufferedReader reader = null;
try {
Process process = Runtime.getRuntime().exec(("ls -il " + path));
reader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
int read;
char[] buffer = new char[4096];
StringBuffer output = new StringBuffer();
while ((read = reader.read(buffer)) > 0) {
output.append(buffer, 0, read);
}
process.waitFor();
String ret = output.toString();
if (!TextUtils.isEmpty(ret)) {
ret = ret.trim();
String[] splitArr = ret.split("\\s+");
if (splitArr.length>0) {
inode = splitArr[0];
}
}
} catch(Exception e) {
Log.e(TAG, "!!! Runtime.getRuntime().exec() exception, cmd:”+cmd);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {}
}
}
}
return inode;
}
I realized after going through a post in the IBM developer forums that the android sdk reads bytes from the mic recording and writes them to the websocket. I am now trying to read bytes from an audio file on memory and write them to the websocket. How should I do this? So far I have:
public class AudioCaptureThread extends Thread{
private static final String TAG = "AudioCaptureThread";
private boolean mStop = false;
private boolean mStopped = false;
private int mSamplingRate = -1;
private IAudioConsumer mIAudioConsumer = null;
// the thread receives high priority because it needs to do real time audio capture
// THREAD_PRIORITY_URGENT_AUDIO = "Standard priority of the most important audio threads"
public AudioCaptureThread(int iSamplingRate, IAudioConsumer IAudioConsumer) {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
mSamplingRate = iSamplingRate;
mIAudioConsumer = IAudioConsumer;
}
// once the thread is started it runs nonstop until it is stopped from the outside
#Override
public void run() {
File path = Activity.getContext.getExternalFilesDir(null);
File file = new File (path, "whatstheweatherlike.wav");
int length = (int) file.length();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] b = new byte[length];
FileInputStream in = null;
try {
in = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
for (int readNum; (readNum = in.read(b)) != -1;) {
bos.write(b, 0, readNum);
}
} catch (IOException e) {
e.printStackTrace();
}
byte[] bytes = bos.toByteArray();
mIAudioConsumer.consume(bytes);
}
However, Activity.getContext is not recognized. I can convert the file to bytes in MainActivity but how do I then write it to the websocket? Am I on the right track or is this not the right way? If it is, how do I solve this problem?
Any help is appreciated!
Activity.getContext is not recognized because there's no reference to Activity, since it's just a Thread. You would have to pass in the Activity, although it would likely make more sense just to pass in the Context if you need it.
You've got the right idea that you can create a FileInputStream and use that. You might like to use our MicrophoneCaptureThread as a reference. It'd be a very similar situation, except you'd be using your FileInputStream instead of reading from the microphone. You can check it out (and an example project that uses it) here: https://github.com/watson-developer-cloud/android-sdk/blob/master/library/src/main/java/com/ibm/watson/developer_cloud/android/library/audio/MicrophoneCaptureThread.java
I have an app for Android which downloads hundreds of files from the Internet. Some files turn out to be 0-byte after download. The app attempts to detect such cases and delete such files after download but sometimes it fails. The problem is more frequent on Android 4.x devices.
Here is the method which does the downloading. I gets the number of actually read bytes from inputStream.read(buffer).
public class Utils
{
public static class DownloadFileData
{
int nTotalSize;
int nDownloadedSize;
}
public interface ProgressCallback
{
void onProgress(long nCurrent, long nMax);
}
public static boolean downloadFile(String sFileURL, File whereToSave, DownloadFileData fileData, ProgressCallback progressCallback)
{
InputStream inputStream = null;
FileOutputStream fileOutput = null;
try
{
URL url = new URL(sFileURL);
URLConnection connection = url.openConnection();
//set up some things on the connection
connection.setDoOutput(true);
connection.connect();
fileOutput = new FileOutputStream(whereToSave);
inputStream = connection.getInputStream();
fileData.nTotalSize = connection.getContentLength();
fileData.nDownloadedSize = 0;
byte[] buffer = new byte[1024];
int bufferLength = 0; //used to store a temporary size of the buffer
// now, read through the input buffer and write the contents to the file
while ((bufferLength = inputStream.read(buffer)) > 0)
{
// if interrupted, don't download the file further and return
// also restore the interrupted flag so that the caller stopped also
if (Thread.interrupted())
{
Thread.currentThread().interrupt();
return false;
}
// add the data in the buffer to the file in the file output stream
fileOutput.write(buffer, 0, bufferLength);
// add up the size so we know how much is downloaded
fileData.nDownloadedSize += bufferLength;
if (null != progressCallback && fileData.nTotalSize > 0)
{
progressCallback.onProgress(fileData.nDownloadedSize, fileData.nTotalSize);
}
}
return true;
}
catch (FileNotFoundException e)
{
return false; // swallow a 404
}
catch (IOException e)
{
return false; // swallow a 404
}
catch (Throwable e)
{
return false;
}
finally
{
// in any case close input and output streams
if (null != inputStream)
{
try
{
inputStream.close();
inputStream = null;
}
catch (Exception e)
{
}
}
if (null != fileOutput)
{
try
{
fileOutput.close();
fileOutput = null;
}
catch (Exception e)
{
}
}
}
}
Here is the piece of code which processes the downloads. Since sometimes the number of read bytes is incorrect (it is > 0 and the real file has the size 0 bytes) I check the size of the downloaded file with outputFile.length(). But this again gives a value > 0 even though the file is really 0 byte. I tried to also just create a new file and read its size with recheckSizeFile.length(). Still the size is determined as > 0 while it's really 0 byte.
Utils.DownloadFileData fileData = new Utils.DownloadFileData();
boolean bDownloadedSuccessully = Utils.downloadFile(app.sCurrenltyDownloadedFile, outputFile, fileData, new Utils.ProgressCallback()
{
... // progress bar is updated here
});
if (bDownloadedSuccessully)
{
boolean bIsGarbage = false;
File recheckSizeFile = new File(sFullPath);
long nDownloadedFileSize = Math.min(recheckSizeFile.length(), Math.min(outputFile.length(), fileData.nDownloadedSize));
// if the file is 0bytes, it's garbage
if (0 == nDownloadedFileSize)
{
bIsGarbage = true;
}
// if this is a video and if of suspiciously small size, it's
// garbage, too
else if (Utils.isStringEndingWith(app.sCurrenltyDownloadedFile, App.VIDEO_FILE_EXTENSIONS) && nDownloadedFileSize < Constants.MIN_NON_GARBAGE_VIDEO_FILE_SIZE)
{
bIsGarbage = true;
}
if (bIsGarbage)
{
++app.nFilesGarbage;
app.updateLastMessageInDownloadLog("File is fake, deleting: " + app.sCurrenltyDownloadedFile);
// delete the garbage file
if (null != outputFile)
{
if (!outputFile.delete())
{
Log.e("MyService", "Failed to delete garbage file " + app.sCurrenltyDownloadedFile);
}
}
}
else
{
... // process the normally downloaded file
}
I am not sure but I think there is a bug in Android with reading file size. Has anyone seen a similar problem? Or am I maybe doing something wrong here?
Thanks!
EDIT: how i determine that the files are 0-byte:
all the files which get downloaded go thru the described routines. When I then later view the download folder with a file browser (Ghost Commander), some of the files (like maybe 10%) are 0-byte. They can't be played by a video player (shown as "broken file" icon).
It looks to me like your problem is that you only check for "garbage" files if the Utils.downloadFile call returns true. If the download fails in the getInputStream call or the first read, you will have created a file with zero length which will never be deleted.
You should call flush() on your FileOutputStream to ensure that all data is written to the file. This should make your issue with 0-byte files occur less often.
To check for 0 byte files using File.length() should work properly. Can you open a shell (adb shell) on the device and run ls -l to see the byte count displayed by it is 0 (maybe your file manager has some weird issues). Also please debug (or put some log statements) that sFullPath contains the correct file paths. I can't see where sFullPath gets set in your code above and why you don't just use outputFile but recreate another File object.
In my android app, I am reading a file from internal storage every time a new game loads.
The first 4 times I do this, it works fine, but on the fifth time it force closes.
Here is my code
private String readFromInternalStorage(String filename) {
FileInputStream fis=null;
byte[] bytes = new byte[1000000];
try {
fis=startGame.openFileInput(filename);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
fis.read(bytes);
} catch (IOException e) {
e.printStackTrace();
}
return new String(bytes);
}
While messing around with the code, I noticed that if I change the length of the byte array, it changes the amount of times I can read a file without it force closing. If I change the length to 2000000, it closes after the second time and if I change it to 100000 it closes after the eighth time. I'm pretty clueless as to why this would happen because I am creating a new byte array every time the method is called so I wouldn't think that the size would change anything.
Update:
After going back and doing some more testing it seems like file input has nothing to do with why my app is force closing. When this code is commented out, the app will load five levels in a row without force closing so I thought that it was the problem, but it still force closes after eight tries so clearly there's something else that's not working. Thanks for your help anyway.
I don't see a "close()" in your code:
http://docs.oracle.com/javase/6/docs/api/java/io/FileInputStream.html#close%28%29
You shouldn't hard-code the array size. Besides you should use finally, in order to make sure the FileInputStream is closed, even when failed.
Here's a code sample that shows how it should be done:
FileInputStream fis;
String info = "";
try {
fis = mContext.openFileInput(this.fileName);
byte[] dataArray = new byte[fis.available()];
if (dataArray.length > 0) {
while (fis.read(dataArray) != -1) {
info = new String(dataArray);
}
Log.i("File Reading" , "Success!");
isOk = true;
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
} finally {
fis.close();
}
a safe version of what you do is e.g.:
private String readFromInternalStorage(String filename) {
FileInputStream fis = null;
File file = new File(startGame.getFilesDir(), filename);
long size = file.length();
// impossible to have more than that (= 2GB)
if (size > Integer.MAX_VALUE) {
Log.d("XXX", "File too big");
return null;
}
int iSize = (int) size;
try {
fis = new FileInputStream(file);
// part of Android since API level 1 - buffer can scale
ByteArrayBuffer bb = new ByteArrayBuffer(iSize);
// some rather small fixed buffer for actual reading
byte[] buffer = new byte[1024];
int read;
while ((read = fis.read(buffer)) != -1) {
// just append data as long as we can read more
bb.append(buffer, 0, read);
}
// return a new string based on the large buffer
return new String(bb.buffer(), 0, bb.length());
} catch (FileNotFoundException e) {
Log.w("XXX", e);
} catch (IOException e) {
Log.w("XXX", e);
} catch (OutOfMemoryError e) {
// this could be left out. Keep if you read several MB large files.
Log.w("XXX", e);
} finally {
// finally is executed even if you return in above code
// fis will be null if new FileInputStream(file) throws
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// ignored, nothing can be done if closing fails
}
}
}
return null;
}