This commit is part of a large scale change to fix errorprone errors that have been downgraded to warnings in the android source tree, so that they can be promoted to errors again. The full list of changes include the following, but not all will be present in any one individual commit: BadAnnotationImplementation BadShiftAmount BanJNDI BoxedPrimitiveEquality ComparableType ComplexBooleanConstant CollectionToArraySafeParameter ConditionalExpressionNumericPromotion DangerousLiteralNull DoubleBraceInitialization DurationFrom DurationTemporalUnit EmptyTopLevelDeclaration EqualsNull EqualsReference FormatString FromTemporalAccessor GetClassOnAnnotation GetClassOnClass HashtableContains IdentityBinaryExpression IdentityHashMapBoxing InstantTemporalUnit InvalidTimeZoneID InvalidZoneId IsInstanceIncompatibleType JUnitParameterMethodNotFound LockOnBoxedPrimitive MathRoundIntLong MislabeledAndroidString MisusedDayOfYear MissingSuperCall MisusedWeekYear ModifyingCollectionWithItself NoCanIgnoreReturnValueOnClasses NonRuntimeAnnotation NullableOnContainingClass NullTernary OverridesJavaxInjectableMethod ParcelableCreator PeriodFrom PreconditionsInvalidPlaceholder ProtoBuilderReturnValueIgnored ProtoFieldNullComparison RandomModInteger RectIntersectReturnValueIgnored ReturnValueIgnored SelfAssignment SelfComparison SelfEquals SizeGreaterThanOrEqualsZero StringBuilderInitWithChar TreeToString TryFailThrowable UnnecessaryCheckNotNull UnusedCollectionModifiedInPlace XorPower See https://errorprone.info/bugpatterns for more information on the checks. Bug: 253827323 Test: m RUN_ERROR_PRONE=true javac-check Change-Id: I8b1533cbea835343dcf589cbaa57c63794be58bd
483 lines
17 KiB
Java
483 lines
17 KiB
Java
/*
|
|
* Copyright (C) 2015 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.messaging.datamodel;
|
|
|
|
import android.content.ContentValues;
|
|
import android.content.Context;
|
|
import android.database.Cursor;
|
|
import android.database.DatabaseUtils;
|
|
import android.database.sqlite.SQLiteDatabase;
|
|
import android.database.sqlite.SQLiteFullException;
|
|
import android.database.sqlite.SQLiteQueryBuilder;
|
|
import android.database.sqlite.SQLiteStatement;
|
|
import android.util.SparseArray;
|
|
|
|
import com.android.messaging.Factory;
|
|
import com.android.messaging.R;
|
|
import com.android.messaging.util.Assert;
|
|
import com.android.messaging.util.BugleGservicesKeys;
|
|
import com.android.messaging.util.DebugUtils;
|
|
import com.android.messaging.util.LogUtil;
|
|
import com.android.messaging.util.UiUtils;
|
|
|
|
import java.util.Locale;
|
|
import java.util.Stack;
|
|
import java.util.regex.Pattern;
|
|
|
|
public class DatabaseWrapper {
|
|
private static final String TAG = LogUtil.BUGLE_DATABASE_TAG;
|
|
|
|
private final SQLiteDatabase mDatabase;
|
|
private final Context mContext;
|
|
private final boolean mLog;
|
|
/**
|
|
* Set mExplainQueryPlanRegexp (via {@link BugleGservicesKeys#EXPLAIN_QUERY_PLAN_REGEXP}
|
|
* to regex matching queries to see query plans. For example, ".*" to show all query plans.
|
|
*/
|
|
// See
|
|
private final String mExplainQueryPlanRegexp;
|
|
private static final int sTimingThreshold = 50; // in milliseconds
|
|
|
|
public static final int INDEX_INSERT_MESSAGE_PART = 0;
|
|
public static final int INDEX_INSERT_MESSAGE = 1;
|
|
public static final int INDEX_QUERY_CONVERSATIONS_LATEST_MESSAGE = 2;
|
|
public static final int INDEX_QUERY_MESSAGES_LATEST_MESSAGE = 3;
|
|
|
|
private final SparseArray<SQLiteStatement> mCompiledStatements;
|
|
|
|
static class TransactionData {
|
|
long time;
|
|
boolean transactionSuccessful;
|
|
}
|
|
|
|
// track transaction on a per thread basis
|
|
private static ThreadLocal<Stack<TransactionData>> sTransactionDepth =
|
|
new ThreadLocal<Stack<TransactionData>>() {
|
|
@Override
|
|
public Stack<TransactionData> initialValue() {
|
|
return new Stack<TransactionData>();
|
|
}
|
|
};
|
|
|
|
private static String[] sFormatStrings = new String[] {
|
|
"took %d ms to %s",
|
|
" took %d ms to %s",
|
|
" took %d ms to %s",
|
|
};
|
|
|
|
DatabaseWrapper(final Context context, final SQLiteDatabase db) {
|
|
mLog = LogUtil.isLoggable(LogUtil.BUGLE_DATABASE_PERF_TAG, LogUtil.VERBOSE);
|
|
mExplainQueryPlanRegexp = Factory.get().getBugleGservices().getString(
|
|
BugleGservicesKeys.EXPLAIN_QUERY_PLAN_REGEXP, null);
|
|
mDatabase = db;
|
|
mContext = context;
|
|
mCompiledStatements = new SparseArray<SQLiteStatement>();
|
|
}
|
|
|
|
public SQLiteStatement getStatementInTransaction(final int index, final String statement) {
|
|
// Use transaction to serialize access to statements
|
|
Assert.isTrue(mDatabase.inTransaction());
|
|
SQLiteStatement compiled = mCompiledStatements.get(index);
|
|
if (compiled == null) {
|
|
compiled = mDatabase.compileStatement(statement);
|
|
Assert.isTrue(compiled.toString().contains(statement.trim()));
|
|
mCompiledStatements.put(index, compiled);
|
|
}
|
|
return compiled;
|
|
}
|
|
|
|
private void maybePlayDebugNoise() {
|
|
DebugUtils.maybePlayDebugNoise(mContext, DebugUtils.DEBUG_SOUND_DB_OP);
|
|
}
|
|
|
|
private static void printTiming(final long t1, final String msg) {
|
|
final int transactionDepth = sTransactionDepth.get().size();
|
|
final long t2 = System.currentTimeMillis();
|
|
final long delta = t2 - t1;
|
|
if (delta > sTimingThreshold) {
|
|
LogUtil.v(LogUtil.BUGLE_DATABASE_PERF_TAG, String.format(Locale.US,
|
|
sFormatStrings[Math.min(sFormatStrings.length - 1, transactionDepth)],
|
|
delta,
|
|
msg));
|
|
}
|
|
}
|
|
|
|
public Context getContext() {
|
|
return mContext;
|
|
}
|
|
|
|
public void beginTransaction() {
|
|
final long t1 = System.currentTimeMillis();
|
|
|
|
// push the current time onto the transaction stack
|
|
final TransactionData f = new TransactionData();
|
|
f.time = t1;
|
|
sTransactionDepth.get().push(f);
|
|
|
|
mDatabase.beginTransaction();
|
|
}
|
|
|
|
public void setTransactionSuccessful() {
|
|
final TransactionData f = sTransactionDepth.get().peek();
|
|
f.transactionSuccessful = true;
|
|
mDatabase.setTransactionSuccessful();
|
|
}
|
|
|
|
public void endTransaction() {
|
|
long t1 = 0;
|
|
long transactionStartTime = 0;
|
|
final TransactionData f = sTransactionDepth.get().pop();
|
|
if (f.transactionSuccessful == false) {
|
|
LogUtil.w(TAG, "endTransaction without setting successful");
|
|
for (final StackTraceElement st : (new Exception()).getStackTrace()) {
|
|
LogUtil.w(TAG, " " + st.toString());
|
|
}
|
|
}
|
|
if (mLog) {
|
|
transactionStartTime = f.time;
|
|
t1 = System.currentTimeMillis();
|
|
}
|
|
try {
|
|
mDatabase.endTransaction();
|
|
} catch (SQLiteFullException ex) {
|
|
LogUtil.e(TAG, "Database full, unable to endTransaction", ex);
|
|
UiUtils.showToastAtBottom(R.string.db_full);
|
|
}
|
|
if (mLog) {
|
|
printTiming(t1, String.format(Locale.US,
|
|
">>> endTransaction (total for this transaction: %d)",
|
|
(System.currentTimeMillis() - transactionStartTime)));
|
|
}
|
|
}
|
|
|
|
public void yieldTransaction() {
|
|
long yieldStartTime = 0;
|
|
if (mLog) {
|
|
yieldStartTime = System.currentTimeMillis();
|
|
}
|
|
final boolean wasYielded = mDatabase.yieldIfContendedSafely();
|
|
if (wasYielded && mLog) {
|
|
printTiming(yieldStartTime, "yieldTransaction");
|
|
}
|
|
}
|
|
|
|
public void insertWithOnConflict(final String searchTable, final String nullColumnHack,
|
|
final ContentValues initialValues, final int conflictAlgorithm) {
|
|
long t1 = 0;
|
|
if (mLog) {
|
|
t1 = System.currentTimeMillis();
|
|
}
|
|
try {
|
|
mDatabase.insertWithOnConflict(searchTable, nullColumnHack, initialValues,
|
|
conflictAlgorithm);
|
|
} catch (SQLiteFullException ex) {
|
|
LogUtil.e(TAG, "Database full, unable to insertWithOnConflict", ex);
|
|
UiUtils.showToastAtBottom(R.string.db_full);
|
|
}
|
|
if (mLog) {
|
|
printTiming(t1, String.format(Locale.US,
|
|
"insertWithOnConflict with %s", searchTable));
|
|
}
|
|
}
|
|
|
|
private void explainQueryPlan(final SQLiteQueryBuilder qb, final SQLiteDatabase db,
|
|
final String[] projection, final String selection,
|
|
@SuppressWarnings("unused")
|
|
final String[] queryArgs,
|
|
final String groupBy,
|
|
@SuppressWarnings("unused")
|
|
final String having,
|
|
final String sortOrder, final String limit) {
|
|
final String queryString = qb.buildQuery(
|
|
projection,
|
|
selection,
|
|
groupBy,
|
|
null/*having*/,
|
|
sortOrder,
|
|
limit);
|
|
explainQueryPlan(db, queryString, queryArgs);
|
|
}
|
|
|
|
private void explainQueryPlan(final SQLiteDatabase db, final String sql,
|
|
final String[] queryArgs) {
|
|
if (!Pattern.matches(mExplainQueryPlanRegexp, sql)) {
|
|
return;
|
|
}
|
|
final Cursor planCursor = db.rawQuery("explain query plan " + sql, queryArgs);
|
|
try {
|
|
if (planCursor != null && planCursor.moveToFirst()) {
|
|
final int detailColumn = planCursor.getColumnIndex("detail");
|
|
final StringBuilder sb = new StringBuilder();
|
|
do {
|
|
sb.append(planCursor.getString(detailColumn));
|
|
sb.append("\n");
|
|
} while (planCursor.moveToNext());
|
|
if (sb.length() > 0) {
|
|
sb.setLength(sb.length() - 1);
|
|
}
|
|
LogUtil.v(TAG, "for query " + sql + "\nplan is: "
|
|
+ sb.toString());
|
|
}
|
|
} catch (final Exception e) {
|
|
LogUtil.w(TAG, "Query plan failed ", e);
|
|
} finally {
|
|
if (planCursor != null) {
|
|
planCursor.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Cursor query(final String searchTable, final String[] projection,
|
|
final String selection, final String[] selectionArgs, final String groupBy,
|
|
final String having, final String orderBy, final String limit) {
|
|
if (mExplainQueryPlanRegexp != null) {
|
|
final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
|
|
qb.setTables(searchTable);
|
|
explainQueryPlan(qb, mDatabase, projection, selection, selectionArgs,
|
|
groupBy, having, orderBy, limit);
|
|
}
|
|
|
|
maybePlayDebugNoise();
|
|
long t1 = 0;
|
|
if (mLog) {
|
|
t1 = System.currentTimeMillis();
|
|
}
|
|
final Cursor cursor = mDatabase.query(searchTable, projection, selection, selectionArgs,
|
|
groupBy, having, orderBy, limit);
|
|
if (mLog) {
|
|
printTiming(
|
|
t1,
|
|
String.format(Locale.US, "query %s with %s ==> %d",
|
|
searchTable, selection, cursor.getCount()));
|
|
}
|
|
return cursor;
|
|
}
|
|
|
|
public Cursor query(final String searchTable, final String[] columns,
|
|
final String selection, final String[] selectionArgs, final String groupBy,
|
|
final String having, final String orderBy) {
|
|
return query(
|
|
searchTable, columns, selection, selectionArgs,
|
|
groupBy, having, orderBy, null);
|
|
}
|
|
|
|
public Cursor query(final SQLiteQueryBuilder qb,
|
|
final String[] projection, final String selection, final String[] queryArgs,
|
|
final String groupBy, final String having, final String sortOrder, final String limit) {
|
|
if (mExplainQueryPlanRegexp != null) {
|
|
explainQueryPlan(qb, mDatabase, projection, selection, queryArgs,
|
|
groupBy, having, sortOrder, limit);
|
|
}
|
|
maybePlayDebugNoise();
|
|
long t1 = 0;
|
|
if (mLog) {
|
|
t1 = System.currentTimeMillis();
|
|
}
|
|
final Cursor cursor = qb.query(mDatabase, projection, selection, queryArgs, groupBy,
|
|
having, sortOrder, limit);
|
|
if (mLog) {
|
|
printTiming(
|
|
t1,
|
|
String.format(Locale.US, "query %s with %s ==> %d",
|
|
qb.getTables(), selection, cursor.getCount()));
|
|
}
|
|
return cursor;
|
|
}
|
|
|
|
public long queryNumEntries(final String table, final String selection,
|
|
final String[] selectionArgs) {
|
|
long t1 = 0;
|
|
if (mLog) {
|
|
t1 = System.currentTimeMillis();
|
|
}
|
|
maybePlayDebugNoise();
|
|
final long retval =
|
|
DatabaseUtils.queryNumEntries(mDatabase, table, selection, selectionArgs);
|
|
if (mLog){
|
|
printTiming(
|
|
t1,
|
|
String.format(Locale.US, "queryNumEntries %s with %s ==> %d", table,
|
|
selection, retval));
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
public Cursor rawQuery(final String sql, final String[] args) {
|
|
if (mExplainQueryPlanRegexp != null) {
|
|
explainQueryPlan(mDatabase, sql, args);
|
|
}
|
|
long t1 = 0;
|
|
if (mLog) {
|
|
t1 = System.currentTimeMillis();
|
|
}
|
|
maybePlayDebugNoise();
|
|
final Cursor cursor = mDatabase.rawQuery(sql, args);
|
|
if (mLog) {
|
|
printTiming(
|
|
t1,
|
|
String.format(Locale.US, "rawQuery %s ==> %d", sql, cursor.getCount()));
|
|
}
|
|
return cursor;
|
|
}
|
|
|
|
public int update(final String table, final ContentValues values,
|
|
final String selection, final String[] selectionArgs) {
|
|
long t1 = 0;
|
|
if (mLog) {
|
|
t1 = System.currentTimeMillis();
|
|
}
|
|
maybePlayDebugNoise();
|
|
int count = 0;
|
|
try {
|
|
count = mDatabase.update(table, values, selection, selectionArgs);
|
|
} catch (SQLiteFullException ex) {
|
|
LogUtil.e(TAG, "Database full, unable to update", ex);
|
|
UiUtils.showToastAtBottom(R.string.db_full);
|
|
}
|
|
if (mLog) {
|
|
printTiming(t1, String.format(Locale.US, "update %s with %s ==> %d",
|
|
table, selection, count));
|
|
}
|
|
return count;
|
|
}
|
|
|
|
public int delete(final String table, final String whereClause, final String[] whereArgs) {
|
|
long t1 = 0;
|
|
if (mLog) {
|
|
t1 = System.currentTimeMillis();
|
|
}
|
|
maybePlayDebugNoise();
|
|
int count = 0;
|
|
try {
|
|
count = mDatabase.delete(table, whereClause, whereArgs);
|
|
} catch (SQLiteFullException ex) {
|
|
LogUtil.e(TAG, "Database full, unable to delete", ex);
|
|
UiUtils.showToastAtBottom(R.string.db_full);
|
|
}
|
|
if (mLog) {
|
|
printTiming(t1,
|
|
String.format(Locale.US, "delete from %s with %s ==> %d", table,
|
|
whereClause, count));
|
|
}
|
|
return count;
|
|
}
|
|
|
|
public long insert(final String table, final String nullColumnHack,
|
|
final ContentValues values) {
|
|
long t1 = 0;
|
|
if (mLog) {
|
|
t1 = System.currentTimeMillis();
|
|
}
|
|
maybePlayDebugNoise();
|
|
long rowId = -1;
|
|
try {
|
|
rowId = mDatabase.insert(table, nullColumnHack, values);
|
|
} catch (SQLiteFullException ex) {
|
|
LogUtil.e(TAG, "Database full, unable to insert", ex);
|
|
UiUtils.showToastAtBottom(R.string.db_full);
|
|
}
|
|
if (mLog) {
|
|
printTiming(t1, String.format(Locale.US, "insert to %s", table));
|
|
}
|
|
return rowId;
|
|
}
|
|
|
|
public long replace(final String table, final String nullColumnHack,
|
|
final ContentValues values) {
|
|
long t1 = 0;
|
|
if (mLog) {
|
|
t1 = System.currentTimeMillis();
|
|
}
|
|
maybePlayDebugNoise();
|
|
long rowId = -1;
|
|
try {
|
|
rowId = mDatabase.replace(table, nullColumnHack, values);
|
|
} catch (SQLiteFullException ex) {
|
|
LogUtil.e(TAG, "Database full, unable to replace", ex);
|
|
UiUtils.showToastAtBottom(R.string.db_full);
|
|
}
|
|
if (mLog) {
|
|
printTiming(t1, String.format(Locale.US, "replace to %s", table));
|
|
}
|
|
return rowId;
|
|
}
|
|
|
|
public void setLocale(final Locale locale) {
|
|
mDatabase.setLocale(locale);
|
|
}
|
|
|
|
public void execSQL(final String sql, final String[] bindArgs) {
|
|
long t1 = 0;
|
|
if (mLog) {
|
|
t1 = System.currentTimeMillis();
|
|
}
|
|
maybePlayDebugNoise();
|
|
try {
|
|
mDatabase.execSQL(sql, bindArgs);
|
|
} catch (SQLiteFullException ex) {
|
|
LogUtil.e(TAG, "Database full, unable to execSQL", ex);
|
|
UiUtils.showToastAtBottom(R.string.db_full);
|
|
}
|
|
|
|
if (mLog) {
|
|
printTiming(t1, String.format(Locale.US, "execSQL %s", sql));
|
|
}
|
|
}
|
|
|
|
public void execSQL(final String sql) {
|
|
long t1 = 0;
|
|
if (mLog) {
|
|
t1 = System.currentTimeMillis();
|
|
}
|
|
maybePlayDebugNoise();
|
|
try {
|
|
mDatabase.execSQL(sql);
|
|
} catch (SQLiteFullException ex) {
|
|
LogUtil.e(TAG, "Database full, unable to execSQL", ex);
|
|
UiUtils.showToastAtBottom(R.string.db_full);
|
|
}
|
|
|
|
if (mLog) {
|
|
printTiming(t1, String.format(Locale.US, "execSQL %s", sql));
|
|
}
|
|
}
|
|
|
|
public int execSQLUpdateDelete(final String sql) {
|
|
long t1 = 0;
|
|
if (mLog) {
|
|
t1 = System.currentTimeMillis();
|
|
}
|
|
maybePlayDebugNoise();
|
|
final SQLiteStatement statement = mDatabase.compileStatement(sql);
|
|
int rowsUpdated = 0;
|
|
try {
|
|
rowsUpdated = statement.executeUpdateDelete();
|
|
} catch (SQLiteFullException ex) {
|
|
LogUtil.e(TAG, "Database full, unable to execSQLUpdateDelete", ex);
|
|
UiUtils.showToastAtBottom(R.string.db_full);
|
|
}
|
|
if (mLog) {
|
|
printTiming(t1, String.format(Locale.US, "execSQLUpdateDelete %s", sql));
|
|
}
|
|
return rowsUpdated;
|
|
}
|
|
|
|
public SQLiteDatabase getDatabase() {
|
|
return mDatabase;
|
|
}
|
|
}
|