I am creating an app that needs to read data from a file. I was initially reading it from the assets folder using a BufferedReader and an InputStreamReader but I was running into memory issues (see Android: File Reading - OutOfMemory Issue). One suggestion was to copy the data from the assets folder to the internal storage (not the SD card) and then access it via RandomAccessFile. So I looked up how to copy files from the assets to internal storage and I found 2 sources:
https://groups.google.com/forum/?fromgroups=#!topic/android-developers/RpXiMYV48Ww
http://developergoodies.blogspot.com/2012/11/copy-android-asset-to-internal-storage.html
I decided to use the code from the second one and modified it for my file. So it looks like this:
public void copyFile() {
//Open your file in assets
Context context = getApplicationContext();
String destinationFile = context.getFilesDir().getPath() + File.separator + "text.txt";
if (!new File(destinationFile).exists()) {
try {
copyFromAssetsToStorage(context, "text.txt", destinationFile);
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void copyStream(InputStream input, OutputStream output) throws IOException {
byte[] buffer = new byte[1024];
int length = Input.read(buffer);
while (length > 0) {
output.write(buffer, 0, length);
length = input.read(buffer);
}
}
private void copyFromAssetsToStorage(Context context, String sourceFile, String destinationFile) throws IOException {
InputStream inputStream = context.getAssets().open(sourceFile);
OutputStream outputStream = new FileOutputStream(destinationFile);
copyStream(inputStream , outputStream );
outputStream.flush();
outputStream.close();
inputStream.close();
}
I am assuming that this copies the file into the app's data directory. I have not been able to test it because I would like to be able to access the file using RandomAccessFile. However, I have never done either one of these two (copying the file from assets and RandomAccessFile) so I am stuck. The work on this app has come to a standstill because this is the only thing that is preventing me from completing it.
Can anyone provide me with corrections, suggestions, and correct implementations of how to access the data using RandomAccessFile? (The data is a list of strings 4-15 characters in length on each line.)
EDIT*
private File createCacheFile(Context context, String filename){
File cacheFile = new File(context.getCacheDir(), filename);
if (cacheFile.exists()) {
return cacheFile ;
}
InputStream inputStream = null;
FileOutputStream fileOutputStream = null;
try {
inputStream = context.getAssets().open(filename);
fileOutputStream = new FileOutputStream(cacheFile);
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length = -1;
while ( (length = inputStream.read(buffer)) > 0) {
fileOutputStream.write(buffer,0,length);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return cacheFile;
}
1- Copy the file from assets to the cache directory
This code just for illustration, you have to do appropriate exception handling and close resources
private File createCacheFile(Context context, String filename){
File cacheFile = new File(context.getCacheDir(), filename);
if (cacheFile.exists()) {
return cacheFile ;
}
InputStream inputStream = context.getAssets().open(filename);
FileOutputStream fileOutputStream = new FileOutputStream(cacheFile);
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length = -1;
while ( (length = inputStream.read(buffer)) > 0) {
fileOutputStream.write(buffer,0,length);
}
fileOutputStream.close();
inputStream.close();
return cacheFile;
}
2- Open the file using RandomAccessFile
File cacheFile = createCacheFile(context, "text.txt");
RandomAccessFile randomAccessFile = new RandomAccessFile(cacheFile, "r");
// Process the file
randomAccessFile.close();
On a side note, you should follow Java naming conventions, e.g. your method and variable name should start with small letter such as copyFromAssetsToStorage and destinationFile
Edit:
You should make a separate try/catch for each close() operation, so if one fails the other still get executed and check that they are not null
finally {
try {
if(fileOutputStream!=null){
fileOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(inputStream!=null){
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
Related
I encrypted multiple files in one file in order.
Now I want to decrypt only one of those files. for this purpose I did this:
I got all bytes of encrypted files as array Like this: (first index: byte offset of every file in the encrypted file, second index: length of file bytes in the encrypted file)
int[] file1 = new int[] {0 ,109624}
int[] file2 = new int[] {109624,40480}
int[] file3 = new int[] {150104,153640}
int[] file4 = new int[] {303744,24320}
Now for getting specific file from the encrypted file and decrypt it I should to read the encrypted file. First of all, skip the bytes of previous files.
for skipping them I use this method (for example I want to encrypt file2):
int fromByte = file2[0];
int fileLength = file2[1];
FileInputStream fis = new FileInputStream(sourceFile);
CipherInputStream cis = new CipherInputStream(fis,cipher);
long skipBytes = cis.skip(fromByte);
Then it reads byte to byte of source file in "while" loop and when length of read file bytes equals to fileLength, it breaks loop.
I brought all the codes at the end here.
When I decrypt first file (from byte:0 and file length:109624 and it dosn't need to "skip bytes"), It works well.
But when I decrypt other files (for example for encrypt file2 it needs to skipt 109624 byte from source file), It doesn't work well and created file is not decrypted.
Why it dosn't work for other files?
Here's my codes:
public void decryptFile(File sourceFile, File targetFile, int fromByte, int lengthToRead, SecretKeySpec keySpec) {
try {
FileInputStream fis = new FileInputStream(sourceFile);
FileOutputStream fos = new FileOutputStream(targetFile);
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE,keySpec);
CipherInputStream cis = new CipherInputStream(fis,cipher);
int readPart,currentLength = 0, bufferSize = 8;
long skipBytes;
byte[] bytes = new byte[bufferSize];
skipBytes = cis.skip(fromByte);
while ((readPart = cis.read(bytes)) != -1) {
fos.write(bytes,0,readPart);
currentLength += bufferSize;
if (currentLength >= lengthToRead) {
break;
}
}
cis.close();
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
}
Here's My codes for encrypt multiple files in on file:
public void encryptFile(File[] sourceFiles, File targetFile, SecretKeySpec keySpec) {
try {
FileOutputStream fos = new FileOutputStream(targetFile);
FileInputStream fis;
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE,keySpec);
CipherOutputStream cos = new CipherOutputStream(fos,cipher);
int readPart,bufferSize = 8;
byte[] bytes = new byte[bufferSize];
for (File sourceFile : sourceFiles) {
fis = new FileInputStream(sourceFile);
while ((readPart = fis.read(bytes)) != -1) {
cos.write(bytes, 0, readPart);
}
fis.close();
}
cos.flush();
cos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
}
I realized the problem, After many reviews.
There are two problem in this codes:
problem 1: in skip method. we should check the skipBytes and if it is not equal to fromByte, it must repeat. for this purpose add these codes:
while (skipBytes<fromByte) {
fromByte -= skipBytes;
skipBytes = cis.skip(fromByte);
}
problem 2: in encrypt function. if the file length is not multiple of 8, encryption method add some bytes until its length is multiple of 8. for fix this problem we should to add those bytes to the file ourselves before encrypt it. for this purpose add these codes before encrypt every file:
int remaining = (int) (sourceFile.length() % 8);
if (remaining>0) {
BufferedWriter bw = new BufferedWriter(new FileWriter(sourceFile,true));
while (remaining<8) {
bw.write(" ");
remaining++;
}
bw.close();
}
This codes modifies source file. If you want to don't modifying source file, instead of above codes, use this codes after ending encryption while loop:
int remainder = (int)(sourceFile.length() % 8);
if (remainder>0) {
StringBuilder sb = new StringBuilder("");
while (remainder<8) {
sb.append(" ");
remainder++;
}
byte[] additionBytes = sb.toString().getBytes(Charset.forName("UTF-8"));
cos.write(additionBytes, 0, additionBytes.length);
}
I want to encrypt video files stored in SD card
Environment.getExternalStorageDirectory()
I have found that Facebook conceal is good for encrypting large files. I have followed this tutorial Make fast cryptographic operations on Android with Conceal
Here is what i have done up to now.
Encryption method
public void encodeAndSaveFile(File videoFile, String path) {
try {
final byte[] encrypt = new byte[(int) videoFile.length()];
ContextWrapper cw = new ContextWrapper(getApplicationContext());
File directory = cw.getDir(path, Context.MODE_PRIVATE);
File mypath = new File(directory, "en1");
Crypto crypto = new Crypto(new SharedPrefsBackedKeyChain(this), new SystemNativeCryptoLibrary());
if (!crypto.isAvailable()) {
return;
}
OutputStream fileStream = new BufferedOutputStream(
new FileOutputStream(mypath));
OutputStream outputStream = crypto.getCipherOutputStream(
fileStream, new Entity("Passwordd"));
outputStream.write(encrypt);
outputStream.close();
} catch (UnsupportedOperationException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
Toast.makeText(WebViewActivity.this,"Encrypted",Toast.LENGTH_LONG).show();
}
Decryption method
private void decodeFile(String filename,String path) {
Crypto crypto = new Crypto(new SharedPrefsBackedKeyChain(this),
new SystemNativeCryptoLibrary());
ContextWrapper cw = new ContextWrapper(getApplicationContext());
File directory = cw.getDir(path, Context.MODE_PRIVATE);
File file = new File(directory, filename);
try {
FileInputStream fileStream = new FileInputStream(file);
InputStream inputStream = crypto.getCipherInputStream(fileStream,
new Entity("Password"));
ByteArrayOutputStream out = new ByteArrayOutputStream();
int read;
byte[] buffer = new byte[1024];
while ((read = inputStream.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
Toast.makeText(WebViewActivity.this,"Decrypted",Toast.LENGTH_LONG).show();
}
But this code throwing following error at the run time
java.lang.illegalArgumentException: File contains a path separator
Then i have changed "mypath: variable of encodeAndSaveFile to this
File mypath = new File(directory, "en1");
and "file" variable of decodeFile to this
File file = new File(directory, filename);
Then no errors but. Encryption is not happening. Please help to solve this or suggest correct method for video encryption with conceal lib.
Am trying to copy a file from a named subfolder in asset folder but am getting a "not found error" when trying to use the file. Apparently it seems am not copying the file right.
Here is what I have done maybe someone can spot my error
Method call:
copyfile("/lollipop/proxy.sh");
Method:
public void copyfile(String file) {
String of = file;
File f = new File(of);
String basedir = getBaseContext().getFilesDir().getAbsolutePath();
if (!f.exists()) {
try {
InputStream in =getAssets().open(file);
FileOutputStream out =getBaseContext().openFileOutput(of, MODE_PRIVATE);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.close();
in.close();
Runtime.getRuntime().exec("chmod 700 " + basedir + "/" + of);
} catch (IOException e) {
Log.e(TAG, "Error reading I/0 stream", e);
}
}
}
Trying to use the proxy.sh fails as the file seems it's never copied but when I remove the " lollipop " directory it works fine. What seems wrong? Tnx
openFileOutput() does not accept subdirectories. Since of points to /lollipop/proxy.sh, you are trying to create a subdirectory.
Those having issues accessing sub directories in asset folder since explanation to this isn't explicitly answered this is how I achieved it.
AssetManager assetManager = getAssets();
String[] files = null;
try {
if (Build.VERSION.SDK_INT >= 21)
files = assetManager.list("api-16");
else
files = assetManager.list("");
} catch (IOException e) {
Log.e(TAG, e.getMessage());
}
if (files != null) {
for (String file : files) {
InputStream in = null;
OutputStream out = null;
try {
if (Build.VERSION.SDK_INT >= 21)
in = assetManager.open("api-16/" + file);
else
in = assetManager.open(file);
out = new FileOutputStream("/data/data/yourpackagename/" + file);
copyFile(in, out);
in.close();
in = null;
out.flush();
out.close();
out = null;
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
}
}
private void copyFile(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
}
method call
Files are now accessible from
/data/data/yourpackagename/
so call the files from there. Using
getFilesDir()
won't work as it gets from
/data/data/yourpackagename/files/
Successfully i wrote program to read single file in asset folder and assign it to text view.
Now i want to read all files and assign it to the text view, will any one of you help me how to do? all the files are text files, thankful to you in advance.
****In Android we can't read file from assets folder u have to copy file from asseset to sdcard than perform reading**
EDIT: this statement is wrong. See comments.
use following code for perform copy from assets folder
private void copyAssets() {
AssetManager assetManager = getAssets();
String[] files = null;
try {
files = assetManager.list("");
} catch (IOException e) {
Log.e("tag", "Failed to get asset file list.", e);
}
for(String filename : files) {
InputStream in = null;
OutputStream out = null;
try {
in = assetManager.open(filename);
out = new FileOutputStream("/sdcard/" + filename);
copyFile(in, out);
in.close();
in = null;
out.flush();
out.close();
out = null;
} catch(IOException e) {
Log.e("tag", "Failed to copy asset file: " + filename, e);
}
}
}
private void copyFile(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[1024];
int read;
while((read = in.read(buffer)) != -1){
out.write(buffer, 0, read);
}
}
Context
I am copying a sqlite db file out of the assets to internal storage.
The file opens fine, but I wanted a additional layer of safety. There is
a slim possibility that when the file gets copied out it doesn't complete.
I decided to favor a check sum technique: specifically the MessageDigest
Java thing. so here's the code:
public ZipCode(Context ctx) {
this.ctx = ctx;
if (!databaseExist(ctx)) {
Log.d("ZipCode", "DB DNE");
inflate_db(ctx);
check_DB(ctx);
} else {
Log.d("ZipCode", "DB Exsits");
check_DB(ctx);
}
}
private static void inflate_db(Context ctx) {
byte[] buffer = new byte[2048];
int length;
AssetManager am = ctx.getAssets();
try {
BufferedInputStream is = new BufferedInputStream(
am.open(ZIPCODE_SQLITE_FAUX_FILE));
GZIPInputStream zis = new GZIPInputStream(is);
File dbfile = ctx.getDatabasePath(ZIPCODE_SQLITE);
FileOutputStream out = new FileOutputStream(dbfile);
while ((length = zis.read(buffer, 0, buffer.length)) > 0) {
out.write(buffer, 0, length);
}
out.flush();
out.close();
zis.close();
} catch (IOException e) {
e.printStackTrace();
Log.d("ERROR", e.getMessage());
}
}
private static void check_DB(Context ctx) {
File dbfile = ctx.getDatabasePath(ZIPCODE_SQLITE);
FileInputStream fis;
MessageDigest digester;
byte[] bytes = new byte[8192];
int byteCount;
try {
digester = MessageDigest.getInstance("MD5");
fis = new FileInputStream(dbfile);
while ((byteCount = fis.read(bytes)) > 0) {
digester.update(bytes, 0, byteCount);
}
String digest = Base64.encodeBytes(digester.digest());
Log.d("MD5 Sum", digest);
fis.close();
return;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
catch (NoSuchAlgorithmException e1) {
e1.printStackTrace();
}
}
Now for the Question
Why is it that on fresh creation the check_DB's Log.d("MD5 Sum", digest);
has one output and say on relaunch (i.e. the DB file exists in internal storage)
the check_DB's Log.d("MD5 Sum", digest); has a different output.
Note:
databaseExist(ctx) checks for the DB file's existence per Android conventions.
private static boolean databaseExist(Context ctx) {
File dbFile = ctx.getDatabasePath(ZIPCODE_SQLITE);
return dbFile.exists();
}
why don't you calculate MD5 of your asset file (in your project directory) to find out which MD5 is right and which is wrong? then it'll be easier to find the problem part.
also, i'd suggest to replace (byteCount = fis.read(bytes)) > 0 with (byteCount = fis.read(bytes)) != -1 according to the FileInputStream reference manual.