Files
packages_apps_Messaging/src/com/android/messaging/datamodel/DatabaseWrapper.java
T
Cole Faust 50b73ca58a Fix errorprone warnings that should be errors
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
2022-10-15 21:33:28 -07:00

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;
}
}