CarrierIdProvider improvements

1. handle insertion conflict. When a constraint violation occurs, the
row that contains the violation is not inserted. But the command continues
executing normally.
2. add read permission.
3. improve memory usage. not allocate a CV for every known carrier.
4. update the installed table version after setTransactionSuccessful

Bug: 64131637
Test: Unit test
Change-Id: If6d558e9263c78611b940170ba1612d8a2279b23
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 38f9fc0..7a6f683 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -111,6 +111,7 @@
                   android:exported="true"
                   android:singleUser="true"
                   android:multiprocess="false"
+                  android:readPermission="android.permission.READ_PRIVILEGED_PHONE_STATE"
                   android:writePermission="android.permission.MODIFY_PHONE_STATE" />
 
         <service
diff --git a/src/com/android/providers/telephony/CarrierIdProvider.java b/src/com/android/providers/telephony/CarrierIdProvider.java
index a712198..4b4ce8f 100644
--- a/src/com/android/providers/telephony/CarrierIdProvider.java
+++ b/src/com/android/providers/telephony/CarrierIdProvider.java
@@ -165,7 +165,7 @@
         mDbHelper.getReadableDatabase();
         s_urlMatcher.addURI(AUTHORITY, "update_db", URL_UPDATE_FROM_PB);
         s_urlMatcher.addURI(AUTHORITY, "get_version", URL_GET_VERSION);
-        initDatabaseFromPb(mDbHelper.getWritableDatabase());
+        updateDatabaseFromPb(mDbHelper.getWritableDatabase());
         return true;
     }
 
@@ -242,7 +242,7 @@
         final int match = s_urlMatcher.match(uri);
         switch (match) {
             case URL_UPDATE_FROM_PB:
-                return initDatabaseFromPb(getWritableDatabase());
+                return updateDatabaseFromPb(getWritableDatabase());
             default:
                 final int count = getWritableDatabase().update(CARRIER_ID_TABLE, values, selection,
                         selectionArgs);
@@ -308,34 +308,47 @@
      * Use version number to detect file update.
      * Update database with data from assets or ota only if version jumps.
      */
-    private int initDatabaseFromPb(SQLiteDatabase db) {
-        Log.d(TAG, "init database from pb file");
+    private int updateDatabaseFromPb(SQLiteDatabase db) {
+        Log.d(TAG, "update database from pb file");
         int rows = 0;
         CarrierIdProto.CarrierList carrierList = getUpdateCarrierList();
+        // No update is needed
         if (carrierList == null) return rows;
-        setAppliedVersion(carrierList.version);
-        List<ContentValues> cvs = new ArrayList<>();
-        for (CarrierIdProto.CarrierId id : carrierList.carrierId) {
-            for (CarrierIdProto.CarrierAttribute attr : id.carrierAttribute) {
-                ContentValues cv = new ContentValues();
-                cv.put(CarrierIdentification.CID, id.canonicalId);
-                cv.put(CarrierIdentification.NAME, id.carrierName);
-                convertCarrierAttrToContentValues(cv, cvs, attr, 0);
-            }
-        }
+
+        ContentValues cv;
+        List<ContentValues> cvs;
         try {
-            // Batch all insertions in single transaction to improve efficiency
+            // Batch all insertions in a single transaction to improve efficiency.
             db.beginTransaction();
             db.delete(CARRIER_ID_TABLE, null, null);
-            for (ContentValues cv : cvs) {
-                if (db.insertOrThrow(CARRIER_ID_TABLE, null, cv) > 0) rows++;
+            for (CarrierIdProto.CarrierId id : carrierList.carrierId) {
+                for (CarrierIdProto.CarrierAttribute attr : id.carrierAttribute) {
+                    cv = new ContentValues();
+                    cv.put(CarrierIdentification.CID, id.canonicalId);
+                    cv.put(CarrierIdentification.NAME, id.carrierName);
+                    cvs = new ArrayList<>();
+                    convertCarrierAttrToContentValues(cv, cvs, attr, 0);
+                    for (ContentValues contentVal : cvs) {
+                        // When a constraint violation occurs, the row that contains the violation
+                        // is not inserted. But the command continues executing normally.
+                        if (db.insertWithOnConflict(CARRIER_ID_TABLE, null, contentVal,
+                                SQLiteDatabase.CONFLICT_IGNORE) > 0) {
+                            rows++;
+                        } else {
+                            Log.e(TAG, "updateDatabaseFromPB insertion failure, row: "
+                                    + rows + "carrier id: " + id.canonicalId);
+                            // TODO metrics
+                        }
+                    }
+                }
             }
-            Log.d(TAG, "init database from pb. inserted rows = " + rows);
+            Log.d(TAG, "update database from pb. inserted rows = " + rows);
             if (rows > 0) {
                 // Notify listener of DB change
                 getContext().getContentResolver().notifyChange(CarrierIdentification.CONTENT_URI,
                         null);
             }
+            setAppliedVersion(carrierList.version);
             db.setTransactionSuccessful();
         } finally {
             db.endTransaction();
@@ -497,4 +510,4 @@
         buffer.flush();
         return buffer.toByteArray();
     }
-}
\ No newline at end of file
+}