Merge branch 'dev/11/fp3/security-aosp-rvc-release' into int/11/fp3
* dev/11/fp3/security-aosp-rvc-release:
Check dir path before updating permissions.
Change-Id: Ia977ad7fbc4184829dfc09e0637d56ac336548f6
diff --git a/Android.bp b/Android.bp
index def7cde..423fa07 100644
--- a/Android.bp
+++ b/Android.bp
@@ -6,7 +6,11 @@
platform_apis: true,
certificate: "platform",
libs: ["telephony-common"],
- static_libs: ["android-common", "telephonyprovider-protos"],
+ static_libs: [
+ "android-common",
+ "telephonyprovider-protos",
+ "android-support-v4"
+ ],
}
filegroup {
diff --git a/assets/latest_carrier_id/carrier_list.pb b/assets/latest_carrier_id/carrier_list.pb
index 12ec024..986ccd8 100644
--- a/assets/latest_carrier_id/carrier_list.pb
+++ b/assets/latest_carrier_id/carrier_list.pb
Binary files differ
diff --git a/assets/latest_carrier_id/carrier_list.textpb b/assets/latest_carrier_id/carrier_list.textpb
index 0d8cb87..7c9b4fe 100644
--- a/assets/latest_carrier_id/carrier_list.textpb
+++ b/assets/latest_carrier_id/carrier_list.textpb
Binary files differ
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 309a57b..9bcbd19 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -16,6 +16,6 @@
<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">"تهيئة شبكة الجوال"</string>
+ <string name="app_label" product="tablet" msgid="9194799012395299737">"إعداد شبكة الجوال"</string>
<string name="app_label" product="default" msgid="8338087656149558019">"مساحة تخزين للهاتف والرسائل"</string>
</resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 1c1c927..dc13503 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/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">"Мобайль Сүлжээний Тохируулга"</string>
- <string name="app_label" product="default" msgid="8338087656149558019">"Гар утас болон Зурвасын Санах ой"</string>
+ <string name="app_label" product="default" msgid="8338087656149558019">"Гар утас болон Мессежийн Санах ой"</string>
</resources>
diff --git a/src/com/android/providers/telephony/MmsProvider.java b/src/com/android/providers/telephony/MmsProvider.java
index 1e33313..b1462ae 100644
--- a/src/com/android/providers/telephony/MmsProvider.java
+++ b/src/com/android/providers/telephony/MmsProvider.java
@@ -25,6 +25,7 @@
import android.content.Intent;
import android.content.UriMatcher;
import android.database.Cursor;
+import android.database.MatrixCursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
@@ -43,18 +44,28 @@
import android.provider.Telephony.Mms.Rate;
import android.provider.Telephony.MmsSms;
import android.provider.Telephony.Threads;
+import android.support.v4.content.FileProvider;
import android.system.ErrnoException;
import android.system.Os;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
+import com.google.android.mms.MmsException;
+import com.google.android.mms.pdu.GenericPdu;
+import com.google.android.mms.pdu.PduComposer;
+import com.google.android.mms.pdu.RetrieveConf;
import com.google.android.mms.pdu.PduHeaders;
+import com.google.android.mms.pdu.PduPersister;
+import com.google.android.mms.pdu.PduParser;
+import com.google.android.mms.pdu.SendReq;
import com.google.android.mms.util.DownloadDrmHelper;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.util.HashSet;
/**
* The class to provide base facility to access MMS related content,
@@ -71,6 +82,15 @@
// The name of parts directory. The full dir is "app_parts".
static final String PARTS_DIR_NAME = "parts";
+ static final String COLUMN_PDU_PATH = "pdu_path";
+
+ private static final int MAX_FILE_NAME_LENGTH = 30;
+
+ private final static String[] PDU_COLUMNS = new String[] {
+ "_id",
+ "pdu_path",
+ "pdu_data"
+ };
@Override
public boolean onCreate() {
@@ -90,6 +110,55 @@
return accessRestricted ? VIEW_PDU_RESTRICTED : TABLE_PDU;
}
+ private byte[] getPduDataFromDB(int msgId, int msgType) {
+ Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, msgId);
+ PduPersister persister = PduPersister.getPduPersister(getContext());
+ byte[] mmsData = null;
+ try {
+ if (Mms.MESSAGE_BOX_INBOX == msgType
+ || Mms.MESSAGE_BOX_SENT == msgType) {
+ GenericPdu pdu = persister.load(uri);
+ if (pdu != null) {
+ mmsData = new PduComposer(getContext(), pdu).make();
+ }
+ }
+ } catch (MmsException e) {
+ Log.e(TAG, "MmsException e=" + e);
+ }
+ return mmsData;
+ }
+
+ private Cursor getPdus(int itemCount, int dataCount, String[] data) {
+ MatrixCursor cursor = new MatrixCursor(PDU_COLUMNS, 1);
+ long token = Binder.clearCallingIdentity();
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ db.beginTransaction();
+ try {
+ for (int i = 0; i < dataCount; i++) {
+ int msgId = Integer.parseInt(data[i * itemCount]);
+ int msgType = Integer.parseInt(data[i * itemCount + 1]);
+ String pduPath = data[i * itemCount + 2];
+ byte[] pduData = getPduDataFromDB(msgId, msgType);
+ if (pduData == null || pduData.length == 0) {
+ Log.e(TAG, "can't get msgId:" + msgId + " pdu data.");
+ continue;
+ }
+ Object[] row = new Object[3];
+ row[0] = msgId;
+ row[1] = pduPath;
+ row[2] = pduData;
+ cursor.addRow(row);
+ }
+ db.setTransactionSuccessful();
+ } catch (Exception e) {
+ Log.e(TAG, "Exception e =", e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ db.endTransaction();
+ }
+ return cursor;
+ }
+
@Override
public Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder) {
@@ -221,6 +290,19 @@
case MMS_THREADS:
qb.setTables(pduTable + " group by thread_id");
break;
+ case MMS_GET_PDU:
+ int itemCount = Integer.parseInt(uri.getQueryParameter("item_count"));
+ int dataCount = Integer.parseInt(uri.getQueryParameter("data_count"));
+ String split = uri.getQueryParameter("data_split");
+ String[] data = null;
+ if (!TextUtils.isEmpty(uri.getQueryParameter("data"))) {
+ data = uri.getQueryParameter("data").split(split);
+ Log.d(TAG, "data.length :" + data.length);
+ return getPdus(itemCount, dataCount, data);
+ } else {
+ Log.e(TAG, "MMS get pdu date return null");
+ return null;
+ }
default:
Log.e(TAG, "query: invalid request: " + uri);
return null;
@@ -304,6 +386,122 @@
}
}
+ private byte[] getPduDataFromFile(String pduPath, String authority) {
+ FileInputStream fileInputStream = null;
+ byte[] data = null;
+ try {
+ Uri fileUri = FileProvider.getUriForFile(getContext(), authority, new File(pduPath));
+ ParcelFileDescriptor parcelFileDescriptor =
+ getContext().getContentResolver().openFileDescriptor(fileUri, "r");
+ fileInputStream = new FileInputStream(parcelFileDescriptor.getFileDescriptor());
+ data = new byte[fileInputStream.available()];
+ fileInputStream.read(data);
+ } catch (Exception e) {
+ Log.e(TAG, "read file exception :", e);
+ } finally {
+ try {
+ if (fileInputStream != null) {
+ fileInputStream.close();
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "close file stream exception :", e);
+ }
+ }
+ return data;
+ }
+
+ private Uri restorePduFile(Uri uri, String pduPath, String authority) {
+ Uri msgUri = null;
+ if (uri == null || TextUtils.isEmpty(pduPath) || TextUtils.isEmpty(authority)) {
+ return null;
+ }
+
+ try {
+ byte[] pduData = getPduDataFromFile(pduPath, authority);
+ PduPersister pduPersister = PduPersister.getPduPersister(getContext());
+ if (pduData != null && pduData.length > 0) {
+ if (Mms.Sent.CONTENT_URI.equals(uri)
+ || Mms.Inbox.CONTENT_URI.equals(uri)) {
+ GenericPdu pdu = new PduParser(pduData, true).parse();
+ msgUri = pduPersister.persist(
+ pdu, uri, true, false, null);
+ } else {
+ Log.e(TAG,"Unsupported uri :" + uri);
+ }
+ }
+ } catch (MmsException e) {
+ Log.e(TAG, "MmsException: ", e);
+ }
+ return msgUri;
+ }
+
+ private String getPduPath(String dir, ContentValues values) {
+ if (dir != null && values != null && values.containsKey(COLUMN_PDU_PATH)) {
+ String path = values.getAsString(COLUMN_PDU_PATH);
+ if (!TextUtils.isEmpty(path)) {
+ return dir + "/" + path;
+ }
+ }
+ return null;
+ }
+
+ private int restoreMms(Uri uri, ContentValues values, String dir, String authority) {
+ int count = 0;
+ Uri msgUri = restorePduFile(uri, getPduPath(dir, values), authority);
+ if (msgUri != null) {
+ String selection = Mms._ID + "=" + msgUri.getLastPathSegment();
+ values.remove(COLUMN_PDU_PATH);
+ ContentValues finalValues = new ContentValues(values);
+ // now only support bulkInsert pdu table.
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ count = db.update(TABLE_PDU, finalValues, selection, null);
+ }
+ return count;
+ }
+
+ @Override
+ public int bulkInsert(Uri uri, ContentValues[] values) {
+ String dir = uri.getQueryParameter("restore_dir");
+ String authority = uri.getQueryParameter("authorities");
+ if (TextUtils.isEmpty(dir)) {
+ return super.bulkInsert(uri, values);
+ }
+
+ Uri insertUri = null;
+ int match = sURLMatcher.match(uri);
+ switch (match) {
+ case MMS_INBOX:
+ insertUri = Mms.Inbox.CONTENT_URI;
+ break;
+ case MMS_SENT:
+ insertUri = Mms.Sent.CONTENT_URI;
+ break;
+ default:
+ return 0;
+ }
+
+ long token = Binder.clearCallingIdentity();
+ int count = 0;
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ for (ContentValues value : values) {
+ count += restoreMms(insertUri, value, dir, authority);
+ }
+
+ Log.d(TAG, "bulkInsert request count: " + values.length
+ + " successfully count : " + count);
+ if (count == values.length) {
+ db.setTransactionSuccessful();
+ }
+ return count;
+ } finally {
+ db.endTransaction();
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+
@Override
public Uri insert(Uri uri, ContentValues values) {
final int callerUid = Binder.getCallingUid();
@@ -603,6 +801,15 @@
return res;
}
+ private String getFileName(String fileLocation) {
+ File f = new File(fileLocation);
+ String fileName = f.getName();
+ if (fileName.length() >= MAX_FILE_NAME_LENGTH) {
+ fileName = fileName.substring(0, MAX_FILE_NAME_LENGTH);
+ }
+ return fileName;
+ }
+
private int getMessageBoxByMatch(int match) {
switch (match) {
case MMS_INBOX_ID:
@@ -687,6 +894,8 @@
selectionArgs, uri);
} else if (TABLE_PART.equals(table)) {
deletedRows = deleteParts(db, finalSelection, selectionArgs);
+ cleanUpWords(db);
+ updateHasAttachment(db);
} else if (TABLE_DRM.equals(table)) {
deletedRows = deleteTempDrmData(db, finalSelection, selectionArgs);
} else {
@@ -701,12 +910,13 @@
static int deleteMessages(Context context, SQLiteDatabase db,
String selection, String[] selectionArgs, Uri uri) {
- Cursor cursor = db.query(TABLE_PDU, new String[] { Mms._ID },
+ Cursor cursor = db.query(TABLE_PDU, new String[] { Mms._ID, Mms.THREAD_ID },
selection, selectionArgs, null, null, null);
if (cursor == null) {
return 0;
}
+ HashSet<Long> threadIds = new HashSet<Long>();
try {
if (cursor.getCount() == 0) {
return 0;
@@ -715,12 +925,18 @@
while (cursor.moveToNext()) {
deleteParts(db, Part.MSG_ID + " = ?",
new String[] { String.valueOf(cursor.getLong(0)) });
+ threadIds.add(cursor.getLong(1));
}
+ cleanUpWords(db);
+ updateHasAttachment(db);
} finally {
cursor.close();
}
int count = db.delete(TABLE_PDU, selection, selectionArgs);
+ for (long thread : threadIds) {
+ MmsSmsDatabaseHelper.updateThread(db, thread);
+ }
if (count > 0) {
Intent intent = new Intent(Mms.Intents.CONTENT_CHANGED_ACTION);
intent.putExtra(Mms.Intents.DELETED_CONTENTS, uri);
@@ -732,6 +948,18 @@
return count;
}
+ private static void cleanUpWords(SQLiteDatabase db) {
+ db.execSQL("DELETE FROM words WHERE source_id not in (select _id from part) AND "
+ + "table_to_use = 2");
+ }
+
+ private static void updateHasAttachment(SQLiteDatabase db) {
+ db.execSQL("UPDATE threads SET has_attachment = CASE "
+ + "(SELECT COUNT(*) FROM part JOIN pdu WHERE part.mid = pdu._id AND "
+ + "pdu.thread_id = threads._id AND part.ct != 'text/plain' "
+ + "AND part.ct != 'application/smil') WHEN 0 THEN 0 ELSE 1 END");
+ }
+
private static int deleteParts(SQLiteDatabase db, String selection,
String[] selectionArgs) {
return deleteDataRows(db, TABLE_PART, selection, selectionArgs);
@@ -1037,6 +1265,7 @@
private static final int MMS_DRM_STORAGE_ID = 18;
private static final int MMS_THREADS = 19;
private static final int MMS_PART_RESET_FILE_PERMISSION = 20;
+ private static final int MMS_GET_PDU = 21;
private static final UriMatcher
sURLMatcher = new UriMatcher(UriMatcher.NO_MATCH);
@@ -1063,6 +1292,7 @@
sURLMatcher.addURI("mms", "drm/#", MMS_DRM_STORAGE_ID);
sURLMatcher.addURI("mms", "threads", MMS_THREADS);
sURLMatcher.addURI("mms", "resetFilePerm/*", MMS_PART_RESET_FILE_PERMISSION);
+ sURLMatcher.addURI("mms", "get-pdu", MMS_GET_PDU);
}
private SQLiteOpenHelper mOpenHelper;
diff --git a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
old mode 100644
new mode 100755
index 068f209..f2cbf49
--- a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
+++ b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
@@ -106,19 +106,6 @@
" AND " + Mms.MESSAGE_BOX + " != 3) " +
" WHERE threads._id = new.thread_id; ";
- private static final String UPDATE_THREAD_COUNT_ON_OLD =
- " UPDATE threads SET message_count = " +
- " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
- " ON threads._id = " + Sms.THREAD_ID +
- " WHERE " + Sms.THREAD_ID + " = old.thread_id" +
- " AND sms." + Sms.TYPE + " != 3) + " +
- " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
- " ON threads._id = " + Mms.THREAD_ID +
- " WHERE " + Mms.THREAD_ID + " = old.thread_id" +
- " AND (m_type=132 OR m_type=130 OR m_type=128)" +
- " AND " + Mms.MESSAGE_BOX + " != 3) " +
- " WHERE threads._id = old.thread_id; ";
-
private static final String SMS_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE =
"BEGIN" +
" UPDATE threads SET" +
@@ -168,21 +155,6 @@
PDU_UPDATE_THREAD_READ_BODY +
"END;";
- private static final String UPDATE_THREAD_SNIPPET_SNIPPET_CS_ON_DELETE =
- " UPDATE threads SET snippet = " +
- " (SELECT snippet FROM" +
- " (SELECT date * 1000 AS date, sub AS snippet, thread_id FROM pdu" +
- " UNION SELECT date, body AS snippet, thread_id FROM sms)" +
- " WHERE thread_id = OLD.thread_id ORDER BY date DESC LIMIT 1) " +
- " WHERE threads._id = OLD.thread_id; " +
- " UPDATE threads SET snippet_cs = " +
- " (SELECT snippet_cs FROM" +
- " (SELECT date * 1000 AS date, sub_cs AS snippet_cs, thread_id FROM pdu" +
- " UNION SELECT date, 0 AS snippet_cs, thread_id FROM sms)" +
- " WHERE thread_id = OLD.thread_id ORDER BY date DESC LIMIT 1) " +
- " WHERE threads._id = OLD.thread_id; ";
-
-
// When a part is inserted, if it is not text/plain or application/smil
// (which both can exist with text-only MMSes), then there is an attachment.
// Set has_attachment=1 in the threads table for the thread in question.
@@ -208,28 +180,6 @@
" WHERE part._id=new._id LIMIT 1); " +
" END";
-
- // When a part is deleted (with the same non-text/SMIL constraint as when
- // we set has_attachment), update the threads table for all threads.
- // Unfortunately we cannot update only the thread that the part was
- // attached to, as it is possible that the part has been orphaned and
- // the message it was attached to is already gone.
- private static final String PART_UPDATE_THREADS_ON_DELETE_TRIGGER =
- "CREATE TRIGGER update_threads_on_delete_part " +
- " AFTER DELETE ON part " +
- " WHEN old.ct != 'text/plain' AND old.ct != 'application/smil' " +
- " BEGIN " +
- " UPDATE threads SET has_attachment = " +
- " CASE " +
- " (SELECT COUNT(*) FROM part JOIN pdu " +
- " WHERE pdu.thread_id = threads._id " +
- " AND part.ct != 'text/plain' AND part.ct != 'application/smil' " +
- " AND part.mid = pdu._id)" +
- " WHEN 0 THEN 0 " +
- " ELSE 1 " +
- " END; " +
- " END";
-
// When the 'thread_id' column in the pdu table is updated, we need to run the trigger to update
// the threads table's has_attachment column, if the message has an attachment in 'part' table
private static final String PDU_UPDATE_THREADS_ON_UPDATE_TRIGGER =
@@ -251,6 +201,9 @@
private static boolean sTriedAutoIncrement = false;
private static boolean sFakeLowStorageTest = false; // for testing only
+ private static final String NO_SUCH_COLUMN_EXCEPTION_MESSAGE = "no such column";
+ private static final String NO_SUCH_TABLE_EXCEPTION_MESSAGE = "no such table";
+
static final String DATABASE_NAME = "mmssms.db";
static final int DATABASE_VERSION = 67;
private static final int IDLE_CONNECTION_TIMEOUT_MS = 30000;
@@ -502,6 +455,54 @@
}
}
+ private static void updateThreadDate(SQLiteDatabase db, long thread_id) {
+ if (thread_id <= 0) {
+ return;
+ }
+
+ try {
+ db.execSQL(
+ " UPDATE threads" +
+ " SET" +
+ " date =" +
+ " (SELECT date FROM" +
+ " (SELECT date * 1000 AS date, thread_id FROM pdu" +
+ " UNION SELECT date, thread_id FROM sms)" +
+ " WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)" +
+ " WHERE threads._id = " + thread_id + ";");
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ }
+ }
+
+ public static void updateThreadsDate(SQLiteDatabase db, String where, String[] whereArgs) {
+ db.beginTransaction();
+ try {
+ if (where == null) {
+ where = "";
+ } else {
+ where = "WHERE (" + where + ")";
+ }
+ String query = "SELECT _id FROM threads " + where;
+ Cursor c = db.rawQuery(query, whereArgs);
+ if (c != null) {
+ try {
+ Log.d(TAG, "updateThread count : " + c.getCount());
+ while (c.moveToNext()) {
+ updateThreadDate(db, c.getInt(0));
+ }
+ } finally {
+ c.close();
+ }
+ }
+ db.setTransactionSuccessful();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ } finally {
+ db.endTransaction();
+ }
+ }
+
public static int deleteOneSms(SQLiteDatabase db, int message_id) {
int thread_id = -1;
// Find the thread ID that the specified SMS belongs to.
@@ -565,6 +566,39 @@
PhoneFactory.localLog(TAG, logMsg);
}
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ try {
+ // Try to access the table and create it if "no such table"
+ db.query(SmsProvider.TABLE_SMS, null, null, null, null, null, null);
+ checkAndUpdateSmsTable(db);
+ } catch (SQLiteException e) {
+ Log.e(TAG, "onOpen: ex. ", e);
+ if (e.getMessage().startsWith(NO_SUCH_TABLE_EXCEPTION_MESSAGE)) {
+ createSmsTables(db);
+ }
+ }
+ try {
+ // Try to access the table and create it if "no such table"
+ db.query(MmsSmsProvider.TABLE_THREADS, null, null, null, null, null, null);
+ checkAndUpdateThreadsTable(db);
+ } catch (SQLiteException e) {
+ Log.e(TAG, "onOpen: ex. ", e);
+ if (e.getMessage().startsWith(NO_SUCH_TABLE_EXCEPTION_MESSAGE)) {
+ createCommonTables(db);
+ }
+ }
+
+ // Improve the performance of deleting Mms.
+ dropMmsTriggers(db);
+ }
+
+ private void dropMmsTriggers(SQLiteDatabase db) {
+ db.execSQL("DROP TRIGGER IF EXISTS update_threads_on_delete_part");
+ db.execSQL("DROP TRIGGER IF EXISTS mms_words_delete");
+ db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_on_delete");
+ }
+
private boolean isInitialCreateDone() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
return sp.getBoolean(INITIAL_CREATE_DONE, false);
@@ -676,8 +710,18 @@
createThreadIdDateIndex(db);
createPartMidIndex(db);
createAddrMsgIdIndex(db);
+ createPduPartIndex(db);
}
+ private void createPduPartIndex(SQLiteDatabase db) {
+ try {
+ db.execSQL("CREATE INDEX IF NOT EXISTS index_part ON " + MmsProvider.TABLE_PART +
+ " (mid);");
+ } catch (Exception ex) {
+ Log.e(TAG, "got exception creating indices: " + ex.toString());
+ }
+ }
+
private void createThreadIdIndex(SQLiteDatabase db) {
try {
db.execSQL("CREATE INDEX IF NOT EXISTS typeThreadIdIndex ON sms" +
@@ -835,9 +879,6 @@
db.execSQL("DROP TRIGGER IF EXISTS update_threads_on_update_part");
db.execSQL(PART_UPDATE_THREADS_ON_UPDATE_TRIGGER);
- db.execSQL("DROP TRIGGER IF EXISTS update_threads_on_delete_part");
- db.execSQL(PART_UPDATE_THREADS_ON_DELETE_TRIGGER);
-
db.execSQL("DROP TRIGGER IF EXISTS update_threads_on_update_pdu");
db.execSQL(PDU_UPDATE_THREADS_ON_UPDATE_TRIGGER);
@@ -913,10 +954,6 @@
" SET index_text = NEW.text WHERE (source_id=NEW._id AND table_to_use=2); " +
" END;");
- db.execSQL("DROP TRIGGER IF EXISTS mms_words_delete");
- db.execSQL("CREATE TRIGGER mms_words_delete AFTER DELETE ON part BEGIN DELETE FROM " +
- " words WHERE source_id = OLD._id AND table_to_use = 2; END;");
-
// Updates threads table whenever a message in pdu is updated.
db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_date_subject_on_update");
db.execSQL("CREATE TRIGGER pdu_update_thread_date_subject_on_update AFTER" +
@@ -925,18 +962,6 @@
PDU_UPDATE_THREAD_CONSTRAINTS +
PDU_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE);
- // Update threads table whenever a message in pdu is deleted
- db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_on_delete");
- db.execSQL("CREATE TRIGGER pdu_update_thread_on_delete " +
- "AFTER DELETE ON pdu " +
- "BEGIN " +
- " UPDATE threads SET " +
- " date = (strftime('%s','now') * 1000)" +
- " WHERE threads._id = old." + Mms.THREAD_ID + "; " +
- UPDATE_THREAD_COUNT_ON_OLD +
- UPDATE_THREAD_SNIPPET_SNIPPET_CS_ON_DELETE +
- "END;");
-
// Updates threads table whenever a message is added to pdu.
db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_on_insert");
db.execSQL("CREATE TRIGGER pdu_update_thread_on_insert AFTER INSERT ON " +
@@ -1122,7 +1147,9 @@
Threads.ARCHIVED + " INTEGER DEFAULT 0," +
Threads.TYPE + " INTEGER DEFAULT 0," +
Threads.ERROR + " INTEGER DEFAULT 0," +
- Threads.HAS_ATTACHMENT + " INTEGER DEFAULT 0);");
+ Threads.HAS_ATTACHMENT + " INTEGER DEFAULT 0," +
+ Threads.ATTACHMENT_INFO + " TEXT," +
+ Threads.NOTIFICATION + " INTEGER DEFAULT 0);");
/**
* This table stores the queue of messages to be sent/downloaded.
@@ -1713,7 +1740,6 @@
// Add insert and delete triggers for keeping it up to date.
db.execSQL(PART_UPDATE_THREADS_ON_INSERT_TRIGGER);
- db.execSQL(PART_UPDATE_THREADS_ON_DELETE_TRIGGER);
}
private void upgradeDatabaseToVersion44(SQLiteDatabase db) {
@@ -1959,6 +1985,42 @@
+ "display_originating_addr; " + e);
}
}
+ private void checkAndUpdateSmsTable(SQLiteDatabase db) {
+ try {
+ db.query(SmsProvider.TABLE_SMS, new String[] {"priority"}, null, null, null, null,
+ null);
+ } catch (SQLiteException e) {
+ Log.e(TAG, "checkAndUpgradeSmsTable: ex. ", e);
+ if (e.getMessage().startsWith(NO_SUCH_COLUMN_EXCEPTION_MESSAGE)) {
+ db.execSQL("ALTER TABLE " + SmsProvider.TABLE_SMS + " ADD COLUMN "
+ + "priority INTEGER DEFAULT -1");
+ }
+ }
+ }
+
+ private void checkAndUpdateThreadsTable(SQLiteDatabase db) {
+ try {
+ db.query(MmsSmsProvider.TABLE_THREADS, new String[] {Threads.ATTACHMENT_INFO},
+ null, null, null, null, null);
+ } catch (SQLiteException e) {
+ Log.e(TAG, "checkAndUpdateThreadsTable: ex. ", e);
+ if (e.getMessage().startsWith(NO_SUCH_COLUMN_EXCEPTION_MESSAGE)) {
+ db.execSQL("ALTER TABLE " + MmsSmsProvider.TABLE_THREADS + " ADD COLUMN "
+ + Threads.ATTACHMENT_INFO + " TEXT");
+ }
+ }
+
+ try {
+ db.query(MmsSmsProvider.TABLE_THREADS, new String[] {Threads.NOTIFICATION},
+ null, null, null, null, null);
+ } catch (SQLiteException e) {
+ Log.e(TAG, "checkAndUpdateThreadsTable: ex. ", e);
+ if (e.getMessage().startsWith(NO_SUCH_COLUMN_EXCEPTION_MESSAGE)) {
+ db.execSQL("ALTER TABLE " + MmsSmsProvider.TABLE_THREADS + " ADD COLUMN "
+ + Threads.NOTIFICATION + " INTEGER DEFAULT 0");
+ }
+ }
+ }
@Override
public synchronized SQLiteDatabase getReadableDatabase() {
@@ -2146,7 +2208,9 @@
Threads.READ + " INTEGER DEFAULT 1," +
Threads.TYPE + " INTEGER DEFAULT 0," +
Threads.ERROR + " INTEGER DEFAULT 0," +
- Threads.HAS_ATTACHMENT + " INTEGER DEFAULT 0);");
+ Threads.HAS_ATTACHMENT + " INTEGER DEFAULT 0," +
+ Threads.ATTACHMENT_INFO + " TEXT," +
+ Threads.NOTIFICATION + " INTEGER DEFAULT 0);");
db.execSQL("INSERT INTO threads_temp SELECT * from threads;");
db.execSQL("DROP TABLE threads;");
diff --git a/src/com/android/providers/telephony/MmsSmsProvider.java b/src/com/android/providers/telephony/MmsSmsProvider.java
old mode 100644
new mode 100755
index f5aeadc..d5253c2
--- a/src/com/android/providers/telephony/MmsSmsProvider.java
+++ b/src/com/android/providers/telephony/MmsSmsProvider.java
@@ -100,7 +100,16 @@
private static final int URI_FIRST_LOCKED_MESSAGE_ALL = 16;
private static final int URI_FIRST_LOCKED_MESSAGE_BY_THREAD_ID = 17;
private static final int URI_MESSAGE_ID_TO_THREAD = 18;
+ private static final int URI_MESSAGES_COUNT = 19;
+ private static final int URI_UPDATE_THREAD_DATE = 20;
+ private static final int URI_SEARCH_MESSAGE = 21;
+ private static final int URI_ADDRESS_TO_THREAD = 22;
+ // Escape character
+ private static final char SEARCH_ESCAPE_CHARACTER = '!';
+ public static final int SEARCH_MODE_CONTENT = 0;
+ public static final int SEARCH_MODE_NAME = 1;
+ private static final long RESULT_FOR_ID_NOT_FOUND = -1L;
/**
* the name of the table that is used to store the queue of
* messages(both MMS and SMS) to be sent/downloaded.
@@ -111,6 +120,7 @@
* the name of the table that is used to store the canonical addresses for both SMS and MMS.
*/
static final String TABLE_CANONICAL_ADDRESSES = "canonical_addresses";
+ private static final String DEFAULT_STRING_ZERO = "0";
/**
* the name of the table that is used to store the conversation threads.
@@ -253,9 +263,14 @@
AUTHORITY, "conversations/#/subject",
URI_CONVERSATIONS_SUBJECT);
+ // URI for obtaining all short message count
+ URI_MATCHER.addURI(AUTHORITY, "messagescount", URI_MESSAGES_COUNT);
+
// URI for deleting obsolete threads.
URI_MATCHER.addURI(AUTHORITY, "conversations/obsolete", URI_OBSOLETE_THREADS);
+ URI_MATCHER.addURI(AUTHORITY, "search-message", URI_SEARCH_MESSAGE);
+
URI_MATCHER.addURI(
AUTHORITY, "messages/byphone/*",
URI_MESSAGES_BY_PHONE);
@@ -265,6 +280,8 @@
// may be present.
URI_MATCHER.addURI(AUTHORITY, "threadID", URI_THREAD_ID);
+ URI_MATCHER.addURI(AUTHORITY, "update-date", URI_UPDATE_THREAD_DATE);
+
// Use this pattern to query the canonical address by given ID.
URI_MATCHER.addURI(AUTHORITY, "canonical-address/#", URI_CANONICAL_ADDRESS);
@@ -299,6 +316,7 @@
URI_MATCHER.addURI(AUTHORITY, "locked/#", URI_FIRST_LOCKED_MESSAGE_BY_THREAD_ID);
URI_MATCHER.addURI(AUTHORITY, "messageIdToThread", URI_MESSAGE_ID_TO_THREAD);
+ URI_MATCHER.addURI(AUTHORITY, "address", URI_ADDRESS_TO_THREAD);
initializeColumnSets();
}
@@ -373,6 +391,8 @@
cursor = getConversationMessages(uri.getPathSegments().get(1), projection,
selection, sortOrder, smsTable, pduTable);
break;
+ case URI_MESSAGES_COUNT:
+ return getAllMessagesCount();
case URI_CONVERSATIONS_RECIPIENTS:
cursor = getConversationById(
uri.getPathSegments().get(1), projection, selection,
@@ -480,6 +500,9 @@
}
break;
}
+ case URI_SEARCH_MESSAGE:
+ cursor = getSearchMessages(uri, db, smsTable, pduTable);
+ break;
case URI_PENDING_MSG: {
String protoName = uri.getQueryParameter("protocol");
String msgId = uri.getQueryParameter("message");
@@ -526,6 +549,15 @@
projection, selection, sortOrder, smsTable, pduTable);
break;
}
+
+ case URI_ADDRESS_TO_THREAD: {
+ String address = uri.getQueryParameter("address_info");
+ if (DEBUG) {
+ Log.d(LOG_TAG,"query threadId by address:"+address);
+ }
+ cursor = getThreadIdsByRecipient(getRecipientIdByAddress(address));
+ break;
+ }
default:
throw new IllegalStateException("Unrecognized URI:" + uri);
}
@@ -536,6 +568,66 @@
return cursor;
}
+ private long getRecipientIdByAddress(String address) {
+ boolean isEmail = Mms.isEmailAddress(address);
+ String refinedAddress = isEmail ? address.toLowerCase() : address;
+ String selection = "address=?";
+ String[] selectionArgs;
+ if (isEmail) {
+ selectionArgs = new String[] { refinedAddress };
+ } else {
+ selection += " OR "
+ + String.format("PHONE_NUMBERS_EQUAL(address, ?, %d)",
+ (mUseStrictPhoneNumberComparation ? 1 : 0));
+ selectionArgs = new String[] { refinedAddress, refinedAddress };
+ }
+ Log.d(LOG_TAG, "getRecipientIdByAddress selection:" + selection);
+ Cursor cursor = null;
+
+ try {
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ cursor = db.query("canonical_addresses",
+ ID_PROJECTION,
+ selection, selectionArgs, null, null, null);
+
+ if (cursor.getCount() == 0) {
+ return RESULT_FOR_ID_NOT_FOUND;
+ }
+
+ if (cursor.moveToFirst()) {
+ return cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID));
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return RESULT_FOR_ID_NOT_FOUND;
+ }
+
+ private Cursor getThreadIdsByRecipient(long recipientId) {
+ if (recipientId == RESULT_FOR_ID_NOT_FOUND)
+ return null;
+ String recipientIds = String.valueOf(recipientId);
+ String THREAD_QUERY = "SELECT _id FROM threads "
+ + "WHERE recipient_ids = ? or recipient_ids like '"
+ + recipientIds + " %' or recipient_ids like '% "
+ + recipientIds + "' or recipient_ids like '% "
+ + recipientIds + " %'";
+
+ if (DEBUG) {
+ Log.v(LOG_TAG, "getThreadId THREAD_QUERY: " + THREAD_QUERY
+ + ", recipientIds=" + recipientIds);
+ }
+ try {
+ return mOpenHelper.getReadableDatabase().rawQuery(THREAD_QUERY,
+ new String[] { recipientIds });
+ } catch (Exception e) {
+ Log.d(LOG_TAG, "get thread by recipient id exception:" + e);
+ return null;
+ }
+ }
+
/**
* Return the canonical address ID for this address.
*/
@@ -603,7 +695,7 @@
for (String address : addresses) {
if (!address.equals(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR)) {
long id = getSingleAddressId(address);
- if (id != -1L) {
+ if (id != RESULT_FOR_ID_NOT_FOUND) {
result.add(id);
} else {
Log.e(LOG_TAG, "getAddressIds: address ID not found for " + address);
@@ -1002,6 +1094,17 @@
}
/**
+ * Return the SMS messages count on phone
+ */
+ private Cursor getAllMessagesCount() {
+ String unionQuery = "select sum(a) AS count, 1 AS _id "
+ + "from (" + "select count(sms._id) as a, 2 AS b from sms, threads"
+ + " where thread_id NOTNULL AND thread_id = threads._id)";
+
+ return mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY);
+ }
+
+ /**
* Return the union of MMS and SMS messages whose recipients
* included this phone number.
*
@@ -1310,8 +1413,14 @@
switch(URI_MATCHER.match(uri)) {
case URI_CONVERSATIONS_MESSAGES:
String threadIdString = uri.getPathSegments().get(1);
- affectedRows = updateConversation(threadIdString, values,
- selection, selectionArgs, callerUid, callerPkg);
+ if (values.containsKey(Threads.NOTIFICATION) ||
+ values.containsKey(Threads.ATTACHMENT_INFO)) {
+ String finalSelection = concatSelections(selection, "_id=" + threadIdString);
+ affectedRows = db.update(TABLE_THREADS, values, finalSelection, null);
+ } else {
+ affectedRows = updateConversation(threadIdString, values,
+ selection, selectionArgs, callerUid, callerPkg);
+ }
break;
case URI_PENDING_MSG:
@@ -1337,6 +1446,9 @@
break;
}
+ case URI_UPDATE_THREAD_DATE:
+ MmsSmsDatabaseHelper.updateThreadsDate(db, selection, selectionArgs);
+ break;
default:
throw new UnsupportedOperationException(
NO_DELETES_INSERTS_OR_UPDATES + uri);
@@ -1414,6 +1526,132 @@
writer.println("Default SMS app: " + defaultSmsApp);
}
+ private Cursor getSearchMessages(Uri uri, SQLiteDatabase db,
+ String smsTable, String pduTable) {
+ String searchString = "%" + uri.getQueryParameter("key_str") + "%";
+ String threadIdString = uri.getQueryParameter("thread_ids");
+ int searchMode = SEARCH_MODE_NAME;
+ if (threadIdString == null || threadIdString.equals(DEFAULT_STRING_ZERO))
+ searchMode = SEARCH_MODE_CONTENT;
+ if (DEBUG) {
+ Log.d(LOG_TAG, "keystr=" + searchString +
+ "|searchMode=" + searchMode + "|threadIdString=" + threadIdString);
+ }
+ String rawQuery = getConversationQueryString(searchMode, smsTable,
+ pduTable, threadIdString);
+ String[] strArray = new String[]{searchString, searchString, searchString,
+ searchString, searchString};
+ return db.rawQuery(rawQuery, strArray);
+ }
+
+ private String getConversationQueryString(int searchMode,
+ String smsTable, String pduTable, final String threadIds) {
+ String nameQuery = "";
+ String smsContentQuery = "";
+ String pduContentQuery = "";
+ String rawQuery = "";
+
+ final String NAME_PROJECTION = "threads._id AS _id,"
+ + "threads.date AS date,"
+ + "threads.message_count AS message_count,"
+ + "threads.recipient_ids AS recipient_ids,"
+ + "threads.snippet AS snippet,"
+ + "threads.snippet_cs AS snippet_cs,"
+ + "threads.read AS read,"
+ + "NULL AS error,"
+ + "threads.has_attachment AS has_attachment,"
+ + "threads.attachment_info AS attachment_info";
+ final String SMS_PROJECTION = "threads._id AS _id,"
+ + smsTable + ".date AS date,"
+ + "threads.message_count AS message_count,"
+ + "threads.recipient_ids AS recipient_ids,"
+ + smsTable + ".body AS snippet,"
+ + "threads.snippet_cs AS snippet_cs,"
+ + "threads.read AS read,"
+ + "NULL AS error,"
+ + "threads.has_attachment AS has_attachment,"
+ + "'SMS' AS attachment_info";
+ final String PDU_PROJECTION = "threads._id AS _id,"
+ + pduTable + ".date * 1000 AS date,"
+ + "threads.message_count AS message_count,"
+ + "threads.recipient_ids AS recipient_ids,"
+ + pduTable + ".sub AS snippet,"
+ + pduTable + ".sub_cs AS snippet_cs,"
+ + "threads.read AS read,"
+ + "NULL AS error,"
+ + "threads.has_attachment AS has_attachment,"
+ + "part.text AS attachment_info";
+
+ if (searchMode == SEARCH_MODE_NAME) {
+ nameQuery = String.format(
+ "SELECT %s FROM threads WHERE threads._id in (%s)",
+ NAME_PROJECTION,
+ threadIds);
+ smsContentQuery = String.format("SELECT %s FROM threads, " + smsTable
+ + " WHERE ("
+ + "(threads._id NOT in (%s))"
+ + " AND (" + smsTable + ".thread_id = " + "threads._id )"
+ + " AND ((" + smsTable + ".body LIKE ? ESCAPE '"
+ + SEARCH_ESCAPE_CHARACTER + "')"
+ + " OR (" + smsTable + ".address LIKE ? ESCAPE '"
+ + SEARCH_ESCAPE_CHARACTER + "')))"
+ + " GROUP BY threads._id",
+ SMS_PROJECTION,
+ threadIds);
+ pduContentQuery = String.format("SELECT %s FROM threads, addr, part, " + pduTable
+ + " WHERE((threads._id NOT in (%s))"
+ + " AND (addr.msg_id=" + pduTable + "._id)"
+ + " AND (addr.type=%d)"
+ + " AND (part.mid=" + pduTable + "._id)"
+ + " AND (part.ct='text/plain')"
+ + " AND (threads._id =" + pduTable + ".thread_id)"
+ + " AND ((part.text LIKE ? ESCAPE '" + SEARCH_ESCAPE_CHARACTER + "')"
+ + " OR (addr.address LIKE ? ESCAPE '" + SEARCH_ESCAPE_CHARACTER + "')"
+ + " OR (" + pduTable + ".sub LIKE ? ESCAPE '"
+ + SEARCH_ESCAPE_CHARACTER + "')))",
+ PDU_PROJECTION,
+ threadIds,
+ PduHeaders.TO);
+
+ rawQuery = String.format(
+ "%s UNION %s UNION %s GROUP BY threads._id ORDER BY date DESC",
+ nameQuery,
+ smsContentQuery,
+ pduContentQuery);
+ } else {
+ smsContentQuery = String.format("SELECT %s FROM threads, " + smsTable
+ + " WHERE ("
+ + "(" + smsTable + ".thread_id = " + "threads._id )"
+ + " AND ((" + smsTable + ".body LIKE ? ESCAPE '"
+ + SEARCH_ESCAPE_CHARACTER + "')"
+ + " OR (" + smsTable + ".address LIKE ? ESCAPE '"
+ + SEARCH_ESCAPE_CHARACTER + "')))"
+ + " GROUP BY threads._id",
+ SMS_PROJECTION);
+ pduContentQuery = String.format("SELECT %s FROM threads, addr, part, " + pduTable
+ + " WHERE((addr.msg_id=" + pduTable + "._id)"
+ + " AND (addr.type=%d)"
+ + " AND (part.mid=" + pduTable + "._id)"
+ + " AND (part.ct='text/plain')"
+ + " AND (threads._id =" + pduTable + ".thread_id)"
+ + " AND ((part.text LIKE ? ESCAPE '" + SEARCH_ESCAPE_CHARACTER + "')"
+ + " OR (addr.address LIKE ? ESCAPE '" + SEARCH_ESCAPE_CHARACTER + "')"
+ + " OR (" + pduTable + ".sub LIKE ? ESCAPE '"
+ + SEARCH_ESCAPE_CHARACTER + "')))",
+ PDU_PROJECTION,
+ PduHeaders.TO);
+
+ rawQuery = String.format(
+ "%s UNION %s GROUP BY threads._id ORDER BY date DESC",
+ smsContentQuery,
+ pduContentQuery);
+ }
+ if (DEBUG) {
+ Log.d(LOG_TAG, "getConversationQueryString = " + rawQuery);
+ }
+ return rawQuery;
+ }
+
@Override
public Bundle call(String method, String arg, Bundle extras) {
if (METHOD_IS_RESTORING.equals(method)) {
diff --git a/src/com/android/providers/telephony/SmsProvider.java b/src/com/android/providers/telephony/SmsProvider.java
old mode 100644
new mode 100755
index 81f732b..2ff78ab
--- a/src/com/android/providers/telephony/SmsProvider.java
+++ b/src/com/android/providers/telephony/SmsProvider.java
@@ -16,6 +16,12 @@
package com.android.providers.telephony;
+import static android.telephony.SmsMessage.ENCODING_16BIT;
+import static android.telephony.SmsMessage.ENCODING_7BIT;
+import static android.telephony.SmsMessage.ENCODING_UNKNOWN;
+import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES;
+import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS;
+
import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.content.ContentProvider;
@@ -37,16 +43,32 @@
import android.provider.Telephony.MmsSms;
import android.provider.Telephony.Sms;
import android.provider.Telephony.Threads;
+import android.telephony.PhoneNumberUtils;
import android.telephony.SmsManager;
import android.telephony.SmsMessage;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.text.SimpleDateFormat;
import com.android.internal.annotations.VisibleForTesting;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
+
+import com.android.internal.telephony.EncodeException;
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.cdma.sms.BearerData;
+import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
+import com.android.internal.telephony.cdma.sms.UserData;
public class SmsProvider extends ContentProvider {
/* No response constant from SmsResponse */
@@ -61,7 +83,16 @@
private static final String TABLE_WORDS = "words";
static final String VIEW_SMS_RESTRICTED = "sms_restricted";
+ private static final int DELETE_SUCCESS = 1;
+ private static final int DELETE_FAIL = 0;
+ private static final int MESSAGE_ID = 1;
+ private static final int SLOT1 = 0;
+ private static final int SLOT2 = 1;
private static final Integer ONE = Integer.valueOf(1);
+ private static final int OFFSET_ADDRESS_LENGTH = 0;
+ private static final int OFFSET_TOA = 1;
+ private static final int OFFSET_ADDRESS_VALUE = 2;
+ private static final int TIMESTAMP_LENGTH = 7; // See TS 23.040 9.2.3.11
private static final String[] CONTACT_QUERY_PROJECTION =
new String[] { Contacts.Phones.PERSON_ID };
@@ -70,6 +101,10 @@
/** Delete any raw messages or message segments marked deleted that are older than an hour. */
static final long RAW_MESSAGE_EXPIRE_AGE_MS = (long) (60 * 60 * 1000);
+ private static final String SMS_BOX_ID = "box_id";
+ private static final String INSERT_SMS_INTO_ICC_SUCCESS = "success";
+ private static final String INSERT_SMS_INTO_ICC_FAIL = "fail";
+
/**
* These are the columns that are available when reading SMS
* messages from the ICC. Columns whose names begin with "is_"
@@ -90,7 +125,8 @@
"type", // depend on getStatusOnIcc
"locked", // Always 0 (false).
"error_code", // Always -1 (NO_ERROR_CODE), previously it was 0 always.
- "_id"
+ "_id",
+ "sub_id"
};
@Override
@@ -299,6 +335,22 @@
return ret;
}
+ case SMS_ALL_ICC1:
+ return getAllMessagesFromIcc(SubscriptionManager.getSubId(SLOT1)[0]);
+
+ case SMS_ICC1:
+ String messageIndexIcc1 = url.getPathSegments().get(MESSAGE_ID);
+ return getSingleMessageFromIcc(SubscriptionManager.getSubId(SLOT1)[0],
+ Integer.parseInt(messageIndexIcc1));
+
+ case SMS_ALL_ICC2:
+ return getAllMessagesFromIcc(SubscriptionManager.getSubId(SLOT2)[0]);
+
+ case SMS_ICC2:
+ String messageIndexIcc2 = url.getPathSegments().get(MESSAGE_ID);
+ return getSingleMessageFromIcc(SubscriptionManager.getSubId(SLOT2)[0],
+ Integer.parseInt(messageIndexIcc2));
+
default:
Log.e(TAG, "Invalid request: " + url);
return null;
@@ -338,7 +390,7 @@
return mCeOpenHelper;
}
- private Object[] convertIccToSms(SmsMessage message, int id) {
+ private Object[] convertIccToSms(SmsMessage message, int id, int subId) {
int statusOnIcc = message.getStatusOnIcc();
int type = Sms.MESSAGE_TYPE_ALL;
switch (statusOnIcc) {
@@ -353,9 +405,7 @@
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];
+ Object[] row = new Object[14];
row[0] = message.getServiceCenterAddress();
row[1] =
(type == Sms.MESSAGE_TYPE_INBOX)
@@ -373,6 +423,7 @@
row[10] = 0; // locked
row[11] = NO_ERROR_CODE;
row[12] = id;
+ row[13] = subId;
return row;
}
@@ -393,7 +444,7 @@
// Use phone app permissions to avoid UID mismatch in AppOpsManager.noteOp() call.
long token = Binder.clearCallingIdentity();
try {
- messages = smsManager.getMessagesFromIcc();
+ messages = SmsManager.getSmsManagerForSubscriptionId(subId).getMessagesFromIcc();
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -404,7 +455,7 @@
"No message in index " + messageIndex + " for subId " + subId);
}
MatrixCursor cursor = new MatrixCursor(ICC_COLUMNS, 1);
- cursor.addRow(convertIccToSms(message, 0));
+ cursor.addRow(convertIccToSms(message, 0, subId));
return cursor;
}
@@ -424,7 +475,8 @@
// Use phone app permissions to avoid UID mismatch in AppOpsManager.noteOp() call
long token = Binder.clearCallingIdentity();
try {
- messages = smsManager.getMessagesFromIcc();
+ messages = SmsManager.getSmsManagerForSubscriptionId(subId)
+ .getMessagesFromIcc();
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -434,7 +486,7 @@
for (int i = 0; i < count; i++) {
SmsMessage message = messages.get(i);
if (message != null) {
- cursor.addRow(convertIccToSms(message, i));
+ cursor.addRow(convertIccToSms(message, i, subId));
}
}
return cursor;
@@ -588,6 +640,8 @@
break;
case SMS_ALL_ICC:
+ return insertMessageIntoIcc(url, initialValues);
+
case SMS_ALL_ICC_SUBID:
int subId;
if (match == SMS_ALL_ICC) {
@@ -761,6 +815,339 @@
return null;
}
+ private Uri insertMessageIntoIcc(Uri uri, ContentValues values) {
+ if (values == null) {
+ return Uri.withAppendedPath(uri, INSERT_SMS_INTO_ICC_FAIL);
+ }
+ int subId = values.getAsInteger(PhoneConstants.SUBSCRIPTION_KEY);
+ String address = values.getAsString(Sms.ADDRESS);
+ String message = values.getAsString(Sms.BODY);
+ int boxId = values.getAsInteger(SMS_BOX_ID);
+ long timestamp = values.getAsLong(Sms.DATE);
+ byte pdu[] = null;
+ int status;
+ if (Sms.isOutgoingFolder(boxId)) {
+ pdu = SmsMessage.getSubmitPdu(null, address, message, false, subId).encodedMessage;
+ status = SmsManager.STATUS_ON_ICC_SENT;
+ } else {
+ pdu = getDeliveryPdu(null, address, message, timestamp, subId);
+ status = SmsManager.STATUS_ON_ICC_READ;
+ }
+ boolean result = SmsManager.getSmsManagerForSubscriptionId(subId).copyMessageToIcc(null,
+ pdu, status);
+ return Uri.withAppendedPath(uri,
+ (result ? INSERT_SMS_INTO_ICC_SUCCESS : INSERT_SMS_INTO_ICC_FAIL));
+ }
+
+ /**
+ * Generate a Delivery PDU byte array. see getSubmitPdu for reference.
+ */
+ public static byte[] getDeliveryPdu(String scAddress, String destinationAddress, String message,
+ long date, int subscription) {
+ if (isCdmaPhone(subscription)) {
+ return getCdmaDeliveryPdu(scAddress, destinationAddress, message, date);
+ } else {
+ return getGsmDeliveryPdu(scAddress, destinationAddress, message, date, null,
+ ENCODING_UNKNOWN);
+ }
+ }
+
+ private static boolean isCdmaPhone(int subscription) {
+ boolean isCdma = false;
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(subscription);
+ if (TelephonyManager.PHONE_TYPE_CDMA == activePhone) {
+ isCdma = true;
+ }
+ return isCdma;
+ }
+
+ public static byte[] getCdmaDeliveryPdu(String scAddress, String destinationAddress,
+ String message, long date) {
+ // Perform null parameter checks.
+ if (message == null || destinationAddress == null) {
+ Log.d(TAG, "getCDMADeliveryPdu,message =null");
+ return null;
+ }
+
+ // according to submit pdu encoding as written in privateGetSubmitPdu
+
+ // MTI = SMS-DELIVERY, UDHI = header != null
+ byte[] header = null;
+ byte mtiByte = (byte) (0x00 | (header != null ? 0x40 : 0x00));
+ ByteArrayOutputStream headerStream = getDeliveryPduHeader(destinationAddress, mtiByte);
+
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream(MAX_USER_DATA_BYTES + 40);
+
+ DataOutputStream dos = new DataOutputStream(byteStream);
+ // int status,Status of message. See TS 27.005 3.1, "<stat>"
+
+ /* 0 = "REC UNREAD" */
+ /* 1 = "REC READ" */
+ /* 2 = "STO UNSENT" */
+ /* 3 = "STO SENT" */
+
+ try {
+ // int uTeleserviceID;
+ int uTeleserviceID = 0; //.TELESERVICE_CT_WAP;// int
+ dos.writeInt(uTeleserviceID);
+
+ // unsigned char bIsServicePresent
+ byte bIsServicePresent = 0;// byte
+ dos.writeInt(bIsServicePresent);
+
+ // uServicecategory
+ int uServicecategory = 0;// int
+ dos.writeInt(uServicecategory);
+
+ // RIL_CDMA_SMS_Address
+ // digit_mode
+ // number_mode
+ // number_type
+ // number_plan
+ // number_of_digits
+ // digits[]
+ CdmaSmsAddress destAddr = CdmaSmsAddress.parse(PhoneNumberUtils
+ .cdmaCheckAndProcessPlusCode(destinationAddress));
+ if (destAddr == null)
+ return null;
+ dos.writeByte(destAddr.digitMode);// int
+ dos.writeByte(destAddr.numberMode);// int
+ dos.writeByte(destAddr.ton);// int
+ Log.d(TAG, "message type=" + destAddr.ton + "destination add=" + destinationAddress
+ + "message=" + message);
+ dos.writeByte(destAddr.numberPlan);// int
+ dos.writeByte(destAddr.numberOfDigits);// byte
+ dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits
+
+ // RIL_CDMA_SMS_Subaddress
+ // Subaddress is not supported.
+ dos.writeByte(0); // subaddressType int
+ dos.writeByte(0); // subaddr_odd byte
+ dos.writeByte(0); // subaddr_nbr_of_digits byte
+
+ SmsHeader smsHeader = new SmsHeader().fromByteArray(headerStream.toByteArray());
+ UserData uData = new UserData();
+ uData.payloadStr = message;
+ // uData.userDataHeader = smsHeader;
+ uData.msgEncodingSet = true;
+ uData.msgEncoding = UserData.ENCODING_UNICODE_16;
+
+ BearerData bearerData = new BearerData();
+ bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
+
+ bearerData.deliveryAckReq = false;
+ bearerData.userAckReq = false;
+ bearerData.readAckReq = false;
+ bearerData.reportReq = false;
+
+ bearerData.userData = uData;
+
+ byte[] encodedBearerData = BearerData.encode(bearerData);
+ if (null != encodedBearerData) {
+ // bearer data len
+ dos.writeByte(encodedBearerData.length);// int
+ Log.d(TAG, "encodedBearerData length=" + encodedBearerData.length);
+
+ // aBearerData
+ dos.write(encodedBearerData, 0, encodedBearerData.length);
+ } else {
+ dos.writeByte(0);
+ }
+
+ } catch (IOException e) {
+ Log.e(TAG, "Error writing dos", e);
+ } finally {
+ try {
+ if (null != byteStream) {
+ byteStream.close();
+ }
+
+ if (null != dos) {
+ dos.close();
+ }
+
+ if (null != headerStream) {
+ headerStream.close();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error close dos", e);
+ }
+ }
+
+ return byteStream.toByteArray();
+ }
+
+ /**
+ * Generate a Delivery PDU byte array. see getSubmitPdu for reference.
+ */
+ public static byte[] getGsmDeliveryPdu(String scAddress, String destinationAddress,
+ String message, long date, byte[] header, int encoding) {
+ // Perform null parameter checks.
+ if (message == null || destinationAddress == null) {
+ return null;
+ }
+
+ // MTI = SMS-DELIVERY, UDHI = header != null
+ byte mtiByte = (byte)(0x00 | (header != null ? 0x40 : 0x00));
+ ByteArrayOutputStream bo = getDeliveryPduHeader(destinationAddress, mtiByte);
+ // User Data (and length)
+ byte[] userData;
+ if (encoding == ENCODING_UNKNOWN) {
+ // First, try encoding it with the GSM alphabet
+ encoding = ENCODING_7BIT;
+ }
+ try {
+ if (encoding == ENCODING_7BIT) {
+ userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header, 0, 0);
+ } else { //assume UCS-2
+ try {
+ userData = encodeUCS2(message, header);
+ } catch (UnsupportedEncodingException uex) {
+ Log.e("GSM", "Implausible UnsupportedEncodingException ",
+ uex);
+ return null;
+ }
+ }
+ } catch (EncodeException ex) {
+ // Encoding to the 7-bit alphabet failed. Let's see if we can
+ // encode it as a UCS-2 encoded message
+ try {
+ userData = encodeUCS2(message, header);
+ encoding = ENCODING_16BIT;
+ } catch (UnsupportedEncodingException uex) {
+ Log.e("GSM", "Implausible UnsupportedEncodingException ",
+ uex);
+ return null;
+ }
+ }
+
+ if (encoding == ENCODING_7BIT) {
+ if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) {
+ // Message too long
+ return null;
+ }
+ bo.write(0x00);
+ } else { //assume UCS-2
+ if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) {
+ // Message too long
+ return null;
+ }
+ // TP-Data-Coding-Scheme
+ // Class 3, UCS-2 encoding, uncompressed
+ bo.write(0x0b);
+ }
+ byte[] timestamp = getTimestamp(date);
+ bo.write(timestamp, 0, timestamp.length);
+
+ bo.write(userData, 0, userData.length);
+ return bo.toByteArray();
+ }
+
+ private static ByteArrayOutputStream getDeliveryPduHeader(
+ String destinationAddress, byte mtiByte) {
+ ByteArrayOutputStream bo = new ByteArrayOutputStream(
+ MAX_USER_DATA_BYTES + 40);
+ bo.write(mtiByte);
+
+ byte[] daBytes;
+ daBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(destinationAddress);
+
+ if (daBytes == null) {
+ Log.d(TAG, "The number can not convert to BCD, it's an An alphanumeric address, " +
+ "destinationAddress = " + destinationAddress);
+ // Convert address to GSM 7 bit packed bytes.
+ try {
+ byte[] numberdata = GsmAlphabet
+ .stringToGsm7BitPacked(destinationAddress);
+ // Get the real address data
+ byte[] addressData = new byte[numberdata.length - 1];
+ System.arraycopy(numberdata, 1, addressData, 0, addressData.length);
+
+ daBytes = new byte[addressData.length + OFFSET_ADDRESS_VALUE];
+ // Get the address length
+ int addressLen = numberdata[0];
+ daBytes[OFFSET_ADDRESS_LENGTH] = (byte) ((addressLen * 7 % 4 != 0 ?
+ addressLen * 7 / 4 + 1 : addressLen * 7 / 4));
+ // Set address type to Alphanumeric according to 3GPP TS 23.040 [9.1.2.5]
+ daBytes[OFFSET_TOA] = (byte) 0xd0;
+ System.arraycopy(addressData, 0, daBytes, OFFSET_ADDRESS_VALUE, addressData.length);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception when encoding to 7 bit data.");
+ }
+ } else {
+ // destination address length in BCD digits, ignoring TON byte and pad
+ // TODO Should be better.
+ bo.write((daBytes.length - 1) * 2
+ - ((daBytes[daBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0));
+ }
+
+ // destination address
+ bo.write(daBytes, 0, daBytes.length);
+
+ // TP-Protocol-Identifier
+ bo.write(0);
+ return bo;
+ }
+
+ private static byte[] encodeUCS2(String message, byte[] header)
+ throws UnsupportedEncodingException {
+ byte[] userData, textPart;
+ textPart = message.getBytes("utf-16be");
+
+ if (header != null) {
+ // Need 1 byte for UDHL
+ userData = new byte[header.length + textPart.length + 1];
+
+ userData[0] = (byte)header.length;
+ System.arraycopy(header, 0, userData, 1, header.length);
+ System.arraycopy(textPart, 0, userData, header.length + 1, textPart.length);
+ }
+ else {
+ userData = textPart;
+ }
+ byte[] ret = new byte[userData.length+1];
+ ret[0] = (byte) (userData.length & 0xff );
+ System.arraycopy(userData, 0, ret, 1, userData.length);
+ return ret;
+ }
+
+ private static byte[] getTimestamp(long time) {
+ // See TS 23.040 9.2.3.11
+ byte[] timestamp = new byte[TIMESTAMP_LENGTH];
+ SimpleDateFormat sdf = new SimpleDateFormat("yyMMddkkmmss:Z", Locale.US);
+ String[] date = sdf.format(time).split(":");
+ // generate timezone value
+ String timezone = date[date.length - 1];
+ String signMark = timezone.substring(0, 1);
+ int hour = Integer.parseInt(timezone.substring(1, 3));
+ int min = Integer.parseInt(timezone.substring(3));
+ int timezoneValue = hour * 4 + min / 15;
+ // append timezone value to date[0] (time string)
+ String timestampStr = date[0] + timezoneValue;
+
+ int digitCount = 0;
+ for (int i = 0; i < timestampStr.length(); i++) {
+ char c = timestampStr.charAt(i);
+ int shift = ((digitCount & 0x01) == 1) ? 4 : 0;
+ timestamp[(digitCount >> 1)] |= (byte)((charToBCD(c) & 0x0F) << shift);
+ digitCount++;
+ }
+
+ if (signMark.equals("-")) {
+ timestamp[timestamp.length - 1] = (byte) (timestamp[timestamp.length - 1] | 0x08);
+ }
+
+ return timestamp;
+ }
+
+ private static int charToBCD(char c) {
+ if (c >= '0' && c <= '9') {
+ return c - '0';
+ } else {
+ throw new RuntimeException ("invalid char for BCD " + c);
+ }
+ }
+
private boolean isSupportedType(int messageType) {
return (messageType == Sms.MESSAGE_TYPE_INBOX)
|| (messageType == Sms.MESSAGE_TYPE_OUTBOX)
@@ -908,6 +1295,16 @@
UserHandle.USER_ALL);
return success ? 1 : 0; // return deleted count
+ case SMS_ICC1:
+ String messageIndexIcc1 = url.getPathSegments().get(MESSAGE_ID);
+ return deleteMessageFromIcc(SubscriptionManager.getSubId(SLOT1)[0],
+ Integer.parseInt(messageIndexIcc1)) ? 1 : 0;
+
+ case SMS_ICC2:
+ String messageIndexIcc2 = url.getPathSegments().get(MESSAGE_ID);
+ return deleteMessageFromIcc(SubscriptionManager.getSubId(SLOT2)[0],
+ Integer.parseInt(messageIndexIcc2)) ? 1 : 0;
+
default:
throw new IllegalArgumentException("Unknown URL");
}
@@ -1082,6 +1479,11 @@
private static final int SMS_ALL_ICC_SUBID = 29;
private static final int SMS_ICC_SUBID = 30;
+ private static final int SMS_ALL_ICC1 = 31;
+ private static final int SMS_ICC1 = 32;
+ private static final int SMS_ALL_ICC2 = 33;
+ private static final int SMS_ICC2 = 34;
+
private static final UriMatcher sURLMatcher =
new UriMatcher(UriMatcher.NO_MATCH);
@@ -1114,6 +1516,10 @@
sURLMatcher.addURI("sms", "icc/#", SMS_ICC);
sURLMatcher.addURI("sms", "icc_subId/#", SMS_ALL_ICC_SUBID);
sURLMatcher.addURI("sms", "icc_subId/#/#", SMS_ICC_SUBID);
+ sURLMatcher.addURI("sms", "icc1", SMS_ALL_ICC1);
+ sURLMatcher.addURI("sms", "icc1/#", SMS_ICC1);
+ sURLMatcher.addURI("sms", "icc2", SMS_ALL_ICC2);
+ sURLMatcher.addURI("sms", "icc2/#", SMS_ICC2);
//we keep these for not breaking old applications
sURLMatcher.addURI("sms", "sim", SMS_ALL_ICC);
sURLMatcher.addURI("sms", "sim/#", SMS_ICC);
diff --git a/src/com/android/providers/telephony/TelephonyBackupAgent.java b/src/com/android/providers/telephony/TelephonyBackupAgent.java
index 5c764c8..930a98a 100644
--- a/src/com/android/providers/telephony/TelephonyBackupAgent.java
+++ b/src/com/android/providers/telephony/TelephonyBackupAgent.java
@@ -631,12 +631,16 @@
ContentValues[] values = new ContentValues[bulkInsertSize];
while (jsonReader.hasNext()) {
ContentValues cv = readSmsValuesFromReader(jsonReader);
- if (doesSmsExist(cv)) {
- continue;
- }
- values[(msgCount++) % bulkInsertSize] = cv;
- if (msgCount % bulkInsertSize == 0) {
- mContentResolver.bulkInsert(Telephony.Sms.CONTENT_URI, values);
+ try {
+ if (mSmsProviderQuery.doesSmsExist(cv)) {
+ continue;
+ }
+ values[(msgCount++) % bulkInsertSize] = cv;
+ if (msgCount % bulkInsertSize == 0) {
+ mContentResolver.bulkInsert(Telephony.Sms.CONTENT_URI, values);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "putSmsMessagesToProvider", e);
}
}
if (msgCount % bulkInsertSize > 0) {
@@ -655,16 +659,20 @@
if (DEBUG) {
Log.d(TAG, "putMmsMessagesToProvider " + mms);
}
- if (doesMmsExist(mms)) {
- if (DEBUG) {
- Log.e(TAG, String.format("Mms: %s already exists", mms.toString()));
- } else {
- Log.w(TAG, "Mms: Found duplicate MMS");
+ try {
+ if (doesMmsExist(mms)) {
+ if (DEBUG) {
+ Log.e(TAG, String.format("Mms: %s already exists", mms.toString()));
+ } else {
+ Log.w(TAG, "Mms: Found duplicate MMS");
+ }
+ continue;
}
- continue;
+ total++;
+ addMmsMessage(mms);
+ } catch (Exception e) {
+ Log.e(TAG, "putMmsMessagesToProvider", e);
}
- total++;
- addMmsMessage(mms);
}
Log.d(TAG, "putMmsMessagesToProvider handled " + total + " new messages.");
}
@@ -673,15 +681,34 @@
static final String[] PROJECTION_ID = {BaseColumns._ID};
private static final int ID_IDX = 0;
- private boolean doesSmsExist(ContentValues smsValues) {
- final String where = String.format(Locale.US, "%s = %d and %s = %s",
- Telephony.Sms.DATE, smsValues.getAsLong(Telephony.Sms.DATE),
- Telephony.Sms.BODY,
- DatabaseUtils.sqlEscapeString(smsValues.getAsString(Telephony.Sms.BODY)));
- try (Cursor cursor = mContentResolver.query(Telephony.Sms.CONTENT_URI, PROJECTION_ID, where,
- null, null)) {
- return cursor != null && cursor.getCount() > 0;
+ /**
+ * Interface to allow mocking method for testing.
+ */
+ public interface SmsProviderQuery {
+ boolean doesSmsExist(ContentValues smsValues);
+ }
+
+ private SmsProviderQuery mSmsProviderQuery = new SmsProviderQuery() {
+ @Override
+ public boolean doesSmsExist(ContentValues smsValues) {
+ // The SMS body might contain '\0' characters (U+0000) such as in the case of
+ // http://b/160801497 . SQLite does not allow '\0' in String literals, but as of SQLite
+ // version 3.32.2 2020-06-04, it does allow them as selectionArgs; therefore, we're
+ // using the latter approach here.
+ final String selection = String.format(Locale.US, "%s=%d AND %s=?",
+ Telephony.Sms.DATE, smsValues.getAsLong(Telephony.Sms.DATE),
+ Telephony.Sms.BODY);
+ String[] selectionArgs = new String[] { smsValues.getAsString(Telephony.Sms.BODY)};
+ try (Cursor cursor = mContentResolver.query(Telephony.Sms.CONTENT_URI, PROJECTION_ID,
+ selection, selectionArgs, null)) {
+ return cursor != null && cursor.getCount() > 0;
+ }
}
+ };
+
+ @VisibleForTesting
+ public void setSmsProviderQuery(SmsProviderQuery smsProviderQuery) {
+ mSmsProviderQuery = smsProviderQuery;
}
private boolean doesMmsExist(Mms mms) {
diff --git a/src/com/android/providers/telephony/TelephonyProvider.java b/src/com/android/providers/telephony/TelephonyProvider.java
index 4966e5a..aca1edb 100644
--- a/src/com/android/providers/telephony/TelephonyProvider.java
+++ b/src/com/android/providers/telephony/TelephonyProvider.java
@@ -169,6 +169,9 @@
private static final int URL_FILTERED = 18;
private static final int URL_FILTERED_ID = 19;
private static final int URL_ENFORCE_MANAGED = 20;
+ // URL_PREFERAPNSET and URL_PREFERAPNSET_USING_SUBID return all APNs for the current
+ // carrier which have an apn_set_id equal to the preferred APN
+ // (if no preferred APN, or preferred APN has no set id, the query will return null)
private static final int URL_PREFERAPNSET = 21;
private static final int URL_PREFERAPNSET_USING_SUBID = 22;
private static final int URL_SIM_APN_LIST = 23;
@@ -2145,6 +2148,17 @@
}
// Update the network type bitmask to keep them sync.
networkTypeBitmask = convertBearerBitmaskToNetworkTypeBitmask(bearerBitmask);
+ // Legacy bearer is deprecated, in order to be compatible with bearer_bitmask till
+ // both are removed (bearer_bitmask is marked as deprecated now), just appends
+ // bearer into bearer_bitmask only.
+ // Use the constant string BEARER instead of the "bearer" by hard code.
+ final String apnBearer = parser.getAttributeValue(null, BEARER);
+ if (apnBearer != null) {
+ final int legacyBearerBitmask =
+ getBitmaskForTech(Integer.parseInt(apnBearer));
+ networkTypeBitmask |=
+ convertBearerBitmaskToNetworkTypeBitmask(legacyBearerBitmask);
+ }
map.put(NETWORK_TYPE_BITMASK, networkTypeBitmask);
}
map.put(BEARER_BITMASK, bearerBitmask);
@@ -3009,10 +3023,13 @@
// intentional fall through from above case
case URL_PREFERAPNSET: {
final int set = getPreferredApnSetId(subId);
- if (set != NO_APN_SET_ID) {
- constraints.add(APN_SET_ID + "=" + set);
+ if (set == NO_APN_SET_ID) {
+ return null;
}
- break;
+ constraints.add(APN_SET_ID + "=" + set);
+ qb.appendWhere(TextUtils.join(" AND ", constraints));
+ return getSubscriptionMatchingAPNList(qb, projectionIn, selection, selectionArgs,
+ sort, subId);
}
case URL_DPC: {
@@ -3147,7 +3164,14 @@
private Cursor getSubscriptionMatchingAPNList(SQLiteQueryBuilder qb, String[] projectionIn,
String selection, String[] selectionArgs, String sort, int subId) {
Cursor ret;
- final TelephonyManager tm = ((TelephonyManager) getContext()
+ Context context = getContext();
+ SubscriptionManager subscriptionManager = (SubscriptionManager) context
+ .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ if (!subscriptionManager.isActiveSubscriptionId(subId)) {
+ return null;
+ }
+
+ final TelephonyManager tm = ((TelephonyManager) context
.getSystemService(Context.TELEPHONY_SERVICE))
.createForSubscriptionId(subId);
SQLiteDatabase db = getReadableDatabase();
diff --git a/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java b/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
index b1cd5e4..a576c44 100644
--- a/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
+++ b/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
@@ -616,6 +616,35 @@
}
/**
+ * Test that crashing for one sms does not block restore of other messages.
+ * @throws Exception
+ */
+ public void testRestoreSms_WithException() throws Exception {
+ mTelephonyBackupAgent.initUnknownSender();
+ JsonReader jsonReader = new JsonReader(new StringReader(addRandomDataToJson(mAllSmsJson)));
+ FakeSmsProvider smsProvider = new FakeSmsProvider(mSmsRows, false);
+ mMockContentResolver.addProvider("sms", smsProvider);
+ TelephonyBackupAgent.SmsProviderQuery smsProviderQuery =
+ new TelephonyBackupAgent.SmsProviderQuery() {
+ int mIteration = 0;
+ @Override
+ public boolean doesSmsExist(ContentValues smsValues) {
+ if (mIteration == 0) {
+ mIteration++;
+ throw new RuntimeException("fake crash for first message");
+ }
+ return false;
+ }
+ };
+ mTelephonyBackupAgent.setSmsProviderQuery(smsProviderQuery);
+
+ mTelephonyBackupAgent.putSmsMessagesToProvider(jsonReader);
+ // the "- 1" is due to exception thrown for one of the messages
+ assertEquals(mSmsRows.length - 1, smsProvider.getRowsAdded());
+ assertEquals(mThreadProvider.mIsThreadArchived, mThreadProvider.mUpdateThreadsArchived);
+ }
+
+ /**
* Test restore mms with the empty json array "[]".
* @throws Exception
*/
@@ -751,11 +780,17 @@
private class FakeSmsProvider extends MockContentProvider {
private int nextRow = 0;
private ContentValues[] mSms;
+ private boolean mCheckInsertedValues = true;
public FakeSmsProvider(ContentValues[] sms) {
this.mSms = sms;
}
+ public FakeSmsProvider(ContentValues[] sms, boolean checkInsertedValues) {
+ this.mSms = sms;
+ mCheckInsertedValues = checkInsertedValues;
+ }
+
@Override
public Uri insert(Uri uri, ContentValues values) {
assertEquals(Telephony.Sms.CONTENT_URI, uri);
@@ -771,7 +806,7 @@
modifiedValues.put(Telephony.Sms.ADDRESS, TelephonyBackupAgent.UNKNOWN_SENDER);
}
- assertEquals(modifiedValues, values);
+ if (mCheckInsertedValues) assertEquals(modifiedValues, values);
return null;
}
diff --git a/tests/src/com/android/providers/telephony/TelephonyProviderTest.java b/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
index 18c8d08..203848a 100644
--- a/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
+++ b/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
@@ -51,11 +51,8 @@
import junit.framework.TestCase;
import org.junit.Test;
-import org.mockito.ArgumentMatchers;
-import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
@@ -123,12 +120,14 @@
private class MockContextWithProvider extends MockContext {
private final MockContentResolver mResolver;
private TelephonyManager mTelephonyManager = mock(TelephonyManager.class);
+ private SubscriptionManager mSubscriptionManager = mock(SubscriptionManager.class);
private final List<String> GRANTED_PERMISSIONS = Arrays.asList(
Manifest.permission.MODIFY_PHONE_STATE, Manifest.permission.WRITE_APN_SETTINGS,
Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
- public MockContextWithProvider(TelephonyProvider telephonyProvider) {
+ public MockContextWithProvider(TelephonyProvider telephonyProvider,
+ Boolean isActiveSubscription) {
mResolver = new MockContentResolver() {
@Override
public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork,
@@ -146,7 +145,8 @@
// return test subId 0 for all operators
doReturn(TEST_OPERATOR).when(mTelephonyManager).getSimOperator(anyInt());
-
+ doReturn(isActiveSubscription).when(mSubscriptionManager)
+ .isActiveSubscriptionId(anyInt());
doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
doReturn(TEST_OPERATOR).when(mTelephonyManager).getSimOperator();
doReturn(TEST_CARRIERID).when(mTelephonyManager).getSimCarrierId();
@@ -171,6 +171,9 @@
if (name.equals(Context.TELEPHONY_SERVICE)) {
Log.d(TAG, "getSystemService: returning mock TM");
return mTelephonyManager;
+ } else if (name.equals(Context.TELEPHONY_SUBSCRIPTION_SERVICE)){
+ Log.d(TAG, "getSystemService: returning mock SubscriptionManager");
+ return mSubscriptionManager;
} else {
Log.d(TAG, "getSystemService: returning null");
return null;
@@ -181,6 +184,8 @@
public String getSystemServiceName(Class<?> serviceClass) {
if (serviceClass.equals(TelephonyManager.class)) {
return Context.TELEPHONY_SERVICE;
+ } else if (serviceClass.equals(SubscriptionManager.class)) {
+ return Context.TELEPHONY_SUBSCRIPTION_SERVICE;
} else {
Log.d(TAG, "getSystemServiceName: returning null");
return null;
@@ -223,12 +228,15 @@
super.setUp();
MockitoAnnotations.initMocks(this);
mTelephonyProviderTestable = new TelephonyProviderTestable();
- mContext = new MockContextWithProvider(mTelephonyProviderTestable);
- mContentResolver = (MockContentResolver) mContext.getContentResolver();
notifyChangeCount = 0;
notifyChangeRestoreCount = 0;
}
+ private void setUpMockContext(boolean isActiveSubId) {
+ mContext = new MockContextWithProvider(mTelephonyProviderTestable, isActiveSubId);
+ mContentResolver = mContext.getContentResolver();
+ }
+
@Override
protected void tearDown() throws Exception {
super.tearDown();
@@ -242,6 +250,8 @@
@Test
@SmallTest
public void testBulkInsertCarriers() {
+ setUpMockContext(true);
+
// insert 2 test contentValues
ContentValues contentValues = new ContentValues();
final String insertApn = "exampleApnName";
@@ -313,6 +323,8 @@
@Test
@SmallTest
public void testMccMncMigration() {
+ setUpMockContext(true);
+
CarrierIdProviderTestable carrierIdProvider = new CarrierIdProviderTestable();
carrierIdProvider.initializeForTesting(mContext);
mContentResolver.addProvider(Telephony.CarrierId.All.CONTENT_URI.getAuthority(),
@@ -372,6 +384,8 @@
@Test
@SmallTest
public void testUpdateConflictingCarriers() {
+ setUpMockContext(true);
+
// insert 2 test contentValues
ContentValues contentValues = new ContentValues();
final String insertApn = "exampleApnName";
@@ -429,6 +443,8 @@
}
private void doSimpleTestForUri(Uri uri) {
+ setUpMockContext(true);
+
// insert test contentValues
ContentValues contentValues = new ContentValues();
final String insertApn = "exampleApnName";
@@ -479,6 +495,8 @@
@Test
@SmallTest
public void testOwnedBy() {
+ setUpMockContext(true);
+
// insert test contentValues
ContentValues contentValues = new ContentValues();
final String insertApn = "exampleApnName";
@@ -542,6 +560,8 @@
@Test
@SmallTest
public void testSimTable() {
+ setUpMockContext(true);
+
// insert test contentValues
ContentValues contentValues = new ContentValues();
final int insertSubId = 11;
@@ -627,6 +647,8 @@
@Test
@SmallTest
public void testEnforceManagedUri() {
+ setUpMockContext(true);
+
mTelephonyProviderTestable.fakeCallingUid(Process.SYSTEM_UID);
final int current = 1;
@@ -749,6 +771,8 @@
* Test URL_TELEPHONY cannot insert, query, update or delete DPC records.
*/
public void testTelephonyUriDpcRecordAccessControl() {
+ setUpMockContext(true);
+
mTelephonyProviderTestable.fakeCallingUid(Process.SYSTEM_UID);
final int current = 1;
@@ -825,6 +849,8 @@
@Test
@SmallTest
public void testDpcUri() {
+ setUpMockContext(true);
+
int dpcRecordId = 0, othersRecordId = 0;
try {
mTelephonyProviderTestable.fakeCallingUid(Process.SYSTEM_UID);
@@ -916,6 +942,8 @@
@Test
@SmallTest
public void testDpcUriOnConflict() {
+ setUpMockContext(true);
+
int dpcRecordId1 = 0, dpcRecordId2 = 0;
try {
mTelephonyProviderTestable.fakeCallingUid(Process.SYSTEM_UID);
@@ -988,6 +1016,8 @@
@Test
@SmallTest
public void testAccessUrlDpcThrowSecurityExceptionFromOtherUid() {
+ setUpMockContext(true);
+
mTelephonyProviderTestable.fakeCallingUid(Process.SYSTEM_UID + 123456);
// Test insert().
@@ -1091,6 +1121,8 @@
}
private void preserveEditedValueInMerge(int value) {
+ setUpMockContext(true);
+
// insert user deleted APN
String carrierName1 = "carrier1";
String numeric1 = "123234";
@@ -1138,6 +1170,8 @@
}
private void preserveDeletedValueInMerge(int value) {
+ setUpMockContext(true);
+
// insert user deleted APN
String carrierName1 = "carrier1";
String numeric1 = "123234";
@@ -1184,6 +1218,8 @@
@Test
@SmallTest
public void testQueryPreferredApn() {
+ setUpMockContext(true);
+
// create APNs
ContentValues preferredValues = new ContentValues();
final String preferredApn = "preferredApn";
@@ -1230,6 +1266,8 @@
@Test
@SmallTest
public void testApnSetId() {
+ setUpMockContext(true);
+
// create APNs
ContentValues values1 = new ContentValues();
final String apn = "apnName";
@@ -1274,6 +1312,8 @@
@Test
@SmallTest
public void testPreferApnSetUrl() {
+ setUpMockContext(true);
+
// create APNs
ContentValues values1 = new ContentValues();
final String apn = "apnName";
@@ -1298,20 +1338,35 @@
values3.put(Carriers.NUMERIC, TEST_OPERATOR);
values3.put(Carriers.APN_SET_ID, 1);
+ // values4 has a matching setId but it belongs to a different carrier
+ ContentValues values4 = new ContentValues();
+ final String apn4 = "fourthApnName";
+ final String name4 = "name4";
+ values4.put(Carriers.APN, apn4);
+ values4.put(Carriers.NAME, name4);
+ values4.put(Carriers.NUMERIC, "999888");
+ values4.put(Carriers.APN_SET_ID, 1);
+
// insert APNs
// we explicitly include subid, as SubscriptionManager.getDefaultSubscriptionId() returns -1
Log.d(TAG, "testPreferApnSetUrl: inserting contentValues=" + values1 + ", " + values2
- + ", " + values3);
+ + ", " + values3 + ", " + values4);
mContentResolver.insert(CONTENT_URI_WITH_SUBID, values1);
mContentResolver.insert(CONTENT_URI_WITH_SUBID, values2);
+ mContentResolver.insert(CONTENT_URI_WITH_SUBID, values4);
Uri uri = mContentResolver.insert(CONTENT_URI_WITH_SUBID, values3);
- // before there's a preferred APN set, assert that all APNs are returned
+ // verify all APNs were correctly inserted
final String[] testProjection = { Carriers.NAME };
Cursor cursor = mContentResolver.query(
+ Carriers.CONTENT_URI, testProjection, null, null, null);
+ assertEquals(4, cursor.getCount());
+
+ // preferapnset/subId returns null when there is no preferred APN
+ cursor = mContentResolver.query(
Uri.withAppendedPath(Carriers.CONTENT_URI, "preferapnset/subId/" + TEST_SUBID),
testProjection, null, null, null);
- assertEquals(3, cursor.getCount());
+ assertNull(cursor);
// set the APN from values3 (apn_set_id = 1) to the preferred APN
final String preferredApnIdString = uri.getLastPathSegment();
@@ -1326,6 +1381,7 @@
cursor = mContentResolver.query(
Uri.withAppendedPath(Carriers.CONTENT_URI, "preferapnset/subId/" + TEST_SUBID),
testProjection, null, null, null);
+ // values4 which was inserted with a different carrier is not included in the results
assertEquals(2, cursor.getCount());
cursor.moveToFirst();
assertEquals(name2, cursor.getString(0));
@@ -1339,6 +1395,8 @@
@Test
@SmallTest
public void testRestoreDefaultApn() {
+ setUpMockContext(true);
+
// setup for multi-SIM
TelephonyManager telephonyManager =
(TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
@@ -1435,6 +1493,8 @@
@Test
@SmallTest
public void testUpdateWfcEnabled() {
+ setUpMockContext(true);
+
// insert test contentValues
ContentValues contentValues = new ContentValues();
final int insertSubId = 1;
@@ -1481,6 +1541,8 @@
@Test
@SmallTest
public void testSIMAPNLIST_MatchTheMVNOAPN() {
+ setUpMockContext(true);
+
// Test on getSubscriptionMatchingAPNList() step 1
final String apnName = "apnName";
final String carrierName = "name";
@@ -1519,6 +1581,7 @@
Carriers.NUMERIC,
Carriers.MVNO_MATCH_DATA
};
+
Cursor cursor = mContentResolver.query(URL_SIM_APN_LIST,
testProjection, null, null, null);
@@ -1534,6 +1597,8 @@
@Test
@SmallTest
public void testSIMAPNLIST_MatchTheMNOAPN() {
+ setUpMockContext(true);
+
// Test on getSubscriptionMatchingAPNList() step 2
final String apnName = "apnName";
final String carrierName = "name";
@@ -1553,6 +1618,7 @@
Carriers.NAME,
Carriers.NUMERIC,
};
+
Cursor cursor = mContentResolver.query(URL_SIM_APN_LIST,
testProjection, null, null, null);
@@ -1565,6 +1631,8 @@
@Test
@SmallTest
public void testSIMAPNLIST_MatchTheCarrierIDANDMNOAPN() {
+ setUpMockContext(true);
+
// Test on getSubscriptionMatchingAPNList() will return the {MCCMNC}
final String apnName = "apnName";
final String carrierName = "name";
@@ -1592,6 +1660,7 @@
Carriers.NAME,
Carriers.CARRIER_ID,
};
+
Cursor cursor = mContentResolver.query(URL_SIM_APN_LIST, testProjection, null, null, null);
// The query based on SIM_APN_LIST will return MNO APN and the APN that has carrier id
@@ -1601,6 +1670,8 @@
@Test
@SmallTest
public void testSIMAPNLIST_MatchTheCarrierAPNAndMVNOAPN() {
+ setUpMockContext(true);
+
final String apnName = "apnName";
final String carrierName = "name";
final String mvnoType = "spn";
@@ -1638,6 +1709,7 @@
Carriers.CARRIER_ID,
Carriers.MVNO_TYPE,
};
+
Cursor cursor = mContentResolver.query(URL_SIM_APN_LIST,
testProjection, null, null, null);
@@ -1648,4 +1720,34 @@
|| !TextUtils.isEmpty(cursor.getString(3)));
}
}
+
+ @Test
+ @SmallTest
+ public void testSIMAPNLIST_isNotActiveSubscription() {
+ setUpMockContext(false);
+
+ // Test on getSubscriptionMatchingAPNList() step 2
+ final String apnName = "apnName";
+ final String carrierName = "name";
+ final String numeric = TEST_OPERATOR;
+
+ // Insert the MNO APN
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(Carriers.APN, apnName);
+ contentValues.put(Carriers.NAME, carrierName);
+ contentValues.put(Carriers.NUMERIC, numeric);
+ mContentResolver.insert(Carriers.CONTENT_URI, contentValues);
+
+ // Query DB
+ final String[] testProjection =
+ {
+ Carriers.APN,
+ Carriers.NAME,
+ Carriers.NUMERIC,
+ };
+ Cursor cursor = mContentResolver.query(URL_SIM_APN_LIST,
+ testProjection, null, null, null);
+
+ assertNull(cursor);
+ }
}