Fix CellBroadcastReceiver to ignore duplicate broadcasts. Before showing an alert dialog or notification, check to see if the broadcast message is already in the database. Bug: 6506785 Change-Id: I3063ef3d5355301e4292437348cf789ff540e0b1
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java index 6bf5abb..5161dbb 100644 --- a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java +++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
@@ -50,6 +50,9 @@ /** Intent extra to indicate a previously unread alert. */ static final String NEW_ALERT_EXTRA = "com.android.cellbroadcastreceiver.NEW_ALERT"; + /** Intent action to display alert dialog/notification, after verifying the alert is new. */ + static final String SHOW_NEW_ALERT_ACTION = "cellbroadcastreceiver.SHOW_NEW_ALERT"; + /** Use the same notification ID for non-emergency alerts. */ static final int NOTIFICATION_ID = 1; @@ -65,10 +68,11 @@ if (Telephony.Sms.Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION.equals(action) || Telephony.Sms.Intents.SMS_CB_RECEIVED_ACTION.equals(action)) { handleCellBroadcastIntent(intent); + } else if (SHOW_NEW_ALERT_ACTION.equals(action)) { + showNewAlert(intent); } else { Log.e(TAG, "Unrecognized intent action: " + action); } - stopSelf(); // this service always stops after processing the intent return START_NOT_STICKY; } @@ -93,6 +97,40 @@ return; } + final Intent alertIntent = new Intent(SHOW_NEW_ALERT_ACTION); + alertIntent.setClass(this, CellBroadcastAlertService.class); + alertIntent.putExtra("message", cbm); + + // write to database on a background thread + new CellBroadcastContentProvider.AsyncCellBroadcastTask(getContentResolver()) + .execute(new CellBroadcastContentProvider.CellBroadcastOperation() { + @Override + public boolean execute(CellBroadcastContentProvider provider) { + if (provider.insertNewBroadcast(cbm)) { + // new message, show the alert or notification on UI thread + startService(alertIntent); + return true; + } else { + return false; + } + } + }); + } + + private void showNewAlert(Intent intent) { + Bundle extras = intent.getExtras(); + if (extras == null) { + Log.e(TAG, "received SHOW_NEW_ALERT_ACTION with no extras!"); + return; + } + + CellBroadcastMessage cbm = (CellBroadcastMessage) extras.get("message"); + + if (cbm == null) { + Log.e(TAG, "received SHOW_NEW_ALERT_ACTION with no message extra"); + return; + } + if (cbm.isEmergencyAlertMessage() || CellBroadcastConfigService .isOperatorDefinedEmergencyId(cbm.getServiceCategory())) { // start alert sound / vibration / TTS and display full-screen alert @@ -101,15 +139,6 @@ // add notification to the bar addToNotificationBar(cbm); } - - // write to database on a background thread - new CellBroadcastContentProvider.AsyncCellBroadcastTask(getContentResolver()) - .execute(new CellBroadcastContentProvider.CellBroadcastOperation() { - @Override - public boolean execute(CellBroadcastContentProvider provider) { - return provider.insertNewBroadcast(cbm); - } - }); } /**
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastContentProvider.java b/src/com/android/cellbroadcastreceiver/CellBroadcastContentProvider.java index 7c26c35..f368725 100644 --- a/src/com/android/cellbroadcastreceiver/CellBroadcastContentProvider.java +++ b/src/com/android/cellbroadcastreceiver/CellBroadcastContentProvider.java
@@ -181,22 +181,72 @@ throw new UnsupportedOperationException("update not supported"); } + private static final String QUERY_BY_SERIAL = Telephony.CellBroadcasts.SERIAL_NUMBER + "=?"; + + private static final String QUERY_BY_SERIAL_PLMN = QUERY_BY_SERIAL + " AND " + + Telephony.CellBroadcasts.PLMN + "=?"; + + private static final String QUERY_BY_SERIAL_PLMN_LAC = QUERY_BY_SERIAL_PLMN + " AND " + + Telephony.CellBroadcasts.LAC + "=?"; + + private static final String QUERY_BY_SERIAL_PLMN_LAC_CID = QUERY_BY_SERIAL_PLMN_LAC + " AND " + + Telephony.CellBroadcasts.CID + "=?"; + + private static final String[] SELECT_ID_COLUMN = {Telephony.CellBroadcasts._ID}; + /** * Internal method to insert a new Cell Broadcast into the database and notify observers. * @param message the message to insert - * @return true if the database was updated, false otherwise + * @return true if the broadcast is new, false if it's a duplicate broadcast. */ boolean insertNewBroadcast(CellBroadcastMessage message) { SQLiteDatabase db = mOpenHelper.getWritableDatabase(); ContentValues cv = message.getContentValues(); - long rowId = db.insert(CellBroadcastDatabaseHelper.TABLE_NAME, null, cv); - if (rowId != -1) { - return true; + // Check for existing alert with same serial number and geo scope + String serial = cv.getAsString(Telephony.CellBroadcasts.SERIAL_NUMBER); + String plmn = cv.getAsString(Telephony.CellBroadcasts.PLMN); + String lac = cv.getAsString(Telephony.CellBroadcasts.LAC); + String cid = cv.getAsString(Telephony.CellBroadcasts.CID); + String selection; + String[] selectionArgs; + + if (plmn != null) { + if (lac != null) { + if (cid != null) { + selection = QUERY_BY_SERIAL_PLMN_LAC_CID; + selectionArgs = new String[] {serial, plmn, lac, cid}; + } else { + selection = QUERY_BY_SERIAL_PLMN_LAC; + selectionArgs = new String[] {serial, plmn, lac}; + } + } else { + selection = QUERY_BY_SERIAL_PLMN; + selectionArgs = new String[] {serial, plmn}; + } } else { - Log.e(TAG, "failed to insert new broadcast into database"); + selection = QUERY_BY_SERIAL; + selectionArgs = new String[] {serial}; + } + + Cursor c = db.query(CellBroadcastDatabaseHelper.TABLE_NAME, SELECT_ID_COLUMN, + selection, selectionArgs, null, null, null); + + if (c.getCount() != 0) { + Log.d(TAG, "ignoring dup broadcast serial=" + serial + " found " + c.getCount()); return false; } + + long rowId = db.insert(CellBroadcastDatabaseHelper.TABLE_NAME, null, cv); + if (rowId == -1) { + Log.e(TAG, "failed to insert new broadcast into database"); + // Return true on DB write failure because we still want to notify the user. + // The CellBroadcastMessage will be passed with the intent, so the message will be + // displayed in the emergency alert dialog, or the dialog that is displayed when + // the user selects the notification for a non-emergency broadcast, even if the + // broadcast could not be written to the database. + } + return true; // broadcast is not a duplicate } /**