1package com.android.launcher3.util;
2
3import android.content.ContentValues;
4import android.content.Context;
5import android.database.Cursor;
6import android.database.sqlite.SQLiteDatabase;
7import android.database.sqlite.SQLiteException;
8import android.database.sqlite.SQLiteFullException;
9import android.database.sqlite.SQLiteOpenHelper;
10import android.util.Log;
11
12import com.android.launcher3.Utilities;
13import com.android.launcher3.config.FeatureFlags;
14
15/**
16 * An extension of {@link SQLiteOpenHelper} with utility methods for a single table cache DB.
17 * Any exception during write operations are ignored, and any version change causes a DB reset.
18 */
19public abstract class SQLiteCacheHelper {
20    private static final String TAG = "SQLiteCacheHelper";
21
22    private static final boolean NO_ICON_CACHE = FeatureFlags.IS_DOGFOOD_BUILD &&
23            Utilities.isPropertyEnabled(LogConfig.MEMORY_ONLY_ICON_CACHE);
24
25    private final String mTableName;
26    private final MySQLiteOpenHelper mOpenHelper;
27
28    private boolean mIgnoreWrites;
29
30    public SQLiteCacheHelper(Context context, String name, int version, String tableName) {
31        if (NO_ICON_CACHE) {
32            name = null;
33        }
34        mTableName = tableName;
35        mOpenHelper = new MySQLiteOpenHelper(context, name, version);
36
37        mIgnoreWrites = false;
38    }
39
40    /**
41     * @see SQLiteDatabase#delete(String, String, String[])
42     */
43    public void delete(String whereClause, String[] whereArgs) {
44        if (mIgnoreWrites) {
45            return;
46        }
47        try {
48            mOpenHelper.getWritableDatabase().delete(mTableName, whereClause, whereArgs);
49        } catch (SQLiteFullException e) {
50            onDiskFull(e);
51        } catch (SQLiteException e) {
52            Log.d(TAG, "Ignoring sqlite exception", e);
53        }
54    }
55
56    /**
57     * @see SQLiteDatabase#insertWithOnConflict(String, String, ContentValues, int)
58     */
59    public void insertOrReplace(ContentValues values) {
60        if (mIgnoreWrites) {
61            return;
62        }
63        try {
64            mOpenHelper.getWritableDatabase().insertWithOnConflict(
65                    mTableName, null, values, SQLiteDatabase.CONFLICT_REPLACE);
66        } catch (SQLiteFullException e) {
67            onDiskFull(e);
68        } catch (SQLiteException e) {
69            Log.d(TAG, "Ignoring sqlite exception", e);
70        }
71    }
72
73    private void onDiskFull(SQLiteFullException e) {
74        Log.e(TAG, "Disk full, all write operations will be ignored", e);
75        mIgnoreWrites = true;
76    }
77
78    /**
79     * @see SQLiteDatabase#query(String, String[], String, String[], String, String, String)
80     */
81    public Cursor query(String[] columns, String selection, String[] selectionArgs) {
82        return mOpenHelper.getReadableDatabase().query(
83                mTableName, columns, selection, selectionArgs, null, null, null);
84    }
85
86    public void clear() {
87        mOpenHelper.clearDB(mOpenHelper.getWritableDatabase());
88    }
89
90    protected abstract void onCreateTable(SQLiteDatabase db);
91
92    /**
93     * A private inner class to prevent direct DB access.
94     */
95    private class MySQLiteOpenHelper extends SQLiteOpenHelper {
96
97        public MySQLiteOpenHelper(Context context, String name, int version) {
98            super(new NoLocaleSqliteContext(context), name, null, version);
99        }
100
101        @Override
102        public void onCreate(SQLiteDatabase db) {
103            onCreateTable(db);
104        }
105
106        @Override
107        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
108            if (oldVersion != newVersion) {
109                clearDB(db);
110            }
111        }
112
113        @Override
114        public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
115            if (oldVersion != newVersion) {
116                clearDB(db);
117            }
118        }
119
120        private void clearDB(SQLiteDatabase db) {
121            db.execSQL("DROP TABLE IF EXISTS " + mTableName);
122            onCreate(db);
123        }
124    }
125}
126