Update CMAS duplicate message detection for carrier requirement.

There is a carrier requirement for CMAS to use only an in-memory
table for duplicate message ID detection. When the max number of
65535 messages is received, the oldest message ID is removed from
the list. After a reboot, a previously received message ID will be
considered a new message. This is required for CMAS lab testing.

The app does not care if there are multiple rows in the SQLite DB
with the same message ID, because the primary key is a unique ID.
Remove the duplicate message detection from the CBContentProvider
and use only the in-memory duplicate detection in CBAlertService.
This is safe for other types of alerts such as ETWS, and has the
benefit that a new alert will not be suppressed due to a very old
alert with the same message ID that was never deleted from the DB.

Message IDs are now stored in an ArrayList as well as a HashSet
in CellBroadcastAlertService. When the 65535 message limit is
reached, an index is used into the ArrayList to remove the oldest
message ID from the HashSet. The new message ID replaces the old
one in the ArrayList, so the size stays fixed at 65535 entries and
no array copies are needed to replace the oldest message ID.

Bug: 7045601
Change-Id: I2d08bf8131aa473648c5810913b62baa09a97756
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
index 07e1bfb..3cc821c 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
@@ -86,7 +86,17 @@
     }
 
     /** Cache of received message IDs, for duplicate message detection. */
-    private static final HashSet<MessageIdAndScope> sCmasIdList = new HashSet<MessageIdAndScope>(8);
+    private static final HashSet<MessageIdAndScope> sCmasIdSet = new HashSet<MessageIdAndScope>(8);
+
+    /** Maximum number of message IDs to save before removing the oldest message ID. */
+    private static final int MAX_MESSAGE_ID_SIZE = 65535;
+
+    /** List of message IDs received, for removing oldest ID when max message IDs are received. */
+    private static final ArrayList<MessageIdAndScope> sCmasIdList =
+            new ArrayList<MessageIdAndScope>(8);
+
+    /** Index of message ID to replace with new message ID when max message IDs are received. */
+    private static int sCmasIdListIndex = 0;
 
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
@@ -123,11 +133,30 @@
             return;
         }
 
-        // Set.add() returns false if message ID has already been added
-        MessageIdAndScope messageIdAndScope = new MessageIdAndScope(message.getSerialNumber(),
+        // Check for duplicate message IDs according to CMAS carrier requirements. Message IDs
+        // are stored in volatile memory. If the maximum of 65535 messages is reached, the
+        // message ID of the oldest message is deleted from the list.
+        MessageIdAndScope newMessageId = new MessageIdAndScope(message.getSerialNumber(),
                 message.getLocation());
-        if (!sCmasIdList.add(messageIdAndScope)) {
-            Log.d(TAG, "ignoring duplicate alert with " + messageIdAndScope);
+
+        // Add the new message ID to the list. It's okay if this is a duplicate message ID,
+        // because the list is only used for removing old message IDs from the hash set.
+        if (sCmasIdList.size() < MAX_MESSAGE_ID_SIZE) {
+            sCmasIdList.add(newMessageId);
+        } else {
+            // Get oldest message ID from the list and replace with the new message ID.
+            MessageIdAndScope oldestId = sCmasIdList.get(sCmasIdListIndex);
+            sCmasIdList.set(sCmasIdListIndex, newMessageId);
+            Log.d(TAG, "message ID limit reached, removing oldest message ID " + oldestId);
+            // Remove oldest message ID from the set.
+            sCmasIdSet.remove(oldestId);
+            if (++sCmasIdListIndex >= MAX_MESSAGE_ID_SIZE) {
+                sCmasIdListIndex = 0;
+            }
+        }
+        // Set.add() returns false if message ID has already been added
+        if (!sCmasIdSet.add(newMessageId)) {
+            Log.d(TAG, "ignoring duplicate alert with " + newMessageId);
             return;
         }
 
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastContentProvider.java b/src/com/android/cellbroadcastreceiver/CellBroadcastContentProvider.java
index 7460f78..b0a67e5 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastContentProvider.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastContentProvider.java
@@ -181,19 +181,6 @@
         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
@@ -203,39 +190,10 @@
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         ContentValues cv = message.getContentValues();
 
-        // 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 {
-            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;
-        }
+        // Note: this method previously queried the database for duplicate message IDs, but this
+        // is not compatible with CMAS carrier requirements and could also cause other emergency
+        // alerts, e.g. ETWS, to not display if the database is filled with old messages.
+        // Use duplicate message ID detection in CellBroadcastAlertService instead of DB query.
 
         long rowId = db.insert(CellBroadcastDatabaseHelper.TABLE_NAME, null, cv);
         if (rowId == -1) {