I have an apk file which contains zipped database (in asset folder). I want to unzip this database during first app run. For that case I am going to use zip4j like so:
public void unzipping() {
String source = "";//here source of zipped database
String destination = "";//here where database should be after installation
String password = "mypassword";
try {
ZipFile zipFile = new ZipFile(source);
if (zipFile.isEncrypted()) {
zipFile.setPassword(password);
}
zipFile.extractAll(destination);
} catch (ZipException e) {
e.printStackTrace();
}
}
The problem is that I don't know what correct path to specify in "source" and "destination".
There is nothing wrong with using encryption to protect user data. But understand, that the attacker can always decompile/memory dump your app and grab the password you are using to read/write to the database.
I'd a SQLCipher , which makes it transparent to the other parts of the app.
Related
I am trying to use Android's internal helpers to get a path from the system for my file first and then put my files where the system wants. Because tomorrow they might change their minds.
I made a simple program to explore this subject. Here is my code;
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String path = letsMakeAfile(this, "myFile.txt");
}
private static String letsMakeAfile(Context context, String nameOfFile) {
String strOfFinalPath ="";
//ask the system what path to use...
String strOfContextPath = context.getFilesDir() + "/";
//line above doesnt work without ' + "/" ' or something on the end
//line above records this path: /data/user/0/com.example.filesexperimenting/files/
//this appears to be an invalid path unless "user" is a hidden directory
Log.d("IDIOT", "strOfContextPath: "+ strOfContextPath);
try
{
File file = new File(strOfContextPath, nameOfFile);
if (file.exists() == false) {
file.mkdirs();
//after this line "makes dirs" is file automatically still made and dropped in?
letsMakeAfile(context, nameOfFile);
//I assume not so Ive made a recursive call
}
else
;
//escape recursion....
strOfFinalPath = file.getAbsolutePath();
//Here I record the path where I hope the file is located
Log.d("IDIOT", "strOfFinalPath: "+ strOfFinalPath);
}
catch (Exception e) {
e.printStackTrace();
Log.d("IDIOT", "CATCH ERROR: "+ e.getLocalizedMessage());
}
//runs without a catch
return strOfFinalPath;
}
}
Logcat:
2019-04-09 09:59:22.901 16819-16819/? D/IDIOT: strOfContextPath: /data/user/0/com.example.filesexperimenting/files/
2019-04-09 09:59:22.901 16819-16819/? D/IDIOT: strOfFinalPath: /data/user/0/com.example.filesexperimenting/files
Ultimately I am getting a path of /data/user/0/com.example.filesexperimenting/files/ from context.getFilesDir() which appears to be an invalid path unless "user" is a hidden directory (then why can I see root?). In Device File Explorer under data the only other directories are app, data and local
What am I missing? I'll assume its something with file.makedirs()
Full disclosure, I am a student and there is not a lot out there on this so your replies, while obvious to you at your experience level, should help others. I have some experience with Java and more with C++ but Android is new to me. Thanks in advance!
So, in talking outside of StackExchange it appears that using java.io like I am trying to in the example can cause some problems because of the preset file directories that may be locked or restricted that Java io might not know about.
Android has it's own method openFileOutput(String name, int mode) that has the ability to create the app resource file and directory it belongs in.
Description copied from class: android.content.Context
Actions:
~Open a private file associated with this Context's application package for writing.
~Creates the file if it doesn't already exist.
~No additional permissions are required for the calling app to read or write the returned file.
Params:
~name – The name of the file to open; can not contain path separators.
~mode – Operating mode.
Returns: The resulting FileOutputStream.
Throws: java.io.FileNotFoundException
If you want to be able to navigate to the location of your saved files through the file explorer (either in Android Studio or the Files app on the phone) you should use Context.getExternalFilesDir().
Context.getFilesDir() returns a directory not accessible by anyone BUT the creating application. So if you would like to see what is in this file you would need to open it with the same application that wrote it. IE: Print the contents to the screen after you save it in your app.
Context.getExternalFilesDir() returns a directory completely accessible by anyone and any application. So files created and saved in this external directory can be seen by Android Studio's file explorer as the OP has screenshot or by any application installed on the phone.
What is nice about both of these methods is that as long as you are only accessing files you have created you never need to ask the user for storage permissions Read or Write. If you would like to write to someone else's external files dir then you do.
Source
Check if sdcard is mounted or not.
if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)){
///mounted
}
Get the path of sd card
File dir= new File(android.os.Environment.getExternalStorageDirectory());
walkdir(dir);
ArrayList<String> filepath= new ArrayList<String>();
//list for storing all file paths
public void walkdir(File dir) {
File listFile[] = dir.listFiles();
if (listFile != null) {
for (int i = 0; i < listFile.length; i++) {
if (listFile[i].isDirectory()) {
// if its a directory need to get the files under that directory
walkdir(listFile[i]);
} else {
// add path of files to your arraylist for later use
//Do what ever u want
filepath.add( listFile[i].getAbsolutePath());
}
}
}
}
Try using this:
context.getFilesDir().getParentFile().getPath()
I am developing an Android application in which I need to get the specified audio file from my website when the user plays it, but I don't want to stream it or download it every time, just the first time. So I was thinking of caching it and play offline whenever the user is in need. So please suggest any method to do so. Or if exists any other method rather than caching like downloading the actual file to file storage and play whenever needed.
If you need to cache files, you should use createTempFile(). For example, the following method extracts the file name from a URL and creates a file with that name in your app's internal cache directory:
private File getTempFile(Context context, String url) {
File file;
try {
String fileName = Uri.parse(url).getLastPathSegment();
file = File.createTempFile(fileName, null,
context.getCacheDir());
} catch (IOException e) {
// Error while creating file
}
return file;
}
You can also see here for more about caching files.
https://developer.android.com/training/data-storage/files.html#WriteCacheFileInternal
Hope this will help.
I'm having a hard time figuring out what is going on with my app. Various users have reported that the app generated image files are gone. However, the database data isn't gone and it is stored at the common location Context.getDatabasePath(). Also, all folders are kept intact just images missing.
So I'm thinking there is some routine in Android causing this? Or some other app cleaning up *.png files? I know my app isn't removing them since I don't have any routine to recursively remove all image files.
Also, the parent folder has the .nomedia file so all child folders shouldn't be touched by the gallery right?
I'm storing these files inside the following path structure where %d is a unique number:
getExternalFilesDir()/projects/p_%d/l_%d/%d.png
This is how I get the projects path creates:
public static File getProjectsDir(Context context)
{
// External app directory handled by the OS. Meaning that when the app is uninstalled all
// the data inside this folder will be also removed.
File appRoot = context.getExternalFilesDir(null);
if (null == appRoot) {
Log.e(TAG,"getProjectsDir() -> External storage not accessible!");
return null;
}
File projectsDir = new File(appRoot, "projects");
// create projects directory
if (!projectsDir.exists()) {
if (!projectsDir.mkdir()) {
Log.e(TAG,"getProjectsDir() -> Unable to create projects folder!");
return null;
} else {
File noMediaFile = new File(projectsDir, ".nomedia");
if (!noMediaFile.exists()) {
try {
if (!noMediaFile.createNewFile()) {
Log.e(TAG,"getProjectsDir() -> no media file failed to be created!");
}
} catch (IOException e) {
Log.e(TAG,"getProjectsDir() -> no media file failed to be created!",e);
}
}
}
}
return projectsDir;
}
According to this, it appears that the DownloadManager deletes any files from third-party apps that haven't been accessed (i.e. "UI-Visible") in over 7 days.
The workaround would be to rename the file after it's downloaded so the DownloadManager can no longer track it.
I'm trying to populate a ListView with a mixture of files stored on the SDcard AND stored as assets in the APK. Using TraceView, I can see that the performance of AssetManager.list() is poor in comparison to File.listFiles(), even though I'm using a filename filter for the SDcard.
Here is a simple method that returns all the png files from a folder on the SDcard:
// The folder on SDcard may contain files other than png, so filter them out
private File[] getMatchingFiles(File path) {
File[] flFiles = path.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
name = name.toLowerCase();
return name.endsWith(".png");
}
});
return flFiles;
}
I invoke that method here and it takes about 12ms to retrieve 16 files:
final String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)||Environment.MEDIA_SHARED.equals(state)) {
File path = Environment.getExternalStoragePublicDirectory(getResources().getString(R.string.path_dir));
if (path.exists()){
File[] files = getMatchingFiles(path);
...
Whereas the am.list method takes 49ms to retrieve just the names of about 6 files!
// Get all filenames from specific Asset Folder and store them in String array
AssetManager am = getAssets();
String path = getResources().getString(R.string.path_dir);
String[] fileNames = am.list(path);
...
Can anyone explain why the performance would be so poor? Is the performance proportional to the number of assets stored in the APK? I'm aware that assets are compressed, but I'm only fetching the names of the assets, which I thought would be stored in a table somewhere.
Coverdriven's comment "stored in a table somewhere" inspired me to solve my own problem which I've been putting off for a while.
This doesn't answer the OP but does offer a different approach and it handles subfolders which CommonsWare's solution doesn't unless you go recursive (which of course is another possible solution). It's specifically aimed at apps which have a large number of assets in subfolders.
I added an ANT pre-build target to run this command (I'm on Windows)
dir assets /b /s /A-d > res\raw\assetfiles
This creates a recursive (/s), barebones (/b) listing of all files, excluding directory entries (/A-d) in my assets folder.
I then created this class to statically load the contents of assetfiles into a hashmap, the key of which is the filename and the value the full path
public class AssetFiles {
// create a hashmap of all files referenced in res/raw/assetfiles
/*map of all the contents of assets located in the subfolder with the name specified in FILES_ROOT
the key is the filename without path, the value is the full path relative to FILES_ROOT
includes the root, e.g. harmonics_data/subfolder/file.extension - this can be passed
directly to AssetManager.open()*/
public static HashMap<String, String> assetFiles = new HashMap<String, String>();
public static final String FILES_ROOT = "harmonics_data";
static {
String line;
String filename;
String path;
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(TidesPlannerApplication.getContext().getResources().openRawResource(R.raw.assetfiles)));
while ((line = reader.readLine()) != null) {
// NB backlash (note the escape) is specific to Windows
filename = line.substring(line.lastIndexOf("\\")+1);
path = line.substring(line.lastIndexOf(FILES_ROOT)).replaceAll("\\\\","/");;
assetFiles.put(filename, path);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static boolean exists(String filename){
return assetFiles.containsKey(filename);
}
public static String getFilename(String filename){
if (exists(filename)){
return assetFiles.get(filename);
} else {
return "";
}
}
}
To use it, I simply call AssetFiles.getFilename(filename) which returns the full path which I can pass to AssetManager.open(). Much much faster!
NB. I haven't finished this class and it's not hardened yet so you'll need to add appropriate exception catches and actions. It's also quite specific to my app in that all of my assets are in subfolders which are in turn located in a subfolder of the assets folder (see FILES_ROOT) but easy to adapt to your situation.
Note also the need to replace backslashes, since Windows generates the assetfiles listing, with forward slashes. You could eliminate this on OSX and *nix platforms.
Can anyone explain why the performance would be so poor?
Reading the contents of a ZIP archive (the APK where the assets are located) is slower than reading the contents of a directory on the filesystem, apparently. In the abstract, this is not especially surprising, as I suspect that this would be true for all major operating systems.
Read in that list() data once, then save it somewhere else for faster access (e.g., database), particularly in a form that is optimized for future lookups (e.g., where a simple database query could give you what you want, vs. having to load and "recursively search it" again).
If you have a deep tree of directories in the assets you can detect firstly if an item is file or directory and then call .list() on it (really accelerates the walking through the tree). This is my solution I've discovered for this:
try {
AssetFileDescriptor desc = getAssets().openFd(path); // Always throws exception: for directories and for files
desc.close(); // Never executes
} catch (Exception e) {
exception_message = e.toString();
}
if (exception_message.endsWith(path)) { // Exception for directory and for file has different message
// Directory
} else {
// File
}
You can approach APK package as it's a ZIP file and read all the entries using Java's builtin ZipFile. It will give you all the file names with their full paths. Perhaps it shouldn't be hard to find which directories you have.
So far this is the fastest approach I've tested.
credit goes to #obastemur's commit on jxcore-android-basics sample project
My app has .txt files in subdirectories in the assets folder. It reads those .txt files and puts them in a textview. It's working great and no problems.
Should I be concerned about the files in the assets folder getting deleted by the user or missing. If this ever could happen, my app would get an error because the file would not be there when it tried to read it into the stream.
Is there a need for me to check the existence of an asset file before I read it or does the asset manager take care of it all? I also was wondering if there's a chance that a user would or could delete and asset file.
Like I say, everything works fine without me inserting code to check for file existence. I just wondered if people use the .exists() statement every time they go to read in a stream from assets.
You may be concerned that the file have been removed and the apk resigned
You can check using:
Arrays.asList(getResources().getAssets().list("")).contains("myFile")
if you really want to check for the file existence:
AssetManager mg = getResources().getAssets();
InputStream is = null;
try {
is = mg.open(pathInAssets);
//File exists so do something with it
} catch (IOException ex) {
//file does not exist
} finally {
if (is != null) {
is.close();
}
}
If your file is located in assets/folder/file.ext, then pathInAssets would be
"folder/file.ext"
Ideally after apk is built, nobody can remove any assets from it, but if someone decompiled it and recompiles than it may be possible.
Though for other scenarios also when an asset is not present in apk at Runtime, we can check the existence of asset.
In our app, we have a provision to build app using gradle, ant and eclipse, and for each build mechanism some of our assets file are bundled in apk and some are not, so to identify if any asset file is present in current build apk at runtime,
we do this as follows:
private boolean isAssetExists(String pathInAssetsDir){
AssetManager assetManager = AppContext.get().getResources().getAssets();
InputStream inputStream = null;
try {
inputStream = assetManager.open(pathInAssetsDir);
if(null != inputStream ) {
return true;
}
} catch(IOException e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
Yes and No.
A normal user would not be able to delete them, but a user on a rooted phone who doesn't know what they're doing… that's a different situation.
If you ask me, the extra code is not needed. Also if you try and open a file that doesn't exist, you will get an exception thrown at some point, catch that and display a dialog if you really want to.
AssetManager am = getAssets();
try {
List<String> mapList = Arrays.asList(am.list("path/in/assets/folder"));
if (mapList.contains("file_to_check")) {
Log.e("ERROR", "exists");
} else {
Log.e("ERROR", "not exists");
}
} catch ( IOException ex){
ex.printStackTrace();
}
Convert to function or method can be easy ;)
I think you should be OK. From having a root around in my phone I can't see any way of deleting the assests without deleting the app as it all seems to be wrapped up in the .apk file. You can do it but I think you need to be rooted or use adb.
I would personally surround any reading/writing with a try/catch block anyway, just to be safe.