Merge RP1A.200204.001
Change-Id: I100fc70f886186fadce408008cd5178d1c3dfd6e
diff --git a/Android.bp b/Android.bp
index e962812..fedcf7f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -4,5 +4,9 @@
srcs: ["src/**/*.java", "proto/**/*.proto"],
platform_apis: true,
certificate: "platform",
- static_libs: ["android-common", "telephonyprovider-protos"],
+ static_libs: [
+ "android-common",
+ "telephonyprovider-protos",
+ "android-support-v4"
+ ],
}
diff --git a/src/com/android/providers/telephony/MmsProvider.java b/src/com/android/providers/telephony/MmsProvider.java
index 96059d7..03fcbe0 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,17 +44,27 @@
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.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,
@@ -70,6 +81,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() {
@@ -89,6 +109,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) {
@@ -220,6 +289,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;
@@ -303,6 +385,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();
@@ -602,6 +800,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:
@@ -686,6 +893,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 {
@@ -700,12 +909,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;
@@ -714,12 +924,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);
@@ -731,6 +947,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);
@@ -1028,6 +1256,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);
@@ -1054,6 +1283,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 d578df4..1ab1f06
--- a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
+++ b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
@@ -105,19 +105,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" +
@@ -167,21 +154,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.
@@ -207,28 +179,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 =
@@ -250,6 +200,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;
@@ -496,6 +449,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.
@@ -558,6 +559,39 @@
Log.wtf(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);
@@ -669,8 +703,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" +
@@ -828,9 +872,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);
@@ -906,10 +947,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" +
@@ -918,18 +955,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 " +
@@ -1115,7 +1140,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.
@@ -1706,7 +1733,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) {
@@ -1952,6 +1978,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() {
@@ -2139,7 +2201,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/TelephonyProvider.java b/src/com/android/providers/telephony/TelephonyProvider.java
index c9f4ee7..0d2a82c 100644
--- a/src/com/android/providers/telephony/TelephonyProvider.java
+++ b/src/com/android/providers/telephony/TelephonyProvider.java
@@ -2139,6 +2139,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);