Loading src/java/com/android/internal/telephony/InboundSmsHandler.java +51 −49 Original line number Original line Diff line number Diff line Loading @@ -125,7 +125,8 @@ public abstract class InboundSmsHandler extends StateMachine { public static final int ID_COLUMN = 7; public static final int ID_COLUMN = 7; public static final String SELECT_BY_ID = "_id=?"; public static final String SELECT_BY_ID = "_id=?"; public static final String SELECT_BY_REFERENCE = "address=? AND reference_number=? AND count=?"; public static final String SELECT_BY_REFERENCE = "address=? AND reference_number=? AND " + "count=? AND deleted=0"; /** New SMS received as an AsyncResult. */ /** New SMS received as an AsyncResult. */ public static final int EVENT_NEW_SMS = 1; public static final int EVENT_NEW_SMS = 1; Loading Loading @@ -161,6 +162,8 @@ public abstract class InboundSmsHandler extends StateMachine { /** URI for raw table of SMS provider. */ /** URI for raw table of SMS provider. */ private static final Uri sRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw"); private static final Uri sRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw"); private static final Uri sRawUriPermanentDelete = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw/permanentDelete"); protected final Context mContext; protected final Context mContext; private final ContentResolver mResolver; private final ContentResolver mResolver; Loading Loading @@ -790,13 +793,13 @@ public abstract class InboundSmsHandler extends StateMachine { if (result == Activity.RESULT_OK) { if (result == Activity.RESULT_OK) { return true; return true; } else { } else { deleteFromRawTable(tracker.getDeleteWhere(), tracker.getDeleteWhereArgs()); deleteFromRawTable(tracker.getDeleteWhere(), tracker.getDeleteWhereArgs(), false); return false; return false; } } } } if (BlockChecker.isBlocked(mContext, tracker.getAddress())) { if (BlockChecker.isBlocked(mContext, tracker.getAddress())) { deleteFromRawTable(tracker.getDeleteWhere(), tracker.getDeleteWhereArgs()); deleteFromRawTable(tracker.getDeleteWhere(), tracker.getDeleteWhereArgs(), true); return false; return false; } } Loading Loading @@ -983,8 +986,10 @@ public abstract class InboundSmsHandler extends StateMachine { /** /** * Helper for {@link SmsBroadcastUndelivered} to delete an old message in the raw table. * Helper for {@link SmsBroadcastUndelivered} to delete an old message in the raw table. */ */ private void deleteFromRawTable(String deleteWhere, String[] deleteWhereArgs) { private void deleteFromRawTable(String deleteWhere, String[] deleteWhereArgs, int rows = mResolver.delete(sRawUri, deleteWhere, deleteWhereArgs); boolean permanentDelete) { Uri uri = permanentDelete ? sRawUriPermanentDelete : sRawUri; int rows = mResolver.delete(uri, deleteWhere, deleteWhereArgs); if (rows == 0) { if (rows == 0) { loge("No rows were deleted from raw table!"); loge("No rows were deleted from raw table!"); } else if (DBG) { } else if (DBG) { Loading Loading @@ -1074,7 +1079,6 @@ public abstract class InboundSmsHandler extends StateMachine { * @return true on success; false on failure to write to database * @return true on success; false on failure to write to database */ */ private int addTrackerToRawTable(InboundSmsTracker tracker) { private int addTrackerToRawTable(InboundSmsTracker tracker) { if (tracker.getMessageCount() != 1) { // check for duplicate message segments // check for duplicate message segments Cursor cursor = null; Cursor cursor = null; try { try { Loading @@ -1085,8 +1089,8 @@ public abstract class InboundSmsHandler extends StateMachine { String address = tracker.getAddress(); String address = tracker.getAddress(); String refNumber = Integer.toString(tracker.getReferenceNumber()); String refNumber = Integer.toString(tracker.getReferenceNumber()); String count = Integer.toString(tracker.getMessageCount()); String count = Integer.toString(tracker.getMessageCount()); String seqNumber = Integer.toString(sequence); String seqNumber = Integer.toString(sequence); String date = Long.toString(tracker.getTimestamp()); // set the delete selection args for multi-part message // set the delete selection args for multi-part message String[] deleteWhereArgs = {address, refNumber, count}; String[] deleteWhereArgs = {address, refNumber, count}; Loading @@ -1094,8 +1098,8 @@ public abstract class InboundSmsHandler extends StateMachine { // Check for duplicate message segments // Check for duplicate message segments cursor = mResolver.query(sRawUri, PDU_PROJECTION, cursor = mResolver.query(sRawUri, PDU_PROJECTION, "address=? AND reference_number=? AND count=? AND sequence=?", "address=? AND reference_number=? AND count=? AND sequence=? AND date=?", new String[] {address, refNumber, count, seqNumber}, null); new String[] {address, refNumber, count, seqNumber, date}, null); // moveToNext() returns false if no duplicates were found // moveToNext() returns false if no duplicates were found if (cursor.moveToNext()) { if (cursor.moveToNext()) { Loading @@ -1110,7 +1114,6 @@ public abstract class InboundSmsHandler extends StateMachine { } } return Intents.RESULT_SMS_DUPLICATED; // reject message return Intents.RESULT_SMS_DUPLICATED; // reject message } } cursor.close(); } catch (SQLException e) { } catch (SQLException e) { loge("Can't access multipart SMS database", e); loge("Can't access multipart SMS database", e); return Intents.RESULT_SMS_GENERIC_ERROR; // reject message return Intents.RESULT_SMS_GENERIC_ERROR; // reject message Loading @@ -1119,7 +1122,6 @@ public abstract class InboundSmsHandler extends StateMachine { cursor.close(); cursor.close(); } } } } } ContentValues values = tracker.getContentValues(); ContentValues values = tracker.getContentValues(); Loading Loading @@ -1212,7 +1214,7 @@ public abstract class InboundSmsHandler extends StateMachine { log("successful broadcast, deleting from raw table."); log("successful broadcast, deleting from raw table."); } } deleteFromRawTable(mDeleteWhere, mDeleteWhereArgs); deleteFromRawTable(mDeleteWhere, mDeleteWhereArgs, false); sendMessage(EVENT_BROADCAST_COMPLETE); sendMessage(EVENT_BROADCAST_COMPLETE); int durationMillis = (int) ((System.nanoTime() - mBroadcastTimeNano) / 1000000); int durationMillis = (int) ((System.nanoTime() - mBroadcastTimeNano) / 1000000); Loading Loading @@ -1318,7 +1320,7 @@ public abstract class InboundSmsHandler extends StateMachine { try { try { // Needs phone package permissions. // Needs phone package permissions. deleteFromRawTable(mSmsFilter.mSmsBroadcastReceiver.mDeleteWhere, deleteFromRawTable(mSmsFilter.mSmsBroadcastReceiver.mDeleteWhere, mSmsFilter.mSmsBroadcastReceiver.mDeleteWhereArgs); mSmsFilter.mSmsBroadcastReceiver.mDeleteWhereArgs, false); } finally { } finally { Binder.restoreCallingIdentity(token); Binder.restoreCallingIdentity(token); } } Loading src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java +8 −3 Original line number Original line Diff line number Diff line Loading @@ -65,6 +65,8 @@ public class SmsBroadcastUndelivered { /** URI for raw table from SmsProvider. */ /** URI for raw table from SmsProvider. */ private static final Uri sRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw"); private static final Uri sRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw"); private static final Uri sRawUriPermanentDelete = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw/permanentDelete"); private static SmsBroadcastUndelivered instance; private static SmsBroadcastUndelivered instance; /** Content resolver to use to access raw table from SmsProvider. */ /** Content resolver to use to access raw table from SmsProvider. */ Loading Loading @@ -135,7 +137,9 @@ public class SmsBroadcastUndelivered { HashSet<SmsReferenceKey> oldMultiPartMessages = new HashSet<SmsReferenceKey>(4); HashSet<SmsReferenceKey> oldMultiPartMessages = new HashSet<SmsReferenceKey>(4); Cursor cursor = null; Cursor cursor = null; try { try { cursor = mResolver.query(sRawUri, PDU_PENDING_MESSAGE_PROJECTION, null, null, null); // query only non-deleted ones cursor = mResolver.query(sRawUri, PDU_PENDING_MESSAGE_PROJECTION, "deleted = 0", null, null); if (cursor == null) { if (cursor == null) { Rlog.e(TAG, "error getting pending message cursor"); Rlog.e(TAG, "error getting pending message cursor"); return; return; Loading Loading @@ -182,8 +186,9 @@ public class SmsBroadcastUndelivered { } } // Delete old incomplete message segments // Delete old incomplete message segments for (SmsReferenceKey message : oldMultiPartMessages) { for (SmsReferenceKey message : oldMultiPartMessages) { int rows = mResolver.delete(sRawUri, InboundSmsHandler.SELECT_BY_REFERENCE, // delete permanently message.getDeleteWhereArgs()); int rows = mResolver.delete(sRawUriPermanentDelete, InboundSmsHandler.SELECT_BY_REFERENCE, message.getDeleteWhereArgs()); if (rows == 0) { if (rows == 0) { Rlog.e(TAG, "No rows were deleted from raw table!"); Rlog.e(TAG, "No rows were deleted from raw table!"); } else if (DBG) { } else if (DBG) { Loading src/java/com/android/internal/telephony/SmsMessageBase.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -52,7 +52,7 @@ public abstract class SmsMessageBase { /** {@hide} */ /** {@hide} */ protected boolean mIsEmail; protected boolean mIsEmail; /** {@hide} */ /** {@hide} Time when SC (service centre) received the message */ protected long mScTimeMillis; protected long mScTimeMillis; /** {@hide} The raw PDU of the message */ /** {@hide} The raw PDU of the message */ Loading tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java +161 −37 Original line number Original line Diff line number Diff line Loading @@ -56,6 +56,7 @@ import org.mockito.Mock; import java.lang.reflect.Method; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.ArrayList; import java.util.List; public class GsmInboundSmsHandlerTest extends TelephonyTest { public class GsmInboundSmsHandlerTest extends TelephonyTest { @Mock @Mock Loading @@ -77,6 +78,9 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { private FakeSmsContentProvider mContentProvider; private FakeSmsContentProvider mContentProvider; private static final Uri sRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw"); private static final Uri sRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw"); private static final Uri sRawUriPermanentDelete = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw/permanentDelete"); private ContentValues mInboundSmsTrackerCV = new ContentValues(); private ContentValues mInboundSmsTrackerCV = new ContentValues(); // For multi-part SMS // For multi-part SMS private ContentValues mInboundSmsTrackerCVPart1; private ContentValues mInboundSmsTrackerCVPart1; Loading @@ -93,13 +97,56 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { "destination_port", "destination_port", "address", "address", "sub_id", "sub_id", "pdu"}; "pdu", private MatrixCursor mRawCursor = new MatrixCursor(mRawColumns); "deleted"}; private List<ArrayList<Object>> mListOfRows = new ArrayList<ArrayList<Object>>(); private int mNumRows = 0; private int mNumRows = 0; private int getColumnIndex(String columnName) { int i = 0; for (String s : mRawColumns) { if (s.equals(columnName)) { break; } i++; } return i; } @Override @Override public int delete(Uri uri, String selection, String[] selectionArgs) { public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; int count = 0; if (mNumRows > 0) { // parse selection and selectionArgs SelectionParams selectionParams = new SelectionParams(); selectionParams.parseSelectionParams(selection, selectionArgs); List<Integer> deleteRows = new ArrayList<Integer>(); int i = -1; for (ArrayList<Object> row : mListOfRows) { i++; // filter based on selection parameters if needed if (selection != null) { if (!selectionParams.isMatch(row)) { continue; } } if (uri.compareTo(sRawUri) == 0) { row.set(getColumnIndex("deleted"), "1"); } else { // save index for removal deleteRows.add(i); } count++; } if (uri.compareTo(sRawUriPermanentDelete) == 0) { for (i = deleteRows.size() - 1; i >= 0; i--) { mListOfRows.remove(i); } } } return count; } } @Override @Override Loading @@ -107,7 +154,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { Uri newUri = null; Uri newUri = null; if (uri.compareTo(sRawUri) == 0) { if (uri.compareTo(sRawUri) == 0) { if (values != null) { if (values != null) { mRawCursor.addRow(convertRawCVtoArrayList(values)); mListOfRows.add(convertRawCVtoArrayList(values)); mNumRows++; mNumRows++; newUri = Uri.withAppendedPath(uri, "" + mNumRows); newUri = Uri.withAppendedPath(uri, "" + mNumRows); } } Loading @@ -120,9 +167,11 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { ArrayList<Object> newRow = new ArrayList<>(); ArrayList<Object> newRow = new ArrayList<>(); for (String key : mRawColumns) { for (String key : mRawColumns) { if (values.containsKey(key)) { if (values.containsKey(key)) { newRow.add(values.get(key)); newRow.add(values.getAsString(key)); } else if (key.equals("_id")) { } else if (key.equals("_id")) { newRow.add(mNumRows + 1); newRow.add(mNumRows + 1); } else if (key.equals("deleted")) { newRow.add("0"); } else { } else { newRow.add(null); newRow.add(null); } } Loading @@ -130,15 +179,11 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { return newRow; return newRow; } } @Override private class SelectionParams { public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { logd("query called for: " + selection); MatrixCursor cursor = new MatrixCursor(projection); if (mNumRows > 0) { // parse selection and selectionArgs String[] paramName = null; String[] paramName = null; String[] paramValue = null; String[] paramValue = null; private void parseSelectionParams(String selection, String[] selectionArgs) { if (selection != null) { if (selection != null) { selection = selection.toLowerCase(); selection = selection.toLowerCase(); String[] selectionParams = selection.toLowerCase().split("and"); String[] selectionParams = selection.toLowerCase().split("and"); Loading @@ -157,38 +202,52 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { } } } } } } } mRawCursor.moveToFirst(); private boolean isMatch(ArrayList<Object> row) { do { ArrayList<Object> row = new ArrayList<>(); // filter based on selection parameters if needed if (selection != null) { boolean match = true; for (int i = 0; i < paramName.length; i++) { for (int i = 0; i < paramName.length; i++) { int columnIndex = 0; for (String columnName : mRawColumns) { for (String columnName : mRawColumns) { int columnIndex = mRawCursor.getColumnIndex(columnName); if (columnName.equals(paramName[i])) { if (columnName.equals(paramName[i]) && if (!paramValue[i].equals(row.get(columnIndex))) { !mRawCursor.getString(columnIndex).equals(paramValue[i])) { return false; match = false; } else { // move on to next param break; break; } } } } if (!match) { columnIndex++; break; } } } return true; } } // move on to next row if current one does not satisfy selection criteria } if (!match) { @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { logd("query called for: " + selection); MatrixCursor cursor = new MatrixCursor(projection); if (mNumRows > 0) { // parse selection and selectionArgs SelectionParams selectionParams = new SelectionParams(); selectionParams.parseSelectionParams(selection, selectionArgs); for (ArrayList<Object> row : mListOfRows) { ArrayList<Object> retRow = new ArrayList<>(); // filter based on selection parameters if needed if (selection != null) { if (!selectionParams.isMatch(row)) { continue; continue; } } } } for (String columnName : projection) { for (String columnName : projection) { int columnIndex = mRawCursor.getColumnIndex(columnName); int columnIndex = getColumnIndex(columnName); row.add(mRawCursor.getString(columnIndex)); retRow.add(row.get(columnIndex)); } cursor.addRow(retRow); } } cursor.addRow(row); } while(mRawCursor.moveToNext()); } } if (cursor != null) { if (cursor != null) { logd("returning rows: " + cursor.getCount()); logd("returning rows: " + cursor.getCount()); Loading Loading @@ -383,6 +442,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { mInboundSmsTrackerCVPart1.put("reference_number", 1); mInboundSmsTrackerCVPart1.put("reference_number", 1); mInboundSmsTrackerCVPart1.put("sequence", 1); mInboundSmsTrackerCVPart1.put("sequence", 1); mInboundSmsTrackerCVPart1.put("count", 2); mInboundSmsTrackerCVPart1.put("count", 2); mInboundSmsTrackerCVPart1.put("date", System.currentTimeMillis()); doReturn(2).when(mInboundSmsTrackerPart1).getMessageCount(); doReturn(2).when(mInboundSmsTrackerPart1).getMessageCount(); doReturn(1).when(mInboundSmsTrackerPart1).getReferenceNumber(); doReturn(1).when(mInboundSmsTrackerPart1).getReferenceNumber(); Loading @@ -391,6 +451,8 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { doReturn(1).when(mInboundSmsTrackerPart1).getIndexOffset(); doReturn(1).when(mInboundSmsTrackerPart1).getIndexOffset(); doReturn(-1).when(mInboundSmsTrackerPart1).getDestPort(); doReturn(-1).when(mInboundSmsTrackerPart1).getDestPort(); doReturn(mSmsPdu).when(mInboundSmsTrackerPart1).getPdu(); doReturn(mSmsPdu).when(mInboundSmsTrackerPart1).getPdu(); doReturn(mInboundSmsTrackerCVPart1.get("date")).when(mInboundSmsTrackerPart1). getTimestamp(); doReturn(mInboundSmsTrackerCVPart1).when(mInboundSmsTrackerPart1).getContentValues(); doReturn(mInboundSmsTrackerCVPart1).when(mInboundSmsTrackerPart1).getContentValues(); // Part 2 // Part 2 Loading @@ -401,6 +463,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { mInboundSmsTrackerCVPart2.put("reference_number", 1); mInboundSmsTrackerCVPart2.put("reference_number", 1); mInboundSmsTrackerCVPart2.put("sequence", 2); mInboundSmsTrackerCVPart2.put("sequence", 2); mInboundSmsTrackerCVPart2.put("count", 2); mInboundSmsTrackerCVPart2.put("count", 2); mInboundSmsTrackerCVPart2.put("date", System.currentTimeMillis()); doReturn(2).when(mInboundSmsTrackerPart2).getMessageCount(); doReturn(2).when(mInboundSmsTrackerPart2).getMessageCount(); doReturn(1).when(mInboundSmsTrackerPart2).getReferenceNumber(); doReturn(1).when(mInboundSmsTrackerPart2).getReferenceNumber(); Loading @@ -409,6 +472,8 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { doReturn(1).when(mInboundSmsTrackerPart2).getIndexOffset(); doReturn(1).when(mInboundSmsTrackerPart2).getIndexOffset(); doReturn(-1).when(mInboundSmsTrackerPart2).getDestPort(); doReturn(-1).when(mInboundSmsTrackerPart2).getDestPort(); doReturn(mSmsPdu).when(mInboundSmsTrackerPart2).getPdu(); doReturn(mSmsPdu).when(mInboundSmsTrackerPart2).getPdu(); doReturn(mInboundSmsTrackerCVPart2.get("date")).when(mInboundSmsTrackerPart2). getTimestamp(); doReturn(mInboundSmsTrackerCVPart2).when(mInboundSmsTrackerPart2).getContentValues(); doReturn(mInboundSmsTrackerCVPart2).when(mInboundSmsTrackerPart2).getContentValues(); } } Loading @@ -422,10 +487,10 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { mSmsHeader.concatRef = new SmsHeader.ConcatRef(); mSmsHeader.concatRef = new SmsHeader.ConcatRef(); doReturn(mSmsHeader).when(mGsmSmsMessage).getUserDataHeader(); doReturn(mSmsHeader).when(mGsmSmsMessage).getUserDataHeader(); doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory) doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory) .makeInboundSmsTracker(any(byte[].class), anyLong(), anyInt(), anyBoolean(), .makeInboundSmsTracker(any(byte[].class), anyLong(), anyInt(), anyBoolean(), anyString(), anyInt(), anyInt(), anyInt(), anyBoolean()); anyString(), anyInt(), anyInt(), anyInt(), anyBoolean()); mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null, mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null, mSmsMessage, null)); mSmsMessage, null)); waitForMs(100); waitForMs(100); Loading @@ -440,7 +505,42 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { mSmsMessage, null)); mSmsMessage, null)); waitForMs(100); waitForMs(100); // verify broadcast intents verifySmsIntentBroadcasts(0); verifySmsIntentBroadcasts(0); // if an additional copy of one of the segments above is received, it should not be kept in // the db and should not be combined with any subsequent messages received from the same // sender // additional copy of part 2 of message doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory) .makeInboundSmsTracker(any(byte[].class), anyLong(), anyInt(), anyBoolean(), anyString(), anyInt(), anyInt(), anyInt(), anyBoolean()); mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null, mSmsMessage, null)); waitForMs(100); // verify no additional broadcasts sent verify(mContext, times(2)).sendBroadcast(any(Intent.class)); // part 1 of new sms recieved from same sender with same parameters, just different // timestamps, should not be combined with the additional part 2 received above // call prepareMultiPartSms() to update timestamps prepareMultiPartSms(); // part 1 of new sms doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory) .makeInboundSmsTracker(any(byte[].class), anyLong(), anyInt(), anyBoolean(), anyString(), anyInt(), anyInt(), anyInt(), anyBoolean()); mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null, mSmsMessage, null)); waitForMs(100); // verify no additional broadcasts sent verify(mContext, times(2)).sendBroadcast(any(Intent.class)); assertEquals("IdleState", getCurrentState().getName()); } } @Test @Test Loading Loading @@ -498,6 +598,30 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { verifyDataSmsIntentBroadcasts(1); verifyDataSmsIntentBroadcasts(1); } } @Test @MediumTest public void testBroadcastUndeliveredDeleted() throws Exception { replaceInstance(SmsBroadcastUndelivered.class, "instance", null, null); SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler); doReturn(0).when(mInboundSmsTracker).getDestPort(); //add a fake entry to db ContentValues rawSms = new ContentValues(); rawSms.put("deleted", 1); mContentProvider.insert(sRawUri, rawSms); //make it a single-part message doReturn(1).when(mInboundSmsTracker).getMessageCount(); //when user unlocks the device, broadcast should not be sent for new message mContext.sendBroadcast(new Intent(Intent.ACTION_USER_UNLOCKED)); waitForMs(100); verify(mContext, times(1)).sendBroadcast(any(Intent.class)); assertEquals("IdleState", getCurrentState().getName()); } @Test @Test @MediumTest @MediumTest public void testBroadcastUndeliveredMultiPart() throws Exception { public void testBroadcastUndeliveredMultiPart() throws Exception { Loading Loading
src/java/com/android/internal/telephony/InboundSmsHandler.java +51 −49 Original line number Original line Diff line number Diff line Loading @@ -125,7 +125,8 @@ public abstract class InboundSmsHandler extends StateMachine { public static final int ID_COLUMN = 7; public static final int ID_COLUMN = 7; public static final String SELECT_BY_ID = "_id=?"; public static final String SELECT_BY_ID = "_id=?"; public static final String SELECT_BY_REFERENCE = "address=? AND reference_number=? AND count=?"; public static final String SELECT_BY_REFERENCE = "address=? AND reference_number=? AND " + "count=? AND deleted=0"; /** New SMS received as an AsyncResult. */ /** New SMS received as an AsyncResult. */ public static final int EVENT_NEW_SMS = 1; public static final int EVENT_NEW_SMS = 1; Loading Loading @@ -161,6 +162,8 @@ public abstract class InboundSmsHandler extends StateMachine { /** URI for raw table of SMS provider. */ /** URI for raw table of SMS provider. */ private static final Uri sRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw"); private static final Uri sRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw"); private static final Uri sRawUriPermanentDelete = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw/permanentDelete"); protected final Context mContext; protected final Context mContext; private final ContentResolver mResolver; private final ContentResolver mResolver; Loading Loading @@ -790,13 +793,13 @@ public abstract class InboundSmsHandler extends StateMachine { if (result == Activity.RESULT_OK) { if (result == Activity.RESULT_OK) { return true; return true; } else { } else { deleteFromRawTable(tracker.getDeleteWhere(), tracker.getDeleteWhereArgs()); deleteFromRawTable(tracker.getDeleteWhere(), tracker.getDeleteWhereArgs(), false); return false; return false; } } } } if (BlockChecker.isBlocked(mContext, tracker.getAddress())) { if (BlockChecker.isBlocked(mContext, tracker.getAddress())) { deleteFromRawTable(tracker.getDeleteWhere(), tracker.getDeleteWhereArgs()); deleteFromRawTable(tracker.getDeleteWhere(), tracker.getDeleteWhereArgs(), true); return false; return false; } } Loading Loading @@ -983,8 +986,10 @@ public abstract class InboundSmsHandler extends StateMachine { /** /** * Helper for {@link SmsBroadcastUndelivered} to delete an old message in the raw table. * Helper for {@link SmsBroadcastUndelivered} to delete an old message in the raw table. */ */ private void deleteFromRawTable(String deleteWhere, String[] deleteWhereArgs) { private void deleteFromRawTable(String deleteWhere, String[] deleteWhereArgs, int rows = mResolver.delete(sRawUri, deleteWhere, deleteWhereArgs); boolean permanentDelete) { Uri uri = permanentDelete ? sRawUriPermanentDelete : sRawUri; int rows = mResolver.delete(uri, deleteWhere, deleteWhereArgs); if (rows == 0) { if (rows == 0) { loge("No rows were deleted from raw table!"); loge("No rows were deleted from raw table!"); } else if (DBG) { } else if (DBG) { Loading Loading @@ -1074,7 +1079,6 @@ public abstract class InboundSmsHandler extends StateMachine { * @return true on success; false on failure to write to database * @return true on success; false on failure to write to database */ */ private int addTrackerToRawTable(InboundSmsTracker tracker) { private int addTrackerToRawTable(InboundSmsTracker tracker) { if (tracker.getMessageCount() != 1) { // check for duplicate message segments // check for duplicate message segments Cursor cursor = null; Cursor cursor = null; try { try { Loading @@ -1085,8 +1089,8 @@ public abstract class InboundSmsHandler extends StateMachine { String address = tracker.getAddress(); String address = tracker.getAddress(); String refNumber = Integer.toString(tracker.getReferenceNumber()); String refNumber = Integer.toString(tracker.getReferenceNumber()); String count = Integer.toString(tracker.getMessageCount()); String count = Integer.toString(tracker.getMessageCount()); String seqNumber = Integer.toString(sequence); String seqNumber = Integer.toString(sequence); String date = Long.toString(tracker.getTimestamp()); // set the delete selection args for multi-part message // set the delete selection args for multi-part message String[] deleteWhereArgs = {address, refNumber, count}; String[] deleteWhereArgs = {address, refNumber, count}; Loading @@ -1094,8 +1098,8 @@ public abstract class InboundSmsHandler extends StateMachine { // Check for duplicate message segments // Check for duplicate message segments cursor = mResolver.query(sRawUri, PDU_PROJECTION, cursor = mResolver.query(sRawUri, PDU_PROJECTION, "address=? AND reference_number=? AND count=? AND sequence=?", "address=? AND reference_number=? AND count=? AND sequence=? AND date=?", new String[] {address, refNumber, count, seqNumber}, null); new String[] {address, refNumber, count, seqNumber, date}, null); // moveToNext() returns false if no duplicates were found // moveToNext() returns false if no duplicates were found if (cursor.moveToNext()) { if (cursor.moveToNext()) { Loading @@ -1110,7 +1114,6 @@ public abstract class InboundSmsHandler extends StateMachine { } } return Intents.RESULT_SMS_DUPLICATED; // reject message return Intents.RESULT_SMS_DUPLICATED; // reject message } } cursor.close(); } catch (SQLException e) { } catch (SQLException e) { loge("Can't access multipart SMS database", e); loge("Can't access multipart SMS database", e); return Intents.RESULT_SMS_GENERIC_ERROR; // reject message return Intents.RESULT_SMS_GENERIC_ERROR; // reject message Loading @@ -1119,7 +1122,6 @@ public abstract class InboundSmsHandler extends StateMachine { cursor.close(); cursor.close(); } } } } } ContentValues values = tracker.getContentValues(); ContentValues values = tracker.getContentValues(); Loading Loading @@ -1212,7 +1214,7 @@ public abstract class InboundSmsHandler extends StateMachine { log("successful broadcast, deleting from raw table."); log("successful broadcast, deleting from raw table."); } } deleteFromRawTable(mDeleteWhere, mDeleteWhereArgs); deleteFromRawTable(mDeleteWhere, mDeleteWhereArgs, false); sendMessage(EVENT_BROADCAST_COMPLETE); sendMessage(EVENT_BROADCAST_COMPLETE); int durationMillis = (int) ((System.nanoTime() - mBroadcastTimeNano) / 1000000); int durationMillis = (int) ((System.nanoTime() - mBroadcastTimeNano) / 1000000); Loading Loading @@ -1318,7 +1320,7 @@ public abstract class InboundSmsHandler extends StateMachine { try { try { // Needs phone package permissions. // Needs phone package permissions. deleteFromRawTable(mSmsFilter.mSmsBroadcastReceiver.mDeleteWhere, deleteFromRawTable(mSmsFilter.mSmsBroadcastReceiver.mDeleteWhere, mSmsFilter.mSmsBroadcastReceiver.mDeleteWhereArgs); mSmsFilter.mSmsBroadcastReceiver.mDeleteWhereArgs, false); } finally { } finally { Binder.restoreCallingIdentity(token); Binder.restoreCallingIdentity(token); } } Loading
src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java +8 −3 Original line number Original line Diff line number Diff line Loading @@ -65,6 +65,8 @@ public class SmsBroadcastUndelivered { /** URI for raw table from SmsProvider. */ /** URI for raw table from SmsProvider. */ private static final Uri sRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw"); private static final Uri sRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw"); private static final Uri sRawUriPermanentDelete = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw/permanentDelete"); private static SmsBroadcastUndelivered instance; private static SmsBroadcastUndelivered instance; /** Content resolver to use to access raw table from SmsProvider. */ /** Content resolver to use to access raw table from SmsProvider. */ Loading Loading @@ -135,7 +137,9 @@ public class SmsBroadcastUndelivered { HashSet<SmsReferenceKey> oldMultiPartMessages = new HashSet<SmsReferenceKey>(4); HashSet<SmsReferenceKey> oldMultiPartMessages = new HashSet<SmsReferenceKey>(4); Cursor cursor = null; Cursor cursor = null; try { try { cursor = mResolver.query(sRawUri, PDU_PENDING_MESSAGE_PROJECTION, null, null, null); // query only non-deleted ones cursor = mResolver.query(sRawUri, PDU_PENDING_MESSAGE_PROJECTION, "deleted = 0", null, null); if (cursor == null) { if (cursor == null) { Rlog.e(TAG, "error getting pending message cursor"); Rlog.e(TAG, "error getting pending message cursor"); return; return; Loading Loading @@ -182,8 +186,9 @@ public class SmsBroadcastUndelivered { } } // Delete old incomplete message segments // Delete old incomplete message segments for (SmsReferenceKey message : oldMultiPartMessages) { for (SmsReferenceKey message : oldMultiPartMessages) { int rows = mResolver.delete(sRawUri, InboundSmsHandler.SELECT_BY_REFERENCE, // delete permanently message.getDeleteWhereArgs()); int rows = mResolver.delete(sRawUriPermanentDelete, InboundSmsHandler.SELECT_BY_REFERENCE, message.getDeleteWhereArgs()); if (rows == 0) { if (rows == 0) { Rlog.e(TAG, "No rows were deleted from raw table!"); Rlog.e(TAG, "No rows were deleted from raw table!"); } else if (DBG) { } else if (DBG) { Loading
src/java/com/android/internal/telephony/SmsMessageBase.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -52,7 +52,7 @@ public abstract class SmsMessageBase { /** {@hide} */ /** {@hide} */ protected boolean mIsEmail; protected boolean mIsEmail; /** {@hide} */ /** {@hide} Time when SC (service centre) received the message */ protected long mScTimeMillis; protected long mScTimeMillis; /** {@hide} The raw PDU of the message */ /** {@hide} The raw PDU of the message */ Loading
tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java +161 −37 Original line number Original line Diff line number Diff line Loading @@ -56,6 +56,7 @@ import org.mockito.Mock; import java.lang.reflect.Method; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.ArrayList; import java.util.List; public class GsmInboundSmsHandlerTest extends TelephonyTest { public class GsmInboundSmsHandlerTest extends TelephonyTest { @Mock @Mock Loading @@ -77,6 +78,9 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { private FakeSmsContentProvider mContentProvider; private FakeSmsContentProvider mContentProvider; private static final Uri sRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw"); private static final Uri sRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw"); private static final Uri sRawUriPermanentDelete = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw/permanentDelete"); private ContentValues mInboundSmsTrackerCV = new ContentValues(); private ContentValues mInboundSmsTrackerCV = new ContentValues(); // For multi-part SMS // For multi-part SMS private ContentValues mInboundSmsTrackerCVPart1; private ContentValues mInboundSmsTrackerCVPart1; Loading @@ -93,13 +97,56 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { "destination_port", "destination_port", "address", "address", "sub_id", "sub_id", "pdu"}; "pdu", private MatrixCursor mRawCursor = new MatrixCursor(mRawColumns); "deleted"}; private List<ArrayList<Object>> mListOfRows = new ArrayList<ArrayList<Object>>(); private int mNumRows = 0; private int mNumRows = 0; private int getColumnIndex(String columnName) { int i = 0; for (String s : mRawColumns) { if (s.equals(columnName)) { break; } i++; } return i; } @Override @Override public int delete(Uri uri, String selection, String[] selectionArgs) { public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; int count = 0; if (mNumRows > 0) { // parse selection and selectionArgs SelectionParams selectionParams = new SelectionParams(); selectionParams.parseSelectionParams(selection, selectionArgs); List<Integer> deleteRows = new ArrayList<Integer>(); int i = -1; for (ArrayList<Object> row : mListOfRows) { i++; // filter based on selection parameters if needed if (selection != null) { if (!selectionParams.isMatch(row)) { continue; } } if (uri.compareTo(sRawUri) == 0) { row.set(getColumnIndex("deleted"), "1"); } else { // save index for removal deleteRows.add(i); } count++; } if (uri.compareTo(sRawUriPermanentDelete) == 0) { for (i = deleteRows.size() - 1; i >= 0; i--) { mListOfRows.remove(i); } } } return count; } } @Override @Override Loading @@ -107,7 +154,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { Uri newUri = null; Uri newUri = null; if (uri.compareTo(sRawUri) == 0) { if (uri.compareTo(sRawUri) == 0) { if (values != null) { if (values != null) { mRawCursor.addRow(convertRawCVtoArrayList(values)); mListOfRows.add(convertRawCVtoArrayList(values)); mNumRows++; mNumRows++; newUri = Uri.withAppendedPath(uri, "" + mNumRows); newUri = Uri.withAppendedPath(uri, "" + mNumRows); } } Loading @@ -120,9 +167,11 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { ArrayList<Object> newRow = new ArrayList<>(); ArrayList<Object> newRow = new ArrayList<>(); for (String key : mRawColumns) { for (String key : mRawColumns) { if (values.containsKey(key)) { if (values.containsKey(key)) { newRow.add(values.get(key)); newRow.add(values.getAsString(key)); } else if (key.equals("_id")) { } else if (key.equals("_id")) { newRow.add(mNumRows + 1); newRow.add(mNumRows + 1); } else if (key.equals("deleted")) { newRow.add("0"); } else { } else { newRow.add(null); newRow.add(null); } } Loading @@ -130,15 +179,11 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { return newRow; return newRow; } } @Override private class SelectionParams { public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { logd("query called for: " + selection); MatrixCursor cursor = new MatrixCursor(projection); if (mNumRows > 0) { // parse selection and selectionArgs String[] paramName = null; String[] paramName = null; String[] paramValue = null; String[] paramValue = null; private void parseSelectionParams(String selection, String[] selectionArgs) { if (selection != null) { if (selection != null) { selection = selection.toLowerCase(); selection = selection.toLowerCase(); String[] selectionParams = selection.toLowerCase().split("and"); String[] selectionParams = selection.toLowerCase().split("and"); Loading @@ -157,38 +202,52 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { } } } } } } } mRawCursor.moveToFirst(); private boolean isMatch(ArrayList<Object> row) { do { ArrayList<Object> row = new ArrayList<>(); // filter based on selection parameters if needed if (selection != null) { boolean match = true; for (int i = 0; i < paramName.length; i++) { for (int i = 0; i < paramName.length; i++) { int columnIndex = 0; for (String columnName : mRawColumns) { for (String columnName : mRawColumns) { int columnIndex = mRawCursor.getColumnIndex(columnName); if (columnName.equals(paramName[i])) { if (columnName.equals(paramName[i]) && if (!paramValue[i].equals(row.get(columnIndex))) { !mRawCursor.getString(columnIndex).equals(paramValue[i])) { return false; match = false; } else { // move on to next param break; break; } } } } if (!match) { columnIndex++; break; } } } return true; } } // move on to next row if current one does not satisfy selection criteria } if (!match) { @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { logd("query called for: " + selection); MatrixCursor cursor = new MatrixCursor(projection); if (mNumRows > 0) { // parse selection and selectionArgs SelectionParams selectionParams = new SelectionParams(); selectionParams.parseSelectionParams(selection, selectionArgs); for (ArrayList<Object> row : mListOfRows) { ArrayList<Object> retRow = new ArrayList<>(); // filter based on selection parameters if needed if (selection != null) { if (!selectionParams.isMatch(row)) { continue; continue; } } } } for (String columnName : projection) { for (String columnName : projection) { int columnIndex = mRawCursor.getColumnIndex(columnName); int columnIndex = getColumnIndex(columnName); row.add(mRawCursor.getString(columnIndex)); retRow.add(row.get(columnIndex)); } cursor.addRow(retRow); } } cursor.addRow(row); } while(mRawCursor.moveToNext()); } } if (cursor != null) { if (cursor != null) { logd("returning rows: " + cursor.getCount()); logd("returning rows: " + cursor.getCount()); Loading Loading @@ -383,6 +442,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { mInboundSmsTrackerCVPart1.put("reference_number", 1); mInboundSmsTrackerCVPart1.put("reference_number", 1); mInboundSmsTrackerCVPart1.put("sequence", 1); mInboundSmsTrackerCVPart1.put("sequence", 1); mInboundSmsTrackerCVPart1.put("count", 2); mInboundSmsTrackerCVPart1.put("count", 2); mInboundSmsTrackerCVPart1.put("date", System.currentTimeMillis()); doReturn(2).when(mInboundSmsTrackerPart1).getMessageCount(); doReturn(2).when(mInboundSmsTrackerPart1).getMessageCount(); doReturn(1).when(mInboundSmsTrackerPart1).getReferenceNumber(); doReturn(1).when(mInboundSmsTrackerPart1).getReferenceNumber(); Loading @@ -391,6 +451,8 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { doReturn(1).when(mInboundSmsTrackerPart1).getIndexOffset(); doReturn(1).when(mInboundSmsTrackerPart1).getIndexOffset(); doReturn(-1).when(mInboundSmsTrackerPart1).getDestPort(); doReturn(-1).when(mInboundSmsTrackerPart1).getDestPort(); doReturn(mSmsPdu).when(mInboundSmsTrackerPart1).getPdu(); doReturn(mSmsPdu).when(mInboundSmsTrackerPart1).getPdu(); doReturn(mInboundSmsTrackerCVPart1.get("date")).when(mInboundSmsTrackerPart1). getTimestamp(); doReturn(mInboundSmsTrackerCVPart1).when(mInboundSmsTrackerPart1).getContentValues(); doReturn(mInboundSmsTrackerCVPart1).when(mInboundSmsTrackerPart1).getContentValues(); // Part 2 // Part 2 Loading @@ -401,6 +463,7 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { mInboundSmsTrackerCVPart2.put("reference_number", 1); mInboundSmsTrackerCVPart2.put("reference_number", 1); mInboundSmsTrackerCVPart2.put("sequence", 2); mInboundSmsTrackerCVPart2.put("sequence", 2); mInboundSmsTrackerCVPart2.put("count", 2); mInboundSmsTrackerCVPart2.put("count", 2); mInboundSmsTrackerCVPart2.put("date", System.currentTimeMillis()); doReturn(2).when(mInboundSmsTrackerPart2).getMessageCount(); doReturn(2).when(mInboundSmsTrackerPart2).getMessageCount(); doReturn(1).when(mInboundSmsTrackerPart2).getReferenceNumber(); doReturn(1).when(mInboundSmsTrackerPart2).getReferenceNumber(); Loading @@ -409,6 +472,8 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { doReturn(1).when(mInboundSmsTrackerPart2).getIndexOffset(); doReturn(1).when(mInboundSmsTrackerPart2).getIndexOffset(); doReturn(-1).when(mInboundSmsTrackerPart2).getDestPort(); doReturn(-1).when(mInboundSmsTrackerPart2).getDestPort(); doReturn(mSmsPdu).when(mInboundSmsTrackerPart2).getPdu(); doReturn(mSmsPdu).when(mInboundSmsTrackerPart2).getPdu(); doReturn(mInboundSmsTrackerCVPart2.get("date")).when(mInboundSmsTrackerPart2). getTimestamp(); doReturn(mInboundSmsTrackerCVPart2).when(mInboundSmsTrackerPart2).getContentValues(); doReturn(mInboundSmsTrackerCVPart2).when(mInboundSmsTrackerPart2).getContentValues(); } } Loading @@ -422,10 +487,10 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { mSmsHeader.concatRef = new SmsHeader.ConcatRef(); mSmsHeader.concatRef = new SmsHeader.ConcatRef(); doReturn(mSmsHeader).when(mGsmSmsMessage).getUserDataHeader(); doReturn(mSmsHeader).when(mGsmSmsMessage).getUserDataHeader(); doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory) doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory) .makeInboundSmsTracker(any(byte[].class), anyLong(), anyInt(), anyBoolean(), .makeInboundSmsTracker(any(byte[].class), anyLong(), anyInt(), anyBoolean(), anyString(), anyInt(), anyInt(), anyInt(), anyBoolean()); anyString(), anyInt(), anyInt(), anyInt(), anyBoolean()); mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null, mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null, mSmsMessage, null)); mSmsMessage, null)); waitForMs(100); waitForMs(100); Loading @@ -440,7 +505,42 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { mSmsMessage, null)); mSmsMessage, null)); waitForMs(100); waitForMs(100); // verify broadcast intents verifySmsIntentBroadcasts(0); verifySmsIntentBroadcasts(0); // if an additional copy of one of the segments above is received, it should not be kept in // the db and should not be combined with any subsequent messages received from the same // sender // additional copy of part 2 of message doReturn(mInboundSmsTrackerPart2).when(mTelephonyComponentFactory) .makeInboundSmsTracker(any(byte[].class), anyLong(), anyInt(), anyBoolean(), anyString(), anyInt(), anyInt(), anyInt(), anyBoolean()); mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null, mSmsMessage, null)); waitForMs(100); // verify no additional broadcasts sent verify(mContext, times(2)).sendBroadcast(any(Intent.class)); // part 1 of new sms recieved from same sender with same parameters, just different // timestamps, should not be combined with the additional part 2 received above // call prepareMultiPartSms() to update timestamps prepareMultiPartSms(); // part 1 of new sms doReturn(mInboundSmsTrackerPart1).when(mTelephonyComponentFactory) .makeInboundSmsTracker(any(byte[].class), anyLong(), anyInt(), anyBoolean(), anyString(), anyInt(), anyInt(), anyInt(), anyBoolean()); mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, new AsyncResult(null, mSmsMessage, null)); waitForMs(100); // verify no additional broadcasts sent verify(mContext, times(2)).sendBroadcast(any(Intent.class)); assertEquals("IdleState", getCurrentState().getName()); } } @Test @Test Loading Loading @@ -498,6 +598,30 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { verifyDataSmsIntentBroadcasts(1); verifyDataSmsIntentBroadcasts(1); } } @Test @MediumTest public void testBroadcastUndeliveredDeleted() throws Exception { replaceInstance(SmsBroadcastUndelivered.class, "instance", null, null); SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler); doReturn(0).when(mInboundSmsTracker).getDestPort(); //add a fake entry to db ContentValues rawSms = new ContentValues(); rawSms.put("deleted", 1); mContentProvider.insert(sRawUri, rawSms); //make it a single-part message doReturn(1).when(mInboundSmsTracker).getMessageCount(); //when user unlocks the device, broadcast should not be sent for new message mContext.sendBroadcast(new Intent(Intent.ACTION_USER_UNLOCKED)); waitForMs(100); verify(mContext, times(1)).sendBroadcast(any(Intent.class)); assertEquals("IdleState", getCurrentState().getName()); } @Test @Test @MediumTest @MediumTest public void testBroadcastUndeliveredMultiPart() throws Exception { public void testBroadcastUndeliveredMultiPart() throws Exception { Loading