Loading core/java/android/app/DownloadManager.java +38 −5 Original line number Diff line number Diff line Loading @@ -839,6 +839,7 @@ public class DownloadManager { private long[] mIds = null; private Integer mStatusFlags = null; private String mFilterString = null; private String mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION; private int mOrderDirection = ORDER_DESCENDING; private boolean mOnlyIncludeVisibleInDownloadsUi = false; Loading @@ -852,6 +853,17 @@ public class DownloadManager { return this; } /** * * Include only the downloads that contains the given string in its name. * @return this object * @hide */ public Query setFilterByString(@Nullable String filter) { mFilterString = filter; return this; } /** * Include only downloads with status matching any the given status flags. * @param flags any combination of the STATUS_* bit flags Loading Loading @@ -911,9 +923,20 @@ public class DownloadManager { List<String> selectionParts = new ArrayList<String>(); String[] selectionArgs = null; int whereArgsCount = (mIds == null) ? 0 : mIds.length; whereArgsCount = (mFilterString == null) ? whereArgsCount : whereArgsCount + 1; selectionArgs = new String[whereArgsCount]; if (whereArgsCount > 0) { if (mIds != null) { selectionParts.add(getWhereClauseForIds(mIds)); selectionArgs = getWhereArgsForIds(mIds); getWhereArgsForIds(mIds, selectionArgs); } if (mFilterString != null) { selectionParts.add(Downloads.Impl.COLUMN_TITLE + " LIKE ?"); selectionArgs[selectionArgs.length - 1] = "%" + mFilterString + "%"; } } if (mStatusFlags != null) { Loading Loading @@ -1486,12 +1509,22 @@ public class DownloadManager { */ static String[] getWhereArgsForIds(long[] ids) { String[] whereArgs = new String[ids.length]; return getWhereArgsForIds(ids, whereArgs); } /** * Get selection args for a clause returned by {@link #getWhereClauseForIds(long[])} * and write it to the supplied args array. */ static String[] getWhereArgsForIds(long[] ids, String[] args) { assert(args.length >= ids.length); for (int i = 0; i < ids.length; i++) { whereArgs[i] = Long.toString(ids[i]); args[i] = Long.toString(ids[i]); } return whereArgs; return args; } /** * This class wraps a cursor returned by DownloadProvider -- the "underlying cursor" -- and * presents a different set of columns, those defined in the DownloadManager.COLUMN_* constants. Loading core/java/android/database/sqlite/SQLiteDatabase.java +2 −1 Original line number Diff line number Diff line Loading @@ -1658,7 +1658,8 @@ public final class SQLiteDatabase extends SQLiteClosable { executeSql(sql, bindArgs); } private int executeSql(String sql, Object[] bindArgs) throws SQLException { /** {@hide} */ public int executeSql(String sql, Object[] bindArgs) throws SQLException { acquireReference(); try { if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) { Loading core/java/android/database/sqlite/SQLiteQueryBuilder.java +234 −34 Original line number Diff line number Diff line Loading @@ -16,17 +16,25 @@ package android.database.sqlite; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentValues; import android.database.Cursor; import android.database.DatabaseUtils; import android.os.Build; import android.os.CancellationSignal; import android.os.OperationCanceledException; import android.provider.BaseColumns; import android.text.TextUtils; import android.util.Log; import libcore.util.EmptyArray; import java.util.Arrays; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.regex.Pattern; Loading Loading @@ -95,9 +103,6 @@ public class SQLiteQueryBuilder if (mWhereClause == null) { mWhereClause = new StringBuilder(inWhere.length() + 16); } if (mWhereClause.length() == 0) { mWhereClause.append('('); } mWhereClause.append(inWhere); } Loading @@ -115,9 +120,6 @@ public class SQLiteQueryBuilder if (mWhereClause == null) { mWhereClause = new StringBuilder(inWhere.length() + 16); } if (mWhereClause.length() == 0) { mWhereClause.append('('); } DatabaseUtils.appendEscapedSQLString(mWhereClause, inWhere); } Loading Loading @@ -376,6 +378,11 @@ public class SQLiteQueryBuilder return null; } final String sql; final String unwrappedSql = buildQuery( projectionIn, selection, groupBy, having, sortOrder, limit); if (mStrict && selection != null && selection.length() > 0) { // Validate the user-supplied selection to detect syntactic anomalies // in the selection string that could indicate a SQL injection attempt. Loading @@ -384,24 +391,166 @@ public class SQLiteQueryBuilder // originally specified. An attacker cannot create an expression that // would escape the SQL expression while maintaining balanced parentheses // in both the wrapped and original forms. String sqlForValidation = buildQuery(projectionIn, "(" + selection + ")", groupBy, // NOTE: The ordering of the below operations is important; we must // execute the wrapped query to ensure the untrusted clause has been // fully isolated. // Validate the unwrapped query db.validateSql(unwrappedSql, cancellationSignal); // will throw if query is invalid // Execute wrapped query for extra protection final String wrappedSql = buildQuery(projectionIn, wrap(selection), groupBy, having, sortOrder, limit); db.validateSql(sqlForValidation, cancellationSignal); // will throw if query is invalid sql = wrappedSql; } else { // Execute unwrapped query sql = unwrappedSql; } String sql = buildQuery( projectionIn, selection, groupBy, having, sortOrder, limit); final String[] sqlArgs = selectionArgs; if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Performing query: " + sql); if (Build.IS_DEBUGGABLE) { Log.d(TAG, sql + " with args " + Arrays.toString(sqlArgs)); } else { Log.d(TAG, sql); } } return db.rawQueryWithFactory( mFactory, sql, selectionArgs, mFactory, sql, sqlArgs, SQLiteDatabase.findEditTable(mTables), cancellationSignal); // will throw if query is invalid } /** * Perform an update by combining all current settings and the * information passed into this method. * * @param db the database to update on * @param selection A filter declaring which rows to return, * formatted as an SQL WHERE clause (excluding the WHERE * itself). Passing null will return all rows for the given URL. * @param selectionArgs You may include ?s in selection, which * will be replaced by the values from selectionArgs, in order * that they appear in the selection. The values will be bound * as Strings. * @return the number of rows updated * @hide */ public int update(@NonNull SQLiteDatabase db, @NonNull ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { Objects.requireNonNull(mTables, "No tables defined"); Objects.requireNonNull(db, "No database defined"); Objects.requireNonNull(values, "No values defined"); final String sql; final String unwrappedSql = buildUpdate(values, selection); if (mStrict) { // Validate the user-supplied selection to detect syntactic anomalies // in the selection string that could indicate a SQL injection attempt. // The idea is to ensure that the selection clause is a valid SQL expression // by compiling it twice: once wrapped in parentheses and once as // originally specified. An attacker cannot create an expression that // would escape the SQL expression while maintaining balanced parentheses // in both the wrapped and original forms. // NOTE: The ordering of the below operations is important; we must // execute the wrapped query to ensure the untrusted clause has been // fully isolated. // Validate the unwrapped query db.validateSql(unwrappedSql, null); // will throw if query is invalid // Execute wrapped query for extra protection final String wrappedSql = buildUpdate(values, wrap(selection)); sql = wrappedSql; } else { // Execute unwrapped query sql = unwrappedSql; } if (selectionArgs == null) { selectionArgs = EmptyArray.STRING; } final String[] rawKeys = values.keySet().toArray(EmptyArray.STRING); final int valuesLength = rawKeys.length; final Object[] sqlArgs = new Object[valuesLength + selectionArgs.length]; for (int i = 0; i < sqlArgs.length; i++) { if (i < valuesLength) { sqlArgs[i] = values.get(rawKeys[i]); } else { sqlArgs[i] = selectionArgs[i - valuesLength]; } } if (Log.isLoggable(TAG, Log.DEBUG)) { if (Build.IS_DEBUGGABLE) { Log.d(TAG, sql + " with args " + Arrays.toString(sqlArgs)); } else { Log.d(TAG, sql); } } return db.executeSql(sql, sqlArgs); } /** * Perform a delete by combining all current settings and the * information passed into this method. * * @param db the database to delete on * @param selection A filter declaring which rows to return, * formatted as an SQL WHERE clause (excluding the WHERE * itself). Passing null will return all rows for the given URL. * @param selectionArgs You may include ?s in selection, which * will be replaced by the values from selectionArgs, in order * that they appear in the selection. The values will be bound * as Strings. * @return the number of rows deleted * @hide */ public int delete(@NonNull SQLiteDatabase db, @Nullable String selection, @Nullable String[] selectionArgs) { Objects.requireNonNull(mTables, "No tables defined"); Objects.requireNonNull(db, "No database defined"); final String sql; final String unwrappedSql = buildDelete(selection); if (mStrict) { // Validate the user-supplied selection to detect syntactic anomalies // in the selection string that could indicate a SQL injection attempt. // The idea is to ensure that the selection clause is a valid SQL expression // by compiling it twice: once wrapped in parentheses and once as // originally specified. An attacker cannot create an expression that // would escape the SQL expression while maintaining balanced parentheses // in both the wrapped and original forms. // NOTE: The ordering of the below operations is important; we must // execute the wrapped query to ensure the untrusted clause has been // fully isolated. // Validate the unwrapped query db.validateSql(unwrappedSql, null); // will throw if query is invalid // Execute wrapped query for extra protection final String wrappedSql = buildDelete(wrap(selection)); sql = wrappedSql; } else { // Execute unwrapped query sql = unwrappedSql; } final String[] sqlArgs = selectionArgs; if (Log.isLoggable(TAG, Log.DEBUG)) { if (Build.IS_DEBUGGABLE) { Log.d(TAG, sql + " with args " + Arrays.toString(sqlArgs)); } else { Log.d(TAG, sql); } } return db.executeSql(sql, sqlArgs); } /** * Construct a SELECT statement suitable for use in a group of * SELECT statements that will be joined through UNION operators Loading Loading @@ -434,28 +583,10 @@ public class SQLiteQueryBuilder String[] projectionIn, String selection, String groupBy, String having, String sortOrder, String limit) { String[] projection = computeProjection(projectionIn); StringBuilder where = new StringBuilder(); boolean hasBaseWhereClause = mWhereClause != null && mWhereClause.length() > 0; if (hasBaseWhereClause) { where.append(mWhereClause.toString()); where.append(')'); } // Tack on the user's selection, if present. if (selection != null && selection.length() > 0) { if (hasBaseWhereClause) { where.append(" AND "); } where.append('('); where.append(selection); where.append(')'); } String where = computeWhere(selection); return buildQueryString( mDistinct, mTables, projection, where.toString(), mDistinct, mTables, projection, where, groupBy, having, sortOrder, limit); } Loading @@ -472,6 +603,42 @@ public class SQLiteQueryBuilder return buildQuery(projectionIn, selection, groupBy, having, sortOrder, limit); } /** {@hide} */ public String buildUpdate(ContentValues values, String selection) { if (values == null || values.size() == 0) { throw new IllegalArgumentException("Empty values"); } StringBuilder sql = new StringBuilder(120); sql.append("UPDATE "); sql.append(mTables); sql.append(" SET "); final String[] rawKeys = values.keySet().toArray(EmptyArray.STRING); for (int i = 0; i < rawKeys.length; i++) { if (i > 0) { sql.append(','); } sql.append(rawKeys[i]); sql.append("=?"); } final String where = computeWhere(selection); appendClause(sql, " WHERE ", where); return sql.toString(); } /** {@hide} */ public String buildDelete(String selection) { StringBuilder sql = new StringBuilder(120); sql.append("DELETE FROM "); sql.append(mTables); final String where = computeWhere(selection); appendClause(sql, " WHERE ", where); return sql.toString(); } /** * Construct a SELECT statement suitable for use in a group of * SELECT statements that will be joined through UNION operators Loading Loading @@ -645,4 +812,37 @@ public class SQLiteQueryBuilder } return null; } private @Nullable String computeWhere(@Nullable String selection) { final boolean hasInternal = !TextUtils.isEmpty(mWhereClause); final boolean hasExternal = !TextUtils.isEmpty(selection); if (hasInternal || hasExternal) { final StringBuilder where = new StringBuilder(); if (hasInternal) { where.append('(').append(mWhereClause).append(')'); } if (hasInternal && hasExternal) { where.append(" AND "); } if (hasExternal) { where.append('(').append(selection).append(')'); } return where.toString(); } else { return null; } } /** * Wrap given argument in parenthesis, unless it's {@code null} or * {@code ()}, in which case return it verbatim. */ private @Nullable String wrap(@Nullable String arg) { if (TextUtils.isEmpty(arg)) { return arg; } else { return "(" + arg + ")"; } } } core/java/android/text/Layout.java +164 −6 File changed.Preview size limit exceeded, changes collapsed. Show changes core/java/android/text/TextLine.java +92 −0 Original line number Diff line number Diff line Loading @@ -338,6 +338,98 @@ class TextLine { return h; } /** * @see #measure(int, boolean, FontMetricsInt) * @return The measure results for all possible offsets */ float[] measureAllOffsets(boolean[] trailing, FontMetricsInt fmi) { float[] measurement = new float[mLen + 1]; int[] target = new int[mLen + 1]; for (int offset = 0; offset < target.length; ++offset) { target[offset] = trailing[offset] ? offset - 1 : offset; } if (target[0] < 0) { measurement[0] = 0; } float h = 0; if (!mHasTabs) { if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) { for (int offset = 0; offset <= mLen; ++offset) { measurement[offset] = measureRun(0, offset, mLen, false, fmi); } return measurement; } if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) { for (int offset = 0; offset <= mLen; ++offset) { measurement[offset] = measureRun(0, offset, mLen, true, fmi); } return measurement; } } char[] chars = mChars; int[] runs = mDirections.mDirections; for (int i = 0; i < runs.length; i += 2) { int runStart = runs[i]; int runLimit = runStart + (runs[i + 1] & Layout.RUN_LENGTH_MASK); if (runLimit > mLen) { runLimit = mLen; } boolean runIsRtl = (runs[i + 1] & Layout.RUN_RTL_FLAG) != 0; int segstart = runStart; for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; ++j) { int codept = 0; if (mHasTabs && j < runLimit) { codept = chars[j]; if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) { codept = Character.codePointAt(chars, j); if (codept > 0xFFFF) { ++j; continue; } } } if (j == runLimit || codept == '\t') { float oldh = h; boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl; float w = measureRun(segstart, j, j, runIsRtl, fmi); h += advance ? w : -w; float baseh = advance ? oldh : h; FontMetricsInt crtfmi = advance ? fmi : null; for (int offset = segstart; offset <= j && offset <= mLen; ++offset) { if (target[offset] >= segstart && target[offset] < j) { measurement[offset] = baseh + measureRun(segstart, offset, j, runIsRtl, crtfmi); } } if (codept == '\t') { if (target[j] == j) { measurement[j] = h; } h = mDir * nextTab(h * mDir); if (target[j + 1] == j) { measurement[j + 1] = h; } } segstart = j + 1; } } } if (target[mLen] == mLen) { measurement[mLen] = h; } return measurement; } /** * Draws a unidirectional (but possibly multi-styled) run of text. * Loading Loading
core/java/android/app/DownloadManager.java +38 −5 Original line number Diff line number Diff line Loading @@ -839,6 +839,7 @@ public class DownloadManager { private long[] mIds = null; private Integer mStatusFlags = null; private String mFilterString = null; private String mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION; private int mOrderDirection = ORDER_DESCENDING; private boolean mOnlyIncludeVisibleInDownloadsUi = false; Loading @@ -852,6 +853,17 @@ public class DownloadManager { return this; } /** * * Include only the downloads that contains the given string in its name. * @return this object * @hide */ public Query setFilterByString(@Nullable String filter) { mFilterString = filter; return this; } /** * Include only downloads with status matching any the given status flags. * @param flags any combination of the STATUS_* bit flags Loading Loading @@ -911,9 +923,20 @@ public class DownloadManager { List<String> selectionParts = new ArrayList<String>(); String[] selectionArgs = null; int whereArgsCount = (mIds == null) ? 0 : mIds.length; whereArgsCount = (mFilterString == null) ? whereArgsCount : whereArgsCount + 1; selectionArgs = new String[whereArgsCount]; if (whereArgsCount > 0) { if (mIds != null) { selectionParts.add(getWhereClauseForIds(mIds)); selectionArgs = getWhereArgsForIds(mIds); getWhereArgsForIds(mIds, selectionArgs); } if (mFilterString != null) { selectionParts.add(Downloads.Impl.COLUMN_TITLE + " LIKE ?"); selectionArgs[selectionArgs.length - 1] = "%" + mFilterString + "%"; } } if (mStatusFlags != null) { Loading Loading @@ -1486,12 +1509,22 @@ public class DownloadManager { */ static String[] getWhereArgsForIds(long[] ids) { String[] whereArgs = new String[ids.length]; return getWhereArgsForIds(ids, whereArgs); } /** * Get selection args for a clause returned by {@link #getWhereClauseForIds(long[])} * and write it to the supplied args array. */ static String[] getWhereArgsForIds(long[] ids, String[] args) { assert(args.length >= ids.length); for (int i = 0; i < ids.length; i++) { whereArgs[i] = Long.toString(ids[i]); args[i] = Long.toString(ids[i]); } return whereArgs; return args; } /** * This class wraps a cursor returned by DownloadProvider -- the "underlying cursor" -- and * presents a different set of columns, those defined in the DownloadManager.COLUMN_* constants. Loading
core/java/android/database/sqlite/SQLiteDatabase.java +2 −1 Original line number Diff line number Diff line Loading @@ -1658,7 +1658,8 @@ public final class SQLiteDatabase extends SQLiteClosable { executeSql(sql, bindArgs); } private int executeSql(String sql, Object[] bindArgs) throws SQLException { /** {@hide} */ public int executeSql(String sql, Object[] bindArgs) throws SQLException { acquireReference(); try { if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) { Loading
core/java/android/database/sqlite/SQLiteQueryBuilder.java +234 −34 Original line number Diff line number Diff line Loading @@ -16,17 +16,25 @@ package android.database.sqlite; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentValues; import android.database.Cursor; import android.database.DatabaseUtils; import android.os.Build; import android.os.CancellationSignal; import android.os.OperationCanceledException; import android.provider.BaseColumns; import android.text.TextUtils; import android.util.Log; import libcore.util.EmptyArray; import java.util.Arrays; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.regex.Pattern; Loading Loading @@ -95,9 +103,6 @@ public class SQLiteQueryBuilder if (mWhereClause == null) { mWhereClause = new StringBuilder(inWhere.length() + 16); } if (mWhereClause.length() == 0) { mWhereClause.append('('); } mWhereClause.append(inWhere); } Loading @@ -115,9 +120,6 @@ public class SQLiteQueryBuilder if (mWhereClause == null) { mWhereClause = new StringBuilder(inWhere.length() + 16); } if (mWhereClause.length() == 0) { mWhereClause.append('('); } DatabaseUtils.appendEscapedSQLString(mWhereClause, inWhere); } Loading Loading @@ -376,6 +378,11 @@ public class SQLiteQueryBuilder return null; } final String sql; final String unwrappedSql = buildQuery( projectionIn, selection, groupBy, having, sortOrder, limit); if (mStrict && selection != null && selection.length() > 0) { // Validate the user-supplied selection to detect syntactic anomalies // in the selection string that could indicate a SQL injection attempt. Loading @@ -384,24 +391,166 @@ public class SQLiteQueryBuilder // originally specified. An attacker cannot create an expression that // would escape the SQL expression while maintaining balanced parentheses // in both the wrapped and original forms. String sqlForValidation = buildQuery(projectionIn, "(" + selection + ")", groupBy, // NOTE: The ordering of the below operations is important; we must // execute the wrapped query to ensure the untrusted clause has been // fully isolated. // Validate the unwrapped query db.validateSql(unwrappedSql, cancellationSignal); // will throw if query is invalid // Execute wrapped query for extra protection final String wrappedSql = buildQuery(projectionIn, wrap(selection), groupBy, having, sortOrder, limit); db.validateSql(sqlForValidation, cancellationSignal); // will throw if query is invalid sql = wrappedSql; } else { // Execute unwrapped query sql = unwrappedSql; } String sql = buildQuery( projectionIn, selection, groupBy, having, sortOrder, limit); final String[] sqlArgs = selectionArgs; if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Performing query: " + sql); if (Build.IS_DEBUGGABLE) { Log.d(TAG, sql + " with args " + Arrays.toString(sqlArgs)); } else { Log.d(TAG, sql); } } return db.rawQueryWithFactory( mFactory, sql, selectionArgs, mFactory, sql, sqlArgs, SQLiteDatabase.findEditTable(mTables), cancellationSignal); // will throw if query is invalid } /** * Perform an update by combining all current settings and the * information passed into this method. * * @param db the database to update on * @param selection A filter declaring which rows to return, * formatted as an SQL WHERE clause (excluding the WHERE * itself). Passing null will return all rows for the given URL. * @param selectionArgs You may include ?s in selection, which * will be replaced by the values from selectionArgs, in order * that they appear in the selection. The values will be bound * as Strings. * @return the number of rows updated * @hide */ public int update(@NonNull SQLiteDatabase db, @NonNull ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { Objects.requireNonNull(mTables, "No tables defined"); Objects.requireNonNull(db, "No database defined"); Objects.requireNonNull(values, "No values defined"); final String sql; final String unwrappedSql = buildUpdate(values, selection); if (mStrict) { // Validate the user-supplied selection to detect syntactic anomalies // in the selection string that could indicate a SQL injection attempt. // The idea is to ensure that the selection clause is a valid SQL expression // by compiling it twice: once wrapped in parentheses and once as // originally specified. An attacker cannot create an expression that // would escape the SQL expression while maintaining balanced parentheses // in both the wrapped and original forms. // NOTE: The ordering of the below operations is important; we must // execute the wrapped query to ensure the untrusted clause has been // fully isolated. // Validate the unwrapped query db.validateSql(unwrappedSql, null); // will throw if query is invalid // Execute wrapped query for extra protection final String wrappedSql = buildUpdate(values, wrap(selection)); sql = wrappedSql; } else { // Execute unwrapped query sql = unwrappedSql; } if (selectionArgs == null) { selectionArgs = EmptyArray.STRING; } final String[] rawKeys = values.keySet().toArray(EmptyArray.STRING); final int valuesLength = rawKeys.length; final Object[] sqlArgs = new Object[valuesLength + selectionArgs.length]; for (int i = 0; i < sqlArgs.length; i++) { if (i < valuesLength) { sqlArgs[i] = values.get(rawKeys[i]); } else { sqlArgs[i] = selectionArgs[i - valuesLength]; } } if (Log.isLoggable(TAG, Log.DEBUG)) { if (Build.IS_DEBUGGABLE) { Log.d(TAG, sql + " with args " + Arrays.toString(sqlArgs)); } else { Log.d(TAG, sql); } } return db.executeSql(sql, sqlArgs); } /** * Perform a delete by combining all current settings and the * information passed into this method. * * @param db the database to delete on * @param selection A filter declaring which rows to return, * formatted as an SQL WHERE clause (excluding the WHERE * itself). Passing null will return all rows for the given URL. * @param selectionArgs You may include ?s in selection, which * will be replaced by the values from selectionArgs, in order * that they appear in the selection. The values will be bound * as Strings. * @return the number of rows deleted * @hide */ public int delete(@NonNull SQLiteDatabase db, @Nullable String selection, @Nullable String[] selectionArgs) { Objects.requireNonNull(mTables, "No tables defined"); Objects.requireNonNull(db, "No database defined"); final String sql; final String unwrappedSql = buildDelete(selection); if (mStrict) { // Validate the user-supplied selection to detect syntactic anomalies // in the selection string that could indicate a SQL injection attempt. // The idea is to ensure that the selection clause is a valid SQL expression // by compiling it twice: once wrapped in parentheses and once as // originally specified. An attacker cannot create an expression that // would escape the SQL expression while maintaining balanced parentheses // in both the wrapped and original forms. // NOTE: The ordering of the below operations is important; we must // execute the wrapped query to ensure the untrusted clause has been // fully isolated. // Validate the unwrapped query db.validateSql(unwrappedSql, null); // will throw if query is invalid // Execute wrapped query for extra protection final String wrappedSql = buildDelete(wrap(selection)); sql = wrappedSql; } else { // Execute unwrapped query sql = unwrappedSql; } final String[] sqlArgs = selectionArgs; if (Log.isLoggable(TAG, Log.DEBUG)) { if (Build.IS_DEBUGGABLE) { Log.d(TAG, sql + " with args " + Arrays.toString(sqlArgs)); } else { Log.d(TAG, sql); } } return db.executeSql(sql, sqlArgs); } /** * Construct a SELECT statement suitable for use in a group of * SELECT statements that will be joined through UNION operators Loading Loading @@ -434,28 +583,10 @@ public class SQLiteQueryBuilder String[] projectionIn, String selection, String groupBy, String having, String sortOrder, String limit) { String[] projection = computeProjection(projectionIn); StringBuilder where = new StringBuilder(); boolean hasBaseWhereClause = mWhereClause != null && mWhereClause.length() > 0; if (hasBaseWhereClause) { where.append(mWhereClause.toString()); where.append(')'); } // Tack on the user's selection, if present. if (selection != null && selection.length() > 0) { if (hasBaseWhereClause) { where.append(" AND "); } where.append('('); where.append(selection); where.append(')'); } String where = computeWhere(selection); return buildQueryString( mDistinct, mTables, projection, where.toString(), mDistinct, mTables, projection, where, groupBy, having, sortOrder, limit); } Loading @@ -472,6 +603,42 @@ public class SQLiteQueryBuilder return buildQuery(projectionIn, selection, groupBy, having, sortOrder, limit); } /** {@hide} */ public String buildUpdate(ContentValues values, String selection) { if (values == null || values.size() == 0) { throw new IllegalArgumentException("Empty values"); } StringBuilder sql = new StringBuilder(120); sql.append("UPDATE "); sql.append(mTables); sql.append(" SET "); final String[] rawKeys = values.keySet().toArray(EmptyArray.STRING); for (int i = 0; i < rawKeys.length; i++) { if (i > 0) { sql.append(','); } sql.append(rawKeys[i]); sql.append("=?"); } final String where = computeWhere(selection); appendClause(sql, " WHERE ", where); return sql.toString(); } /** {@hide} */ public String buildDelete(String selection) { StringBuilder sql = new StringBuilder(120); sql.append("DELETE FROM "); sql.append(mTables); final String where = computeWhere(selection); appendClause(sql, " WHERE ", where); return sql.toString(); } /** * Construct a SELECT statement suitable for use in a group of * SELECT statements that will be joined through UNION operators Loading Loading @@ -645,4 +812,37 @@ public class SQLiteQueryBuilder } return null; } private @Nullable String computeWhere(@Nullable String selection) { final boolean hasInternal = !TextUtils.isEmpty(mWhereClause); final boolean hasExternal = !TextUtils.isEmpty(selection); if (hasInternal || hasExternal) { final StringBuilder where = new StringBuilder(); if (hasInternal) { where.append('(').append(mWhereClause).append(')'); } if (hasInternal && hasExternal) { where.append(" AND "); } if (hasExternal) { where.append('(').append(selection).append(')'); } return where.toString(); } else { return null; } } /** * Wrap given argument in parenthesis, unless it's {@code null} or * {@code ()}, in which case return it verbatim. */ private @Nullable String wrap(@Nullable String arg) { if (TextUtils.isEmpty(arg)) { return arg; } else { return "(" + arg + ")"; } } }
core/java/android/text/Layout.java +164 −6 File changed.Preview size limit exceeded, changes collapsed. Show changes
core/java/android/text/TextLine.java +92 −0 Original line number Diff line number Diff line Loading @@ -338,6 +338,98 @@ class TextLine { return h; } /** * @see #measure(int, boolean, FontMetricsInt) * @return The measure results for all possible offsets */ float[] measureAllOffsets(boolean[] trailing, FontMetricsInt fmi) { float[] measurement = new float[mLen + 1]; int[] target = new int[mLen + 1]; for (int offset = 0; offset < target.length; ++offset) { target[offset] = trailing[offset] ? offset - 1 : offset; } if (target[0] < 0) { measurement[0] = 0; } float h = 0; if (!mHasTabs) { if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) { for (int offset = 0; offset <= mLen; ++offset) { measurement[offset] = measureRun(0, offset, mLen, false, fmi); } return measurement; } if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) { for (int offset = 0; offset <= mLen; ++offset) { measurement[offset] = measureRun(0, offset, mLen, true, fmi); } return measurement; } } char[] chars = mChars; int[] runs = mDirections.mDirections; for (int i = 0; i < runs.length; i += 2) { int runStart = runs[i]; int runLimit = runStart + (runs[i + 1] & Layout.RUN_LENGTH_MASK); if (runLimit > mLen) { runLimit = mLen; } boolean runIsRtl = (runs[i + 1] & Layout.RUN_RTL_FLAG) != 0; int segstart = runStart; for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; ++j) { int codept = 0; if (mHasTabs && j < runLimit) { codept = chars[j]; if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) { codept = Character.codePointAt(chars, j); if (codept > 0xFFFF) { ++j; continue; } } } if (j == runLimit || codept == '\t') { float oldh = h; boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl; float w = measureRun(segstart, j, j, runIsRtl, fmi); h += advance ? w : -w; float baseh = advance ? oldh : h; FontMetricsInt crtfmi = advance ? fmi : null; for (int offset = segstart; offset <= j && offset <= mLen; ++offset) { if (target[offset] >= segstart && target[offset] < j) { measurement[offset] = baseh + measureRun(segstart, offset, j, runIsRtl, crtfmi); } } if (codept == '\t') { if (target[j] == j) { measurement[j] = h; } h = mDir * nextTab(h * mDir); if (target[j + 1] == j) { measurement[j + 1] = h; } } segstart = j + 1; } } } if (target[mLen] == mLen) { measurement[mLen] = h; } return measurement; } /** * Draws a unidirectional (but possibly multi-styled) run of text. * Loading