Merge "Fix handling of URIs with subId"
diff --git a/src/com/android/providers/telephony/CarrierIdProvider.java b/src/com/android/providers/telephony/CarrierIdProvider.java
index 08bb608..a14923e 100644
--- a/src/com/android/providers/telephony/CarrierIdProvider.java
+++ b/src/com/android/providers/telephony/CarrierIdProvider.java
@@ -20,21 +20,35 @@
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.SharedPreferences;
 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.provider.Telephony.CarrierIdentification;
 import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.nano.CarrierIdProto;
 
+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;
 
+import libcore.io.IoUtils;
+
 /**
  * This class provides the ability to query the Carrier Identification databases
  * (A.K.A. cid) which is stored in a SQLite database.
@@ -57,6 +71,43 @@
 
     private static final String DATABASE_NAME = "carrierIdentification.db";
     private static final int DATABASE_VERSION = 2;
+
+    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 PREF_FILE = CarrierIdProvider.class.getSimpleName();
+
+    /**
+     * index 0: {@link CarrierIdentification#MCCMNC}
+     */
+    private static final int MCCMNC_INDEX                = 0;
+    /**
+     * index 1: {@link CarrierIdentification#IMSI_PREFIX_XPATTERN}
+     */
+    private static final int IMSI_PREFIX_INDEX           = 1;
+    /**
+     * index 2: {@link CarrierIdentification#GID1}
+     */
+    private static final int GID1_INDEX                  = 2;
+    /**
+     * index 3: {@link CarrierIdentification#GID2}
+     */
+    private static final int GID2_INDEX                  = 3;
+    /**
+     * index 4: {@link CarrierIdentification#PLMN}
+     */
+    private static final int PLMN_INDEX                  = 4;
+    /**
+     * index 5: {@link CarrierIdentification#SPN}
+     */
+    private static final int SPN_INDEX                   = 5;
+    /**
+     * index 6: {@link CarrierIdentification#APN}
+     */
+    private static final int APN_INDEX                   = 6;
+    /**
+     * ending index of carrier attribute list.
+     */
+    private static final int CARRIER_ATTR_END_IDX        = APN_INDEX;
     /**
      * The authority string for the CarrierIdProvider
      */
@@ -103,6 +154,7 @@
         Log.d(TAG, "onCreate");
         mDbHelper = new CarrierIdDatabaseHelper(getContext());
         mDbHelper.getReadableDatabase();
+        updateFromAssetsIfNeeded(mDbHelper.getWritableDatabase());
         return true;
     }
 
@@ -222,4 +274,191 @@
             }
         }
     }
+
+    /**
+     * 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.
+     */
+    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) {
+        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);
+                }
+            }
+            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);
+        }
+    }
+
+    /**
+     * recursively loop through carrier attribute list to get all combinations.
+     */
+    private void convertCarrierAttrToContentValues(ContentValues cv, List<ContentValues> cvs,
+            CarrierIdProto.CarrierAttribute attr, int index) {
+        if (index > CARRIER_ATTR_END_IDX) {
+            cvs.add(new ContentValues(cv));
+            return;
+        }
+        boolean found = false;
+        switch(index) {
+            case MCCMNC_INDEX:
+                for (String str : attr.mccmncTuple) {
+                    cv.put(CarrierIdentification.MCCMNC, str);
+                    convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
+                    cv.remove(CarrierIdentification.MCCMNC);
+                    found = true;
+                }
+                break;
+            case IMSI_PREFIX_INDEX:
+                for (String str : attr.imsiPrefixXpattern) {
+                    cv.put(CarrierIdentification.IMSI_PREFIX_XPATTERN, str);
+                    convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
+                    cv.remove(CarrierIdentification.IMSI_PREFIX_XPATTERN);
+                    found = true;
+                }
+                break;
+            case GID1_INDEX:
+                for (String str : attr.gid1) {
+                    cv.put(CarrierIdentification.GID1, str);
+                    convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
+                    cv.remove(CarrierIdentification.GID1);
+                    found = true;
+                }
+                break;
+            case GID2_INDEX:
+                for (String str : attr.gid2) {
+                    cv.put(CarrierIdentification.GID2, str);
+                    convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
+                    cv.remove(CarrierIdentification.GID2);
+                    found = true;
+                }
+                break;
+            case PLMN_INDEX:
+                for (String str : attr.plmn) {
+                    cv.put(CarrierIdentification.PLMN, str);
+                    convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
+                    cv.remove(CarrierIdentification.PLMN);
+                    found = true;
+                }
+                break;
+            case SPN_INDEX:
+                for (String str : attr.spn) {
+                    cv.put(CarrierIdentification.SPN, str);
+                    convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
+                    cv.remove(CarrierIdentification.SPN);
+                    found = true;
+                }
+                break;
+            case APN_INDEX:
+                for (String str : attr.preferredApn) {
+                    cv.put(CarrierIdentification.APN, str);
+                    convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
+                    cv.remove(CarrierIdentification.APN);
+                    found = true;
+                }
+                break;
+            default:
+                Log.e(TAG, "unsupported index: " + index);
+                break;
+        }
+        // if attribute at index is empty, move forward to the next attribute
+        if (!found) {
+            convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
+        }
+    }
+
+    /**
+     * util function to convert inputStream to byte array before parsing proto data.
+     */
+    private static byte[] readInputStreamToByteArray(InputStream inputStream) throws IOException {
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        int nRead;
+        int size = 16 * 1024; // Read 16k chunks
+        byte[] data = new byte[size];
+        while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
+            buffer.write(data, 0, nRead);
+        }
+        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 6beaf9b..eebb681 100644
--- a/src/com/android/providers/telephony/TelephonyProvider.java
+++ b/src/com/android/providers/telephony/TelephonyProvider.java
@@ -112,6 +112,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -189,6 +190,7 @@
 
     private static final int INVALID_APN_ID = -1;
     private static final List<String> CARRIERS_UNIQUE_FIELDS = new ArrayList<String>();
+    private static final Map<String, String> CARRIERS_UNIQUE_FIELDS_DEFAULTS = new HashMap();
 
     private static Boolean s_apnSourceServiceExists;
 
@@ -200,24 +202,26 @@
         // 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
-        CARRIERS_UNIQUE_FIELDS.add(NUMERIC);
-        CARRIERS_UNIQUE_FIELDS.add(MCC);
-        CARRIERS_UNIQUE_FIELDS.add(MNC);
-        CARRIERS_UNIQUE_FIELDS.add(APN);
-        CARRIERS_UNIQUE_FIELDS.add(PROXY);
-        CARRIERS_UNIQUE_FIELDS.add(PORT);
-        CARRIERS_UNIQUE_FIELDS.add(MMSPROXY);
-        CARRIERS_UNIQUE_FIELDS.add(MMSPORT);
-        CARRIERS_UNIQUE_FIELDS.add(MMSC);
-        CARRIERS_UNIQUE_FIELDS.add(CARRIER_ENABLED);
-        CARRIERS_UNIQUE_FIELDS.add(BEARER);
-        CARRIERS_UNIQUE_FIELDS.add(MVNO_TYPE);
-        CARRIERS_UNIQUE_FIELDS.add(MVNO_MATCH_DATA);
-        CARRIERS_UNIQUE_FIELDS.add(PROFILE_ID);
-        CARRIERS_UNIQUE_FIELDS.add(PROTOCOL);
-        CARRIERS_UNIQUE_FIELDS.add(ROAMING_PROTOCOL);
-        CARRIERS_UNIQUE_FIELDS.add(USER_EDITABLE);
-        CARRIERS_UNIQUE_FIELDS.add(OWNED_BY);
+        CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(NUMERIC, "");
+        CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(MCC, "");
+        CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(MNC, "");
+        CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(APN, "");
+        CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(PROXY, "");
+        CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(PORT, "");
+        CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(MMSPROXY, "");
+        CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(MMSPORT, "");
+        CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(MMSC, "");
+        CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(CARRIER_ENABLED, "1");
+        CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(BEARER, "0");
+        CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(MVNO_TYPE, "");
+        CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(MVNO_MATCH_DATA, "");
+        CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(PROFILE_ID, "0");
+        CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(PROTOCOL, "IP");
+        CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(ROAMING_PROTOCOL, "IP");
+        CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(USER_EDITABLE, "1");
+        CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(OWNED_BY, String.valueOf(OWNED_BY_OTHERS));
+
+        CARRIERS_UNIQUE_FIELDS.addAll(CARRIERS_UNIQUE_FIELDS_DEFAULTS.keySet());
     }
 
     @VisibleForTesting
@@ -1638,32 +1642,19 @@
                     PROFILE_ID };
             String selection = TextUtils.join("=? AND ", CARRIERS_UNIQUE_FIELDS) + "=?";
             int i = 0;
-            String[] selectionArgs = new String[18];
-            selectionArgs[i++] = row.getAsString(NUMERIC);
-            selectionArgs[i++] = row.getAsString(MCC);
-            selectionArgs[i++] = row.getAsString(MNC);
-            selectionArgs[i++] = row.containsKey(APN) ? row.getAsString(APN) : "";
-            selectionArgs[i++] = row.containsKey(PROXY) ? row.getAsString(PROXY) : "";
-            selectionArgs[i++] = row.containsKey(PORT) ? row.getAsString(PORT) : "";
-            selectionArgs[i++] = row.containsKey(MMSPROXY) ? row.getAsString(MMSPROXY) : "";
-            selectionArgs[i++] = row.containsKey(MMSPORT) ? row.getAsString(MMSPORT) : "";
-            selectionArgs[i++] = row.containsKey(MMSC) ? row.getAsString(MMSC) : "";
-            selectionArgs[i++] = row.containsKey(CARRIER_ENABLED) &&
-                    (row.getAsString(CARRIER_ENABLED).equals("0") ||
-                            row.getAsString(CARRIER_ENABLED).equals("false")) ?
-                    "0" : "1";
-            selectionArgs[i++] = row.containsKey(BEARER) ? row.getAsString(BEARER) : "0";
-            selectionArgs[i++] = row.containsKey(MVNO_TYPE) ? row.getAsString(MVNO_TYPE) : "";
-            selectionArgs[i++] = row.containsKey(MVNO_MATCH_DATA) ?
-                    row.getAsString(MVNO_MATCH_DATA) : "";
-            selectionArgs[i++] = row.containsKey(PROFILE_ID) ? row.getAsString(PROFILE_ID) : "0";
-            selectionArgs[i++] = row.containsKey(PROTOCOL) ? row.getAsString(PROTOCOL) : "IP";
-            selectionArgs[i++] = row.containsKey(ROAMING_PROTOCOL) ? row.getAsString(
-                    ROAMING_PROTOCOL) : "IP";
-            selectionArgs[i++] = row.containsKey(USER_EDITABLE) ? row.getAsString(
-                    USER_EDITABLE) : "1";
-            selectionArgs[i++] = row.containsKey(OWNED_BY) ? row.getAsString(
-                    OWNED_BY) : Integer.toString(OWNED_BY_OTHERS);
+            String[] selectionArgs = new String[CARRIERS_UNIQUE_FIELDS.size()];
+            for (String field : CARRIERS_UNIQUE_FIELDS) {
+                if (CARRIER_ENABLED.equals(field)) {
+                    // for CARRIER_ENABLED we overwrite the value "false" with "0"
+                    selectionArgs[i++] = row.containsKey(CARRIER_ENABLED) &&
+                            (row.getAsString(CARRIER_ENABLED).equals("0") ||
+                                    row.getAsString(CARRIER_ENABLED).equals("false")) ?
+                            "0" : CARRIERS_UNIQUE_FIELDS_DEFAULTS.get(CARRIER_ENABLED);
+                } else {
+                    selectionArgs[i++] = row.containsKey(field) ?
+                            row.getAsString(field) : CARRIERS_UNIQUE_FIELDS_DEFAULTS.get(field);
+                }
+            }
 
             Cursor c = db.query(table, columns, selection, selectionArgs, null, null, null);