Issues with SupportSQLiteOpenHelper syntax

I am attempting to implement encryption of an Android Room Database using SupportHelper and SupportFactory classes I obtained from Github in conjunction with importing androidx.db.SupportSQLOpenHelper and androidx.sqlite.db.SupportSQLiteDatabase;

The current issue I am getting is a statement as follows:

standardHelper =
new SupportSQLiteOpenHelper(configuration.context, configuration.name,
null, configuration.callback.version, hook) { }

it generates the following error/warning

Class ‘Anynymous class derived from SupportSQLIteOpenHelper’ must either be declared abstract or implement method ‘getDatabaseName() in SupportSQLiteOpenHelper’ (note, it also looks for 3 other methods … close(), getWritableDatabase, getReadableDatabase).

How do I resolve this? Thanks in advance for your assistance


My full SupportHelper class is as follows:

import static androidx.constraintlayout.widget.Constraints.TAG;
import android.content.Context;
import android.database.sqlite.SQLiteException;
import android.util.Log;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;

public class SupportHelper implements SupportSQLiteOpenHelper
{
private SupportSQLiteOpenHelper standardHelper;
private byte passphrase;
private final boolean clearPassphrase;
private Context context;
private Context appContext;
private SupportSQLiteDatabase db;
private SupportSQLiteDatabase resultBack;
private SQLiteDatabaseHook hook;
SupportHelper(final SupportSQLiteOpenHelper.Configuration configuration,
byte passphrase, final SQLiteDatabaseHook hook,
boolean clearPassphrase) {
context = configuration.context;
this.passphrase = passphrase;
appContext = context.getApplicationContext();
net.sqlcipher.database.SQLiteDatabase.loadLibs(appContext);
this.clearPassphrase = clearPassphrase;
this.hook = hook;

    standardHelper =
            new SupportSQLiteOpenHelper(configuration.context, configuration.name,
                    null, configuration.callback.version, hook) {

                public void onCreate(SupportSQLiteDatabase db) {
                    Log.i(TAG, "CheckingInRoomDatabase SupportHelper onCreate: ");
                    configuration.callback.onCreate(db);
                    Log.i(TAG, "CheckingInRoomDatabase SupportHelper onCreate Callback: ");
                }

                public void onUpgrade(SupportSQLiteDatabase db, int oldVersion,
                                      int newVersion) {
                    Log.i(TAG, "CheckingInRoomDatabase SupportHelper onUpgrade: ");
                    configuration.callback.onUpgrade(db, oldVersion,
                            newVersion);
                }

                public void onDowngrade(SupportSQLiteDatabase db, int oldVersion,
                                        int newVersion) {
                    Log.i(TAG, "CheckingInRoomDatabase SupportHelper onDowngrade: ");
                    configuration.callback.onDowngrade(db, oldVersion,
                            newVersion);
                }

                public void onOpen(SupportSQLiteDatabase db) {
                    Log.i(TAG, "CheckingInRoomDatabase SupportHelper onOpen: ");
                    configuration.callback.onOpen(db);
                }

                public void onConfigure(SupportSQLiteDatabase db) {
                    Log.i(TAG, "CheckingInRoomDatabase SupportHelper onConfigure: ");
                    configuration.callback.onConfigure(db);
                }
            };
}

@Override
public String getDatabaseName() {

    return standardHelper.getDatabaseName();
}

@Override
public void setWriteAheadLoggingEnabled(boolean enabled) {
    Log.i(TAG, "CheckingInRoomDatabase SupportHelper setWriteAheadLoggingEnabled: ");
    //standardHelper.setWriteAheadLoggingEnabled(enabled);
    Log.i(TAG, "setWriteAheadLoggingEnabled: ");
}

@Override
public SupportSQLiteDatabase getWritableDatabase() {
    SupportSQLiteDatabase result;
    Log.i(TAG, "CheckingInRoomDatabase SupportHelper getWritableDatabase2: ");
    try {
        result = standardHelper.getWritableDatabase();
    } catch (SQLiteException ex){
        if(passphrase != null){
            boolean isCleared = true;
            for(byte b : passphrase){
                isCleared = isCleared && (b == (byte)0);
            }
            if (isCleared) {
                throw new IllegalStateException("The passphrase appears to be cleared. This happens by " +
                        "default the first time you use the factory to open a database, so we can remove the " +
                        "cleartext passphrase from memory. If you close the database yourself, please use a " +
                        "fresh SupportFactory to reopen it. If something else (e.g., Room) closed the " +
                        "database, and you cannot control that, use SupportFactory boolean constructor option " +
                        "to opt out of the automatic password clearing step. See the project README for more information.", ex);
            }
        }
        throw ex;
    }
    if (clearPassphrase && passphrase != null) {
        for (int i = 0; i < passphrase.length; i++) {
            passphrase[i] = (byte)0;
        }
    }
    resultBack = result;
    return result;
}

@Override
public SupportSQLiteDatabase getReadableDatabase() {
    Log.i(TAG, "CheckingInRoomDatabase SupportHelper getReadableDatabase: ");
    return getWritableDatabase();
}

@Override
public void close() {
    Log.i(TAG, "CheckingInRoomDatabase SupportHelper close: ");
    standardHelper.close();
}

}


My full Support factory classs is as follows

import androidx.sqlite.db.SupportSQLiteOpenHelper;

public class SupportFactory implements SupportSQLiteOpenHelper.Factory {
private final byte passphrase;
private final SQLiteDatabaseHook hook;
private final boolean clearPassphrase;

public SupportFactory(byte[] passphrase) {
    this(passphrase, (SQLiteDatabaseHook)null);
}

public SupportFactory(byte[] passphrase, SQLiteDatabaseHook hook) {
    this(passphrase, hook, true);
}

public SupportFactory(byte[] passphrase, SQLiteDatabaseHook hook,
                      boolean clearPassphrase) {
    this.passphrase = passphrase;
    this.hook = hook;
    this.clearPassphrase = clearPassphrase;
}

@Override
public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) {
    return new SupportHelper(configuration, passphrase, hook, clearPassphrase);
}

}


Hi @grgmobile,

Thank you for your interest in SQLCipher. Both SQLCipher for Android [1], and SQLCipher for Android legacy edition [2] contain SupportHelper implementations which you can use to integrate directly with the Room API.


  1. sqlcipher-android/SupportHelper.java at master · sqlcipher/sqlcipher-android · GitHub ↩︎

  2. android-database-sqlcipher/SupportHelper.java at master · sqlcipher/android-database-sqlcipher · GitHub ↩︎

1 Like

Thanks, I replaced my code with your different versions from Github.

It does not like the constructor for openHelper = new SQLiteOpenHelper(configuration.context, configuration.name, password, null, configuration.callback.version, minimumSupportedVersion, null, hook, enableWriteAheadLogging)

Hi @grgmobile,

Which SQLCipher for Android library are you using? There are examples within the GitHub repositories for usage. We would need to know if you are using the new or legacy edition of the library (i.e., sqlcipher-android, or android-database-sqlcipher respectively) in order to provide further direction.

Thees are the imports from the example you identified off GitHub. It is a new implementation, so unless there is a reason not to, I would prefer to use new libraries and not a legacy version. Thanks again for your help.

import androidx.annotation.Nullable;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;

import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteDatabaseHook;
import net.sqlcipher.database.SQLiteOpenHelper;

Hi @grgmobile,

The sqlcipher-android library [1] contains a test suite which you might find useful. An example of using the Room API with SQLCipher can be found here [2].


  1. GitHub - sqlcipher/sqlcipher-android: SQLCipher for Android provides an interface to SQLCipher databases on the Android platform. ↩︎

  2. sqlcipher-android/RoomUpsertTest.java at master · sqlcipher/sqlcipher-android · GitHub ↩︎

Thanks for your assistance. I have resolved this particular part of the implementation. My issue now is that I get an exception on the first time trying to read or write to the database.

The exception is as follows:

Caused by: android.database.sqlite.SQLiteException: file is not a database (code 26): , while compiling: SELECT COUNT(*) FROM sqlite_schema;

Attached is my “CheckingInRoomDatabase”. Do I need to add additional code to open the database as encrypted and supply the passphrase i.e. in the onOpen() method?

Blockquote
package com.grgmobilesolutions.peepsconnection;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.TypeConverters;
import androidx.sqlite.db.SupportSQLiteDatabase;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.sql.Time;
import androidx.room.Dao;
import androidx.room.Database;
import androidx.room.Delete;
import androidx.room.Entity;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.PrimaryKey;
import androidx.room.Query;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
import static androidx.constraintlayout.widget.Constraints.TAG;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SupportOpenHelperFactory;

import com.bugfender.sdk.Bugfender;

@Database(entities = {CheckInTable.class,
CommuteTable.class,
CommuteRecipientsTable.class,
CommuteRouteAddressTable.class,
CheckInRecipientsTable.class,
ContactAddressTable.class,
ContactGroupsTable.class,
ContactGroupMembershipTable.class,
ContactTable.class,
ContactTokenTable.class,
CheckInRequestTable.class,
ContactDownloadTable.class,
CheckInChatMessageTable.class,
CheckInPicturesTable.class,
ContactPreferencesTable.class,
ContactPermissionsTable.class,
CheckInRouteAddressTable.class,
CurrentLocationTable.class,
CustomGeofencePerimeterMarkerTable.class,
ContactRejectTable.class,
FirebaseUpdatesPendingTable.class},
version = 1005,
exportSchema = false)

public abstract class CheckingInRoomDatabase extends RoomDatabase {
@TypeConverters({TimestampConverter.class})
private static CheckingInRoomDatabase INSTANCE;

public abstract CheckInTableDao checkInTableDao();
public abstract ContactTableDao contactTableDao();
public abstract ContactTokenTableDao contactTokenTableDao();
public abstract CommuteTableDao commuteTableDao();
public abstract CheckInChatMessageTableDao checkInChatMessageTableDao();
public abstract CheckInPicturesTableDao checkInPicturesTableDao();
public abstract CommuteRecipientsTableDao commuteRecipientsTableDao();
public abstract CommuteRouteAddressTableDao commuteRouteAddressTableDao();
public abstract ContactAddressTableDao contactAddressTableDao();
public abstract ContactGroupMembershipTableDao contactGroupMembershipTableDao();
public abstract CheckInRecipientsTableDao checkInRecipientsTableDao();
public abstract CheckInRequestTableDao checkInRequestTableDao();
public abstract ContactGroupTableDao contactGroupTableDao();
public abstract ContactDownloadTableDao contactDownloadTableDao();
public abstract CheckInRouteAddressTableDao checkInRouteAddressTableDao();
public abstract ContactPreferencesTableDao contactPreferencesTableDao();
public abstract ContactPermissionsTableDao contactPermissionsTableDao();
public abstract ContactRejectTableDao contactRejectTableDao();
public abstract CurrentLocationTableDao currentLocationTableDao();
public abstract CustomGeofencePerimeterMarkerTableDao customGeofencePerimeterMarkerTableDao();
public abstract FirebaseUpdatesPendingTableDao firebaseUpdatesPendingTableDao();

public int i;
public Context context;
public static CheckingInRoomDatabase db;
public static File databaseFile;
static CheckingInRoomDatabase getDatabase(final Context context) {

    Log.i(TAG, "CheckingInRoomDatabase getDatabase: In Checking In Room Database");
    if (INSTANCE == null) {
        Log.i(TAG, "CheckingInRoomDatabase getDatabase Instance == null first: ");
        synchronized (CheckingInRoomDatabase.class) {
            if (INSTANCE == null) {
                //Log.i(TAG, "CheckingInRoomDatabase getDatabase Instance == null second:   ");
                char[] ch = AppConfig.PASSPHRASE;
                // Declaring a byte array
                byte[] passPhrase = new byte[ch.length];
                // Iterating over the char array
                for (int i = 0; i < ch.length; i++) {
                    // Converting each char into its byte equivalent
                    passPhrase[i] = (byte) ch[i];
                }
                if (!AppConfig.DEBUG) {
                    System.loadLibrary("sqlcipher");
                    databaseFile = context.getDatabasePath("checking_in_database.db");
                    SupportOpenHelperFactory factory = new SupportOpenHelperFactory(passPhrase);
                    INSTANCE = Room.databaseBuilder(context, CheckingInRoomDatabase.class, databaseFile.getAbsolutePath())
                            .openHelperFactory(factory)
                            .fallbackToDestructiveMigration()
                            .addCallback(sRoomDatabaseCallback)
                            .build();
                    //INSTANCE.clearAllTables();

                }
                else {
                    INSTANCE =
                            Room.databaseBuilder(context,
                                            CheckingInRoomDatabase.class,
                                            "checking_in_database.db")
                                    .fallbackToDestructiveMigration()
                                    .addCallback(sRoomDatabaseCallback)
                                    .build();
                }

            }
        }

        Log.i(TAG, "CheckingInRoomDatabase getDatabase: database being populated");
    }
    Log.i(TAG, "CheckingInRoomDatabase getDatabase database returned: ");
    return INSTANCE;

}

private static RoomDatabase.Callback sRoomDatabaseCallback =
        new RoomDatabase.Callback() {

            @Override
            public void onOpen(@NonNull SupportSQLiteDatabase db) {
                super.onOpen(db);
                Log.i(TAG, "CheckingInRoomDatabase onOpen: in Room Database CallBack");
            }
            @Override
            public void onCreate(@NonNull SupportSQLiteDatabase db) {
                super.onCreate(db);
                Log.i(TAG, "CheckingInRoomDatabase onCreate: in Room Database CallBack");
            }
        };

}

Blockquote

Hi @grgmobile,

  • Does the database file exist?
  • Is the database file already encrypted when you receive this error?

You can test whether you can access the database file with the password using a static method on the SQLiteDatabase class if you’d prefer to sidestep Room for this situation.