Merge "Add assets/carrier_list.pb in TelephonyProvider"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 224fe2f..38f9fc0 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -23,6 +23,7 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <uses-permission android:name="android.permission.USE_RESERVED_DISK" />
 
     <protected-broadcast android:name="android.provider.action.EXTERNAL_PROVIDER_CHANGE" />
     <protected-broadcast android:name="android.intent.action.CONTENT_CHANGED" />
diff --git a/src/com/android/providers/telephony/CarrierIdProvider.java b/src/com/android/providers/telephony/CarrierIdProvider.java
index 91fdf21..7acea68 100644
--- a/src/com/android/providers/telephony/CarrierIdProvider.java
+++ b/src/com/android/providers/telephony/CarrierIdProvider.java
@@ -21,13 +21,13 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.content.UriMatcher;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
-import android.os.FileUtils;
-import android.os.UserHandle;
+import android.os.Environment;
 import android.provider.Telephony.CarrierIdentification;
 import android.text.TextUtils;
 import android.util.Log;
@@ -38,11 +38,8 @@
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -73,9 +70,13 @@
     private static final int DATABASE_VERSION = 3;
 
     private static final String ASSETS_PB_FILE = "carrier_list.pb";
-    private static final String ASSETS_FILE_CHECKSUM_PREF_KEY = "assets_checksum";
+    private static final String VERSION_PREF_KEY = "version";
+    private static final String OTA_UPDATED_PB_PATH = "misc/carrierid/" + ASSETS_PB_FILE;
     private static final String PREF_FILE = CarrierIdProvider.class.getSimpleName();
 
+    private static final UriMatcher s_urlMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+    private static final int URL_UPDATE_FROM_PB = 1;
+
     /**
      * index 0: {@link CarrierIdentification#MCCMNC}
      */
@@ -160,7 +161,8 @@
         Log.d(TAG, "onCreate");
         mDbHelper = new CarrierIdDatabaseHelper(getContext());
         mDbHelper.getReadableDatabase();
-        updateFromAssetsIfNeeded(mDbHelper.getWritableDatabase());
+        s_urlMatcher.addURI(AUTHORITY, "update_db", URL_UPDATE_FROM_PB);
+        initDatabaseFromPb(mDbHelper.getWritableDatabase());
         return true;
     }
 
@@ -224,13 +226,21 @@
                     + " selection=" + selection
                     + " selectionArgs=" + Arrays.toString(selectionArgs));
         }
-        final int count = getWritableDatabase().update(CARRIER_ID_TABLE, values, selection,
-                selectionArgs);
-        Log.d(TAG, "  update.count=" + count);
-        if (count > 0) {
-            getContext().getContentResolver().notifyChange(CarrierIdentification.CONTENT_URI, null);
+
+        final int match = s_urlMatcher.match(uri);
+        switch (match) {
+            case URL_UPDATE_FROM_PB:
+                return initDatabaseFromPb(getWritableDatabase());
+            default:
+                final int count = getWritableDatabase().update(CARRIER_ID_TABLE, values, selection,
+                        selectionArgs);
+                Log.d(TAG, "  update.count=" + count);
+                if (count > 0) {
+                    getContext().getContentResolver().notifyChange(
+                            CarrierIdentification.CONTENT_URI, null);
+                }
+                return count;
         }
-        return count;
     }
 
     /**
@@ -282,74 +292,40 @@
     }
 
     /**
-     * use check sum to detect assets file update.
-     * update database with data from assets only if checksum has been changed
-     * and OTA update is unavailable.
+     * Parse and persist pb file as database default values.
+     * Use version number to detect file update.
+     * Update database with data from assets or ota only if version jumps.
      */
-    private void updateFromAssetsIfNeeded(SQLiteDatabase db) {
-        //TODO skip update from assets if OTA update is available.
-        final File assets;
-        OutputStream outputStream = null;
-        InputStream inputStream = null;
-        try {
-            // create a temp file to compute check sum.
-            assets = new File(getContext().getCacheDir(), ASSETS_PB_FILE);
-            outputStream = new FileOutputStream(assets);
-            inputStream = getContext().getAssets().open(ASSETS_PB_FILE);
-            outputStream.write(readInputStreamToByteArray(inputStream));
-        } catch (IOException ex) {
-            Log.e(TAG, "assets file not found: " + ex);
-            return;
-        } finally {
-            IoUtils.closeQuietly(outputStream);
-            IoUtils.closeQuietly(inputStream);
-        }
-        long checkSum = getChecksum(assets);
-        if (checkSum != getAssetsChecksum()) {
-            initDatabaseFromPb(assets, db);
-            setAssetsChecksum(checkSum);
-        }
-    }
-
-    /**
-     * parse and persist pb file as database default values.
-     */
-    private void initDatabaseFromPb(File pb, SQLiteDatabase db) {
+    private int initDatabaseFromPb(SQLiteDatabase db) {
         Log.d(TAG, "init database from pb file");
-        InputStream inputStream = null;
-        try {
-            inputStream = new FileInputStream(pb);
-            byte[] bytes = readInputStreamToByteArray(inputStream);
-            CarrierIdProto.CarrierList carrierList = CarrierIdProto.CarrierList.parseFrom(bytes);
-            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);
-                }
+        int rows = 0;
+        CarrierIdProto.CarrierList carrierList = getUpdateCarrierList();
+        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);
             }
-            db.delete(CARRIER_ID_TABLE, null, null);
-            int rows = 0;
-            for (ContentValues cv : cvs) {
-                if (db.insertOrThrow(CARRIER_ID_TABLE, null, cv) > 0) rows++;
-            }
-            Log.d(TAG, "init database from pb. inserted rows = " + rows);
-            if (rows > 0) {
-                // Notify listener of DB change
-                getContext().getContentResolver().notifyChange(CarrierIdentification.CONTENT_URI,
-                        null);
-            }
-        } catch (IOException ex) {
-            Log.e(TAG, "init database from pb failure: " + ex);
-        } finally {
-            IoUtils.closeQuietly(inputStream);
         }
+        db.delete(CARRIER_ID_TABLE, null, null);
+        for (ContentValues cv : cvs) {
+            if (db.insertOrThrow(CARRIER_ID_TABLE, null, cv) > 0) rows++;
+        }
+        Log.d(TAG, "init database from pb. inserted rows = " + rows);
+        if (rows > 0) {
+            // Notify listener of DB change
+            getContext().getContentResolver().notifyChange(CarrierIdentification.CONTENT_URI,
+                    null);
+        }
+        return rows;
     }
 
     /**
-     * recursively loop through carrier attribute list to get all combinations.
+     * Recursively loop through carrier attribute list to get all combinations.
      */
     private void convertCarrierAttrToContentValues(ContentValues cv, List<ContentValues> cvs,
             CarrierIdProto.CarrierAttribute attr, int index) {
@@ -358,7 +334,7 @@
             return;
         }
         boolean found = false;
-        switch(index) {
+        switch (index) {
             case MCCMNC_INDEX:
                 for (String str : attr.mccmncTuple) {
                     cv.put(CarrierIdentification.MCCMNC, str);
@@ -417,10 +393,10 @@
                 break;
             case ICCID_PREFIX_INDEX:
                 for (String str : attr.iccidPrefix) {
-                     cv.put(CarrierIdentification.ICCID_PREFIX, str);
-                     convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
-                     cv.remove(CarrierIdentification.ICCID_PREFIX);
-                     found = true;
+                    cv.put(CarrierIdentification.ICCID_PREFIX, str);
+                    convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
+                    cv.remove(CarrierIdentification.ICCID_PREFIX);
+                    found = true;
                 }
                 break;
             default:
@@ -434,7 +410,62 @@
     }
 
     /**
-     * util function to convert inputStream to byte array before parsing proto data.
+     * Return the update carrierList.
+     * Get the latest version from the last applied, assets and ota file. if the latest version
+     * is newer than the last applied, update is required. Otherwise no update is required and
+     * the returned carrierList will be null.
+     */
+    private CarrierIdProto.CarrierList getUpdateCarrierList() {
+        int version = getAppliedVersion();
+        CarrierIdProto.CarrierList carrierList = null;
+        CarrierIdProto.CarrierList assets = null;
+        CarrierIdProto.CarrierList ota = null;
+        InputStream is = null;
+
+        try {
+            is = getContext().getAssets().open(ASSETS_PB_FILE);
+            assets = CarrierIdProto.CarrierList.parseFrom(readInputStreamToByteArray(is));
+        } catch (IOException ex) {
+            Log.e(TAG, "read carrier list from assets pb failure: " + ex);
+        } finally {
+            IoUtils.closeQuietly(is);
+        }
+        try {
+            is = new FileInputStream(new File(Environment.getDataDirectory(), OTA_UPDATED_PB_PATH));
+            ota = CarrierIdProto.CarrierList.parseFrom(readInputStreamToByteArray(is));
+        } catch (IOException ex) {
+            Log.e(TAG, "read carrier list from ota pb failure: " + ex);
+        } finally {
+            IoUtils.closeQuietly(is);
+        }
+
+        // compare version
+        if (assets != null && assets.version > version) {
+            carrierList = assets;
+            version = assets.version;
+        }
+        if (ota != null && ota.version > version) {
+            carrierList = ota;
+            version = ota.version;
+        }
+        Log.d(TAG, "latest version: " + version + " need update: " + (carrierList != null));
+        return carrierList;
+    }
+
+    private int getAppliedVersion() {
+        final SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
+        return sp.getInt(VERSION_PREF_KEY, -1);
+    }
+
+    private void setAppliedVersion(int version) {
+        final SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = sp.edit();
+        editor.putInt(VERSION_PREF_KEY, version);
+        editor.apply();
+    }
+
+    /**
+     * Util function to convert inputStream to byte array before parsing proto data.
      */
     private static byte[] readInputStreamToByteArray(InputStream inputStream) throws IOException {
         ByteArrayOutputStream buffer = new ByteArrayOutputStream();
@@ -447,32 +478,4 @@
         buffer.flush();
         return buffer.toByteArray();
     }
-
-    /**
-     * util function to calculate checksum of a file
-     */
-    private long getChecksum(File file) {
-        long checksum = -1;
-        try {
-            checksum = FileUtils.checksumCrc32(file);
-            if (VDBG) Log.d(TAG, "Checksum for " + file.getAbsolutePath() + " is " + checksum);
-        } catch (FileNotFoundException e) {
-            Log.e(TAG, "FileNotFoundException for " + file.getAbsolutePath() + ":" + e);
-        } catch (IOException e) {
-            Log.e(TAG, "IOException for " + file.getAbsolutePath() + ":" + e);
-        }
-        return checksum;
-    }
-
-    private long getAssetsChecksum() {
-        SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
-        return sp.getLong(ASSETS_FILE_CHECKSUM_PREF_KEY, -1);
-    }
-
-    private void setAssetsChecksum(long checksum) {
-        SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
-        SharedPreferences.Editor editor = sp.edit();
-        editor.putLong(ASSETS_FILE_CHECKSUM_PREF_KEY, checksum);
-        editor.apply();
-    }
 }
\ No newline at end of file
diff --git a/src/com/android/providers/telephony/TelephonyProvider.java b/src/com/android/providers/telephony/TelephonyProvider.java
index 9b893fe..cce9607 100644
--- a/src/com/android/providers/telephony/TelephonyProvider.java
+++ b/src/com/android/providers/telephony/TelephonyProvider.java
@@ -40,6 +40,7 @@
 import static android.provider.Telephony.Carriers.MVNO_MATCH_DATA;
 import static android.provider.Telephony.Carriers.MVNO_TYPE;
 import static android.provider.Telephony.Carriers.NAME;
+import static android.provider.Telephony.Carriers.NETWORK_TYPE_BITMASK;
 import static android.provider.Telephony.Carriers.NUMERIC;
 import static android.provider.Telephony.Carriers.OWNED_BY;
 import static android.provider.Telephony.Carriers.OWNED_BY_OTHERS;
@@ -126,7 +127,7 @@
     private static final boolean DBG = true;
     private static final boolean VDBG = false; // STOPSHIP if true
 
-    private static final int DATABASE_VERSION = 23 << 16;
+    private static final int DATABASE_VERSION = 24 << 16;
     private static final int URL_UNKNOWN = 0;
     private static final int URL_TELEPHONY = 1;
     private static final int URL_CURRENT = 2;
@@ -220,7 +221,7 @@
     static {
         // Columns not included in UNIQUE constraint: name, current, edited, user, server, password,
         // authtype, type, protocol, roaming_protocol, sub_id, modem_cognitive, max_conns,
-        // wait_time, max_conns_time, mtu, bearer_bitmask, user_visible
+        // wait_time, max_conns_time, mtu, bearer_bitmask, user_visible, network_type_bitmask
         CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(NUMERIC, "");
         CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(MCC, "");
         CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(MNC, "");
@@ -268,6 +269,7 @@
                 CARRIER_ENABLED + " BOOLEAN DEFAULT 1," +
                 BEARER + " INTEGER DEFAULT 0," +
                 BEARER_BITMASK + " INTEGER DEFAULT 0," +
+                NETWORK_TYPE_BITMASK + " INTEGER DEFAULT 0," +
                 MVNO_TYPE + " TEXT DEFAULT ''," +
                 MVNO_MATCH_DATA + " TEXT DEFAULT ''," +
                 SUBSCRIPTION_ID + " INTEGER DEFAULT "
@@ -286,7 +288,8 @@
                 // here it means we will accept both (user edited + new apn_conf definition)
                 // Columns not included in UNIQUE constraint: name, current, edited,
                 // user, server, password, authtype, type, sub_id, modem_cognitive, max_conns,
-                // wait_time, max_conns_time, mtu, bearer_bitmask, user_visible.
+                // wait_time, max_conns_time, mtu, bearer_bitmask, user_visible,
+                // network_type_bitmask.
                 "UNIQUE (" + TextUtils.join(", ", CARRIERS_UNIQUE_FIELDS) + "));";
     }
 
@@ -880,52 +883,9 @@
                 oldVersion = 18 << 16 | 6;
             }
             if (oldVersion < (19 << 16 | 6)) {
-                // Upgrade steps from version 18 are:
-                // 1. Create a temp table- done in createCarriersTable()
-                // 2. copy over APNs from old table to new table - done in copyDataToTmpTable()
-                // 3. Drop the existing table.
-                // 4. Copy over the tmp table.
-                Cursor c;
-                String[] proj = {"_id"};
-                if (VDBG) {
-                    c = db.query(CARRIERS_TABLE, proj, null, null, null, null, null);
-                    log("dbh.onUpgrade:- before upgrading total number of rows: " + c.getCount());
-                    c.close();
-                }
-
-                c = db.query(CARRIERS_TABLE, null, null, null, null, null, null);
-
-                if (VDBG) {
-                    log("dbh.onUpgrade:- starting data copy of existing rows: " +
-                            + ((c == null) ? 0 : c.getCount()));
-                }
-
-                db.execSQL("DROP TABLE IF EXISTS " + CARRIERS_TABLE_TMP);
-
-                createCarriersTable(db, CARRIERS_TABLE_TMP);
-
-                copyDataToTmpTable(db, c);
-                c.close();
-
-                db.execSQL("DROP TABLE IF EXISTS " + CARRIERS_TABLE);
-
-                db.execSQL("ALTER TABLE " + CARRIERS_TABLE_TMP + " rename to " + CARRIERS_TABLE +
-                        ";");
-
-                if (VDBG) {
-                    c = db.query(CARRIERS_TABLE, proj, null, null, null, null, null);
-                    log("dbh.onUpgrade:- after upgrading total number of rows: " + c.getCount());
-                    c.close();
-                    c = db.query(CARRIERS_TABLE, proj, IS_UNEDITED, null, null, null, null);
-                    log("dbh.onUpgrade:- after upgrading total number of rows with " + IS_UNEDITED +
-                            ": " + c.getCount());
-                    c.close();
-                    c = db.query(CARRIERS_TABLE, proj, IS_EDITED, null, null, null, null);
-                    log("dbh.onUpgrade:- after upgrading total number of rows with " + IS_EDITED +
-                            ": " + c.getCount());
-                    c.close();
-                }
-                oldVersion = 19 << 16 | 6;
+                // Do nothing. This is to avoid recreating table twice. Table is anyway recreated
+                // for version 24 and that takes care of updates for this version as well.
+                // This version added more fields protocol and roaming protocol to the primary key.
             }
             if (oldVersion < (20 << 16 | 6)) {
                 try {
@@ -995,11 +955,57 @@
                 }
                 oldVersion = 23 << 16 | 6;
             }
+            if (oldVersion < (24 << 16 | 6)) {
+                Cursor c = null;
+                String[] proj = {"_id"};
+                recreateDB(c, db, proj, /* version */24);
+                if (VDBG) {
+                    c = db.query(CARRIERS_TABLE, proj, null, null, null, null, null);
+                    log("dbh.onUpgrade:- after upgrading total number of rows: " + c.getCount());
+                    c.close();
+                    c = db.query(CARRIERS_TABLE, proj, NETWORK_TYPE_BITMASK, null, null, null, null);
+                    log("dbh.onUpgrade:- after upgrading total number of rows with "
+                            + NETWORK_TYPE_BITMASK + ": " + c.getCount());
+                    c.close();
+                }
+                oldVersion = 24 << 16 | 6;
+            }
             if (DBG) {
                 log("dbh.onUpgrade:- db=" + db + " oldV=" + oldVersion + " newV=" + newVersion);
             }
         }
 
+        private void recreateDB(Cursor c, SQLiteDatabase db, String[] proj, int version) {
+            // Upgrade steps are:
+            // 1. Create a temp table- done in createCarriersTable()
+            // 2. copy over APNs from old table to new table - done in copyDataToTmpTable()
+            // 3. Drop the existing table.
+            // 4. Copy over the tmp table.
+            if (VDBG) {
+                c = db.query(CARRIERS_TABLE, proj, null, null, null, null, null);
+                log("dbh.onUpgrade:- before upgrading total number of rows: " + c.getCount());
+                c.close();
+            }
+
+            c = db.query(CARRIERS_TABLE, null, null, null, null, null, null);
+
+            if (VDBG) {
+                log("dbh.onUpgrade:- starting data copy of existing rows: " +
+                        + ((c == null) ? 0 : c.getCount()));
+            }
+
+            db.execSQL("DROP TABLE IF EXISTS " + CARRIERS_TABLE_TMP);
+
+            createCarriersTable(db, CARRIERS_TABLE_TMP);
+
+            copyDataToTmpTable(db, c);
+            c.close();
+
+            db.execSQL("DROP TABLE IF EXISTS " + CARRIERS_TABLE);
+
+            db.execSQL("ALTER TABLE " + CARRIERS_TABLE_TMP + " rename to " + CARRIERS_TABLE + ";");
+        }
+
         private void preserveUserAndCarrierApns(SQLiteDatabase db) {
             if (VDBG) log("preserveUserAndCarrierApns");
             XmlPullParser confparser;
@@ -1194,6 +1200,8 @@
                 while (c.moveToNext()) {
                     ContentValues cv = new ContentValues();
                     copyApnValuesV17(cv, c);
+                    // Sync bearer bitmask and network type bitmask
+                    getNetworkTypeBitmaskFromCursor(cv, c);
                     try {
                         db.insertWithOnConflict(CARRIERS_TABLE_TMP, null, cv,
                                 SQLiteDatabase.CONFLICT_ABORT);
@@ -1270,6 +1278,11 @@
                         int bearer_bitmask = ServiceState.getBitmaskForTech(
                                 Integer.parseInt(bearerStr));
                         cv.put(BEARER_BITMASK, bearer_bitmask);
+
+                        int networkTypeBitmask = ServiceState.getBitmaskForTech(
+                                ServiceState.rilRadioTechnologyToNetworkType(
+                                        Integer.parseInt(bearerStr)));
+                        cv.put(NETWORK_TYPE_BITMASK, networkTypeBitmask);
                     }
 
                     int userEditedColumnIdx = c.getColumnIndex("user_edited");
@@ -1339,6 +1352,37 @@
             }
         }
 
+        /**
+         * If NETWORK_TYPE_BITMASK does not exist (upgrade from version 23 to version 24), generate
+         * NETWORK_TYPE_BITMASK with the use of BEARER_BITMASK. If NETWORK_TYPE_BITMASK existed
+         * (upgrade from version 24 to forward), always map NETWORK_TYPE_BITMASK to BEARER_BITMASK.
+         */
+        private void getNetworkTypeBitmaskFromCursor(ContentValues cv, Cursor c) {
+            int columnIndex = c.getColumnIndex(NETWORK_TYPE_BITMASK);
+            if (columnIndex != -1) {
+                getStringValueFromCursor(cv, c, NETWORK_TYPE_BITMASK);
+                // Map NETWORK_TYPE_BITMASK to BEARER_BITMASK if NETWORK_TYPE_BITMASK existed;
+                String fromCursor = c.getString(columnIndex);
+                if (!TextUtils.isEmpty(fromCursor) && fromCursor.matches("\\d+")) {
+                    int networkBitmask = Integer.valueOf(fromCursor);
+                    int bearerBitmask = ServiceState.convertNetworkTypeBitmaskToBearerBitmask(
+                            networkBitmask);
+                    cv.put(BEARER_BITMASK, String.valueOf(bearerBitmask));
+                }
+                return;
+            }
+            columnIndex = c.getColumnIndex(BEARER_BITMASK);
+            if (columnIndex != -1) {
+                String fromCursor = c.getString(columnIndex);
+                if (!TextUtils.isEmpty(fromCursor) && fromCursor.matches("\\d+")) {
+                    int bearerBitmask = Integer.valueOf(fromCursor);
+                    int networkBitmask = ServiceState.convertBearerBitmaskToNetworkTypeBitmask(
+                            bearerBitmask);
+                    cv.put(NETWORK_TYPE_BITMASK, String.valueOf(networkBitmask));
+                }
+            }
+        }
+
         private void getIntValueFromCursor(ContentValues cv, Cursor c, String key) {
             int columnIndex = c.getColumnIndex(key);
             if (columnIndex != -1) {
@@ -1410,10 +1454,26 @@
             addBoolAttribute(parser, "user_visible", map, USER_VISIBLE);
             addBoolAttribute(parser, "user_editable", map, USER_EDITABLE);
 
+            int networkTypeBitmask = 0;
+            String networkTypeList = parser.getAttributeValue(null, "network_type_bitmask");
+            if (networkTypeList != null) {
+                networkTypeBitmask = ServiceState.getBitmaskFromString(networkTypeList);
+            }
+            map.put(NETWORK_TYPE_BITMASK, networkTypeBitmask);
+
             int bearerBitmask = 0;
-            String bearerList = parser.getAttributeValue(null, "bearer_bitmask");
-            if (bearerList != null) {
-                bearerBitmask = ServiceState.getBitmaskFromString(bearerList);
+            if (networkTypeList != null) {
+                bearerBitmask =
+                        ServiceState.convertNetworkTypeBitmaskToBearerBitmask(networkTypeBitmask);
+            } else {
+                String bearerList = parser.getAttributeValue(null, "bearer_bitmask");
+                if (bearerList != null) {
+                    bearerBitmask = ServiceState.getBitmaskFromString(bearerList);
+                }
+                // Update the network type bitmask to keep them sync.
+                networkTypeBitmask = ServiceState.convertBearerBitmaskToNetworkTypeBitmask(
+                        bearerBitmask);
+                map.put(NETWORK_TYPE_BITMASK, networkTypeBitmask);
             }
             map.put(BEARER_BITMASK, bearerBitmask);
 
@@ -1556,10 +1616,10 @@
                         if (VDBG) {
                             log("mergeFieldsAndUpdateDb: Calling separateRowsNeeded() oldType=" +
                                     oldType + " old bearer=" + oldRow.getInt(oldRow.getColumnIndex(
-                                    BEARER_BITMASK)) +
+                                    BEARER_BITMASK)) +  " old networkType=" +
+                                    oldRow.getInt(oldRow.getColumnIndex(NETWORK_TYPE_BITMASK)) +
                                     " old profile_id=" + oldRow.getInt(oldRow.getColumnIndex(
-                                    PROFILE_ID)) +
-                                    " newRow " + newRow);
+                                    PROFILE_ID)) + " newRow " + newRow);
                         }
 
                         // If separate rows are needed, do not need to merge any further
@@ -1585,8 +1645,7 @@
                         newRow.put(TYPE, mergedType.toString());
                     }
                 }
-                mergedValues.put(TYPE, newRow.getAsString(
-                        TYPE));
+                mergedValues.put(TYPE, newRow.getAsString(TYPE));
             }
 
             if (newRow.containsKey(BEARER_BITMASK)) {
@@ -1602,7 +1661,38 @@
                 mergedValues.put(BEARER_BITMASK, newRow.getAsInteger(BEARER_BITMASK));
             }
 
+            if (newRow.containsKey(NETWORK_TYPE_BITMASK)) {
+                int oldBitmask = oldRow.getInt(oldRow.getColumnIndex(NETWORK_TYPE_BITMASK));
+                int newBitmask = newRow.getAsInteger(NETWORK_TYPE_BITMASK);
+                if (oldBitmask != newBitmask) {
+                    if (oldBitmask == 0 || newBitmask == 0) {
+                        newRow.put(NETWORK_TYPE_BITMASK, 0);
+                    } else {
+                        newRow.put(NETWORK_TYPE_BITMASK, (oldBitmask | newBitmask));
+                    }
+                }
+                mergedValues.put(NETWORK_TYPE_BITMASK, newRow.getAsInteger(NETWORK_TYPE_BITMASK));
+            }
+
+            if (newRow.containsKey(BEARER_BITMASK)
+                    && newRow.containsKey(NETWORK_TYPE_BITMASK)) {
+                syncBearerBitmaskAndNetworkTypeBitmask(mergedValues);
+            }
+
             if (!onUpgrade) {
+                // Do not overwrite a carrier or user edit with EDITED=UNEDITED
+                if (newRow.containsKey(EDITED)) {
+                    int oldEdited = oldRow.getInt(oldRow.getColumnIndex(EDITED));
+                    int newEdited = newRow.getAsInteger(EDITED);
+                    if (newEdited == UNEDITED && (oldEdited == CARRIER_EDITED
+                                || oldEdited == CARRIER_DELETED
+                                || oldEdited == CARRIER_DELETED_BUT_PRESENT_IN_XML
+                                || oldEdited == USER_EDITED
+                                || oldEdited == USER_DELETED
+                                || oldEdited == USER_DELETED_BUT_PRESENT_IN_XML)) {
+                        newRow.remove(EDITED);
+                    }
+                }
                 mergedValues.putAll(newRow);
             }
 
@@ -1719,6 +1809,7 @@
                     TYPE,
                     EDITED,
                     BEARER_BITMASK,
+                    NETWORK_TYPE_BITMASK,
                     PROFILE_ID };
             String selection = TextUtils.join("=? AND ", CARRIERS_UNIQUE_FIELDS) + "=?";
             int i = 0;
@@ -2352,6 +2443,7 @@
         int subId = SubscriptionManager.getDefaultSubscriptionId();
 
         checkPermission();
+        syncBearerBitmaskAndNetworkTypeBitmask(initialValues);
 
         boolean notify = false;
         SQLiteDatabase db = getWritableDatabase();
@@ -2656,6 +2748,7 @@
         int subId = SubscriptionManager.getDefaultSubscriptionId();
 
         checkPermission();
+        syncBearerBitmaskAndNetworkTypeBitmask(values);
 
         SQLiteDatabase db = getWritableDatabase();
         int match = s_urlMatcher.match(url);
@@ -2911,6 +3004,31 @@
     }
 
     /**
+     * Sync the bearer bitmask and network type bitmask when inserting and updating.
+     * Since bearerBitmask is deprecating, map the networkTypeBitmask to bearerBitmask if
+     * networkTypeBitmask was provided. But if networkTypeBitmask was not provided, map the
+     * bearerBitmask to networkTypeBitmask.
+     */
+    private static void syncBearerBitmaskAndNetworkTypeBitmask(ContentValues values) {
+        if (values.containsKey(NETWORK_TYPE_BITMASK)) {
+            int convertedBitmask = ServiceState.convertNetworkTypeBitmaskToBearerBitmask(
+                    values.getAsInteger(NETWORK_TYPE_BITMASK));
+            if (values.containsKey(BEARER_BITMASK)
+                    && convertedBitmask != values.getAsInteger(BEARER_BITMASK)) {
+                loge("Network type bitmask and bearer bitmask are not compatible.");
+            }
+            values.put(BEARER_BITMASK, ServiceState.convertNetworkTypeBitmaskToBearerBitmask(
+                    values.getAsInteger(NETWORK_TYPE_BITMASK)));
+        } else {
+            if (values.containsKey(BEARER_BITMASK)) {
+                int convertedBitmask = ServiceState.convertBearerBitmaskToNetworkTypeBitmask(
+                        values.getAsInteger(BEARER_BITMASK));
+                values.put(NETWORK_TYPE_BITMASK, convertedBitmask);
+            }
+        }
+    }
+
+    /**
      * Log with debug
      *
      * @param s is string log
diff --git a/tests/src/com/android/providers/telephony/TelephonyProviderTest.java b/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
index b935a31..7fd7f61 100644
--- a/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
+++ b/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
@@ -815,4 +815,133 @@
             // Should catch SecurityException.
         }
     }
+
+    /**
+     * Verify that user/carrier edited/deleted APNs have priority in the EDITED field over
+     * insertions which set EDITED=UNEDITED. In these cases instead of merging the APNs using the
+     * new APN's value we keep the old value.
+     */
+    @Test
+    @SmallTest
+    public void testPreserveEdited() {
+        preserveEditedValueInMerge(Carriers.USER_EDITED);
+    }
+
+    @Test
+    @SmallTest
+    public void testPreserveUserDeleted() {
+        preserveDeletedValueInMerge(Carriers.USER_DELETED);
+    }
+
+    @Test
+    @SmallTest
+    public void testPreserveUserDeletedButPresentInXml() {
+        preserveDeletedValueInMerge(Carriers.USER_DELETED_BUT_PRESENT_IN_XML);
+    }
+
+    @Test
+    @SmallTest
+    public void testPreserveCarrierEdited() {
+        preserveEditedValueInMerge(Carriers.CARRIER_EDITED);
+    }
+
+    @Test
+    @SmallTest
+    public void testPreserveCarrierDeleted() {
+        preserveDeletedValueInMerge(Carriers.CARRIER_DELETED);
+    }
+
+    @Test
+    @SmallTest
+    public void testPreserveCarrierDeletedButPresentInXml() {
+        preserveDeletedValueInMerge(Carriers.CARRIER_DELETED_BUT_PRESENT_IN_XML);
+    }
+
+    private void preserveEditedValueInMerge(int value) {
+        // insert user deleted APN
+        String carrierName1 = "carrier1";
+        String numeric1 = "123234";
+        String mcc1 = "123";
+        String mnc1 = "234";
+        ContentValues editedValue = new ContentValues();
+        editedValue.put(Carriers.NAME, carrierName1);
+        editedValue.put(Carriers.NUMERIC, numeric1);
+        editedValue.put(Carriers.MCC, mcc1);
+        editedValue.put(Carriers.MNC, mnc1);
+        editedValue.put(Carriers.EDITED, value);
+        assertNotNull(mContentResolver.insert(URI_TELEPHONY, editedValue));
+
+        Cursor cur = mContentResolver.query(URI_TELEPHONY, null, null, null, null);
+        assertEquals(1, cur.getCount());
+
+        // insert APN that conflicts with edited APN
+        String carrierName2 = "carrier2";
+        ContentValues values = new ContentValues();
+        values.put(Carriers.NAME, carrierName2);
+        values.put(Carriers.NUMERIC, numeric1);
+        values.put(Carriers.MCC, mcc1);
+        values.put(Carriers.MNC, mnc1);
+        values.put(Carriers.EDITED, Carriers.UNEDITED);
+        mContentResolver.insert(URI_TELEPHONY, values);
+
+        String[] testProjection = {
+            Carriers.NAME,
+            Carriers.APN,
+            Carriers.EDITED,
+            Carriers.TYPE,
+            Carriers.PROTOCOL,
+            Carriers.BEARER_BITMASK,
+        };
+        final int indexOfName = 0;
+        final int indexOfEdited = 2;
+
+        // Assert that the conflicting APN is merged into the existing user-edited APN, so only 1
+        // APN exists in the db
+        cur = mContentResolver.query(URI_TELEPHONY, testProjection, null, null, null);
+        assertEquals(1, cur.getCount());
+        cur.moveToFirst();
+        assertEquals(carrierName2, cur.getString(indexOfName));
+        assertEquals(value, cur.getInt(indexOfEdited));
+    }
+
+    private void preserveDeletedValueInMerge(int value) {
+        // insert user deleted APN
+        String carrierName1 = "carrier1";
+        String numeric1 = "123234";
+        String mcc1 = "123";
+        String mnc1 = "234";
+        ContentValues editedValue = new ContentValues();
+        editedValue.put(Carriers.NAME, carrierName1);
+        editedValue.put(Carriers.NUMERIC, numeric1);
+        editedValue.put(Carriers.MCC, mcc1);
+        editedValue.put(Carriers.MNC, mnc1);
+        editedValue.put(Carriers.EDITED, value);
+        assertNotNull(mContentResolver.insert(URI_TELEPHONY, editedValue));
+
+        // insert APN that conflicts with edited APN
+        String carrierName2 = "carrier2";
+        ContentValues values = new ContentValues();
+        values.put(Carriers.NAME, carrierName2);
+        values.put(Carriers.NUMERIC, numeric1);
+        values.put(Carriers.MCC, mcc1);
+        values.put(Carriers.MNC, mnc1);
+        values.put(Carriers.EDITED, Carriers.UNEDITED);
+        mContentResolver.insert(URI_TELEPHONY, values);
+
+        String[] testProjection = {
+            Carriers.NAME,
+            Carriers.APN,
+            Carriers.EDITED,
+            Carriers.TYPE,
+            Carriers.PROTOCOL,
+            Carriers.BEARER_BITMASK,
+        };
+        final int indexOfEdited = 2;
+
+        // Assert that the conflicting APN is merged into the existing user-deleted APN.
+        // Entries marked deleted will not show up in queries so we verify that no APNs can
+        // be seen
+        Cursor cur = mContentResolver.query(URI_TELEPHONY, testProjection, null, null, null);
+        assertEquals(0, cur.getCount());
+    }
 }