From 04cef1afe4039d8603c1aa7d40970e0aa76bbe55 Mon Sep 17 00:00:00 2001 From: k3b <1374583+k3b@users.noreply.github.com> Date: Fri, 11 Dec 2020 12:58:29 +0100 Subject: [PATCH] #155: Fix crash if missing file permissions --- app/build.gradle | 2 +- .../androFotoFinder/AndroFotoFinderApp.java | 83 +++++++++++++++---- .../queries/DatabaseHelper.java | 19 ++++- .../queries/MediaRepositoryApiWrapper.java | 46 ++++++---- 4 files changed, 112 insertions(+), 38 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index bfdf146d..6b0ee2c1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,7 +19,7 @@ android { minSdkVersion 14 // Android 4.0 Ice Cream Sandwich (API 14); Android 4.4 KitKat (API 19); Android 5.0 Lollipop (API 21); // Android 6.0 Marshmallow (API 23); Android 7.0 Nougat (API 24) - maxSdkVersion 28 // #155: android-10=api29 + // maxSdkVersion 28 // #155: android-10=api29 targetSdkVersion 28 diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java index 8706ffba..943993cd 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java @@ -48,6 +48,7 @@ import de.k3b.android.androFotoFinder.queries.MediaContentproviderRepository; import de.k3b.android.androFotoFinder.queries.MediaContentproviderRepositoryImpl; import de.k3b.android.androFotoFinder.queries.MediaDBRepository; +import de.k3b.android.androFotoFinder.queries.MediaRepositoryApiWrapper; import de.k3b.android.androFotoFinder.queries.MergedMediaRepository; import de.k3b.android.io.AndroidFileFacade; import de.k3b.android.io.DocumentFileTranslator; @@ -106,31 +107,77 @@ public static void setMediaImageDbReplacement(Context context, boolean useMediaI Global.useAo10MediaImageDbReplacement = useMediaImageDbReplacement; - final MediaContentproviderRepository mediaContentproviderRepository = new MediaContentproviderRepository(context); if (Global.useAo10MediaImageDbReplacement) { - final SQLiteDatabase writableDatabase = DatabaseHelper.getWritableDatabase(context); - final MediaDBRepository mediaDBRepository = new MediaDBRepository(writableDatabase); - FotoSql.setMediaDBApi(new MergedMediaRepository(mediaDBRepository, mediaContentproviderRepository)); + registerAo10MediaImageDbReplacement(context); + } else { + registerMediaContentProvider(context, oldMediaDBApi); + } + } + } - MediaContent2DBUpdateService.instance = new MediaContent2DBUpdateService(context, writableDatabase); + private static void registerMediaContentProvider(Context context, IMediaRepositoryApi oldMediaDBApi) { + final MediaContentproviderRepository mediaContentproviderRepository = new MediaContentproviderRepository(context); + PhotoChangeNotifyer.unregisterContentObserver(context, GlobalMediaContentObserver.getInstance(context)); + if ((oldMediaDBApi != null) && (MediaContent2DBUpdateService.instance != null)) { + // switching from mediaImageDbReplacement to Contentprovider + MediaContent2DBUpdateService.instance.clearMediaCopy(); + } + FotoSql.setMediaDBApi(mediaContentproviderRepository); + MediaContent2DBUpdateService.instance = null; + } - if (FotoSql.getCount(new QueryParameter().addWhere("1 = 1")) == 0) { - // database is empty; reload from Contentprovider - MediaContent2DBUpdateService.instance.rebuild(context, null); - } + /** + * Android-10-ff use copy of media database for reading to circumvent android-10-media-contentprovider-restrictions + */ + private static IMediaRepositoryApi registerAo10MediaImageDbReplacement(Context context) { + File databaseFile = DatabaseHelper.getDatabasePath(context); + try { + final SQLiteDatabase writableDatabase = DatabaseHelper.getWritableDatabase(context); + //!!! throws SQLiteCantOpenDatabaseException("Failed to open database '/storage/emulated/0/databases/APhotoManager.db'") if no permission - PhotoChangeNotifyer.registerContentObserver(context, GlobalMediaContentObserver.getInstance(context)); + final MediaDBRepository mediaDBRepository = new MediaDBRepository(writableDatabase); + final MediaContentproviderRepository mediaContentproviderRepository = new MediaContentproviderRepository(context); - } else { - PhotoChangeNotifyer.unregisterContentObserver(context, GlobalMediaContentObserver.getInstance(context)); - if ((oldMediaDBApi != null) && (MediaContent2DBUpdateService.instance != null)) { - // switching from mediaImageDbReplacement to Contentprovider - MediaContent2DBUpdateService.instance.clearMediaCopy(); - } - FotoSql.setMediaDBApi(mediaContentproviderRepository); - MediaContent2DBUpdateService.instance = null; + // read from copy database, write to both: copy-database and content-provider + final MergedMediaRepository mediaDBApi = new MergedMediaRepository(mediaDBRepository, mediaContentproviderRepository); + FotoSql.setMediaDBApi(mediaDBApi); + + MediaContent2DBUpdateService.instance = new MediaContent2DBUpdateService(context, writableDatabase); + + if (FotoSql.getCount(new QueryParameter().addWhere("1 = 1")) == 0) { + // database is empty; reload from Contentprovider + MediaContent2DBUpdateService.instance.rebuild(context, null); } + + PhotoChangeNotifyer.registerContentObserver(context, GlobalMediaContentObserver.getInstance(context)); + return mediaDBApi; + } catch (RuntimeException ignore) { + Log.w(Global.LOG_CONTEXT, + "Cannot open Database (missing permissions) " + + DatabaseHelper.getDatabasePath(context) + " " + + ignore.getMessage(), ignore); + FotoSql.setMediaDBApi(new MediaDBRepositoryLoadOnDemand(context)); + } + return null; + } + + /** + * if Open Database failes because of missing File permissions + * postpone opening database until permission is granted + */ + private static class MediaDBRepositoryLoadOnDemand extends MediaRepositoryApiWrapper { + + private final Context context; + + public MediaDBRepositoryLoadOnDemand(Context context) { + super(null); + this.context = context; + } + + @Override + protected IMediaRepositoryApi getReadChild() { + return registerAo10MediaImageDbReplacement(context); } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/DatabaseHelper.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/DatabaseHelper.java index 62daffd6..14653642 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/DatabaseHelper.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/DatabaseHelper.java @@ -24,6 +24,8 @@ import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; +import java.io.File; + import de.k3b.android.androFotoFinder.transactionlog.TransactionLogSql; import de.k3b.android.util.DatabaseContext; @@ -39,20 +41,33 @@ public class DatabaseHelper extends SQLiteOpenHelper { public static final int DATABASE_VERSION_2_MEDIA_DB_COPY = 2; public static final int DATABASE_VERSION = DatabaseHelper.DATABASE_VERSION_2_MEDIA_DB_COPY; + public static final String DATABASE_NAME = "APhotoManager"; private static DatabaseHelper instance = null; + private static DatabaseContext databaseContext = null; public DatabaseHelper(final Context context, final String databaseName) { super(context, databaseName, null, DatabaseHelper.DATABASE_VERSION); } public static SQLiteDatabase getWritableDatabase(Context context) { + return getInstance(context).getWritableDatabase(); + } + + public static File getDatabasePath(Context context) { + getInstance(context); + return databaseContext.getDatabasePath(DATABASE_NAME); + } + + private static DatabaseHelper getInstance(Context context) { if (instance == null) { - instance = new DatabaseHelper(new DatabaseContext(context), "APhotoManager"); + databaseContext = new DatabaseContext(context); + instance = new DatabaseHelper(databaseContext, DATABASE_NAME); } - return instance.getWritableDatabase(); + return instance; } + public static void version2Upgrade_RecreateMediDbCopy(final SQLiteDatabase db) { for (String sql : MediaDBRepository.Impl.DDL) { db.execSQL(sql); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaRepositoryApiWrapper.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaRepositoryApiWrapper.java index 702de338..0530b615 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaRepositoryApiWrapper.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaRepositoryApiWrapper.java @@ -33,9 +33,9 @@ * Created by k3b on 30.11.2019. */ public class MediaRepositoryApiWrapper implements IMediaRepositoryApi { - protected final IMediaRepositoryApi readChild; - protected final IMediaRepositoryApi writeChild; - protected final IMediaRepositoryApi transactionChild; + private final IMediaRepositoryApi readChild; + private final IMediaRepositoryApi writeChild; + private final IMediaRepositoryApi transactionChild; /** * count the non path write calls @@ -54,27 +54,27 @@ public MediaRepositoryApiWrapper(IMediaRepositoryApi readChild, IMediaRepository @Override public Cursor createCursorForQuery(StringBuilder out_debugMessage, String dbgContext, QueryParameter parameters, VISIBILITY visibility, CancellationSignal cancellationSignal) { - return readChild.createCursorForQuery(out_debugMessage, dbgContext, parameters, visibility, cancellationSignal); + return getReadChild().createCursorForQuery(out_debugMessage, dbgContext, parameters, visibility, cancellationSignal); } @Override public Cursor createCursorForQuery(StringBuilder out_debugMessage, String dbgContext, String from, String sqlWhereStatement, String[] sqlWhereParameters, String sqlSortOrder, CancellationSignal cancellationSignal, String... sqlSelectColums) { - return readChild.createCursorForQuery(out_debugMessage, dbgContext, from, sqlWhereStatement, sqlWhereParameters, sqlSortOrder, cancellationSignal, sqlSelectColums); + return getReadChild().createCursorForQuery(out_debugMessage, dbgContext, from, sqlWhereStatement, sqlWhereParameters, sqlSortOrder, cancellationSignal, sqlSelectColums); } @Override public int execUpdate(String dbgContext, long id, ContentValues values) { - return writeChild.execUpdate(dbgContext, id, values); + return getWriteChild().execUpdate(dbgContext, id, values); } @Override public int execUpdate(String dbgContext, String path, ContentValues values, VISIBILITY visibility) { - return writeChild.execUpdate(dbgContext, path, values, visibility); + return getWriteChild().execUpdate(dbgContext, path, values, visibility); } @Override public int exexUpdateImpl(String dbgContext, ContentValues values, String sqlWhere, String[] selectionArgs) { - return writeChild.exexUpdateImpl(dbgContext, values, sqlWhere, selectionArgs); + return getWriteChild().exexUpdateImpl(dbgContext, values, sqlWhere, selectionArgs); } /** @@ -88,7 +88,7 @@ public int exexUpdateImpl(String dbgContext, ContentValues values, String sqlWhe */ @Override public Long insertOrUpdateMediaDatabase(String dbgContext, String dbUpdateFilterJpgFullPathName, ContentValues values, VISIBILITY visibility, Long updateSuccessValue) { - return writeChild.insertOrUpdateMediaDatabase(dbgContext, dbUpdateFilterJpgFullPathName, values, visibility, updateSuccessValue); + return getWriteChild().insertOrUpdateMediaDatabase(dbgContext, dbUpdateFilterJpgFullPathName, values, visibility, updateSuccessValue); } /** @@ -99,7 +99,7 @@ public Long insertOrUpdateMediaDatabase(String dbgContext, String dbUpdateFilter */ @Override public Uri execInsert(String dbgContext, ContentValues values) { - return writeChild.execInsert(dbgContext, values); + return getWriteChild().execInsert(dbgContext, values); } /** @@ -107,36 +107,48 @@ public Uri execInsert(String dbgContext, ContentValues values) { */ @Override public int deleteMedia(String dbgContext, String where, String[] selectionArgs, boolean preventDeleteImageFile) { - return writeChild.deleteMedia(dbgContext, where, selectionArgs, preventDeleteImageFile); + return getWriteChild().deleteMedia(dbgContext, where, selectionArgs, preventDeleteImageFile); } @Override public ContentValues getDbContent(long id) { - return readChild.getDbContent(id); + return getReadChild().getDbContent(id); } @Override public long getCurrentUpdateId() { - return transactionChild.getCurrentUpdateId(); + return getTransactionChild().getCurrentUpdateId(); } @Override public boolean mustRequery(long updateId) { - return transactionChild.mustRequery(updateId); + return getTransactionChild().mustRequery(updateId); } @Override public void beginTransaction() { - transactionChild.beginTransaction(); + getTransactionChild().beginTransaction(); } @Override public void setTransactionSuccessful() { - transactionChild.setTransactionSuccessful(); + getTransactionChild().setTransactionSuccessful(); } @Override public void endTransaction() { - transactionChild.endTransaction(); + getTransactionChild().endTransaction(); + } + + protected IMediaRepositoryApi getReadChild() { + return readChild; + } + + protected IMediaRepositoryApi getWriteChild() { + return writeChild; + } + + protected IMediaRepositoryApi getTransactionChild() { + return transactionChild; } }