Snap for 6439596 from ddb62c707a0be7353e5d5c14d40cd2b83e10638a to qt-aml-tzdata-release
Change-Id: I904979de6266fc7608c8c40373928a8f3a816157
diff --git a/OWNERS b/OWNERS
index bce6270..92458db 100644
--- a/OWNERS
+++ b/OWNERS
@@ -10,4 +10,3 @@
tgunn@google.com
breadley@google.com
nazaninb@google.com
-sarahchin@google.com
diff --git a/TEST_MAPPING b/TEST_MAPPING
deleted file mode 100644
index ee02cd3..0000000
--- a/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "presubmit": [
- {
- "name": "CtsTelephonyProviderTestCases"
- }
- ]
-}
diff --git a/assets/carrier_list.pb b/assets/carrier_list.pb
index 0c5e5b9..3519d2c 100644
--- a/assets/carrier_list.pb
+++ b/assets/carrier_list.pb
Binary files differ
diff --git a/assets/carrier_list.textpb b/assets/carrier_list.textpb
index ab01907..b1c6825 100644
--- a/assets/carrier_list.textpb
+++ b/assets/carrier_list.textpb
Binary files differ
diff --git a/assets/sdk29_carrier_id/carrier_list.pb b/assets/sdk29_carrier_id/carrier_list.pb
deleted file mode 100644
index 125de1a..0000000
--- a/assets/sdk29_carrier_id/carrier_list.pb
+++ /dev/null
Binary files differ
diff --git a/assets/sdk29_carrier_id/carrier_list.textpb b/assets/sdk29_carrier_id/carrier_list.textpb
deleted file mode 100644
index 648571e..0000000
--- a/assets/sdk29_carrier_id/carrier_list.textpb
+++ /dev/null
Binary files differ
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 6cfcdfb..f3baf24 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" product="tablet" msgid="9194799012395299737">"Configuration du réseau mobile"</string>
- <string name="app_label" product="default" msgid="8338087656149558019">"Stockage tél. et SMS/MMS"</string>
+ <string name="app_label" product="default" msgid="8338087656149558019">"Téléphone et stockage des messages"</string>
</resources>
diff --git a/src/com/android/providers/telephony/CellBroadcastProvider.java b/src/com/android/providers/telephony/CellBroadcastProvider.java
index ccb3f4a..4c8a7e2 100644
--- a/src/com/android/providers/telephony/CellBroadcastProvider.java
+++ b/src/com/android/providers/telephony/CellBroadcastProvider.java
@@ -61,7 +61,7 @@
private static final String DATABASE_NAME = "cellbroadcasts.db";
/** Database version. */
- private static final int DATABASE_VERSION = 2;
+ private static final int DATABASE_VERSION = 1;
/** URI matcher for ContentProvider queries. */
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
@@ -243,7 +243,6 @@
return "CREATE TABLE " + tableName + " ("
+ CellBroadcasts._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ CellBroadcasts.SUB_ID + " INTEGER,"
- + CellBroadcasts.SLOT_INDEX + " INTEGER DEFAULT 0,"
+ CellBroadcasts.GEOGRAPHICAL_SCOPE + " INTEGER,"
+ CellBroadcasts.PLMN + " TEXT,"
+ CellBroadcasts.LAC + " INTEGER,"
@@ -300,16 +299,7 @@
}
@Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- if (DBG) {
- Log.d(TAG, "onUpgrade: oldV=" + oldVersion + " newV=" + newVersion);
- }
- if (newVersion == 2) {
- db.execSQL("ALTER TABLE " + CELL_BROADCASTS_TABLE_NAME + " ADD COLUMN "
- + CellBroadcasts.SLOT_INDEX + " INTEGER DEFAULT 0;");
- Log.d(TAG, "add slotIndex column");
- }
- }
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
}
private class CellBroadcastPermissionChecker implements PermissionChecker {
diff --git a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
index 4add85d..7ecfc46 100644
--- a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
+++ b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
@@ -17,7 +17,6 @@
package com.android.providers.telephony;
import static android.provider.Telephony.RcsColumns.IS_RCS_TABLE_SCHEMA_CODE_COMPLETE;
-import static com.android.internal.telephony.SmsResponse.NO_ERROR_CODE;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
@@ -546,7 +545,7 @@
intent.putExtra(Intents.EXTRA_IS_INITIAL_CREATE, true);
}
- mContext.sendBroadcast(intent, android.Manifest.permission.READ_SMS);
+ mContext.sendBroadcast(intent);
}
createMmsTables(db);
createSmsTables(db);
@@ -1013,7 +1012,7 @@
"service_center TEXT," +
"locked INTEGER DEFAULT 0," +
"sub_id INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID + ", " +
- "error_code INTEGER DEFAULT " + NO_ERROR_CODE + ", " +
+ "error_code INTEGER DEFAULT 0," +
"creator TEXT," +
"seen INTEGER DEFAULT 0" +
");";
@@ -1817,7 +1816,7 @@
private void upgradeDatabaseToVersion48(SQLiteDatabase db) {
// Add 'error_code' column to sms table.
- db.execSQL("ALTER TABLE sms ADD COLUMN error_code INTEGER DEFAULT " + NO_ERROR_CODE);
+ db.execSQL("ALTER TABLE sms ADD COLUMN error_code INTEGER DEFAULT 0");
}
private void upgradeDatabaseToVersion51(SQLiteDatabase db) {
diff --git a/src/com/android/providers/telephony/MmsSmsProvider.java b/src/com/android/providers/telephony/MmsSmsProvider.java
index 1170aad..dbf85ae 100644
--- a/src/com/android/providers/telephony/MmsSmsProvider.java
+++ b/src/com/android/providers/telephony/MmsSmsProvider.java
@@ -305,7 +305,6 @@
private SQLiteOpenHelper mOpenHelper;
private boolean mUseStrictPhoneNumberComparation;
- private int mMinMatch;
private static final String METHOD_IS_RESTORING = "is_restoring";
private static final String IS_RESTORING_KEY = "restoring";
@@ -317,9 +316,6 @@
mUseStrictPhoneNumberComparation =
getContext().getResources().getBoolean(
com.android.internal.R.bool.config_use_strict_phone_number_comparation);
- mMinMatch =
- getContext().getResources().getInteger(
- com.android.internal.R.integer.config_phonenumber_compare_min_match);
TelephonyBackupAgent.DeferredSmsMmsRestoreService.startIfFilesExist(getContext());
return true;
}
@@ -336,6 +332,16 @@
final String pduTable = MmsProvider.getPduTable(accessRestricted);
final String smsTable = SmsProvider.getSmsTable(accessRestricted);
+ // If access is restricted, we don't allow subqueries in the query.
+ if (accessRestricted) {
+ try {
+ SqlQueryChecker.checkQueryParametersForSubqueries(projection, selection, sortOrder);
+ } catch (IllegalArgumentException e) {
+ Log.w(LOG_TAG, "Query rejected: " + e.getMessage());
+ return null;
+ }
+ }
+
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
Cursor cursor = null;
final int match = URI_MATCHER.match(uri);
@@ -546,7 +552,7 @@
selectionArgs = new String[] { refinedAddress };
} else {
selection += " OR PHONE_NUMBERS_EQUAL(address, ?, " +
- (mUseStrictPhoneNumberComparation ? "1)" : "0, " + mMinMatch + ")");
+ (mUseStrictPhoneNumberComparation ? 1 : 0) + ")";
selectionArgs = new String[] { refinedAddress, refinedAddress };
}
@@ -1001,14 +1007,13 @@
* FROM pdu, (SELECT msg_id AS address_msg_id
* FROM addr
* WHERE (address='<phoneNumber>' OR
- * PHONE_NUMBERS_EQUAL(addr.address, '<phoneNumber>', 1/0, none/mMinMatch)))
+ * PHONE_NUMBERS_EQUAL(addr.address, '<phoneNumber>', 1/0)))
* AS matching_addresses
* WHERE pdu._id = matching_addresses.address_msg_id
* UNION
* SELECT ...
* FROM sms
- * WHERE (address='<phoneNumber>' OR
- * PHONE_NUMBERS_EQUAL(sms.address, '<phoneNumber>', 1/0, none/mMinMatch));
+ * WHERE (address='<phoneNumber>' OR PHONE_NUMBERS_EQUAL(sms.address, '<phoneNumber>', 1/0));
*/
private Cursor getMessagesByPhoneNumber(
String phoneNumber, String[] projection, String selection,
@@ -1023,7 +1028,7 @@
selection,
"(address=" + escapedPhoneNumber + " OR PHONE_NUMBERS_EQUAL(address, " +
escapedPhoneNumber +
- (mUseStrictPhoneNumberComparation ? ", 1))" : ", 0, " + mMinMatch + "))"));
+ (mUseStrictPhoneNumberComparation ? ", 1))" : ", 0))"));
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
@@ -1035,7 +1040,7 @@
"FROM addr WHERE (address=" + escapedPhoneNumber +
" OR PHONE_NUMBERS_EQUAL(addr.address, " +
escapedPhoneNumber +
- (mUseStrictPhoneNumberComparation ? ", 1))) " : ", 0, " + mMinMatch + "))) ") +
+ (mUseStrictPhoneNumberComparation ? ", 1))) " : ", 0))) ") +
"AS matching_addresses");
smsQueryBuilder.setTables(smsTable);
diff --git a/src/com/android/providers/telephony/SQLiteTokenizer.java b/src/com/android/providers/telephony/SQLiteTokenizer.java
new file mode 100644
index 0000000..39749fb
--- /dev/null
+++ b/src/com/android/providers/telephony/SQLiteTokenizer.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2019 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.providers.telephony;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.function.Consumer;
+
+/**
+ * Copied from frameworks/base/core/java/android/database/sqlite/SQLiteTokenizer.java
+ *
+ * SQL Tokenizer specialized to extract tokens from SQL (snippets).
+ * <p>
+ * Based on sqlite3GetToken() in tokenzie.c in SQLite.
+ * <p>
+ * Source for v3.8.6 (which android uses): http://www.sqlite.org/src/artifact/ae45399d6252b4d7
+ * (Latest source as of now: http://www.sqlite.org/src/artifact/78c8085bc7af1922)
+ * <p>
+ * Also draft spec: http://www.sqlite.org/draft/tokenreq.html
+ */
+public class SQLiteTokenizer {
+ private static boolean isAlpha(char ch) {
+ return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || (ch == '_');
+ }
+
+ private static boolean isNum(char ch) {
+ return ('0' <= ch && ch <= '9');
+ }
+
+ private static boolean isAlNum(char ch) {
+ return isAlpha(ch) || isNum(ch);
+ }
+
+ private static boolean isAnyOf(char ch, String set) {
+ return set.indexOf(ch) >= 0;
+ }
+
+ private static IllegalArgumentException genException(String message, String sql) {
+ throw new IllegalArgumentException(message + " in '" + sql + "'");
+ }
+
+ private static char peek(String s, int index) {
+ return index < s.length() ? s.charAt(index) : '\0';
+ }
+
+ public static final int OPTION_NONE = 0;
+
+ /**
+ * Require that SQL contains only tokens; any comments or values will result
+ * in an exception.
+ */
+ public static final int OPTION_TOKEN_ONLY = 1 << 0;
+
+ /**
+ * Tokenize the given SQL, returning the list of each encountered token.
+ *
+ * @throws IllegalArgumentException if invalid SQL is encountered.
+ */
+ public static List<String> tokenize(@Nullable String sql, int options) {
+ final ArrayList<String> res = new ArrayList<>();
+ tokenize(sql, options, res::add);
+ return res;
+ }
+
+ /**
+ * Tokenize the given SQL, sending each encountered token to the given
+ * {@link Consumer}.
+ *
+ * @throws IllegalArgumentException if invalid SQL is encountered.
+ */
+ public static void tokenize(@Nullable String sql, int options, Consumer<String> checker) {
+ if (sql == null) {
+ return;
+ }
+ int pos = 0;
+ final int len = sql.length();
+ while (pos < len) {
+ final char ch = peek(sql, pos);
+
+ // Regular token.
+ if (isAlpha(ch)) {
+ final int start = pos;
+ pos++;
+ while (isAlNum(peek(sql, pos))) {
+ pos++;
+ }
+ final int end = pos;
+
+ final String token = sql.substring(start, end);
+ checker.accept(token);
+
+ continue;
+ }
+
+ // Handle quoted tokens
+ if (isAnyOf(ch, "'\"`")) {
+ final int quoteStart = pos;
+ pos++;
+
+ for (;;) {
+ pos = sql.indexOf(ch, pos);
+ if (pos < 0) {
+ throw genException("Unterminated quote", sql);
+ }
+ if (peek(sql, pos + 1) != ch) {
+ break;
+ }
+ // Quoted quote char -- e.g. "abc""def" is a single string.
+ pos += 2;
+ }
+ final int quoteEnd = pos;
+ pos++;
+
+ if (ch != '\'') {
+ // Extract the token
+ final String tokenUnquoted = sql.substring(quoteStart + 1, quoteEnd);
+
+ final String token;
+
+ // Unquote if needed. i.e. "aa""bb" -> aa"bb
+ if (tokenUnquoted.indexOf(ch) >= 0) {
+ token = tokenUnquoted.replaceAll(
+ String.valueOf(ch) + ch, String.valueOf(ch));
+ } else {
+ token = tokenUnquoted;
+ }
+ checker.accept(token);
+ } else {
+ if ((options &= OPTION_TOKEN_ONLY) != 0) {
+ throw genException("Non-token detected", sql);
+ }
+ }
+ continue;
+ }
+ // Handle tokens enclosed in [...]
+ if (ch == '[') {
+ final int quoteStart = pos;
+ pos++;
+
+ pos = sql.indexOf(']', pos);
+ if (pos < 0) {
+ throw genException("Unterminated quote", sql);
+ }
+ final int quoteEnd = pos;
+ pos++;
+
+ final String token = sql.substring(quoteStart + 1, quoteEnd);
+
+ checker.accept(token);
+ continue;
+ }
+ if ((options &= OPTION_TOKEN_ONLY) != 0) {
+ throw genException("Non-token detected", sql);
+ }
+
+ // Detect comments.
+ if (ch == '-' && peek(sql, pos + 1) == '-') {
+ pos += 2;
+ pos = sql.indexOf('\n', pos);
+ if (pos < 0) {
+ // We disallow strings ending in an inline comment.
+ throw genException("Unterminated comment", sql);
+ }
+ pos++;
+
+ continue;
+ }
+ if (ch == '/' && peek(sql, pos + 1) == '*') {
+ pos += 2;
+ pos = sql.indexOf("*/", pos);
+ if (pos < 0) {
+ throw genException("Unterminated comment", sql);
+ }
+ pos += 2;
+
+ continue;
+ }
+
+ // Semicolon is never allowed.
+ if (ch == ';') {
+ throw genException("Semicolon is not allowed", sql);
+ }
+
+ // For this purpose, we can simply ignore other characters.
+ // (Note it doesn't handle the X'' literal properly and reports this X as a token,
+ // but that should be fine...)
+ pos++;
+ }
+ }
+
+ /**
+ * Test if given token is a
+ * <a href="https://www.sqlite.org/lang_keywords.html">SQLite reserved
+ * keyword</a>.
+ */
+ public static boolean isKeyword(@NonNull String token) {
+ switch (token.toUpperCase(Locale.US)) {
+ case "ABORT": case "ACTION": case "ADD": case "AFTER":
+ case "ALL": case "ALTER": case "ANALYZE": case "AND":
+ case "AS": case "ASC": case "ATTACH": case "AUTOINCREMENT":
+ case "BEFORE": case "BEGIN": case "BETWEEN": case "BINARY":
+ case "BY": case "CASCADE": case "CASE": case "CAST":
+ case "CHECK": case "COLLATE": case "COLUMN": case "COMMIT":
+ case "CONFLICT": case "CONSTRAINT": case "CREATE": case "CROSS":
+ case "CURRENT": case "CURRENT_DATE": case "CURRENT_TIME": case "CURRENT_TIMESTAMP":
+ case "DATABASE": case "DEFAULT": case "DEFERRABLE": case "DEFERRED":
+ case "DELETE": case "DESC": case "DETACH": case "DISTINCT":
+ case "DO": case "DROP": case "EACH": case "ELSE":
+ case "END": case "ESCAPE": case "EXCEPT": case "EXCLUDE":
+ case "EXCLUSIVE": case "EXISTS": case "EXPLAIN": case "FAIL":
+ case "FILTER": case "FOLLOWING": case "FOR": case "FOREIGN":
+ case "FROM": case "FULL": case "GLOB": case "GROUP":
+ case "GROUPS": case "HAVING": case "IF": case "IGNORE":
+ case "IMMEDIATE": case "IN": case "INDEX": case "INDEXED":
+ case "INITIALLY": case "INNER": case "INSERT": case "INSTEAD":
+ case "INTERSECT": case "INTO": case "IS": case "ISNULL":
+ case "JOIN": case "KEY": case "LEFT": case "LIKE":
+ case "LIMIT": case "MATCH": case "NATURAL": case "NO":
+ case "NOCASE": case "NOT": case "NOTHING": case "NOTNULL":
+ case "NULL": case "OF": case "OFFSET": case "ON":
+ case "OR": case "ORDER": case "OTHERS": case "OUTER":
+ case "OVER": case "PARTITION": case "PLAN": case "PRAGMA":
+ case "PRECEDING": case "PRIMARY": case "QUERY": case "RAISE":
+ case "RANGE": case "RECURSIVE": case "REFERENCES": case "REGEXP":
+ case "REINDEX": case "RELEASE": case "RENAME": case "REPLACE":
+ case "RESTRICT": case "RIGHT": case "ROLLBACK": case "ROW":
+ case "ROWS": case "RTRIM": case "SAVEPOINT": case "SELECT":
+ case "SET": case "TABLE": case "TEMP": case "TEMPORARY":
+ case "THEN": case "TIES": case "TO": case "TRANSACTION":
+ case "TRIGGER": case "UNBOUNDED": case "UNION": case "UNIQUE":
+ case "UPDATE": case "USING": case "VACUUM": case "VALUES":
+ case "VIEW": case "VIRTUAL": case "WHEN": case "WHERE":
+ case "WINDOW": case "WITH": case "WITHOUT":
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Test if given token is a
+ * <a href="https://www.sqlite.org/lang_corefunc.html">SQLite reserved
+ * function</a>.
+ */
+ public static boolean isFunction(@NonNull String token) {
+ switch (token.toLowerCase(Locale.US)) {
+ case "abs": case "avg": case "char": case "coalesce":
+ case "count": case "glob": case "group_concat": case "hex":
+ case "ifnull": case "instr": case "length": case "like":
+ case "likelihood": case "likely": case "lower": case "ltrim":
+ case "max": case "min": case "nullif": case "random":
+ case "randomblob": case "replace": case "round": case "rtrim":
+ case "substr": case "sum": case "total": case "trim":
+ case "typeof": case "unicode": case "unlikely": case "upper":
+ case "zeroblob":
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Test if given token is a
+ * <a href="https://www.sqlite.org/datatype3.html">SQLite reserved type</a>.
+ */
+ public static boolean isType(@NonNull String token) {
+ switch (token.toUpperCase(Locale.US)) {
+ case "INT": case "INTEGER": case "TINYINT": case "SMALLINT":
+ case "MEDIUMINT": case "BIGINT": case "INT2": case "INT8":
+ case "CHARACTER": case "VARCHAR": case "NCHAR": case "NVARCHAR":
+ case "TEXT": case "CLOB": case "BLOB": case "REAL":
+ case "DOUBLE": case "FLOAT": case "NUMERIC": case "DECIMAL":
+ case "BOOLEAN": case "DATE": case "DATETIME":
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/src/com/android/providers/telephony/SmsProvider.java b/src/com/android/providers/telephony/SmsProvider.java
index ddfa14c..1213c37 100644
--- a/src/com/android/providers/telephony/SmsProvider.java
+++ b/src/com/android/providers/telephony/SmsProvider.java
@@ -16,8 +16,6 @@
package com.android.providers.telephony;
-import static com.android.internal.telephony.SmsResponse.NO_ERROR_CODE;
-
import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.content.ContentProvider;
@@ -77,7 +75,7 @@
// N.B.: These columns must appear in the same order as the
// calls to add appear in convertIccToSms.
"service_center_address", // getServiceCenterAddress
- "address", // getDisplayOriginatingAddress or getRecipientAddress
+ "address", // getDisplayOriginatingAddress
"message_class", // getMessageClass
"body", // getDisplayMessageBody
"date", // getTimestampMillis
@@ -85,9 +83,9 @@
"index_on_icc", // getIndexOnIcc
"is_status_report", // isStatusReportMessage
"transport_type", // Always "sms".
- "type", // depend on getStatusOnIcc
+ "type", // Always MESSAGE_TYPE_ALL.
"locked", // Always 0 (false).
- "error_code", // Always -1 (NO_ERROR_CODE), previously it was 0 always.
+ "error_code", // Always 0
"_id"
};
@@ -124,6 +122,16 @@
final String smsTable = getSmsTable(accessRestricted);
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ // If access is restricted, we don't allow subqueries in the query.
+ if (accessRestricted) {
+ try {
+ SqlQueryChecker.checkQueryParametersForSubqueries(projectionIn, selection, sort);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Query rejected: " + e.getMessage());
+ return null;
+ }
+ }
+
// Generate the body of the query.
int match = sURLMatcher.match(url);
SQLiteDatabase db = getReadableDatabase(match);
@@ -294,39 +302,21 @@
}
private Object[] convertIccToSms(SmsMessage message, int id) {
- int statusOnIcc = message.getStatusOnIcc();
- int type = Sms.MESSAGE_TYPE_ALL;
- switch (statusOnIcc) {
- case SmsManager.STATUS_ON_ICC_READ:
- case SmsManager.STATUS_ON_ICC_UNREAD:
- type = Sms.MESSAGE_TYPE_INBOX;
- break;
- case SmsManager.STATUS_ON_ICC_SENT:
- type = Sms.MESSAGE_TYPE_SENT;
- break;
- case SmsManager.STATUS_ON_ICC_UNSENT:
- type = Sms.MESSAGE_TYPE_OUTBOX;
- break;
- }
// N.B.: These calls must appear in the same order as the
// columns appear in ICC_COLUMNS.
Object[] row = new Object[13];
row[0] = message.getServiceCenterAddress();
- row[1] =
- (type == Sms.MESSAGE_TYPE_INBOX)
- ? message.getDisplayOriginatingAddress()
- : message.getRecipientAddress();
-
+ row[1] = message.getDisplayOriginatingAddress();
row[2] = String.valueOf(message.getMessageClass());
row[3] = message.getDisplayMessageBody();
row[4] = message.getTimestampMillis();
- row[5] = statusOnIcc;
+ row[5] = Sms.STATUS_NONE;
row[6] = message.getIndexOnIcc();
row[7] = message.isStatusReportMessage();
row[8] = "sms";
- row[9] = type;
+ row[9] = TextBasedSmsColumns.MESSAGE_TYPE_ALL;
row[10] = 0; // locked
- row[11] = NO_ERROR_CODE;
+ row[11] = 0; // error_code
row[12] = id;
return row;
}
@@ -652,12 +642,7 @@
db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv);
}
if (rowID > 0) {
- Uri uri = null;
- if (table == TABLE_SMS) {
- uri = Uri.withAppendedPath(Sms.CONTENT_URI, String.valueOf(rowID));
- } else {
- uri = Uri.withAppendedPath(url, String.valueOf(rowID));
- }
+ Uri uri = Uri.withAppendedPath(url, String.valueOf(rowID));
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.d(TAG, "insert " + uri + " succeeded");
}
diff --git a/src/com/android/providers/telephony/SqlQueryChecker.java b/src/com/android/providers/telephony/SqlQueryChecker.java
new file mode 100644
index 0000000..a98dd25
--- /dev/null
+++ b/src/com/android/providers/telephony/SqlQueryChecker.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2019 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.providers.telephony;
+
+public class SqlQueryChecker {
+ private static final String SELECT_TOKEN = "select";
+
+ static void checkToken(String token) {
+ if (SELECT_TOKEN.equals(token.toLowerCase())) {
+ throw new IllegalArgumentException("SELECT token not allowed in query");
+ }
+ }
+
+ /**
+ * Check the query parameters to see if they contain subqueries. Throws an
+ * {@link IllegalArgumentException} if they do. See
+ * {@link android.content.ContentProvider#query} for the definitions of the arguments.
+ */
+ static void checkQueryParametersForSubqueries(String[] projection,
+ String selection, String sortOrder) {
+ if (projection != null) {
+ for (String proj : projection) {
+ SQLiteTokenizer.tokenize(proj, SQLiteTokenizer.OPTION_NONE,
+ SqlQueryChecker::checkToken);
+ }
+ }
+ SQLiteTokenizer.tokenize(selection, SQLiteTokenizer.OPTION_NONE,
+ SqlQueryChecker::checkToken);
+ SQLiteTokenizer.tokenize(sortOrder, SQLiteTokenizer.OPTION_NONE,
+ SqlQueryChecker::checkToken);
+ }
+}
diff --git a/src/com/android/providers/telephony/SqlTokenFinder.java b/src/com/android/providers/telephony/SqlTokenFinder.java
new file mode 100644
index 0000000..9be49fe
--- /dev/null
+++ b/src/com/android/providers/telephony/SqlTokenFinder.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2019 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.providers.telephony;
+
+import android.annotation.Nullable;
+
+import java.util.function.Consumer;
+
+/**
+ * Simple SQL parser to check statements for usage of prohibited/sensitive fields. Mostly copied
+ * from
+ * packages/providers/ContactsProvider/src/com/android/providers/contacts/sqlite/SqlChecker.java
+ */
+public class SqlTokenFinder{
+ private static boolean isAlpha(char ch) {
+ return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || (ch == '_');
+ }
+
+ private static boolean isNum(char ch) {
+ return ('0' <= ch && ch <= '9');
+ }
+
+ private static boolean isAlNum(char ch) {
+ return isAlpha(ch) || isNum(ch);
+ }
+
+ private static boolean isAnyOf(char ch, String set) {
+ return set.indexOf(ch) >= 0;
+ }
+
+ private static char peek(String s, int index) {
+ return index < s.length() ? s.charAt(index) : '\0';
+ }
+
+ /**
+ * SQL Tokenizer specialized to extract tokens from SQL (snippets).
+ *
+ * Based on sqlite3GetToken() in tokenzie.c in SQLite.
+ *
+ * Source for v3.8.6 (which android uses): http://www.sqlite.org/src/artifact/ae45399d6252b4d7
+ * (Latest source as of now: http://www.sqlite.org/src/artifact/78c8085bc7af1922)
+ *
+ * Also draft spec: http://www.sqlite.org/draft/tokenreq.html
+ */
+ public static void findTokens(@Nullable String sql, Consumer<String> checker) {
+ if (sql == null) {
+ return;
+ }
+ int pos = 0;
+ final int len = sql.length();
+ while (pos < len) {
+ final char ch = peek(sql, pos);
+
+ // Regular token.
+ if (isAlpha(ch)) {
+ final int start = pos;
+ pos++;
+ while (isAlNum(peek(sql, pos))) {
+ pos++;
+ }
+ final int end = pos;
+
+ final String token = sql.substring(start, end);
+ checker.accept(token);
+
+ continue;
+ }
+
+ // Handle quoted tokens
+ if (isAnyOf(ch, "'\"`")) {
+ final int quoteStart = pos;
+ pos++;
+
+ for (;;) {
+ pos = sql.indexOf(ch, pos);
+ if (pos < 0) {
+ throw new IllegalArgumentException("Unterminated quote in" + sql);
+ }
+ if (peek(sql, pos + 1) != ch) {
+ break;
+ }
+ // Quoted quote char -- e.g. "abc""def" is a single string.
+ pos += 2;
+ }
+ final int quoteEnd = pos;
+ pos++;
+
+ if (ch != '\'') {
+ // Extract the token
+ final String tokenUnquoted = sql.substring(quoteStart + 1, quoteEnd);
+
+ final String token;
+
+ // Unquote if needed. i.e. "aa""bb" -> aa"bb
+ if (tokenUnquoted.indexOf(ch) >= 0) {
+ token = tokenUnquoted.replaceAll(
+ String.valueOf(ch) + ch, String.valueOf(ch));
+ } else {
+ token = tokenUnquoted;
+ }
+ checker.accept(token);
+ }
+ continue;
+ }
+ // Handle tokens enclosed in [...]
+ if (ch == '[') {
+ final int quoteStart = pos;
+ pos++;
+
+ pos = sql.indexOf(']', pos);
+ if (pos < 0) {
+ throw new IllegalArgumentException("Unterminated quote in" + sql);
+ }
+ final int quoteEnd = pos;
+ pos++;
+
+ final String token = sql.substring(quoteStart + 1, quoteEnd);
+
+ checker.accept(token);
+ continue;
+ }
+
+ // Detect comments.
+ if (ch == '-' && peek(sql, pos + 1) == '-') {
+ pos += 2;
+ pos = sql.indexOf('\n', pos);
+ if (pos < 0) {
+ // We disallow strings ending in an inline comment.
+ throw new IllegalArgumentException("Unterminated comment in" + sql);
+ }
+ pos++;
+
+ continue;
+ }
+ if (ch == '/' && peek(sql, pos + 1) == '*') {
+ pos += 2;
+ pos = sql.indexOf("*/", pos);
+ if (pos < 0) {
+ throw new IllegalArgumentException("Unterminated comment in" + sql);
+ }
+ pos += 2;
+
+ continue;
+ }
+
+ // Semicolon is never allowed.
+ if (ch == ';') {
+ throw new IllegalArgumentException("Semicolon is not allowed in " + sql);
+ }
+
+ // For this purpose, we can simply ignore other characters.
+ // (Note it doesn't handle the X'' literal properly and reports this X as a token,
+ // but that should be fine...)
+ pos++;
+ }
+ }
+}
diff --git a/src/com/android/providers/telephony/TelephonyProvider.java b/src/com/android/providers/telephony/TelephonyProvider.java
index 0edbffc..c564a07 100644
--- a/src/com/android/providers/telephony/TelephonyProvider.java
+++ b/src/com/android/providers/telephony/TelephonyProvider.java
@@ -106,6 +106,7 @@
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
import android.text.TextUtils;
+import android.util.EventLog;
import android.util.Log;
import android.util.Pair;
import android.util.Xml;
@@ -206,6 +207,10 @@
private static final String DEFAULT_PROTOCOL = "IP";
private static final String DEFAULT_ROAMING_PROTOCOL = "IP";
+ // Used to check if certain queries contain subqueries that may attempt to access sensitive
+ // fields in the carriers db.
+ private static final String SQL_SELECT_TOKEN = "select";
+
private static final UriMatcher s_urlMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private static final ContentValues s_currentNullMap;
@@ -479,41 +484,7 @@
}
@VisibleForTesting
- public static int getVersion(Context context) {
- if (VDBG) log("getVersion:+");
- // Get the database version, combining a static schema version and the XML version
- Resources r = context.getResources();
- if (r == null) {
- loge("resources=null, return version=" + Integer.toHexString(DATABASE_VERSION));
- return DATABASE_VERSION;
- }
- XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
- try {
- XmlUtils.beginDocument(parser, "apns");
- int publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
- int version = DATABASE_VERSION | publicversion;
- if (VDBG) log("getVersion:- version=0x" + Integer.toHexString(version));
- return version;
- } catch (Exception e) {
- loge("Can't get version of APN database" + e + " return version=" +
- Integer.toHexString(DATABASE_VERSION));
- return DATABASE_VERSION;
- } finally {
- parser.close();
- }
- }
-
- static public ContentValues setDefaultValue(ContentValues values) {
- if (!values.containsKey(SUBSCRIPTION_ID)) {
- int subId = SubscriptionManager.getDefaultSubscriptionId();
- values.put(SUBSCRIPTION_ID, subId);
- }
-
- return values;
- }
-
- @VisibleForTesting
- public class DatabaseHelper extends SQLiteOpenHelper {
+ public static class DatabaseHelper extends SQLiteOpenHelper {
// Context to access resources with
private Context mContext;
@@ -530,6 +501,31 @@
setWriteAheadLoggingEnabled(false);
}
+ @VisibleForTesting
+ public static int getVersion(Context context) {
+ if (VDBG) log("getVersion:+");
+ // Get the database version, combining a static schema version and the XML version
+ Resources r = context.getResources();
+ if (r == null) {
+ loge("resources=null, return version=" + Integer.toHexString(DATABASE_VERSION));
+ return DATABASE_VERSION;
+ }
+ XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
+ try {
+ XmlUtils.beginDocument(parser, "apns");
+ int publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
+ int version = DATABASE_VERSION | publicversion;
+ if (VDBG) log("getVersion:- version=0x" + Integer.toHexString(version));
+ return version;
+ } catch (Exception e) {
+ loge("Can't get version of APN database" + e + " return version=" +
+ Integer.toHexString(DATABASE_VERSION));
+ return DATABASE_VERSION;
+ } finally {
+ parser.close();
+ }
+ }
+
@Override
public void onCreate(SQLiteDatabase db) {
if (DBG) log("dbh.onCreate:+ db=" + db);
@@ -609,7 +605,7 @@
return checksum;
}
- private byte[] toByteArray(InputStream input) throws IOException {
+ private static byte[] toByteArray(InputStream input) throws IOException {
byte[] buffer = new byte[128];
int bytesRead;
ByteArrayOutputStream output = new ByteArrayOutputStream();
@@ -767,8 +763,6 @@
log("dbh.onUpgrade:+ db=" + db + " oldV=" + oldVersion + " newV=" + newVersion);
}
- deletePreferredApnId(mContext);
-
if (oldVersion < (5 << 16 | 6)) {
// 5 << 16 is the Database version and 6 in the xml version.
@@ -1884,7 +1878,7 @@
e + " for cv " + cv);
// Insertion failed which could be due to a conflict. Check if that is
// the case and merge the entries
- Cursor oldRow = selectConflictingRow(db,
+ Cursor oldRow = DatabaseHelper.selectConflictingRow(db,
CARRIERS_TABLE_TMP, cv);
if (oldRow != null) {
ContentValues mergedValues = new ContentValues();
@@ -2117,6 +2111,15 @@
}
}
+ static public ContentValues setDefaultValue(ContentValues values) {
+ if (!values.containsKey(SUBSCRIPTION_ID)) {
+ int subId = SubscriptionManager.getDefaultSubscriptionId();
+ values.put(SUBSCRIPTION_ID, subId);
+ }
+
+ return values;
+ }
+
private void insertAddingDefaults(SQLiteDatabase db, ContentValues row) {
row = setDefaultValue(row);
try {
@@ -2159,264 +2162,264 @@
}
}
}
- }
- public static void mergeFieldsAndUpdateDb(SQLiteDatabase db, String table, Cursor oldRow,
- ContentValues newRow, ContentValues mergedValues,
- boolean onUpgrade, Context context) {
- if (newRow.containsKey(TYPE)) {
- // Merge the types
- String oldType = oldRow.getString(oldRow.getColumnIndex(TYPE));
- String newType = newRow.getAsString(TYPE);
+ public static void mergeFieldsAndUpdateDb(SQLiteDatabase db, String table, Cursor oldRow,
+ ContentValues newRow, ContentValues mergedValues,
+ boolean onUpgrade, Context context) {
+ if (newRow.containsKey(TYPE)) {
+ // Merge the types
+ String oldType = oldRow.getString(oldRow.getColumnIndex(TYPE));
+ String newType = newRow.getAsString(TYPE);
- if (!oldType.equalsIgnoreCase(newType)) {
- if (oldType.equals("") || newType.equals("")) {
- newRow.put(TYPE, "");
- } else {
- String[] oldTypes = oldType.toLowerCase().split(",");
- String[] newTypes = newType.toLowerCase().split(",");
+ if (!oldType.equalsIgnoreCase(newType)) {
+ if (oldType.equals("") || newType.equals("")) {
+ newRow.put(TYPE, "");
+ } else {
+ String[] oldTypes = oldType.toLowerCase().split(",");
+ String[] newTypes = newType.toLowerCase().split(",");
- if (VDBG) {
- log("mergeFieldsAndUpdateDb: Calling separateRowsNeeded() oldType=" +
- oldType + " old bearer=" + oldRow.getInt(oldRow.getColumnIndex(
- BEARER_BITMASK)) + " old networkType=" +
- oldRow.getInt(oldRow.getColumnIndex(NETWORK_TYPE_BITMASK)) +
- " old profile_id=" + oldRow.getInt(oldRow.getColumnIndex(
- PROFILE_ID)) + " newRow " + newRow);
- }
-
- // If separate rows are needed, do not need to merge any further
- if (separateRowsNeeded(db, table, oldRow, newRow, context, oldTypes,
- newTypes)) {
- if (VDBG) log("mergeFieldsAndUpdateDb: separateRowsNeeded() returned " +
- "true");
- return;
- }
-
- // Merge the 2 types
- ArrayList<String> mergedTypes = new ArrayList<String>();
- mergedTypes.addAll(Arrays.asList(oldTypes));
- for (String s : newTypes) {
- if (!mergedTypes.contains(s.trim())) {
- mergedTypes.add(s);
+ if (VDBG) {
+ log("mergeFieldsAndUpdateDb: Calling separateRowsNeeded() oldType=" +
+ oldType + " old bearer=" + oldRow.getInt(oldRow.getColumnIndex(
+ BEARER_BITMASK)) + " old networkType=" +
+ oldRow.getInt(oldRow.getColumnIndex(NETWORK_TYPE_BITMASK)) +
+ " old profile_id=" + oldRow.getInt(oldRow.getColumnIndex(
+ PROFILE_ID)) + " newRow " + newRow);
}
+
+ // If separate rows are needed, do not need to merge any further
+ if (separateRowsNeeded(db, table, oldRow, newRow, context, oldTypes,
+ newTypes)) {
+ if (VDBG) log("mergeFieldsAndUpdateDb: separateRowsNeeded() returned " +
+ "true");
+ return;
+ }
+
+ // Merge the 2 types
+ ArrayList<String> mergedTypes = new ArrayList<String>();
+ mergedTypes.addAll(Arrays.asList(oldTypes));
+ for (String s : newTypes) {
+ if (!mergedTypes.contains(s.trim())) {
+ mergedTypes.add(s);
+ }
+ }
+ StringBuilder mergedType = new StringBuilder();
+ for (int i = 0; i < mergedTypes.size(); i++) {
+ mergedType.append((i == 0 ? "" : ",") + mergedTypes.get(i));
+ }
+ newRow.put(TYPE, mergedType.toString());
}
- StringBuilder mergedType = new StringBuilder();
- for (int i = 0; i < mergedTypes.size(); i++) {
- mergedType.append((i == 0 ? "" : ",") + mergedTypes.get(i));
+ }
+ mergedValues.put(TYPE, newRow.getAsString(TYPE));
+ }
+
+ if (newRow.containsKey(BEARER_BITMASK)) {
+ int oldBearer = oldRow.getInt(oldRow.getColumnIndex(BEARER_BITMASK));
+ int newBearer = newRow.getAsInteger(BEARER_BITMASK);
+ if (oldBearer != newBearer) {
+ if (oldBearer == 0 || newBearer == 0) {
+ newRow.put(BEARER_BITMASK, 0);
+ } else {
+ newRow.put(BEARER_BITMASK, (oldBearer | newBearer));
}
- newRow.put(TYPE, mergedType.toString());
}
+ mergedValues.put(BEARER_BITMASK, newRow.getAsInteger(BEARER_BITMASK));
}
- mergedValues.put(TYPE, newRow.getAsString(TYPE));
+
+ if (newRow.containsKey(NETWORK_TYPE_BITMASK)) {
+ int oldBitmask = oldRow.getInt(oldRow.getColumnIndex(NETWORK_TYPE_BITMASK));
+ int newBitmask = newRow.getAsInteger(NETWORK_TYPE_BITMASK);
+ if (oldBitmask != newBitmask) {
+ if (oldBitmask == 0 || newBitmask == 0) {
+ newRow.put(NETWORK_TYPE_BITMASK, 0);
+ } else {
+ newRow.put(NETWORK_TYPE_BITMASK, (oldBitmask | newBitmask));
+ }
+ }
+ mergedValues.put(NETWORK_TYPE_BITMASK, newRow.getAsInteger(NETWORK_TYPE_BITMASK));
+ }
+
+ if (newRow.containsKey(BEARER_BITMASK)
+ && newRow.containsKey(NETWORK_TYPE_BITMASK)) {
+ syncBearerBitmaskAndNetworkTypeBitmask(mergedValues);
+ }
+
+ if (!onUpgrade) {
+ // Do not overwrite a carrier or user edit with EDITED=UNEDITED
+ if (newRow.containsKey(EDITED_STATUS)) {
+ int oldEdited = oldRow.getInt(oldRow.getColumnIndex(EDITED_STATUS));
+ int newEdited = newRow.getAsInteger(EDITED_STATUS);
+ if (newEdited == UNEDITED && (oldEdited == CARRIER_EDITED
+ || oldEdited == CARRIER_DELETED
+ || oldEdited == CARRIER_DELETED_BUT_PRESENT_IN_XML
+ || oldEdited == USER_EDITED
+ || oldEdited == USER_DELETED
+ || oldEdited == USER_DELETED_BUT_PRESENT_IN_XML)) {
+ newRow.remove(EDITED_STATUS);
+ }
+ }
+ mergedValues.putAll(newRow);
+ }
+
+ if (mergedValues.size() > 0) {
+ db.update(table, mergedValues, "_id=" + oldRow.getInt(oldRow.getColumnIndex("_id")),
+ null);
+ }
}
- if (newRow.containsKey(BEARER_BITMASK)) {
- int oldBearer = oldRow.getInt(oldRow.getColumnIndex(BEARER_BITMASK));
- int newBearer = newRow.getAsInteger(BEARER_BITMASK);
- if (oldBearer != newBearer) {
- if (oldBearer == 0 || newBearer == 0) {
- newRow.put(BEARER_BITMASK, 0);
+ private static boolean separateRowsNeeded(SQLiteDatabase db, String table, Cursor oldRow,
+ ContentValues newRow, Context context,
+ String[] oldTypes, String[] newTypes) {
+ // If this APN falls under persist_apns_for_plmn, and the
+ // only difference between old type and new type is that one has dun, and
+ // the APNs have profile_id 0 or not set, then set the profile_id to 1 for
+ // the dun APN/remove dun from type. This will ensure both oldRow and newRow exist
+ // separately in db.
+
+ boolean match = false;
+
+ // Check if APN falls under persist_apns_for_plmn
+ if (context.getResources() != null) {
+ String[] persistApnsForPlmns = context.getResources().getStringArray(
+ R.array.persist_apns_for_plmn);
+ for (String s : persistApnsForPlmns) {
+ if (s.equalsIgnoreCase(newRow.getAsString(NUMERIC))) {
+ match = true;
+ break;
+ }
+ }
+ } else {
+ loge("separateRowsNeeded: resources=null");
+ }
+
+ if (!match) return false;
+
+ // APN falls under persist_apns_for_plmn
+ // Check if only difference between old type and new type is that
+ // one has dun
+ ArrayList<String> oldTypesAl = new ArrayList<String>(Arrays.asList(oldTypes));
+ ArrayList<String> newTypesAl = new ArrayList<String>(Arrays.asList(newTypes));
+ ArrayList<String> listWithDun = null;
+ ArrayList<String> listWithoutDun = null;
+ boolean dunInOld = false;
+ if (oldTypesAl.size() == newTypesAl.size() + 1) {
+ listWithDun = oldTypesAl;
+ listWithoutDun = newTypesAl;
+ dunInOld = true;
+ } else if (oldTypesAl.size() + 1 == newTypesAl.size()) {
+ listWithDun = newTypesAl;
+ listWithoutDun = oldTypesAl;
+ } else {
+ return false;
+ }
+
+ if (listWithDun.contains("dun") && !listWithoutDun.contains("dun")) {
+ listWithoutDun.add("dun");
+ if (!listWithDun.containsAll(listWithoutDun)) {
+ return false;
+ }
+
+ // Only difference between old type and new type is that
+ // one has dun
+ // Check if profile_id is 0/not set
+ if (oldRow.getInt(oldRow.getColumnIndex(PROFILE_ID)) == 0) {
+ if (dunInOld) {
+ // Update oldRow to remove dun from its type field
+ ContentValues updateOldRow = new ContentValues();
+ StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for (String s : listWithDun) {
+ if (!s.equalsIgnoreCase("dun")) {
+ sb.append(first ? s : "," + s);
+ first = false;
+ }
+ }
+ String updatedType = sb.toString();
+ if (VDBG) {
+ log("separateRowsNeeded: updating type in oldRow to " + updatedType);
+ }
+ updateOldRow.put(TYPE, updatedType);
+ db.update(table, updateOldRow,
+ "_id=" + oldRow.getInt(oldRow.getColumnIndex("_id")), null);
+ return true;
+ } else {
+ if (VDBG) log("separateRowsNeeded: adding profile id 1 to newRow");
+ // Update newRow to set profile_id to 1
+ newRow.put(PROFILE_ID, new Integer(1));
+ }
} else {
- newRow.put(BEARER_BITMASK, (oldBearer | newBearer));
+ return false;
+ }
+
+ // If match was found, both oldRow and newRow need to exist
+ // separately in db. Add newRow to db.
+ try {
+ db.insertWithOnConflict(table, null, newRow, SQLiteDatabase.CONFLICT_REPLACE);
+ if (VDBG) log("separateRowsNeeded: added newRow with profile id 1 to db");
+ return true;
+ } catch (SQLException e) {
+ loge("Exception on trying to add new row after updating profile_id");
}
}
- mergedValues.put(BEARER_BITMASK, newRow.getAsInteger(BEARER_BITMASK));
- }
- if (newRow.containsKey(NETWORK_TYPE_BITMASK)) {
- int oldBitmask = oldRow.getInt(oldRow.getColumnIndex(NETWORK_TYPE_BITMASK));
- int newBitmask = newRow.getAsInteger(NETWORK_TYPE_BITMASK);
- if (oldBitmask != newBitmask) {
- if (oldBitmask == 0 || newBitmask == 0) {
- newRow.put(NETWORK_TYPE_BITMASK, 0);
- } else {
- newRow.put(NETWORK_TYPE_BITMASK, (oldBitmask | newBitmask));
- }
- }
- mergedValues.put(NETWORK_TYPE_BITMASK, newRow.getAsInteger(NETWORK_TYPE_BITMASK));
- }
-
- if (newRow.containsKey(BEARER_BITMASK)
- && newRow.containsKey(NETWORK_TYPE_BITMASK)) {
- syncBearerBitmaskAndNetworkTypeBitmask(mergedValues);
- }
-
- if (!onUpgrade) {
- // Do not overwrite a carrier or user edit with EDITED=UNEDITED
- if (newRow.containsKey(EDITED_STATUS)) {
- int oldEdited = oldRow.getInt(oldRow.getColumnIndex(EDITED_STATUS));
- int newEdited = newRow.getAsInteger(EDITED_STATUS);
- if (newEdited == UNEDITED && (oldEdited == CARRIER_EDITED
- || oldEdited == CARRIER_DELETED
- || oldEdited == CARRIER_DELETED_BUT_PRESENT_IN_XML
- || oldEdited == USER_EDITED
- || oldEdited == USER_DELETED
- || oldEdited == USER_DELETED_BUT_PRESENT_IN_XML)) {
- newRow.remove(EDITED_STATUS);
- }
- }
- mergedValues.putAll(newRow);
- }
-
- if (mergedValues.size() > 0) {
- db.update(table, mergedValues, "_id=" + oldRow.getInt(oldRow.getColumnIndex("_id")),
- null);
- }
- }
-
- private static boolean separateRowsNeeded(SQLiteDatabase db, String table, Cursor oldRow,
- ContentValues newRow, Context context,
- String[] oldTypes, String[] newTypes) {
- // If this APN falls under persist_apns_for_plmn, and the
- // only difference between old type and new type is that one has dun, and
- // the APNs have profile_id 0 or not set, then set the profile_id to 1 for
- // the dun APN/remove dun from type. This will ensure both oldRow and newRow exist
- // separately in db.
-
- boolean match = false;
-
- // Check if APN falls under persist_apns_for_plmn
- if (context.getResources() != null) {
- String[] persistApnsForPlmns = context.getResources().getStringArray(
- R.array.persist_apns_for_plmn);
- for (String s : persistApnsForPlmns) {
- if (s.equalsIgnoreCase(newRow.getAsString(NUMERIC))) {
- match = true;
- break;
- }
- }
- } else {
- loge("separateRowsNeeded: resources=null");
- }
-
- if (!match) return false;
-
- // APN falls under persist_apns_for_plmn
- // Check if only difference between old type and new type is that
- // one has dun
- ArrayList<String> oldTypesAl = new ArrayList<String>(Arrays.asList(oldTypes));
- ArrayList<String> newTypesAl = new ArrayList<String>(Arrays.asList(newTypes));
- ArrayList<String> listWithDun = null;
- ArrayList<String> listWithoutDun = null;
- boolean dunInOld = false;
- if (oldTypesAl.size() == newTypesAl.size() + 1) {
- listWithDun = oldTypesAl;
- listWithoutDun = newTypesAl;
- dunInOld = true;
- } else if (oldTypesAl.size() + 1 == newTypesAl.size()) {
- listWithDun = newTypesAl;
- listWithoutDun = oldTypesAl;
- } else {
return false;
}
- if (listWithDun.contains("dun") && !listWithoutDun.contains("dun")) {
- listWithoutDun.add("dun");
- if (!listWithDun.containsAll(listWithoutDun)) {
- return false;
+ public static Cursor selectConflictingRow(SQLiteDatabase db, String table,
+ ContentValues row) {
+ // Conflict is possible only when numeric, mcc, mnc (fields without any default value)
+ // are set in the new row
+ if (!row.containsKey(NUMERIC) || !row.containsKey(MCC) || !row.containsKey(MNC)) {
+ loge("dbh.selectConflictingRow: called for non-conflicting row: " + row);
+ return null;
}
- // Only difference between old type and new type is that
- // one has dun
- // Check if profile_id is 0/not set
- if (oldRow.getInt(oldRow.getColumnIndex(PROFILE_ID)) == 0) {
- if (dunInOld) {
- // Update oldRow to remove dun from its type field
- ContentValues updateOldRow = new ContentValues();
- StringBuilder sb = new StringBuilder();
- boolean first = true;
- for (String s : listWithDun) {
- if (!s.equalsIgnoreCase("dun")) {
- sb.append(first ? s : "," + s);
- first = false;
- }
- }
- String updatedType = sb.toString();
- if (VDBG) {
- log("separateRowsNeeded: updating type in oldRow to " + updatedType);
- }
- updateOldRow.put(TYPE, updatedType);
- db.update(table, updateOldRow,
- "_id=" + oldRow.getInt(oldRow.getColumnIndex("_id")), null);
- return true;
+ String[] columns = { "_id",
+ TYPE,
+ EDITED_STATUS,
+ BEARER_BITMASK,
+ NETWORK_TYPE_BITMASK,
+ PROFILE_ID };
+ String selection = TextUtils.join("=? AND ", CARRIERS_UNIQUE_FIELDS) + "=?";
+ int i = 0;
+ String[] selectionArgs = new String[CARRIERS_UNIQUE_FIELDS.size()];
+ for (String field : CARRIERS_UNIQUE_FIELDS) {
+ if (!row.containsKey(field)) {
+ selectionArgs[i++] = CARRIERS_UNIQUE_FIELDS_DEFAULTS.get(field);
} else {
- if (VDBG) log("separateRowsNeeded: adding profile id 1 to newRow");
- // Update newRow to set profile_id to 1
- newRow.put(PROFILE_ID, new Integer(1));
+ if (CARRIERS_BOOLEAN_FIELDS.contains(field)) {
+ // for boolean fields we overwrite the strings "true" and "false" with "1"
+ // and "0"
+ selectionArgs[i++] = convertStringToIntString(row.getAsString(field));
+ } else {
+ selectionArgs[i++] = row.getAsString(field);
+ }
}
+ }
+
+ Cursor c = db.query(table, columns, selection, selectionArgs, null, null, null);
+
+ if (c != null) {
+ if (c.getCount() == 1) {
+ if (VDBG) log("dbh.selectConflictingRow: " + c.getCount() + " conflicting " +
+ "row found");
+ if (c.moveToFirst()) {
+ return c;
+ } else {
+ loge("dbh.selectConflictingRow: moveToFirst() failed");
+ }
+ } else {
+ loge("dbh.selectConflictingRow: Expected 1 but found " + c.getCount() +
+ " matching rows found for cv " + row);
+ }
+ c.close();
} else {
- return false;
+ loge("dbh.selectConflictingRow: Error - c is null; no matching row found for " +
+ "cv " + row);
}
- // If match was found, both oldRow and newRow need to exist
- // separately in db. Add newRow to db.
- try {
- db.insertWithOnConflict(table, null, newRow, SQLiteDatabase.CONFLICT_REPLACE);
- if (VDBG) log("separateRowsNeeded: added newRow with profile id 1 to db");
- return true;
- } catch (SQLException e) {
- loge("Exception on trying to add new row after updating profile_id");
- }
- }
-
- return false;
- }
-
- public static Cursor selectConflictingRow(SQLiteDatabase db, String table,
- ContentValues row) {
- // Conflict is possible only when numeric, mcc, mnc (fields without any default value)
- // are set in the new row
- if (!row.containsKey(NUMERIC) || !row.containsKey(MCC) || !row.containsKey(MNC)) {
- loge("dbh.selectConflictingRow: called for non-conflicting row: " + row);
return null;
}
-
- String[] columns = { "_id",
- TYPE,
- EDITED_STATUS,
- BEARER_BITMASK,
- NETWORK_TYPE_BITMASK,
- PROFILE_ID };
- String selection = TextUtils.join("=? AND ", CARRIERS_UNIQUE_FIELDS) + "=?";
- int i = 0;
- String[] selectionArgs = new String[CARRIERS_UNIQUE_FIELDS.size()];
- for (String field : CARRIERS_UNIQUE_FIELDS) {
- if (!row.containsKey(field)) {
- selectionArgs[i++] = CARRIERS_UNIQUE_FIELDS_DEFAULTS.get(field);
- } else {
- if (CARRIERS_BOOLEAN_FIELDS.contains(field)) {
- // for boolean fields we overwrite the strings "true" and "false" with "1"
- // and "0"
- selectionArgs[i++] = convertStringToIntString(row.getAsString(field));
- } else {
- selectionArgs[i++] = row.getAsString(field);
- }
- }
- }
-
- Cursor c = db.query(table, columns, selection, selectionArgs, null, null, null);
-
- if (c != null) {
- if (c.getCount() == 1) {
- if (VDBG) log("dbh.selectConflictingRow: " + c.getCount() + " conflicting " +
- "row found");
- if (c.moveToFirst()) {
- return c;
- } else {
- loge("dbh.selectConflictingRow: moveToFirst() failed");
- }
- } else {
- loge("dbh.selectConflictingRow: Expected 1 but found " + c.getCount() +
- " matching rows found for cv " + row);
- }
- c.close();
- } else {
- loge("dbh.selectConflictingRow: Error - c is null; no matching row found for " +
- "cv " + row);
- }
-
- return null;
}
/**
@@ -2569,6 +2572,25 @@
if (isNewBuild) {
if (!apnSourceServiceExists(getContext())) {
+ // Call getReadableDatabase() to make sure onUpgrade is called
+ if (VDBG) log("onCreate: calling getReadableDatabase to trigger onUpgrade");
+ SQLiteDatabase db = getReadableDatabase();
+
+ // Get rid of old preferred apn shared preferences
+ SubscriptionManager sm = SubscriptionManager.from(getContext());
+ if (sm != null) {
+ List<SubscriptionInfo> subInfoList = sm.getAllSubscriptionInfoList();
+ for (SubscriptionInfo subInfo : subInfoList) {
+ SharedPreferences spPrefFile = getContext().getSharedPreferences(
+ PREF_FILE_APN + subInfo.getSubscriptionId(), Context.MODE_PRIVATE);
+ if (spPrefFile != null) {
+ SharedPreferences.Editor editor = spPrefFile.edit();
+ editor.clear();
+ editor.apply();
+ }
+ }
+ }
+
// Update APN DB
updateApnDb();
}
@@ -2679,9 +2701,29 @@
}
}
- private void deletePreferredApnId(Context context) {
- SharedPreferences sp = context.getSharedPreferences(PREF_FILE_APN,
+ private void deletePreferredApnId() {
+ SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE_APN,
Context.MODE_PRIVATE);
+
+ // Before deleting, save actual preferred apns (not the ids) in a separate SP.
+ // NOTE: This code to call setPreferredApn() can be removed since the function is now called
+ // from setPreferredApnId(). However older builds (pre oc-mr1) do not have that change, so
+ // when devices upgrade from those builds and this function is called, this code is needed
+ // otherwise the preferred APN will be lost.
+ Map<String, ?> allPrefApnId = sp.getAll();
+ for (String key : allPrefApnId.keySet()) {
+ // extract subId from key by removing COLUMN_APN_ID
+ try {
+ int subId = Integer.parseInt(key.replace(COLUMN_APN_ID, ""));
+ long apnId = getPreferredApnId(subId, false);
+ if (apnId != INVALID_APN_ID) {
+ setPreferredApn(apnId, subId);
+ }
+ } catch (Exception e) {
+ loge("Skipping over key " + key + " due to exception " + e);
+ }
+ }
+
SharedPreferences.Editor editor = sp.edit();
editor.clear();
editor.apply();
@@ -2722,27 +2764,19 @@
private long getPreferredApnIdFromApn(int subId) {
log("getPreferredApnIdFromApn: for subId " + subId);
SQLiteDatabase db = getReadableDatabase();
-
- List<String> whereList = new ArrayList<>();
- List<String> whereArgsList = new ArrayList<>();
+ String where = TextUtils.join("=? and ", CARRIERS_UNIQUE_FIELDS) + "=?";
+ String[] whereArgs = new String[CARRIERS_UNIQUE_FIELDS.size()];
SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE_FULL_APN,
Context.MODE_PRIVATE);
- for (String key : CARRIERS_UNIQUE_FIELDS) {
- String value = sp.getString(key + subId, null);
- if (value == null) {
- continue;
- } else {
- whereList.add(key);
- whereArgsList.add(value);
- }
- }
- if (whereList.size() == 0) return INVALID_APN_ID;
-
- String where = TextUtils.join("=? and ", whereList) + "=?";
- String[] whereArgs = new String[whereArgsList.size()];
- whereArgs = whereArgsList.toArray(whereArgs);
-
long apnId = INVALID_APN_ID;
+ int i = 0;
+ for (String key : CARRIERS_UNIQUE_FIELDS) {
+ whereArgs[i] = sp.getString(key + subId, null);
+ if (whereArgs[i] == null) {
+ return INVALID_APN_ID;
+ }
+ i++;
+ }
Cursor c = db.query(CARRIERS_TABLE, new String[]{"_id"}, where, whereArgs, null, null,
null);
if (c != null) {
@@ -2802,7 +2836,7 @@
List<String> constraints = new ArrayList<String>();
int match = s_urlMatcher.match(url);
- checkPermission();
+ checkQueryPermission(match, projectionIn, selection, sort);
switch (match) {
case URL_TELEPHONY_USING_SUBID: {
subIdString = url.getLastPathSegment();
@@ -3011,6 +3045,69 @@
return ret;
}
+ private void checkQueryPermission(int match, String[] projectionIn, String selection,
+ String sort) {
+ // Determine if we need to do a check for fields in the selection
+ boolean selectionOrSortContainsSensitiveFields;
+ try {
+ selectionOrSortContainsSensitiveFields = containsSensitiveFields(selection);
+ selectionOrSortContainsSensitiveFields |= containsSensitiveFields(sort);
+ } catch (IllegalArgumentException e) {
+ // Malformed sql, check permission anyway and return.
+ checkPermission();
+ return;
+ }
+
+ if (selectionOrSortContainsSensitiveFields) {
+ try {
+ checkPermission();
+ } catch (SecurityException e) {
+ EventLog.writeEvent(0x534e4554, "124107808", Binder.getCallingUid());
+ throw e;
+ }
+ }
+
+ if (match != URL_SIMINFO && match != URL_SIMINFO_USING_SUBID) {
+ if (projectionIn != null) {
+ for (String column : projectionIn) {
+ if (TYPE.equals(column) ||
+ MMSC.equals(column) ||
+ MMSPROXY.equals(column) ||
+ MMSPORT.equals(column) ||
+ MVNO_TYPE.equals(column) ||
+ MVNO_MATCH_DATA.equals(column) ||
+ APN.equals(column)) {
+ } else {
+ checkPermission();
+ break;
+ }
+ }
+ } else {
+ // null returns all columns, so need permission check
+ checkPermission();
+ }
+ } else {
+ // if querying siminfo, caller should have read privilege permissions
+ checkPhonePrivilegePermission();
+ }
+ }
+
+ private boolean containsSensitiveFields(String sqlStatement) {
+ try {
+ SqlTokenFinder.findTokens(sqlStatement, s -> {
+ switch (s.toLowerCase()) {
+ case USER:
+ case PASSWORD:
+ case SQL_SELECT_TOKEN:
+ throw new SecurityException();
+ }
+ });
+ } catch (SecurityException e) {
+ return true;
+ }
+ return false;
+ }
+
/**
* To find the current sim APN. Query APN based on {MCC, MNC, MVNO} to support backward
* compatibility but will move to carrier id in the future.
@@ -3188,10 +3285,10 @@
log("insert: exception " + e);
// Insertion failed which could be due to a conflict. Check if that is the case
// and merge the entries
- Cursor oldRow = selectConflictingRow(db, CARRIERS_TABLE, values);
+ Cursor oldRow = DatabaseHelper.selectConflictingRow(db, CARRIERS_TABLE, values);
if (oldRow != null) {
ContentValues mergedValues = new ContentValues();
- mergeFieldsAndUpdateDb(db, CARRIERS_TABLE, oldRow, values,
+ DatabaseHelper.mergeFieldsAndUpdateDb(db, CARRIERS_TABLE, oldRow, values,
mergedValues, false, getContext());
oldRow.close();
notify = true;
@@ -3234,7 +3331,7 @@
values = new ContentValues();
}
- values = setDefaultValue(values);
+ values = DatabaseHelper.setDefaultValue(values);
if (!values.containsKey(EDITED_STATUS)) {
values.put(EDITED_STATUS, CARRIER_EDITED);
}
@@ -3365,7 +3462,7 @@
case URL_DELETE:
{
// Delete preferred APN for all subIds
- deletePreferredApnId(getContext());
+ deletePreferredApnId();
// Delete unedited entries
count = db.delete(CARRIERS_TABLE, "(" + where + unedited + " and " +
IS_NOT_OWNED_BY_DPC, whereArgs);
@@ -3601,10 +3698,10 @@
// Update failed which could be due to a conflict. Check if that is
// the case and merge the entries
log("update: exception " + e);
- Cursor oldRow = selectConflictingRow(db, CARRIERS_TABLE, values);
+ Cursor oldRow = DatabaseHelper.selectConflictingRow(db, CARRIERS_TABLE, values);
if (oldRow != null) {
ContentValues mergedValues = new ContentValues();
- mergeFieldsAndUpdateDb(db, CARRIERS_TABLE, oldRow, values,
+ DatabaseHelper.mergeFieldsAndUpdateDb(db, CARRIERS_TABLE, oldRow, values,
mergedValues, false, getContext());
oldRow.close();
db.delete(CARRIERS_TABLE, _ID + "=?" + " and " + IS_NOT_OWNED_BY_DPC,
@@ -3893,7 +3990,7 @@
SQLiteDatabase db = getWritableDatabase();
// Delete preferred APN for all subIds
- deletePreferredApnId(getContext());
+ deletePreferredApnId();
// Delete entries in db
try {
diff --git a/tests/src/com/android/providers/telephony/TelephonyDatabaseHelperTest.java b/tests/src/com/android/providers/telephony/TelephonyDatabaseHelperTest.java
index 26df3c0..8287352 100644
--- a/tests/src/com/android/providers/telephony/TelephonyDatabaseHelperTest.java
+++ b/tests/src/com/android/providers/telephony/TelephonyDatabaseHelperTest.java
@@ -59,7 +59,7 @@
public void setUp() {
Log.d(TAG, "setUp() +");
mContext = InstrumentationRegistry.getContext();
- mHelper = new TelephonyProviderTestable().new DatabaseHelper(mContext);
+ mHelper = new TelephonyProvider.DatabaseHelper(mContext);
mInMemoryDbHelper = new InMemoryTelephonyProviderV5DbHelper();
Log.d(TAG, "setUp() -");
}
@@ -69,7 +69,7 @@
Log.d(TAG, "databaseHelperOnUpgrade_hasApnSetIdField");
// (5 << 16 | 6) is the first upgrade trigger in onUpgrade
SQLiteDatabase db = mInMemoryDbHelper.getWritableDatabase();
- mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.getVersion(mContext));
+ mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.DatabaseHelper.getVersion(mContext));
// the upgraded db must have the APN_SET_ID field
Cursor cursor = db.query("carriers", null, null, null, null, null, null);
@@ -84,7 +84,7 @@
Log.d(TAG, "databaseHelperOnUpgrade_hasSubscriptionTypeField");
// (5 << 16 | 6) is the first upgrade trigger in onUpgrade
SQLiteDatabase db = mInMemoryDbHelper.getWritableDatabase();
- mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.getVersion(mContext));
+ mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.DatabaseHelper.getVersion(mContext));
// the upgraded db must have the Telephony.Carriers.CARRIER_ID field
Cursor cursor = db.query("carriers", null, null, null, null, null, null);
@@ -97,7 +97,7 @@
public void databaseHelperOnUpgrade_hasCountryIsoField() {
Log.d(TAG, "databaseHelperOnUpgrade_hasCountryIsoField");
SQLiteDatabase db = mInMemoryDbHelper.getWritableDatabase();
- mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.getVersion(mContext));
+ mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.DatabaseHelper.getVersion(mContext));
// the upgraded db must have the Telephony.Carriers.CARRIER_ID field
Cursor cursor = db.query("simInfo", null, null, null, null, null, null);
@@ -110,7 +110,7 @@
public void databaseHelperOnUpgrade_hasProfileClassField() {
Log.d(TAG, "databaseHelperOnUpgrade_hasProfileClassField");
SQLiteDatabase db = mInMemoryDbHelper.getWritableDatabase();
- mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.getVersion(mContext));
+ mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.DatabaseHelper.getVersion(mContext));
// the upgraded db must have the PROFILE_CLASS field
Cursor cursor = db.query("siminfo", null, null, null, null, null, null);
@@ -124,7 +124,7 @@
Log.d(TAG, "databaseHelperOnUpgrade_hasSkip464XlatField");
// (5 << 16 | 6) is the first upgrade trigger in onUpgrade
SQLiteDatabase db = mInMemoryDbHelper.getWritableDatabase();
- mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.getVersion(mContext));
+ mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.DatabaseHelper.getVersion(mContext));
// the upgraded db must have the Telephony.Carriers.CARRIER_ID field
Cursor cursor = db.query("carriers", null, null, null, null, null, null);
@@ -138,7 +138,7 @@
Log.d(TAG, "databaseHelperOnUpgrade_columnsMatchNewlyCreatedDb");
// (5 << 16 | 6) is the first upgrade trigger in onUpgrade
SQLiteDatabase db = mInMemoryDbHelper.getWritableDatabase();
- mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.getVersion(mContext));
+ mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.DatabaseHelper.getVersion(mContext));
// compare upgraded carriers table to a carriers table created from scratch
db.execSQL(TelephonyProvider.getStringForCarrierTableCreation("carriers_full"));
@@ -174,7 +174,7 @@
Log.d(TAG, "databaseHelperOnUpgrade_hasSubscriptionTypeField");
// (5 << 16 | 6) is the first upgrade trigger in onUpgrade
SQLiteDatabase db = mInMemoryDbHelper.getWritableDatabase();
- mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.getVersion(mContext));
+ mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.DatabaseHelper.getVersion(mContext));
// the upgraded db must have the SubscriptionManager.SUBSCRIPTION_TYPE field
Cursor cursor = db.query("siminfo", null, null, null, null, null, null);