I have an app that write JSON backups and read them in production since december 2020.
Lately, without any update for this part, the behaviour of Ionic File changed.
I figured out in another thread that apps aren't allowed anymore to read and write in root directory since Android 11, so I changed it to the root/Documents directory, but I sitll have a strange issue.
Here's the behaviour: the app can write and read the file, and re-write it if necessary.
Everything's going well until I uninstall and reinstall the app, then neither read or write work.
In this case, IonicFile.readAsText returns null even if the file exists and is filled.
https://github.com/sebferrer/life-notes/blob/master/front/src/app/infra/importer-exporter.service.ts#L86
And IonicFile.writeExistingFile returns an error with the message "undefinedNaN".
https://github.com/sebferrer/life-notes/blob/master/front/src/app/infra/importer-exporter.service.ts#L146
Here's a workflow to help understand the issue:
I install the app. The file doesn't exist yet.
I try to read the file via IonicFile.readAsText: that throws the NOT_FOUND_ERR error. This is the expected behaviour as the file doesn't exist yet.
I create the file with IonicFile.createFile. Success.
I write on it via IonicFile.writeExistingFile. Success.
I write on it again via IonicFile.writeExistingFile. Success.
I read it via IonicFile.readAsText. Success: IonicFile.readAsText returns the file content.
I uninstall the app. The file created before still exists and didn't change.
I reinstall the app. The file created before still exists and didn't change.
I try to read the file as before via IonicFile.readAsText. Unexpected behaviour: no error is thrown, but IonicFile.readAsText returns null instead of the file content.
I try to write on the file as before via IonicFile.writeExistingFile. Unexpected behaviour: IonicFile.writeExistingFile throws an error this "undefinedNaN" message.
It's like when I uninstall and reinstall the app, the reinstalled app haven't any R/W right about the files generated before. But it worked perfectly before.
I'm so confused right now, any help would be more than welcome.
Edit:
It appears to be a permission issue introduced by the newer versions of Android (see comments below).
Related
I am building an Ionic 4 App + PouchDB, I have built the app by executing the command ionic cordova run android and for a few days, it works well. Recently though, the app just won't open anymore and I have no idea how to check what went wrong.
Prior to this, whenever something went wrong in the app, I am able to check it using the chrome://inspect. However, with the app not being able to open at all, it's impossible to check the cause of the problem since I am also unable to check using chrome://inspect.
When I tried to run it using ionic cordova run android -l, I get the same result, which is the app loads and closes a few moments later, and I'm still unable to get into chrome://inspect.
I had a hunch that it has something to do with PouchDB. The app starts to behave this way when the storage is almost reaching 200mb. Upon reading the PouchDB FAQ, it's stated;
In PhoneGap/Cordova, you can have unlimited data on both iOS and Android by using the SQLite Plugin.
But I am already using the SQLite Plugin for PouchDB, I have used cordova-sqlite adapter for my PouchDB too.
Below is an example of line in my code:
this._userdb = new PouchDB('user.db', { adapter: 'cordova-sqlite'});
With the app being unable to open, I'm out of ideas on how to retrieve the data stored inside PouchDB since I can't even get into the chrome://inspect at all (I can't see any console.log() for the stored data).
I feel like clearing the app data would allow the app to be opened as how it used to be but I really need to do a backup of the data stored inside PouchDB but I really have no idea how other than getting the console.log() of the data.
Is there any other way I can access the data stored inside the PouchDB to do the backup?
After many failed attempts to figure out how to retrieve the data stored inside PouchDB , I found one solution which is to pull the APK and extract the backup file.
By referring to this article, open CMD and shell into your device by;
adb shell
Provided that you know the app's package name, proceed to pull the APK by running;
adb backup -noapk com.app.your.package.name
**Note: Some devices like Samsung Galaxy stock Android 11 requires password to 'Backup my data'
Once pulled, you will find a backup.ab file and you need to extract this file. At this step, as a Windows user, I was unable to use the openssl method. I get an error using Python too.
But I found a solution that worked well for a Windows user. According to that solution;
Download Android Backup Processor
Go into the directory android-backup-tookit\android-backup-processor\executable (this directory should have a abp.jar file
Copy your backup.ab into this directory.
Open CMD, and run;
java -jar abp.jar unpack backup.ab test.tar
If the device was required password during the backup process, you will be asked to enter password. Enter the same password you provided during backup. Be sure to read the README for further details.
Once done, you will find test.tar file in the same directory. To view the file, simply extract it. The databases created inside the app should be in \test\apps\com.yourappname\f. The sqlite databases files can be viewed using DB Browser for SQLite.
So it seems that when I run my app on my phone / a phone emulator that the database file is not ending up in the correct directory over there. It seems that the app is looking for it in data/data/com.mobiletextadventure/databases. It doesn't find it there and then creates a new empty database in that directory as far as I can tell and uses that.
If I manually copy the database file to data/data/com.mobiletextadventure/databases after I have already installed the app and then run it, it picks up the database and doesn't create a fresh empty one. This is what I want obviously, but I need it to do this on initial install rather than having to manually copy it across after installing.
In my project in android studio the database is in android/assets/ folder. How do I find out where exactly this database is being copied to my phone / emulator? It's clearly not going into data/data/com.mobiletextadventure/databases, but if I can get it to install into there then my app will work fine without any manual intervention.
Another way of asking this question might be... when an app is installed to phone/emulator, what exactly happens to the files in /android/assets? Where are they copied to on the phone/emulator and why?
How do I find out where exactly this database is being copied to my phone / emulator?
It is not getting copied anywhere, unless you do the copying.
when an app is installed to phone/emulator, what exactly happens to the files in /android/assets?
Absolutely nothing. They are part of the APK. You can obtain an InputStream on an asset via AssetManager (see getAssets() on Context).
If you are seeking to package a database as an asset and use it, you can see how SQLiteAssetHelper does that.
In the end I was able to achieve it by following this post:
https://blog.reigndesign.com/blog/using-your-own-sqlite-database-in-android-applications/
I initially got an uknown error code 14 after implementing it but all I had to do was replace the checkDataBase() code with the code below and it worked perfectly:
File dbFile = myContext.getDatabasePath(DB_NAME);
return dbFile.exists();
A working app of mine started throwing warnings to logcat all of a sudden on my 4.4.4 Kitkat device:
W/ContextImpl﹕ Failed to ensure directory: /storage/emulated/0/Android/data/com.example.app/files/Pictures
All the photos and other data are unaccessable to the app.
After some digging it turns out that there's a seemingly 0 byte file in /storage/emulated/0/Android/data with the package name of my app: com.example.app. No wonder that Android can't create a directory with the same name.
I have absolutely no idea how, when and this file got created. Or better to say, how, when and why the original directory got corrupted.
The strange thing is, that even though it's listed when I call either list() or listFiles() on the data dir itself, calling exists(), isFile(), isDirectory() on the file itself will all return false.
The file seems to have no uid or gid and no date/time associated with it. It can neither be renamed nor deleted. Trying to clear the data of the app will also not remove it, nor will uninstalling the app.
What to do now? Changing the package name of the app so that a new directory can be created is obviously not an option here.
Rebooting removed the file, solving the problem. Still no idea how and why this happened though.
On first time app launch I create a file using context.getFilesDir() to get the storage path.
This works fine, except for a very small number of cases this method returns the "/" root folder. As a result the app crashes with:
java.io.FileNotFoundException: /my_filename (Read-only file system)
When I debug on my device the file path is:
/data/data/my.package/files/my_filename
This code is called in the onCreate of a SherlockActivity. So the context is that of the activity. Strangely, the failure is rare and there is no commonality among the devices where it is failing.
Update:
Based on the feedback so far, this code is supposed to work and the occasional failures may be due to odd devices. Trying to work around this issue is an overkill for my use case where file storage is not really mandatory. I'll try to migrate my code to use SharedPreferences.
There is a confirmed bug in all pre-4.4 Android devices, which occurs rarely. The reason for the bug is a racing condition in creating app private directory on first launch.
The suggested fix (by Google Android team member) is to try the Context.getFilesDir() method again after first one failed with a "null" return value.
use
Environment.getExternalStorageDirectory().getAbsolutePath();
to get the external storage directory.
use
Environment.getCacheDir();
for using the application's sandboxed cache directory.
I’m trying to create an app that is able to access and modify a protected database within /data/data/. This process obviously requires root privileges and I am testing this on a rooted device. The general code to access the SQLite database is complete and works against a test database that is located elsewhere (on /sdcard/).
However when I want the application to access the database within /data/data/, it obviously fails as I am trying to access it as a normal user. I have read on the topic of using the su binary on Android for a bit now, and as far as I understand it usually used to execute shell commands only.
So my initial idea of making this work was to simply change the permissions of the file when the application starts, and change it back when it quits. So before actually bothering with implementing that in the application itself, I used my file explorer to change the permission to rw-rw-rw-. However my application was still not able to open the database.
My next idea was to use the sqlite3 program directly from the shell, but I found out, that my ROM does not come with it, so I would have to distribute it myself (Titanium Backup seems to do that).
However there is something that makes me wonder if there might not be a better way: I am using Root Explorer as my file explorer and it has a built-in way to browse any SQLite database. Given that it does not seem to ship with a custom sqlite3 binary, and that my phone does not have one itself, the access seems to happen using the normal Java tools. But how does the app get root rights then?
Is there a way to make an Android application run as root? Or did I forget setting something for the permissions earlier which prevented me from accessing it? Or does anyone know how Root Explorer does it?
You cannot raise the permissions of an already running process as far as I know. The simplest answer would be to copy it somewhere using the root shell / command line edit it, then copy it back as root again. And yes, I did read your question, just didn't explain the answer fully. Hopefully it's clear now. Not sure if root explorer does that or something else, but it would work.