I'm new to Android Studio 3.0, emulating on a Nexus 4, Marshmallow. I'm trying to build simple "Save File" and "Load File" parts of my app. Here's the "Save File" part:
String filename = "myFile01"; // Then "myFile02", "myFile03", etc...
String userData = "Some useful data here...";
try {
// Adapted from: https://www.youtube.com/watch?v=_15mKw--RG0
FileOutputStream fileOutputStream = openFileOutput(filename, MODE_PRIVATE); // creates a file with given filename
fileOutputStream.write(userData.getBytes()); // puts userData into the file
fileOutputStream.close();
Toast.makeText(getApplicationContext(), "File saved!", Toast.LENGTH_LONG).show();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
The above code will be called again and again as the user creates and saves additional files. Later, the user may want to view all the saved files and load one. I'll have a ListView displaying all the files... but I need help reading the current directory to get that list.
I thought I read somewhere that in Android, there's one flat directory for your app to save and retrieve files. So I was hoping if I saved a bunch of files and then called a read() method, all my saved files would simply be in the default directory, no need to search. That seems to be a bad assumption; here's why:
Here's my code looking in the default directory and listing all the files found within there. First, I need the path of said default directory:
// Get current directory adapted from: https://stackoverflow.com/questions/5527764/get-application-directory
String packName, currDir;
PackageManager m = getPackageManager();
packName = getPackageName();
PackageInfo p = null;
try {
p = m.getPackageInfo(packName, 0);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
currDir = p.applicationInfo.dataDir;
And then I open "currDir," and store the names of all the local files in an array:
// get list of files adapted from: https://stackoverflow.com/questions/9317483/showing-a-list-of-files-in-a-listview#9317583
File dir = new File(currDir);
File[] filelist = dir.listFiles();
String[] fileArr = new String[filelist.length];
for (int i = 0; i < fileArr.length; i++) {
fileArr[i] = filelist[i].getName();
}
The plan from here is to load the "fileArr" into a ListView and go from there. But when I step through the debugger, I see this as the contents of "fileArr":
"cache"
"code_cache"
"files"
This is true no matter how many files I've saved previously.
BTW, in the debugger, the assignments for packName and currDir look 100% correct:
packName = com.mydomain.myapp
currDir = /data/user/0/com.mydomain.myapp
So... I'm kinda assuming that my saved files are actually here:
/data/user/0/com.mydomain.myapp/files
And therefore, I should append this to my "get current directory" code:
// Get current directory adapted from: https://stackoverflow.com/questions/5527764/get-application-directory
String packName, currDir;
...everything from before...
currDir = p.applicationInfo.dataDir+"/files"; // <---- appending "+"/files"
Or am I way off? Any advice will be appreciated, thanks!
First of all, if you want to save your files in the app's directory, then you should call create a directory,
File directoryDefault = new File(Environment.DIRECTORY_DOCUMENTS, "YOUR_FOLDER_NAME");
if (!directoryDefault.exists()) {
directoryDefault.mkdir();
}
Then you have to save whatever files you have to save in the above mentioned default directory. Afterwards, when you want to list all the files available in that directory, you should call,
private ArrayList<String> fileNames() {
ArrayList<String> namesArray = new ArrayList<>();
File[] arrayFiles = directoryDefault.listFiles();
for (File file : arrayFiles) {
namesArray.add(file.getName());
}
return namesArray;
}
Related
I`m making an app to test the Opencv haarcascades and it seems like my app does not see the .xml files. My permission include:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
My code for collecting the file names and path is:
public ArrayList<String []> getLongAndShortFileHandles(String root_dir) {
ArrayList<String[]> ret = new ArrayList<>();
String path = Environment.getExternalStorageDirectory().toString()+"/"+root_dir;
File directory = new File(path);
File[] files = directory.listFiles();
Log.e(TAG, "getLongAndShortFileHandles: Has dir "+path+", with "+((files==null) ? "null" : files.length) + " number of files");
for (int i = 0; i < Objects.requireNonNull(files).length; i++)
{
Log.e(TAG, "getLongAndShortFileHandles: HAS FILE "+files[i].getName());
String [] temp = new String [] {files[i].getAbsolutePath(),files[i].getName()};
ret.add(temp);
}
return ret;
}
The debug shows that no files are present when I call this function to populate my RecycleView subclass like so:
this.cascade_selector = (RecyclerView) findViewById(R.id.cascade_view);
this.cascade_selector.setAdapter(new FileAdaptah(getLongAndShortFileHandles("cascade_app/cascades"),this, CASCADE));
this.xml_ano_selector = (RecyclerView) findViewById(R.id.annotation_view);
this.xml_ano_selector.setAdapter(new FileAdaptah(getLongAndShortFileHandles("cascade_app/annotations"),this, ANNOTATION_XML));
The result in the app does show the output for second call but not for the first one.
If the first one is replaced with any other folder like DCIM, or Music it shows the files.
However if I add .xml file into any directory it will not be shown.
EDIT:
I was playing around with different directories and files and determined that the problem goes further then just .xml it seem to be all the ascii encoded text file formats I could think of. All binaries like .jpg, .wav, .mp4, .bin and etc. are found in when put in same directory.
EDIT (2):
So I`ve established that if I rename any of the text file extensions into any binary one (e.g. 50.xml -> 50.jpg they are detected by the script.
This prompted me to try different filename filters, but they didn't yield any success.
I`ve also tried to use Directory Stream instead but to no avail:
#RequiresApi(api = Build.VERSION_CODES.O)
public ArrayList<String[]> getFiles(String suffix, String root_dir){
ArrayList<String[]> ret = new ArrayList<String[]>();
try {
root_dir = Environment.getExternalStorageDirectory().toString() + "/" + root_dir;
Path dirName = Paths.get(root_dir);
DirectoryStream<Path> paths = Files.newDirectoryStream(dirName, "*.pdf");
paths.forEach(path -> {
Log.e(TAG, "getFiles: HAS FILE : "+path.toString());
if (path.endsWith(suffix)) {
String sPath = path.toString();
String[] splitted = sPath.split(File.separator);
ret.add(new String[]{sPath, splitted[splitted.length - 1]});
}
});
} catch (IOException exception) {
exception.printStackTrace();
}
return ret;
}
I`ve started to suspect it might be a bug, so I provide my environment information: My android studio is version 4.1.2 and my target SDK is 30, while my minimum accepted is 19. My test device is a physical Galaxy A40 with android 11.
EDIT (3):
So I've noticed that the second snippet for getFiles has leftover glob expression from the point I've copied the original snippet. Out of interest I've modified and removed it but in either case nothing changed.
EDIT (4):
So I've tried to access file directly with the following code:
this.test = (Button) findViewById(R.id.select_to_test);
this.test.setOnClickListener(v -> {
String path = Environment.getExternalStorageDirectory().toString()+"/cascade_app/cascades/50.xml";
Intent intent = new Intent(v.getContext(),CheckCascade.class);
intent.putExtra("fh",path);
File debugFile = new File(path);
Toast.makeText(this, "File "+path+" does "+((debugFile.exists())? "exist":"not exist"), Toast.LENGTH_SHORT).show();
startActivity(intent);
});
}
and it seem to detect it no problem.
So after some time I was helped by a kind person who suggested to check if my app needs
this permission. This solved the problem! I hope this can help people who will have same issue.
Below is the working method to Import and Export SQLite database. Its Working just fine in all android versions excluding Android Pie. When I am trying to import in Android pie, it shows successful toast but database is not being restored. Can anybody help me workaround in Android Pie(API 28).
private void importDB() {
try {
File sd = Environment.getExternalStorageDirectory();
File cur_db_pat = new File(this.getDatabasePath(DATABASE_NAME).getAbsolutePath());
if (sd.canWrite()) {
String backupDBPath = bac_dir_nam +"/" + DATABASE_NAME;
File currentDB = new File(sd, backupDBPath);
FileChannel src = new FileInputStream(currentDB).getChannel();
FileChannel dst = new FileOutputStream(cur_db_pat).getChannel();
dst.transferFrom(src, 0, src.size());
src.close();
dst.close();
Toast.makeText(getBaseContext(), cur_db_pat.toString(),
Toast.LENGTH_LONG).show();
}
} catch (Exception e) {
Toast.makeText(getBaseContext(), e.toString(), Toast.LENGTH_LONG)
.show();
}
}
private void exportDB() {
try {
File sd = Environment.getExternalStorageDirectory();
File cur_db_pat = new File(this.getDatabasePath(DATABASE_NAME).getAbsolutePath());
if (sd.canWrite()) {
String backupDBPath = bac_dir_nam+"/" + DATABASE_NAME;
File backupDB = new File(sd, backupDBPath);
FileChannel src = new FileInputStream(cur_db_pat).getChannel();
FileChannel dst = new FileOutputStream(backupDB).getChannel();
dst.transferFrom(src, 0, src.size());
src.close();
dst.close();
Toast.makeText(getBaseContext(), backupDB.toString(),
Toast.LENGTH_LONG).show();
}
} catch (Exception e) {
Toast.makeText(getBaseContext(), e.toString(), Toast.LENGTH_LONG)
.show();
}
}
I don't have much experience with file system. So an example would help a lot.
In Android Pie+ SQLite has changed to default to using the generally more efficient Write Ahead Logging (WAL) instead of Journal mode.
As such there will be two files with the same name as the database but suffixed with -shm (shared memory file) and -wal (write ahead log) and their presence is what I believe causes the issue(s).
Temporary Files Used By SQLite (see 2.2 and 2.3)
One fix would be to disable Write Ahead Logging using use the SQliteDatabase disableWriteAheadLogging method and the previous method would work as before but with the less efficient journal mode.
(if using a subclass of SQliteOpenHelper then override the onConfigure method to invoke this method. ) disableWriteAheadLogging.
Another fix is to delete these two files when restoring. To avoid the potential for corruption you have to ensure that the database was adequately checkpointed before making the backup. see PRAGMA checkpoint;
The following is a snippet that deletes these two files when restoring (noting that the backup is assumed to have been taken with adequate checkpointing):-
// Added for Android 9+ to delete shm and wal file if they exist
File dbshm = new File(dbfile.getPath() + "-shm");
File dbwal = new File(dbfile.getPath()+ "-wal");
if (dbshm.exists()) {
dbshm.delete();
}
if (dbwal.exists()) {
dbwal.delete();
}
Another fix would be to additionally backup and subsequently restore the -shm and -wal files.
You may also wish considering the potential benefits of renaming the original files when importing/restoring, checking the new files after they have been copied (e.g. using PRAGMA integrity_check;) if the results indicat no issues then delete the renamed original files, otherwise delete the imported files and rename the original files to their original name, indicating that the import failed.
In your class for Db WorkHelper ovverride onOpen() method and set disableWriteAheadLogging then call onOpen() standard, if version of android sdk 28 , sure then old version remain old modality.
#Override
public void onOpen(SQLiteDatabase database) {
super.onOpen(database);
if(Build.VERSION.SDK_INT >= 28)
{
database.disableWriteAheadLogging();
}
}
In my case WORK perfect.
Unlike what the other commenters have suggested, you can't rely on the database consisting of a single file after write-ahead logging is disabled, and you can't assume that the -shl and -wal filenames remain correct. This is all an implementation detail of sqlite3 / Android and therefore subject to change at any time (just like the old code broke).
One way of doing this that I expect to continue working is to use sqlite3's .dump command to convert the database into SQL that can later be executed in order to recreate the database.
I haven't tested the following, but expect that something similar to it should work:
// Copyright 2021 Google LLC.
// SPDX-License-Identifier: Apache-2.0
// Untested:
private byte[] exportDatabase(SQLiteDatabase database) throws IOException {
Process process = new ProcessBuilder()
.command("/system/bin/sqlite3", database.getPath(), ".dump")
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.start();
try (InputStream inputStream = process.getInputStream()) {
// [read the full contents of inputStream and save them somewhere]
return ByteStreams.toByteArray(inputStream);
} finally {
waitForProcess(process);
}
}
private void importDatabase(String databasePath, InputStream backedUpData) throws IOException {
// restore the database:
Process process = new ProcessBuilder()
.command("/system/bin/sqlite3", databasePath)
.redirectInput(ProcessBuilder.Redirect.PIPE)
.start();
try (OutputStream outputStream = process.getOutputStream()) {
// now write the backed-up contents back to outputStream
ByteStreams.copy(backedUpData, outputStream);
}
waitForProcess(process);
}
private static void waitForProcess(Process process) {
try {
process.waitFor();
} catch (InterruptedException e) {
// ignore interruption, restore interrupt flag
Thread.currentThread().interrupt();
}
}
Obviously, you'll have to ensure that:
The database that you're backing up isn't currently open.
The database that you're restoring doesn't already exist.
I tried to move a photo taken by the camera to a folder named "raspberrypi" I created. But the .renameTo() keeps returning false. I cannot find the reason. To clarify, the photos I am trying to move are taken by the camera, so they aren't in any folder to begin with.
imagesEncodedList is an ArrayList of File path Strings.
boolean bool=false;
for(int i=0; i<imagesEncodedList.size();i++){
File from;
File to=null;
try{
from=new File(imagesEncodedList.get(i));
String dateString=new SimpleDateFormat("MM_dd_yyyy_HH:mm:ss").format(Calendar.getInstance().getTime());
to=new File(getPublicDir(),"SideBySide4_ImportedPhoto"+i+"_"+dateString+".jpg");
bool=from.renameTo(to);
}catch(Exception e){
e.printStackTrace();
}
MediaScannerConnection.scanFile(this,
new String[]{to.getPath()},
null,
null);
}
Toast.makeText(this, "Success?: "+bool, Toast.LENGTH_LONG)
.show();
Here is my getPublicDir() function implementation:
public File getPublicDir() {
// Get the directory for the user's public pictures directory.
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "raspberry");
if (!file.mkdirs()) {
Log.e("PUBLIC DIRECTORY", "Directory not created");
}
return file;
}
renameTo only works if source and target are on the same disk partition. If they're not, you'll have to copy the source file and delete it afterwards.
context.getFilesDir() is on a different partition (/data) than Environment.getExternalStoragePublicDirectory (typically accessible under /sdcard).
This is my function for converting blob to mp3:
private void convertByyeToMP3(byte[] bytearray,String trackName) {
try {
ContextWrapper c = new ContextWrapper(getApplicationContext());
File directory = new File(c.getFilesDir().getAbsolutePath()
+ "/Music");
if (!directory.exists()){
directory.mkdir();
}
File tempMp3 = File.createTempFile(trackName, ".mp3",
directory);
FileOutputStream fos = new FileOutputStream(tempMp3);
fos.write(bytearray);
fos.close();
Log.d("Byte array to mp3 conversion: ", "successfull");
} catch (Exception ex) {
Log.d("In convertToByteToMp3 Function:", ex.toString());
}
}
When I execute this function ,I can see the created mp3 files in my app folder but when I try to play them Using my own code or using ES File Explorer, they both can't play it.
This is the function I use play my music:
private MediaPlayer mp = new MediaPlayer();
private void playSong(String songPath) {
try {
mp.reset();
mp.setDataSource(songPath);
mp.prepare();
mp.start();
} catch (IOException e) {
Log.v(getString(R.string.app_name), e.getMessage());
}
}
And I use this sample code to play the track:
ContextWrapper c = new ContextWrapper(getApplicationContext());
File directory = new File(c.getFilesDir().getAbsolutePath() + "/Music");
playSong(directory.getPath() + File.separator + "kurchina");
This is where I read database and send the blob:
cursor = mDbHelper.GetTables();
byte[] blob = null;
DATAS data = new DATAS();
while (cursor.moveToNext()) {
blob = cursor.getBlob(cursor.getColumnIndex("data"));
if (blob != null) {convertByyeToMP3(blob,data_MusicName);}
db.addDATAS(data);
}
FYIs:
-Read and Write permissions added to manifest.
-Path and filename are check and they exist
-blob byte is not corrupted
There are all sorts of things that might have gone wrong, either in the code that you have shown us or elsewhere. So you need to do your own troubleshooting. Methodically.
Figure out if the problem is with the song file you have extracted or the way you are playing it. For example, try to play the extracted file using a free-standing mp3 player utility.
Assuming that the problem is the extracted file, the next thing is to figure out if the file is the same as the one that you originally inserted into the database. Compare the file sizes and the checksums using the relevant external applications.
and so on.
Found the problem.
It didn't play because the music files were stored in my app folder which is only accessible using a rooted device.
When I copied the music to my sdcard they played well, but in my app folder using rooted nexus 7 I couldn't play it even with an mp3-player app.
I'm sure this is a trivial question, but I failed to find an answer.
I'm making an Android app from which I want to open the image viewer
showing several images. I know how to do this with only one image:
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
File file1 = new File("/mnt/sdcard/photos/20397a.jpg");
intent.setDataAndType(Uri.fromFile(file1), "image/jpg");
startActivity(intent);
This works perfectly. But how do I pass several images to the viewer?
Thanks!!
L.
I want to open the image viewer
There is no "the image viewer" in Android. Devices and users may have many, many different apps that are capable of viewing image/jpeg files loaded from a local file.
But how do I pass several images to the viewer?
Sorry, but there is no standard Intent to open multiple files of any sort.
Also, please do not hardcode /mnt/sdcard/ in your app. Please use the proper methods on the Environment class to determine directories on external storage.
You need to list all files you want to view in a array. Then you display one of the array and when you drag, you show the next image.
ArrayList list;
private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // Ansi date format
list = new ArrayList();
String path = "c:/temp/";
File dir = new File(path);
for (String dirListing : dir.list()) {
if ((dirListing.endsWith(".jpg")) ||
(dirListing.endsWith(".png")) ||
(dirListing.endsWith(".gif"))) {
try { // write all file-info to a arraylist
File f = new File(path+dirListing);
list.add(f.getCanonicalPath());
list.add(f.getName());
list.add(String.valueOf(f.length()));
String lastModified = dateFormat.format(new Date(f.lastModified()));
list.add(lastModified);
}
catch (IOException e) {
e.printStackTrace();
}
catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
}
}
Now you can read the array and display then one by one.
Do not use Hard Coded Paths.
Change this line :
File file1 = new File("/mnt/sdcard/photos/20397a.jpg");
to
File sdDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
It will locate to
/storage/emulated/0/Picture
You can remove Environment.DIRECTORY_PICTURES If you want the parent directory of your sdcard.
Since you are specifying a single file 20397a.jpg that's why you are unable to see other images.
And if you want to see other contents other than images then change 'image/jpg' to 'image/*'