denzilferreira / aware-client

AWARE's Android client. This application allows researchers to easily collect sensor/plugin data locally or remotely to an AWARE server instance.
https://awareframework.com
88 stars 78 forks source link

SQLiteCantOpenDatabaseException - Android 10 #276

Open ChristopherBull opened 4 years ago

ChristopherBull commented 4 years ago

I am experiencing app crashes on every app startup (OnePlus 7t Pro & Google Pixel 3a, both Android 10). The app is failing to create (and therefore not load) the AWARE databases. On fresh app installs (uninstall the app and delete the AWARE folder), the app crashes immediately after AWARE requests contacts and storage permissions.

Development context/background:

Reproducing the issue:

Stacktrace

Logcat stacktrace: stack.txt

This is a stacktrace of the OnePlus 7T Pro (Android 10), with a fresh install of my app, and checked that the AWARE folder did not exist before (and still does not after the app crashed).

Relevant error stacktraces start at: 2019-10-21 14:50:28.066

I have copied what I believe to be the pertinent errors from Logcat, to give you somewhere to jump to in the trace file.

2019-10-21 14:50:28.069 31891-31891/? E/SQLiteLog: (14) cannot open file at line 36683 of [c255889bd9] 2019-10-21 14:50:28.069 31891-31891/? E/SQLiteLog: (14) os_unix.c:36683: (2) open(/storage/emulated/0/AWARE/aware.db) - 2019-10-21 14:50:28.069 31891-31891/? E/SQLiteDatabase: Failed to open database '/storage/emulated/0/AWARE/aware.db'. android.database.sqlite.SQLiteCantOpenDatabaseException: unknown error (code 14 SQLITE_CANTOPEN): Could not open database

Main crash occurs here:

--------- beginning of crash 2019-10-21 14:50:28.299 31891-31891/? E/AndroidRuntime: FATAL EXCEPTION: main Process: uk.ac.lancaster.spaceapp, PID: 31891 java.lang.RuntimeException: Unable to start service com.aware.utils.Scheduler@cbacc2c with Intent { cmp=uk.ac.lancaster.spaceapp/com.aware.utils.Scheduler }: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.database.sqlite.SQLiteDatabase.validateSql(java.lang.String, android.os.CancellationSignal)' on a null object reference at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:4222) at android.app.ActivityThread.access$2100(ActivityThread.java:231) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1984) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7682) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:516) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950) Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.database.sqlite.SQLiteDatabase.validateSql(java.lang.String, android.os.CancellationSignal)' on a null object reference at android.database.sqlite.SQLiteQueryBuilder.query(SQLiteQueryBuilder.java:496) at android.database.sqlite.SQLiteQueryBuilder.query(SQLiteQueryBuilder.java:392) at com.aware.providers.Scheduler_Provider.query(Scheduler_Provider.java:198) at android.content.ContentProvider.query(ContentProvider.java:1276) at android.content.ContentProvider.query(ContentProvider.java:1369) at android.content.ContentProvider$Transport.query(ContentProvider.java:275) at android.content.ContentResolver.query(ContentResolver.java:962) at android.content.ContentResolver.query(ContentResolver.java:890) at android.content.ContentResolver.query(ContentResolver.java:846) at com.aware.utils.Scheduler.onStartCommand(Scheduler.java:635) at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:4204) at android.app.ActivityThread.access$2100(ActivityThread.java:231)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1984)  at android.os.Handler.dispatchMessage(Handler.java:107)  at android.os.Looper.loop(Looper.java:214)  at android.app.ActivityThread.main(ActivityThread.java:7682)  at java.lang.reflect.Method.invoke(Native Method)  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:516)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950) 

Note: All devices (Android 9 and 10) spew out the first batch of SQLiteCantOpenDatabaseException errors during the first app load after a fresh install, but presumably the applications that launch successfully (Android 9) are able to eventually create the AWARE DBs.

ChristopherBull commented 4 years ago

I tried digging for more useful info, but was not very successful.

I grabbed a more succinct stacktrace: stack.txt

Two lines that stood out, just before all the Failed to open database exceptions:

E/Perf: Fail to get file list uk.ac.lancaster.spaceapp
E/Perf: getFolderSize() : Exception_1 = java.lang.NullPointerException: Attempt to get length of null array

I'm not sure why it is failing with file operations (double-checked storage permissions were given). I couldn't find getFolderSize() in the AWARE codebase. Perhaps it is an Aware dependecy?

nleo575 commented 4 years ago

@denzilferreira This issue could be directly related to the contacts plugin. I filed issue #275. Has there been any progress made with either of these issues?

ChristopherBull commented 4 years ago

I have figured out the problem I was facing. The solution was simple in the end, but not documented.

You need to add this file res/values/bools.xml, with the following contents:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item name="standalone" type="bool" format="boolean">true</item>
</resources>

Whilst I am happy with own implementation, some changes need to be made before I would consider this resolved as the default behaviour crashes on Android 10 devices. It would be great to:

  1. Document the booleans used in bools.xml somewhere. I have checked and there is no mention of standalone boolean anywhere in this repo's README.md, nor awareframework.com
  2. Use better defaults for Android 10, so the default behaviour is not to crash.

I was reading the source (following the stacktraces I provided) and centred on the following method:

/* com.aware.utils.DatabaseHelper.java */
private synchronized SQLiteDatabase getDatabaseFile() {
    try {
        File aware_folder;
        if (mContext.getResources().getBoolean(R.bool.internalstorage)) {
            // Internal storage.  This is not acceassible to any other apps and is removed once
            // app is uninstalled.  Plugins can't use it.  Hard-coded to off, only change if
            // you know what you are doing.  Beware!
            aware_folder = mContext.getFilesDir();
        } else if (!mContext.getResources().getBoolean(R.bool.standalone)) {
            // sdcard/AWARE/ (shareable, does not delete when uninstalling)
            aware_folder = new File(Environment.getExternalStoragePublicDirectory("AWARE").toString());
        } else {
            if (isEmulator()) {
                aware_folder = mContext.getFilesDir();
            } else {
                // sdcard/Android/<app_package_name>/AWARE/ (not shareable, deletes when uninstalling package)
                aware_folder = new File(ContextCompat.getExternalFilesDirs(mContext, null)[0] + "/AWARE");
            }
        }

        if (!aware_folder.exists()) {
            aware_folder.mkdirs();
        }

        database = SQLiteDatabase.openOrCreateDatabase(new File(aware_folder, this.databaseName).getPath(), this.cursorFactory);
        return database;
    } catch (SQLiteException e) {
        return null;
    }
}

It makes reference to where files/folders are to be stored/read. I checked my Android 9 devices' filesystem and found the AWARE folder in Internal storage/AWARE/ (I did not have a bools.xml file, so it used default values, presumably). On Android 10, it was not in any of the suggested locations (and crashes). After setting the booleans in bools.xml (as mentioned above), the app no longer crashes and the files appear in Internal storage/Android/data/<app_package_name>/files/AWARE/.

I have not extensively tested on a matrix of various devices and Android versions, but doing this fixes the problem I was facing.

It is possible it has something to do with Privacy changes in Android 10

TylerADavis commented 3 years ago

Thanks @ChristopherBull ! I was running into this issue as well, and adding the file you mentioned resolved the errors I was getting related to being unable to open the SQLite db.

I ran into this issue while running the android simulator targeting API 30.