1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package androidx.sqlite.db.framework;
18
19import android.content.Context;
20import android.database.DatabaseErrorHandler;
21import android.database.sqlite.SQLiteDatabase;
22import android.database.sqlite.SQLiteOpenHelper;
23import android.os.Build;
24
25import androidx.sqlite.db.SupportSQLiteDatabase;
26import androidx.sqlite.db.SupportSQLiteOpenHelper;
27
28class FrameworkSQLiteOpenHelper implements SupportSQLiteOpenHelper {
29    private final OpenHelper mDelegate;
30
31    FrameworkSQLiteOpenHelper(Context context, String name,
32            Callback callback) {
33        mDelegate = createDelegate(context, name, callback);
34    }
35
36    private OpenHelper createDelegate(Context context, String name, Callback callback) {
37        final FrameworkSQLiteDatabase[] dbRef = new FrameworkSQLiteDatabase[1];
38        return new OpenHelper(context, name, dbRef, callback);
39    }
40
41    @Override
42    public String getDatabaseName() {
43        return mDelegate.getDatabaseName();
44    }
45
46    @Override
47    @androidx.annotation.RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
48    public void setWriteAheadLoggingEnabled(boolean enabled) {
49        mDelegate.setWriteAheadLoggingEnabled(enabled);
50    }
51
52    @Override
53    public SupportSQLiteDatabase getWritableDatabase() {
54        return mDelegate.getWritableSupportDatabase();
55    }
56
57    @Override
58    public SupportSQLiteDatabase getReadableDatabase() {
59        return mDelegate.getReadableSupportDatabase();
60    }
61
62    @Override
63    public void close() {
64        mDelegate.close();
65    }
66
67    static class OpenHelper extends SQLiteOpenHelper {
68        /**
69         * This is used as an Object reference so that we can access the wrapped database inside
70         * the constructor. SQLiteOpenHelper requires the error handler to be passed in the
71         * constructor.
72         */
73        final FrameworkSQLiteDatabase[] mDbRef;
74        final Callback mCallback;
75        // see b/78359448
76        private boolean mMigrated;
77
78        OpenHelper(Context context, String name, final FrameworkSQLiteDatabase[] dbRef,
79                final Callback callback) {
80            super(context, name, null, callback.version,
81                    new DatabaseErrorHandler() {
82                        @Override
83                        public void onCorruption(SQLiteDatabase dbObj) {
84                            FrameworkSQLiteDatabase db = dbRef[0];
85                            if (db != null) {
86                                callback.onCorruption(db);
87                            }
88                        }
89                    });
90            mCallback = callback;
91            mDbRef = dbRef;
92        }
93
94        synchronized SupportSQLiteDatabase getWritableSupportDatabase() {
95            mMigrated = false;
96            SQLiteDatabase db = super.getWritableDatabase();
97            if (mMigrated) {
98                // there might be a connection w/ stale structure, we should re-open.
99                close();
100                return getWritableSupportDatabase();
101            }
102            return getWrappedDb(db);
103        }
104
105        synchronized SupportSQLiteDatabase getReadableSupportDatabase() {
106            mMigrated = false;
107            SQLiteDatabase db = super.getReadableDatabase();
108            if (mMigrated) {
109                // there might be a connection w/ stale structure, we should re-open.
110                close();
111                return getReadableSupportDatabase();
112            }
113            return getWrappedDb(db);
114        }
115
116        FrameworkSQLiteDatabase getWrappedDb(SQLiteDatabase sqLiteDatabase) {
117            FrameworkSQLiteDatabase dbRef = mDbRef[0];
118            if (dbRef == null) {
119                dbRef = new FrameworkSQLiteDatabase(sqLiteDatabase);
120                mDbRef[0] = dbRef;
121            }
122            return mDbRef[0];
123        }
124
125        @Override
126        public void onCreate(SQLiteDatabase sqLiteDatabase) {
127            mCallback.onCreate(getWrappedDb(sqLiteDatabase));
128        }
129
130        @Override
131        public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
132            mMigrated = true;
133            mCallback.onUpgrade(getWrappedDb(sqLiteDatabase), oldVersion, newVersion);
134        }
135
136        @Override
137        public void onConfigure(SQLiteDatabase db) {
138            mCallback.onConfigure(getWrappedDb(db));
139        }
140
141        @Override
142        public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
143            mMigrated = true;
144            mCallback.onDowngrade(getWrappedDb(db), oldVersion, newVersion);
145        }
146
147        @Override
148        public void onOpen(SQLiteDatabase db) {
149            if (!mMigrated) {
150                // if we've migrated, we'll re-open the db so we  should not call the callback.
151                mCallback.onOpen(getWrappedDb(db));
152            }
153        }
154
155        @Override
156        public synchronized void close() {
157            super.close();
158            mDbRef[0] = null;
159        }
160    }
161}
162