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
     }
 
     /**