Skip to content

Commit 04cef1a

Browse files
committed
#155: Fix crash if missing file permissions
1 parent 461b889 commit 04cef1a

File tree

4 files changed

+112
-38
lines changed

4 files changed

+112
-38
lines changed

Diff for: app/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ android {
1919

2020
minSdkVersion 14 // Android 4.0 Ice Cream Sandwich (API 14); Android 4.4 KitKat (API 19); Android 5.0 Lollipop (API 21);
2121
// Android 6.0 Marshmallow (API 23); Android 7.0 Nougat (API 24)
22-
maxSdkVersion 28 // #155: android-10=api29
22+
// maxSdkVersion 28 // #155: android-10=api29
2323

2424
targetSdkVersion 28
2525

Diff for: app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java

+65-18
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import de.k3b.android.androFotoFinder.queries.MediaContentproviderRepository;
4949
import de.k3b.android.androFotoFinder.queries.MediaContentproviderRepositoryImpl;
5050
import de.k3b.android.androFotoFinder.queries.MediaDBRepository;
51+
import de.k3b.android.androFotoFinder.queries.MediaRepositoryApiWrapper;
5152
import de.k3b.android.androFotoFinder.queries.MergedMediaRepository;
5253
import de.k3b.android.io.AndroidFileFacade;
5354
import de.k3b.android.io.DocumentFileTranslator;
@@ -106,31 +107,77 @@ public static void setMediaImageDbReplacement(Context context, boolean useMediaI
106107

107108
Global.useAo10MediaImageDbReplacement = useMediaImageDbReplacement;
108109

109-
final MediaContentproviderRepository mediaContentproviderRepository = new MediaContentproviderRepository(context);
110110

111111
if (Global.useAo10MediaImageDbReplacement) {
112-
final SQLiteDatabase writableDatabase = DatabaseHelper.getWritableDatabase(context);
113-
final MediaDBRepository mediaDBRepository = new MediaDBRepository(writableDatabase);
114-
FotoSql.setMediaDBApi(new MergedMediaRepository(mediaDBRepository, mediaContentproviderRepository));
112+
registerAo10MediaImageDbReplacement(context);
113+
} else {
114+
registerMediaContentProvider(context, oldMediaDBApi);
115+
}
116+
}
117+
}
115118

116-
MediaContent2DBUpdateService.instance = new MediaContent2DBUpdateService(context, writableDatabase);
119+
private static void registerMediaContentProvider(Context context, IMediaRepositoryApi oldMediaDBApi) {
120+
final MediaContentproviderRepository mediaContentproviderRepository = new MediaContentproviderRepository(context);
121+
PhotoChangeNotifyer.unregisterContentObserver(context, GlobalMediaContentObserver.getInstance(context));
122+
if ((oldMediaDBApi != null) && (MediaContent2DBUpdateService.instance != null)) {
123+
// switching from mediaImageDbReplacement to Contentprovider
124+
MediaContent2DBUpdateService.instance.clearMediaCopy();
125+
}
126+
FotoSql.setMediaDBApi(mediaContentproviderRepository);
127+
MediaContent2DBUpdateService.instance = null;
128+
}
117129

118-
if (FotoSql.getCount(new QueryParameter().addWhere("1 = 1")) == 0) {
119-
// database is empty; reload from Contentprovider
120-
MediaContent2DBUpdateService.instance.rebuild(context, null);
121-
}
130+
/**
131+
* Android-10-ff use copy of media database for reading to circumvent android-10-media-contentprovider-restrictions
132+
*/
133+
private static IMediaRepositoryApi registerAo10MediaImageDbReplacement(Context context) {
134+
File databaseFile = DatabaseHelper.getDatabasePath(context);
135+
try {
136+
final SQLiteDatabase writableDatabase = DatabaseHelper.getWritableDatabase(context);
137+
//!!! throws SQLiteCantOpenDatabaseException("Failed to open database '/storage/emulated/0/databases/APhotoManager.db'") if no permission
122138

123-
PhotoChangeNotifyer.registerContentObserver(context, GlobalMediaContentObserver.getInstance(context));
139+
final MediaDBRepository mediaDBRepository = new MediaDBRepository(writableDatabase);
140+
final MediaContentproviderRepository mediaContentproviderRepository = new MediaContentproviderRepository(context);
124141

125-
} else {
126-
PhotoChangeNotifyer.unregisterContentObserver(context, GlobalMediaContentObserver.getInstance(context));
127-
if ((oldMediaDBApi != null) && (MediaContent2DBUpdateService.instance != null)) {
128-
// switching from mediaImageDbReplacement to Contentprovider
129-
MediaContent2DBUpdateService.instance.clearMediaCopy();
130-
}
131-
FotoSql.setMediaDBApi(mediaContentproviderRepository);
132-
MediaContent2DBUpdateService.instance = null;
142+
// read from copy database, write to both: copy-database and content-provider
143+
final MergedMediaRepository mediaDBApi = new MergedMediaRepository(mediaDBRepository, mediaContentproviderRepository);
144+
FotoSql.setMediaDBApi(mediaDBApi);
145+
146+
MediaContent2DBUpdateService.instance = new MediaContent2DBUpdateService(context, writableDatabase);
147+
148+
if (FotoSql.getCount(new QueryParameter().addWhere("1 = 1")) == 0) {
149+
// database is empty; reload from Contentprovider
150+
MediaContent2DBUpdateService.instance.rebuild(context, null);
133151
}
152+
153+
PhotoChangeNotifyer.registerContentObserver(context, GlobalMediaContentObserver.getInstance(context));
154+
return mediaDBApi;
155+
} catch (RuntimeException ignore) {
156+
Log.w(Global.LOG_CONTEXT,
157+
"Cannot open Database (missing permissions) "
158+
+ DatabaseHelper.getDatabasePath(context) + " "
159+
+ ignore.getMessage(), ignore);
160+
FotoSql.setMediaDBApi(new MediaDBRepositoryLoadOnDemand(context));
161+
}
162+
return null;
163+
}
164+
165+
/**
166+
* if Open Database failes because of missing File permissions
167+
* postpone opening database until permission is granted
168+
*/
169+
private static class MediaDBRepositoryLoadOnDemand extends MediaRepositoryApiWrapper {
170+
171+
private final Context context;
172+
173+
public MediaDBRepositoryLoadOnDemand(Context context) {
174+
super(null);
175+
this.context = context;
176+
}
177+
178+
@Override
179+
protected IMediaRepositoryApi getReadChild() {
180+
return registerAo10MediaImageDbReplacement(context);
134181
}
135182
}
136183

Diff for: app/src/main/java/de/k3b/android/androFotoFinder/queries/DatabaseHelper.java

+17-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import android.database.sqlite.SQLiteOpenHelper;
2525
import android.util.Log;
2626

27+
import java.io.File;
28+
2729
import de.k3b.android.androFotoFinder.transactionlog.TransactionLogSql;
2830
import de.k3b.android.util.DatabaseContext;
2931

@@ -39,20 +41,33 @@ public class DatabaseHelper extends SQLiteOpenHelper {
3941
public static final int DATABASE_VERSION_2_MEDIA_DB_COPY = 2;
4042

4143
public static final int DATABASE_VERSION = DatabaseHelper.DATABASE_VERSION_2_MEDIA_DB_COPY;
44+
public static final String DATABASE_NAME = "APhotoManager";
4245

4346
private static DatabaseHelper instance = null;
47+
private static DatabaseContext databaseContext = null;
4448

4549
public DatabaseHelper(final Context context, final String databaseName) {
4650
super(context, databaseName, null, DatabaseHelper.DATABASE_VERSION);
4751
}
4852

4953
public static SQLiteDatabase getWritableDatabase(Context context) {
54+
return getInstance(context).getWritableDatabase();
55+
}
56+
57+
public static File getDatabasePath(Context context) {
58+
getInstance(context);
59+
return databaseContext.getDatabasePath(DATABASE_NAME);
60+
}
61+
62+
private static DatabaseHelper getInstance(Context context) {
5063
if (instance == null) {
51-
instance = new DatabaseHelper(new DatabaseContext(context), "APhotoManager");
64+
databaseContext = new DatabaseContext(context);
65+
instance = new DatabaseHelper(databaseContext, DATABASE_NAME);
5266
}
53-
return instance.getWritableDatabase();
67+
return instance;
5468
}
5569

70+
5671
public static void version2Upgrade_RecreateMediDbCopy(final SQLiteDatabase db) {
5772
for (String sql : MediaDBRepository.Impl.DDL) {
5873
db.execSQL(sql);

Diff for: app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaRepositoryApiWrapper.java

+29-17
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@
3333
* Created by k3b on 30.11.2019.
3434
*/
3535
public class MediaRepositoryApiWrapper implements IMediaRepositoryApi {
36-
protected final IMediaRepositoryApi readChild;
37-
protected final IMediaRepositoryApi writeChild;
38-
protected final IMediaRepositoryApi transactionChild;
36+
private final IMediaRepositoryApi readChild;
37+
private final IMediaRepositoryApi writeChild;
38+
private final IMediaRepositoryApi transactionChild;
3939

4040
/**
4141
* count the non path write calls
@@ -54,27 +54,27 @@ public MediaRepositoryApiWrapper(IMediaRepositoryApi readChild, IMediaRepository
5454

5555
@Override
5656
public Cursor createCursorForQuery(StringBuilder out_debugMessage, String dbgContext, QueryParameter parameters, VISIBILITY visibility, CancellationSignal cancellationSignal) {
57-
return readChild.createCursorForQuery(out_debugMessage, dbgContext, parameters, visibility, cancellationSignal);
57+
return getReadChild().createCursorForQuery(out_debugMessage, dbgContext, parameters, visibility, cancellationSignal);
5858
}
5959

6060
@Override
6161
public Cursor createCursorForQuery(StringBuilder out_debugMessage, String dbgContext, String from, String sqlWhereStatement, String[] sqlWhereParameters, String sqlSortOrder, CancellationSignal cancellationSignal, String... sqlSelectColums) {
62-
return readChild.createCursorForQuery(out_debugMessage, dbgContext, from, sqlWhereStatement, sqlWhereParameters, sqlSortOrder, cancellationSignal, sqlSelectColums);
62+
return getReadChild().createCursorForQuery(out_debugMessage, dbgContext, from, sqlWhereStatement, sqlWhereParameters, sqlSortOrder, cancellationSignal, sqlSelectColums);
6363
}
6464

6565
@Override
6666
public int execUpdate(String dbgContext, long id, ContentValues values) {
67-
return writeChild.execUpdate(dbgContext, id, values);
67+
return getWriteChild().execUpdate(dbgContext, id, values);
6868
}
6969

7070
@Override
7171
public int execUpdate(String dbgContext, String path, ContentValues values, VISIBILITY visibility) {
72-
return writeChild.execUpdate(dbgContext, path, values, visibility);
72+
return getWriteChild().execUpdate(dbgContext, path, values, visibility);
7373
}
7474

7575
@Override
7676
public int exexUpdateImpl(String dbgContext, ContentValues values, String sqlWhere, String[] selectionArgs) {
77-
return writeChild.exexUpdateImpl(dbgContext, values, sqlWhere, selectionArgs);
77+
return getWriteChild().exexUpdateImpl(dbgContext, values, sqlWhere, selectionArgs);
7878
}
7979

8080
/**
@@ -88,7 +88,7 @@ public int exexUpdateImpl(String dbgContext, ContentValues values, String sqlWhe
8888
*/
8989
@Override
9090
public Long insertOrUpdateMediaDatabase(String dbgContext, String dbUpdateFilterJpgFullPathName, ContentValues values, VISIBILITY visibility, Long updateSuccessValue) {
91-
return writeChild.insertOrUpdateMediaDatabase(dbgContext, dbUpdateFilterJpgFullPathName, values, visibility, updateSuccessValue);
91+
return getWriteChild().insertOrUpdateMediaDatabase(dbgContext, dbUpdateFilterJpgFullPathName, values, visibility, updateSuccessValue);
9292
}
9393

9494
/**
@@ -99,44 +99,56 @@ public Long insertOrUpdateMediaDatabase(String dbgContext, String dbUpdateFilter
9999
*/
100100
@Override
101101
public Uri execInsert(String dbgContext, ContentValues values) {
102-
return writeChild.execInsert(dbgContext, values);
102+
return getWriteChild().execInsert(dbgContext, values);
103103
}
104104

105105
/**
106106
* Deletes media items specified by where with the option to prevent cascade delete of the image.
107107
*/
108108
@Override
109109
public int deleteMedia(String dbgContext, String where, String[] selectionArgs, boolean preventDeleteImageFile) {
110-
return writeChild.deleteMedia(dbgContext, where, selectionArgs, preventDeleteImageFile);
110+
return getWriteChild().deleteMedia(dbgContext, where, selectionArgs, preventDeleteImageFile);
111111
}
112112

113113
@Override
114114
public ContentValues getDbContent(long id) {
115-
return readChild.getDbContent(id);
115+
return getReadChild().getDbContent(id);
116116
}
117117

118118
@Override
119119
public long getCurrentUpdateId() {
120-
return transactionChild.getCurrentUpdateId();
120+
return getTransactionChild().getCurrentUpdateId();
121121
}
122122

123123
@Override
124124
public boolean mustRequery(long updateId) {
125-
return transactionChild.mustRequery(updateId);
125+
return getTransactionChild().mustRequery(updateId);
126126
}
127127

128128
@Override
129129
public void beginTransaction() {
130-
transactionChild.beginTransaction();
130+
getTransactionChild().beginTransaction();
131131
}
132132

133133
@Override
134134
public void setTransactionSuccessful() {
135-
transactionChild.setTransactionSuccessful();
135+
getTransactionChild().setTransactionSuccessful();
136136
}
137137

138138
@Override
139139
public void endTransaction() {
140-
transactionChild.endTransaction();
140+
getTransactionChild().endTransaction();
141+
}
142+
143+
protected IMediaRepositoryApi getReadChild() {
144+
return readChild;
145+
}
146+
147+
protected IMediaRepositoryApi getWriteChild() {
148+
return writeChild;
149+
}
150+
151+
protected IMediaRepositoryApi getTransactionChild() {
152+
return transactionChild;
141153
}
142154
}

0 commit comments

Comments
 (0)