[automerger skipped] DO NOT MERGE Check permissions for URL_SIMINFO am: caab85d914 am: e7ee89118e am: a8daa7c551 -s ours am: 85b15220ea -s ours
am skip reason: subject contains skip directive

Change-Id: Id420ae239007abfa46a9e264bb054382acb006d4
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..eabd567
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,9 @@
+android_app {
+    name: "TelephonyProvider",
+    privileged: true,
+    srcs: ["src/**/*.java"],
+    platform_apis: true,
+    certificate: "platform",
+    libs: ["telephony-common"],
+    static_libs: ["android-common"],
+}
diff --git a/Android.mk b/Android.mk
deleted file mode 100644
index 3ff05c9..0000000
--- a/Android.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_PRIVILEGED_MODULE := true
-
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-
-LOCAL_PACKAGE_NAME := TelephonyProvider
-LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_CERTIFICATE := platform
-
-LOCAL_JAVA_LIBRARIES += telephony-common
-LOCAL_STATIC_JAVA_LIBRARIES += android-common
-
-include $(BUILD_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 31e5364..4ae9acf 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -21,6 +21,7 @@
 
     <uses-permission android:name="android.permission.RECEIVE_SMS" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
     <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" />
@@ -66,6 +67,13 @@
                   android:singleUser="true"
                   android:readPermission="android.permission.READ_SMS" />
 
+        <provider android:name="SmsChangesProvider"
+                  android:authorities="sms-changes"
+                  android:multiprocess="false"
+                  android:exported="true"
+                  android:singleUser="true"
+                  android:readPermission="android.permission.READ_SMS" />
+
         <!-- This is a singleton provider that is used by all users.
              A new instance is not created for each user. And the db is shared
              as well.
@@ -112,6 +120,12 @@
                   android:singleUser="true"
                   android:multiprocess="false" />
 
+        <provider android:name="RcsProvider"
+                  android:authorities="rcs"
+                  android:multiprocess="false"
+                  android:exported="false"
+                  android:singleUser="true" />
+
         <service
             android:name=".TelephonyBackupAgent$DeferredSmsMmsRestoreService"
             android:exported="false" />
diff --git a/OWNERS b/OWNERS
index 0716d33..92458db 100644
--- a/OWNERS
+++ b/OWNERS
@@ -6,4 +6,7 @@
 mpq@google.com
 jminjie@google.com
 shuoq@google.com
-
+hallliu@google.com
+tgunn@google.com
+breadley@google.com
+nazaninb@google.com
diff --git a/assets/README b/assets/README
new file mode 100644
index 0000000..ad6a29c
--- /dev/null
+++ b/assets/README
@@ -0,0 +1,12 @@
+====== carrier_list.textpb ======
+
+DO NOT MANUALLY EDIT THIS FILE
+
+This file is the textpb verion of carrier_list.pb files under assets/, for readability purpose only.
+
+This file is not build into pb, thus modification of this file won't take any effect.
+
+===== carrier_list.pb =====
+DO NOT MANUALLY EDIT THIS FILE
+
+This file defines carrier id and should be single versioned.
diff --git a/assets/carrier_list.pb b/assets/carrier_list.pb
index 856c5bc..125de1a 100644
--- a/assets/carrier_list.pb
+++ b/assets/carrier_list.pb
Binary files differ
diff --git a/assets/carrier_list.textpb b/assets/carrier_list.textpb
index 23196a5..648571e 100644
--- a/assets/carrier_list.textpb
+++ b/assets/carrier_list.textpb
Binary files differ
diff --git a/assets/sdk28_carrier_id/carrier_list.pb b/assets/sdk28_carrier_id/carrier_list.pb
new file mode 100644
index 0000000..856c5bc
--- /dev/null
+++ b/assets/sdk28_carrier_id/carrier_list.pb
Binary files differ
diff --git a/assets/sdk28_carrier_id/carrier_list.textpb b/assets/sdk28_carrier_id/carrier_list.textpb
new file mode 100644
index 0000000..23196a5
--- /dev/null
+++ b/assets/sdk28_carrier_id/carrier_list.textpb
@@ -0,0 +1,7806 @@
+carrier_id {
+  canonical_id: 1
+  carrier_name: "T-Mobile - US"
+  carrier_attribute {
+    mccmnc_tuple: "310026"
+    mccmnc_tuple: "310160"
+    mccmnc_tuple: "310200"
+    mccmnc_tuple: "310210"
+    mccmnc_tuple: "310220"
+    mccmnc_tuple: "310230"
+    mccmnc_tuple: "310240"
+    mccmnc_tuple: "310250"
+    mccmnc_tuple: "310260"
+    mccmnc_tuple: "31026"
+    mccmnc_tuple: "310270"
+    mccmnc_tuple: "310300"
+    mccmnc_tuple: "310310"
+    mccmnc_tuple: "310490"
+    mccmnc_tuple: "310530"
+    mccmnc_tuple: "310590"
+    mccmnc_tuple: "310640"
+    mccmnc_tuple: "310660"
+    mccmnc_tuple: "310800"
+  }
+}
+carrier_id {
+  canonical_id: 2
+  carrier_name: "EE"
+  carrier_attribute {
+    mccmnc_tuple: "23430"
+    imsi_prefix_xpattern: "2343041"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "23430"
+    gid1: "ee"
+    gid1: "eeff"
+  }
+}
+carrier_id {
+  canonical_id: 3
+  carrier_name: "Telekom"
+  carrier_attribute {
+    mccmnc_tuple: "26201"
+    mccmnc_tuple: "26206"
+    gid1: "01"
+    gid1: "02"
+    gid1: "03"
+    gid1: "04"
+    gid1: "99"
+    gid1: "4B"
+    gid1: "4b"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "26201"
+    mccmnc_tuple: "26206"
+    spn: "Telekom.de"
+    spn: "T-Mobile D"
+    spn: "Business"
+    spn: "Privat"
+  }
+}
+carrier_id {
+  canonical_id: 4
+  carrier_name: "T-Mobile/tele.ring"
+  carrier_attribute {
+    mccmnc_tuple: "23203"
+    gid1: "01"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "23207"
+    imsi_prefix_xpattern: "999999"
+  }
+}
+carrier_id {
+  canonical_id: 5
+  carrier_name: "T-Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "20416"
+    mccmnc_tuple: "20420"
+    gid1: "FF"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "20416"
+    mccmnc_tuple: "20420"
+    spn: "T-Mobile  NL"
+  }
+}
+carrier_id {
+  canonical_id: 6
+  carrier_name: "T-Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "23001"
+    spn: "T-Mobile CZ"
+    spn: "KAKTUS"
+    spn: "Mobil CZ"
+    spn: "RWE Mobil"
+    spn: "innogy"
+    spn: "PREmobil"
+  }
+}
+carrier_id {
+  canonical_id: 7
+  carrier_name: "T-Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "26002"
+    gid1: "11"
+    gid1: "12"
+    gid1: "16"
+    gid1: "17"
+    gid1: "21"
+    gid1: "22"
+    gid1: "24"
+    gid1: "26"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "26002"
+    spn: "T-Mobile.pl"
+    spn: "T-Mobile.pl Q"
+  }
+}
+carrier_id {
+  canonical_id: 8
+  carrier_name: "Magyar Telekom"
+  carrier_attribute {
+    mccmnc_tuple: "21630"
+    spn: "Telekom HU"
+    spn: "T-Mobile H"
+  }
+}
+carrier_id {
+  canonical_id: 9
+  carrier_name: "Hrvaski Telekom"
+  carrier_attribute {
+    mccmnc_tuple: "21901"
+    spn: "T-Mobile HR"
+    spn: "HT HR"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "21901"
+    gid1: "01"
+    gid1: "02"
+    gid1: "03"
+    gid1: "99"
+  }
+}
+carrier_id {
+  canonical_id: 10
+  carrier_name: "T-Mobile - ME"
+  carrier_attribute {
+    mccmnc_tuple: "22004"
+  }
+}
+carrier_id {
+  canonical_id: 11
+  carrier_name: "Slovak Telekom"
+  carrier_attribute {
+    mccmnc_tuple: "23102"
+    spn: "Telekom SK"
+    spn: "Juro.sk"
+  }
+}
+carrier_id {
+  canonical_id: 12
+  carrier_name: "T-Mobile - UA"
+  carrier_attribute {
+    mccmnc_tuple: "25502"
+  }
+}
+carrier_id {
+  canonical_id: 13
+  carrier_name: "Mkedonski Telecom AD Skopje"
+  carrier_attribute {
+    mccmnc_tuple: "29401"
+    spn: "Telekom.mk"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "29401"
+    gid1: "FF"
+  }
+}
+carrier_id {
+  canonical_id: 14
+  carrier_name: "A1"
+  carrier_attribute {
+    mccmnc_tuple: "23201"
+    mccmnc_tuple: "23211"
+    mccmnc_tuple: "23212"
+  }
+}
+carrier_id {
+  canonical_id: 15
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "50503"
+  }
+}
+carrier_id {
+  canonical_id: 16
+  carrier_name: "Swisscom"
+  carrier_attribute {
+    mccmnc_tuple: "22801"
+    spn: "Swisscom"
+  }
+}
+carrier_id {
+  canonical_id: 18
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "20205"
+    spn: "vodafone GR"
+    spn: "CU"
+    spn: "CU-X"
+  }
+}
+carrier_id {
+  canonical_id: 19
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "27201"
+    spn: "vodafone IE"
+    spn: "Vodafone IE"
+  }
+}
+carrier_id {
+  canonical_id: 20
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "20404"
+    imsi_prefix_xpattern: "204040"
+    imsi_prefix_xpattern: "204041"
+    imsi_prefix_xpattern: "204042"
+    imsi_prefix_xpattern: "204044"
+    imsi_prefix_xpattern: "204045"
+    imsi_prefix_xpattern: "204047"
+    imsi_prefix_xpattern: "204048"
+    imsi_prefix_xpattern: "204049"
+    spn: ""
+    spn: "vodafone NL"
+  }
+}
+carrier_id {
+  canonical_id: 21
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "53001"
+  }
+}
+carrier_id {
+  canonical_id: 22
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "26801"
+    spn: ""
+    spn: "vodafone P"
+    spn: "Vodafone"
+  }
+}
+carrier_id {
+  canonical_id: 23
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "22601"
+    spn: "Vodafone RO"
+  }
+}
+carrier_id {
+  canonical_id: 24
+  carrier_name: "Vodacom"
+  carrier_attribute {
+    mccmnc_tuple: "65501"
+  }
+}
+carrier_id {
+  canonical_id: 25
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "26202"
+    imsi_prefix_xpattern: "262020"
+    imsi_prefix_xpattern: "2620211"
+    imsi_prefix_xpattern: "2620212"
+    imsi_prefix_xpattern: "2620213"
+    imsi_prefix_xpattern: "2620214"
+    imsi_prefix_xpattern: "2620215"
+    imsi_prefix_xpattern: "2620216"
+    imsi_prefix_xpattern: "2620217"
+    imsi_prefix_xpattern: "2620218"
+    imsi_prefix_xpattern: "26202190"
+    imsi_prefix_xpattern: "262021910"
+    imsi_prefix_xpattern: "262021911"
+    imsi_prefix_xpattern: "262021912"
+    imsi_prefix_xpattern: "2620219130"
+    imsi_prefix_xpattern: "2620219131"
+    imsi_prefix_xpattern: "2620219132"
+    imsi_prefix_xpattern: "2620219133"
+    imsi_prefix_xpattern: "2620219134"
+    imsi_prefix_xpattern: "2620219141"
+    imsi_prefix_xpattern: "2620219142"
+    imsi_prefix_xpattern: "2620219143"
+    imsi_prefix_xpattern: "2620219144"
+    imsi_prefix_xpattern: "2620219149"
+    imsi_prefix_xpattern: "262021915"
+    imsi_prefix_xpattern: "262021916"
+    imsi_prefix_xpattern: "262021917"
+    imsi_prefix_xpattern: "262021918"
+    imsi_prefix_xpattern: "262021919"
+    imsi_prefix_xpattern: "26202192"
+    imsi_prefix_xpattern: "26202193"
+    imsi_prefix_xpattern: "26202194"
+    imsi_prefix_xpattern: "26202195"
+    imsi_prefix_xpattern: "26202196"
+    imsi_prefix_xpattern: "26202197"
+    imsi_prefix_xpattern: "26202198"
+    imsi_prefix_xpattern: "26202199"
+    imsi_prefix_xpattern: "262022"
+    imsi_prefix_xpattern: "262023"
+    imsi_prefix_xpattern: "262024"
+    imsi_prefix_xpattern: "262025"
+    imsi_prefix_xpattern: "262026"
+    imsi_prefix_xpattern: "262027"
+    imsi_prefix_xpattern: "262028"
+    imsi_prefix_xpattern: "262029"
+    spn: ""
+    spn: "Vodafone"
+    spn: "Vodafone.de"
+  }
+}
+carrier_id {
+  canonical_id: 26
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "21401"
+    spn: ""
+    spn: "vodafone"
+    spn: "vodafone ES"
+    spn: "Vodafone"
+  }
+}
+carrier_id {
+  canonical_id: 27
+  carrier_name: "SFR"
+  carrier_attribute {
+    mccmnc_tuple: "20810"
+  }
+}
+carrier_id {
+  canonical_id: 28
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "23415"
+    mccmnc_tuple: "23491"
+    spn: ""
+    spn: "vodafone UK"
+  }
+}
+carrier_id {
+  canonical_id: 29
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "22210"
+    spn: ""
+    spn: "vodafone IT"
+    spn: "Vodafone"
+  }
+}
+carrier_id {
+  canonical_id: 30
+  carrier_name: "Optus"
+  carrier_attribute {
+    mccmnc_tuple: "50502"
+    mccmnc_tuple: "50590"
+  }
+}
+carrier_id {
+  canonical_id: 31
+  carrier_name: "Singtel"
+  carrier_attribute {
+    mccmnc_tuple: "52501"
+    mccmnc_tuple: "52502"
+  }
+}
+carrier_id {
+  canonical_id: 32
+  carrier_name: "Orange"
+  carrier_attribute {
+    mccmnc_tuple: "20800"
+    mccmnc_tuple: "20801"
+    mccmnc_tuple: "20802"
+  }
+}
+carrier_id {
+  canonical_id: 33
+  carrier_name: "TIM"
+  carrier_attribute {
+    mccmnc_tuple: "22201"
+  }
+}
+carrier_id {
+  canonical_id: 34
+  carrier_name: "Movistar"
+  carrier_attribute {
+    mccmnc_tuple: "21405"
+    mccmnc_tuple: "21407"
+  }
+}
+carrier_id {
+  canonical_id: 450
+  carrier_name: "Mobiland"
+  carrier_attribute {
+    mccmnc_tuple: "21303"
+  }
+}
+carrier_id {
+  canonical_id: 451
+  carrier_name: "Etisalat"
+  carrier_attribute {
+    mccmnc_tuple: "42402"
+  }
+}
+carrier_id {
+  canonical_id: 452
+  carrier_name: "AWCC"
+  carrier_attribute {
+    mccmnc_tuple: "41201"
+  }
+}
+carrier_id {
+  canonical_id: 453
+  carrier_name: "Roshan"
+  carrier_attribute {
+    mccmnc_tuple: "41220"
+  }
+}
+carrier_id {
+  canonical_id: 454
+  carrier_name: "New1"
+  carrier_attribute {
+    mccmnc_tuple: "41230"
+  }
+}
+carrier_id {
+  canonical_id: 455
+  carrier_name: "Areeba Afghanistan"
+  carrier_attribute {
+    mccmnc_tuple: "41240"
+  }
+}
+carrier_id {
+  canonical_id: 456
+  carrier_name: "Afghan Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "41288"
+  }
+}
+carrier_id {
+  canonical_id: 457
+  carrier_name: "APUA PCS"
+  carrier_attribute {
+    mccmnc_tuple: "344030"
+  }
+}
+carrier_id {
+  canonical_id: 491
+  carrier_name: "Advanced Communications Technologies Pty. Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "50524"
+  }
+}
+carrier_id {
+  canonical_id: 492
+  carrier_name: "Localstar Holding Pty. Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "50588"
+  }
+}
+carrier_id {
+  canonical_id: 493
+  carrier_name: "Setar GSM"
+  carrier_attribute {
+    mccmnc_tuple: "36301"
+  }
+}
+carrier_id {
+  canonical_id: 495
+  carrier_name: "Bakcell Limited Liabil ity Company"
+  carrier_attribute {
+    mccmnc_tuple: "40002"
+  }
+}
+carrier_id {
+  canonical_id: 529
+  carrier_name: "Claro BR"
+  carrier_attribute {
+    mccmnc_tuple: "72405"
+    mccmnc_tuple: "72438"
+  }
+}
+carrier_id {
+  canonical_id: 530
+  carrier_name: "Vivo"
+  carrier_attribute {
+    mccmnc_tuple: "72406"
+    mccmnc_tuple: "72410"
+    mccmnc_tuple: "72411"
+    mccmnc_tuple: "72423"
+  }
+}
+carrier_id {
+  canonical_id: 532
+  carrier_name: "Maxitel MG"
+  carrier_attribute {
+    mccmnc_tuple: "72408"
+  }
+}
+carrier_id {
+  canonical_id: 533
+  carrier_name: "Telepar Cel"
+  carrier_attribute {
+    mccmnc_tuple: "72409"
+  }
+}
+carrier_id {
+  canonical_id: 536
+  carrier_name: "Americel"
+  carrier_attribute {
+    mccmnc_tuple: "72412"
+  }
+}
+carrier_id {
+  canonical_id: 537
+  carrier_name: "Telesp Cel"
+  carrier_attribute {
+    mccmnc_tuple: "72413"
+  }
+}
+carrier_id {
+  canonical_id: 538
+  carrier_name: "Maxitel BA"
+  carrier_attribute {
+    mccmnc_tuple: "72414"
+  }
+}
+carrier_id {
+  canonical_id: 539
+  carrier_name: "Sercomtel"
+  carrier_attribute {
+    mccmnc_tuple: "72415"
+  }
+}
+carrier_id {
+  canonical_id: 540
+  carrier_name: "Brasil Telecom GSM"
+  carrier_attribute {
+    mccmnc_tuple: "72416"
+  }
+}
+carrier_id {
+  canonical_id: 541
+  carrier_name: "Ceterp Cel"
+  carrier_attribute {
+    mccmnc_tuple: "72417"
+  }
+}
+carrier_id {
+  canonical_id: 542
+  carrier_name: "Datora"
+  carrier_attribute {
+    mccmnc_tuple: "72418"
+  }
+}
+carrier_id {
+  canonical_id: 543
+  carrier_name: "Telemig Cel"
+  carrier_attribute {
+    mccmnc_tuple: "72419"
+  }
+}
+carrier_id {
+  canonical_id: 544
+  carrier_name: "Telerj Cel"
+  carrier_attribute {
+    mccmnc_tuple: "72421"
+  }
+}
+carrier_id {
+  canonical_id: 546
+  carrier_name: "Telebrasilia Cel"
+  carrier_attribute {
+    mccmnc_tuple: "72425"
+  }
+}
+carrier_id {
+  canonical_id: 547
+  carrier_name: "Telegoias Cel"
+  carrier_attribute {
+    mccmnc_tuple: "72427"
+  }
+}
+carrier_id {
+  canonical_id: 548
+  carrier_name: "Telemat Cel"
+  carrier_attribute {
+    mccmnc_tuple: "72429"
+  }
+}
+carrier_id {
+  canonical_id: 562
+  carrier_name: "Teleamapa Cel"
+  carrier_attribute {
+    mccmnc_tuple: "72457"
+  }
+}
+carrier_id {
+  canonical_id: 563
+  carrier_name: "Telaima Cel"
+  carrier_attribute {
+    mccmnc_tuple: "72459"
+  }
+}
+carrier_id {
+  canonical_id: 564
+  carrier_name: "Bhutan Telecom Ltd"
+  carrier_attribute {
+    mccmnc_tuple: "40211"
+  }
+}
+carrier_id {
+  canonical_id: 566
+  carrier_name: "Mascom Wireless (Pty) Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "65201"
+  }
+}
+carrier_id {
+  canonical_id: 567
+  carrier_name: "Orange Botswana (Pty) Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "65202"
+  }
+}
+carrier_id {
+  canonical_id: 568
+  carrier_name: "Velcom"
+  carrier_attribute {
+    mccmnc_tuple: "25701"
+  }
+}
+carrier_id {
+  canonical_id: 569
+  carrier_name: "MTS"
+  carrier_attribute {
+    mccmnc_tuple: "25702"
+  }
+}
+carrier_id {
+  canonical_id: 570
+  carrier_name: "Belize Telecommunications Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "70267"
+  }
+}
+carrier_id {
+  canonical_id: 571
+  carrier_name: "International Telecommunications Ltd. (INTELCO)"
+  carrier_attribute {
+    mccmnc_tuple: "70268"
+  }
+}
+carrier_id {
+  canonical_id: 572
+  carrier_name: "Clearnet"
+  carrier_attribute {
+    mccmnc_tuple: "30236"
+  }
+}
+carrier_id {
+  canonical_id: 574
+  carrier_name: "Ice Wireless"
+  carrier_attribute {
+    mccmnc_tuple: "30262"
+  }
+}
+carrier_id {
+  canonical_id: 575
+  carrier_name: "Aliant Mobility"
+  carrier_attribute {
+    mccmnc_tuple: "30263"
+  }
+}
+carrier_id {
+  canonical_id: 576
+  carrier_name: "Bell Mobility"
+  carrier_attribute {
+    mccmnc_tuple: "30264"
+    mccmnc_tuple: "302610"
+    mccmnc_tuple: "302630"
+    mccmnc_tuple: "302640"
+  }
+}
+carrier_id {
+  canonical_id: 577
+  carrier_name: "Tbay Mobility"
+  carrier_attribute {
+    mccmnc_tuple: "302656"
+  }
+}
+carrier_id {
+  canonical_id: 578
+  carrier_name: "MTS Mobility"
+  carrier_attribute {
+    mccmnc_tuple: "302660"
+    mccmnc_tuple: "302370"
+    gid1: "2c"
+    gid1: "4d"
+  }
+}
+carrier_id {
+  canonical_id: 579
+  carrier_name: "CityTel Mobility"
+  carrier_attribute {
+    mccmnc_tuple: "30267"
+  }
+}
+carrier_id {
+  canonical_id: 580
+  carrier_name: "Sask Tel Mobility"
+  carrier_attribute {
+    mccmnc_tuple: "30268"
+    mccmnc_tuple: "302654"
+    mccmnc_tuple: "302680"
+    mccmnc_tuple: "302780"
+  }
+}
+carrier_id {
+  canonical_id: 581
+  carrier_name: "Globalstar"
+  carrier_attribute {
+    mccmnc_tuple: "30271"
+  }
+}
+carrier_id {
+  canonical_id: 624
+  carrier_name: "Colombia Móvil S.A."
+  carrier_attribute {
+    mccmnc_tuple: "732103"
+    mccmnc_tuple: "732111"
+  }
+}
+carrier_id {
+  canonical_id: 625
+  carrier_name: "Telefónica Móviles Colombia S.A."
+  carrier_attribute {
+    mccmnc_tuple: "732123"
+  }
+}
+carrier_id {
+  canonical_id: 626
+  carrier_name: "Avantel"
+  carrier_attribute {
+    mccmnc_tuple: "732130"
+  }
+}
+carrier_id {
+  canonical_id: 627
+  carrier_name: "Instituto Costarricense de Electricidad - ICE"
+  carrier_attribute {
+    mccmnc_tuple: "71201"
+  }
+}
+carrier_id {
+  canonical_id: 655
+  carrier_name: "Barablu Mobile Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "23807"
+  }
+}
+carrier_id {
+  canonical_id: 656
+  carrier_name: "Telia"
+  carrier_attribute {
+    mccmnc_tuple: "23820"
+    mccmnc_tuple: "23830"
+  }
+}
+carrier_id {
+  canonical_id: 657
+  carrier_name: "Tele2"
+  carrier_attribute {
+    mccmnc_tuple: "23877"
+  }
+}
+carrier_id {
+  canonical_id: 658
+  carrier_name: "Orange Dominicana, S.A."
+  carrier_attribute {
+    mccmnc_tuple: "37001"
+  }
+}
+carrier_id {
+  canonical_id: 667
+  carrier_name: "Telia"
+  carrier_attribute {
+    mccmnc_tuple: "24801"
+  }
+}
+carrier_id {
+  canonical_id: 668
+  carrier_name: "Elisa"
+  carrier_attribute {
+    mccmnc_tuple: "24802"
+  }
+}
+carrier_id {
+  canonical_id: 669
+  carrier_name: "Tele2"
+  carrier_attribute {
+    mccmnc_tuple: "24803"
+  }
+}
+carrier_id {
+  canonical_id: 670
+  carrier_name: "OY Top Connect"
+  carrier_attribute {
+    mccmnc_tuple: "24804"
+  }
+}
+carrier_id {
+  canonical_id: 671
+  carrier_name: "AS Bravocom Mobiil"
+  carrier_attribute {
+    mccmnc_tuple: "24805"
+  }
+}
+carrier_id {
+  canonical_id: 672
+  carrier_name: "OY ViaTel"
+  carrier_attribute {
+    mccmnc_tuple: "24806"
+  }
+}
+carrier_id {
+  canonical_id: 673
+  carrier_name: "Televõrgu AS"
+  carrier_attribute {
+    mccmnc_tuple: "24807"
+  }
+}
+carrier_id {
+  canonical_id: 674
+  carrier_name: "Siseministeerium (Ministry of Interior)"
+  carrier_attribute {
+    mccmnc_tuple: "24871"
+  }
+}
+carrier_id {
+  canonical_id: 675
+  carrier_name: "Orange Egypt"
+  carrier_attribute {
+    mccmnc_tuple: "60201"
+  }
+}
+carrier_id {
+  canonical_id: 676
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "60202"
+  }
+}
+carrier_id {
+  canonical_id: 678
+  carrier_name: "Orange"
+  carrier_attribute {
+    mccmnc_tuple: "21403"
+  }
+}
+carrier_id {
+  canonical_id: 679
+  carrier_name: "Yoigo"
+  carrier_attribute {
+    mccmnc_tuple: "21404"
+  }
+}
+carrier_id {
+  canonical_id: 681
+  carrier_name: "ETH MTN"
+  carrier_attribute {
+    mccmnc_tuple: "63601"
+  }
+}
+carrier_id {
+  canonical_id: 682
+  carrier_name: "Finnet Networks Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "24404"
+  }
+}
+carrier_id {
+  canonical_id: 717
+  carrier_name: "Virgin"
+  carrier_attribute {
+    mccmnc_tuple: "23430"
+    mccmnc_tuple: "23438"
+    gid1: "2800000000000000"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "20601"
+    gid1: "28"
+  }
+}
+carrier_id {
+  canonical_id: 718
+  carrier_name: "EE"
+  carrier_attribute {
+    mccmnc_tuple: "23430"
+    imsi_prefix_xpattern: "234300"
+    imsi_prefix_xpattern: "234301"
+    imsi_prefix_xpattern: "234302"
+    imsi_prefix_xpattern: "234303"
+    imsi_prefix_xpattern: "2343040"
+    imsi_prefix_xpattern: "2343042"
+    imsi_prefix_xpattern: "2343043"
+    imsi_prefix_xpattern: "2343044"
+    imsi_prefix_xpattern: "2343045"
+    imsi_prefix_xpattern: "2343046"
+    imsi_prefix_xpattern: "2343047"
+    imsi_prefix_xpattern: "2343048"
+    imsi_prefix_xpattern: "2343049"
+    imsi_prefix_xpattern: "234305"
+    imsi_prefix_xpattern: "234306"
+    imsi_prefix_xpattern: "234307"
+    imsi_prefix_xpattern: "234308"
+    imsi_prefix_xpattern: "234309"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "23433"
+    imsi_prefix_xpattern: "x"
+  }
+}
+carrier_id {
+  canonical_id: 720
+  carrier_name: "Cable and Wireless Guensey Ltd"
+  carrier_attribute {
+    mccmnc_tuple: "23455"
+  }
+}
+carrier_id {
+  canonical_id: 733
+  carrier_name: "Gibtelecom GSM"
+  carrier_attribute {
+    mccmnc_tuple: "26601"
+  }
+}
+carrier_id {
+  canonical_id: 734
+  carrier_name: "Cloud9 Mobile Communications"
+  carrier_attribute {
+    mccmnc_tuple: "26609"
+  }
+}
+carrier_id {
+  canonical_id: 735
+  carrier_name: "Tele Greenland"
+  carrier_attribute {
+    mccmnc_tuple: "29001"
+  }
+}
+carrier_id {
+  canonical_id: 736
+  carrier_name: "Gamcel"
+  carrier_attribute {
+    mccmnc_tuple: "60701"
+  }
+}
+carrier_id {
+  canonical_id: 737
+  carrier_name: "Africell"
+  carrier_attribute {
+    mccmnc_tuple: "60702"
+  }
+}
+carrier_id {
+  canonical_id: 738
+  carrier_name: "Comium Services Ltd"
+  carrier_attribute {
+    mccmnc_tuple: "60703"
+  }
+}
+carrier_id {
+  canonical_id: 739
+  carrier_name: "Orange"
+  carrier_attribute {
+    mccmnc_tuple: "61101"
+  }
+}
+carrier_id {
+  canonical_id: 740
+  carrier_name: "Sotelgui"
+  carrier_attribute {
+    mccmnc_tuple: "61102"
+  }
+}
+carrier_id {
+  canonical_id: 741
+  carrier_name: "Cellcom Guinée SA"
+  carrier_attribute {
+    mccmnc_tuple: "61105"
+  }
+}
+carrier_id {
+  canonical_id: 742
+  carrier_name: "Orange Caraïbe Mobiles"
+  carrier_attribute {
+    mccmnc_tuple: "34001"
+  }
+}
+carrier_id {
+  canonical_id: 743
+  carrier_name: "Outremer Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "34002"
+  }
+}
+carrier_id {
+  canonical_id: 744
+  carrier_name: "Saint Martin et Saint Barthelemy Telcell Sarl"
+  carrier_attribute {
+    mccmnc_tuple: "34003"
+  }
+}
+carrier_id {
+  canonical_id: 745
+  carrier_name: "Bouygues Telecom Caraïbe"
+  carrier_attribute {
+    mccmnc_tuple: "34020"
+  }
+}
+carrier_id {
+  canonical_id: 746
+  carrier_name: "Orange"
+  carrier_attribute {
+    mccmnc_tuple: "62701"
+  }
+}
+carrier_id {
+  canonical_id: 747
+  carrier_name: "COSMOTE"
+  carrier_attribute {
+    mccmnc_tuple: "20201"
+  }
+}
+carrier_id {
+  canonical_id: 749
+  carrier_name: "Info Quest S.A."
+  carrier_attribute {
+    mccmnc_tuple: "20209"
+  }
+}
+carrier_id {
+  canonical_id: 759
+  carrier_name: "3G Radio System/HKCSL3G"
+  carrier_attribute {
+    mccmnc_tuple: "45402"
+  }
+}
+carrier_id {
+  canonical_id: 760
+  carrier_name: "3HK"
+  carrier_attribute {
+    mccmnc_tuple: "45403"
+  }
+}
+carrier_id {
+  canonical_id: 761
+  carrier_name: "SmarTone HK"
+  carrier_attribute {
+    mccmnc_tuple: "45406"
+  }
+}
+carrier_id {
+  canonical_id: 762
+  carrier_name: "MVNO/China Unicom International Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "45407"
+  }
+}
+carrier_id {
+  canonical_id: 763
+  carrier_name: "MVNO/Trident"
+  carrier_attribute {
+    mccmnc_tuple: "45408"
+  }
+}
+carrier_id {
+  canonical_id: 764
+  carrier_name: "MVNO/China Motion Telecom (HK) Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "45409"
+  }
+}
+carrier_id {
+  canonical_id: 765
+  carrier_name: "GSM1800New World PCS Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "45410"
+  }
+}
+carrier_id {
+  canonical_id: 766
+  carrier_name: "MVNO/CHKTL"
+  carrier_attribute {
+    mccmnc_tuple: "45411"
+  }
+}
+carrier_id {
+  canonical_id: 767
+  carrier_name: "中國移動香港 China Mobile HK"
+  carrier_attribute {
+    mccmnc_tuple: "45412"
+    mccmnc_tuple: "45413"
+  }
+}
+carrier_id {
+  canonical_id: 768
+  carrier_name: "3G Radio System/SMT3G"
+  carrier_attribute {
+    mccmnc_tuple: "45415"
+  }
+}
+carrier_id {
+  canonical_id: 769
+  carrier_name: "PCCW"
+  carrier_attribute {
+    mccmnc_tuple: "45416"
+  }
+}
+carrier_id {
+  canonical_id: 770
+  carrier_name: "GSM7800/Hong Kong CSL Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "45418"
+  }
+}
+carrier_id {
+  canonical_id: 772
+  carrier_name: "Public Mobile Networks/Reserved"
+  carrier_attribute {
+    mccmnc_tuple: "45420"
+    mccmnc_tuple: "45421"
+    mccmnc_tuple: "45422"
+    mccmnc_tuple: "45423"
+    mccmnc_tuple: "45424"
+    mccmnc_tuple: "45425"
+    mccmnc_tuple: "45426"
+    mccmnc_tuple: "45427"
+    mccmnc_tuple: "45428"
+    mccmnc_tuple: "45429"
+    mccmnc_tuple: "45430"
+    mccmnc_tuple: "45431"
+    mccmnc_tuple: "45432"
+    mccmnc_tuple: "45433"
+    mccmnc_tuple: "45434"
+    mccmnc_tuple: "45435"
+    mccmnc_tuple: "45436"
+    mccmnc_tuple: "45437"
+    mccmnc_tuple: "45438"
+    mccmnc_tuple: "45439"
+  }
+}
+carrier_id {
+  canonical_id: 773
+  carrier_name: "Claro HN"
+  carrier_attribute {
+    mccmnc_tuple: "708001"
+    mccmnc_tuple: "70801"
+  }
+}
+carrier_id {
+  canonical_id: 787
+  carrier_name: "Telkomsel"
+  carrier_attribute {
+    mccmnc_tuple: "51010"
+  }
+}
+carrier_id {
+  canonical_id: 788
+  carrier_name: "XL/AXIS"
+  carrier_attribute {
+    mccmnc_tuple: "51008"
+    mccmnc_tuple: "51011"
+  }
+}
+carrier_id {
+  canonical_id: 789
+  carrier_name: "Indosat - M3"
+  carrier_attribute {
+    mccmnc_tuple: "51021"
+  }
+}
+carrier_id {
+  canonical_id: 792
+  carrier_name: "3"
+  carrier_attribute {
+    mccmnc_tuple: "27202"
+  }
+}
+carrier_id {
+  canonical_id: 793
+  carrier_name: "Meteor Mobile Communications Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "27203"
+  }
+}
+carrier_id {
+  canonical_id: 794
+  carrier_name: "Eircom"
+  carrier_attribute {
+    mccmnc_tuple: "27207"
+  }
+}
+carrier_id {
+  canonical_id: 795
+  carrier_name: "Clever Communications Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "27209"
+  }
+}
+carrier_id {
+  canonical_id: 796
+  carrier_name: "Partner Communications Co. Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "42501"
+  }
+}
+carrier_id {
+  canonical_id: 797
+  carrier_name: "Cellcom Israel Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "42502"
+  }
+}
+carrier_id {
+  canonical_id: 798
+  carrier_name: "Pelephone Communications Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "42503"
+  }
+}
+carrier_id {
+  canonical_id: 802
+  carrier_name: "Idea"
+  carrier_attribute {
+    mccmnc_tuple: "40404"
+    mccmnc_tuple: "40407"
+    mccmnc_tuple: "40412"
+    mccmnc_tuple: "40414"
+    mccmnc_tuple: "40419"
+    mccmnc_tuple: "40422"
+    mccmnc_tuple: "40424"
+    mccmnc_tuple: "40444"
+    mccmnc_tuple: "40456"
+    mccmnc_tuple: "40478"
+    mccmnc_tuple: "40482"
+    mccmnc_tuple: "40487"
+    mccmnc_tuple: "40489"
+    mccmnc_tuple: "40570"
+    mccmnc_tuple: "40545"
+    mccmnc_tuple: "405799"
+    mccmnc_tuple: "405845"
+    mccmnc_tuple: "405846"
+    mccmnc_tuple: "405848"
+    mccmnc_tuple: "405849"
+    mccmnc_tuple: "405850"
+    mccmnc_tuple: "405852"
+    mccmnc_tuple: "405853"
+  }
+}
+carrier_id {
+  canonical_id: 849
+  carrier_name: "Orange Jordan"
+  carrier_attribute {
+    mccmnc_tuple: "41677"
+  }
+}
+carrier_id {
+  canonical_id: 850
+  carrier_name: "NTT DOCOMO"
+  carrier_attribute {
+    mccmnc_tuple: "44010"
+  }
+}
+carrier_id {
+  canonical_id: 865
+  carrier_name: "Safaricom"
+  carrier_attribute {
+    mccmnc_tuple: "63902"
+  }
+}
+carrier_id {
+  canonical_id: 866
+  carrier_name: "Kencell Communications Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "63903"
+  }
+}
+carrier_id {
+  canonical_id: 867
+  carrier_name: "Bitel GSM"
+  carrier_attribute {
+    mccmnc_tuple: "43701"
+  }
+}
+carrier_id {
+  canonical_id: 868
+  carrier_name: "Mobitel (Cam GSM)"
+  carrier_attribute {
+    mccmnc_tuple: "45601"
+  }
+}
+carrier_id {
+  canonical_id: 869
+  carrier_name: "Smart"
+  carrier_attribute {
+    mccmnc_tuple: "45602"
+    mccmnc_tuple: "45605"
+    mccmnc_tuple: "45606"
+  }
+}
+carrier_id {
+  canonical_id: 870
+  carrier_name: "S Telecom (CDMA) (reserved)"
+  carrier_attribute {
+    mccmnc_tuple: "45603"
+  }
+}
+carrier_id {
+  canonical_id: 871
+  carrier_name: "Camshin (Shinawatra)"
+  carrier_attribute {
+    mccmnc_tuple: "45618"
+  }
+}
+carrier_id {
+  canonical_id: 872
+  carrier_name: "HURI - SNPT"
+  carrier_attribute {
+    mccmnc_tuple: "65401"
+  }
+}
+carrier_id {
+  canonical_id: 892
+  carrier_name: "Telia"
+  carrier_attribute {
+    mccmnc_tuple: "24601"
+  }
+}
+carrier_id {
+  canonical_id: 893
+  carrier_name: "BITĖ"
+  carrier_attribute {
+    mccmnc_tuple: "24602"
+  }
+}
+carrier_id {
+  canonical_id: 894
+  carrier_name: "Tele2"
+  carrier_attribute {
+    mccmnc_tuple: "24603"
+  }
+}
+carrier_id {
+  canonical_id: 895
+  carrier_name: "P&T Luxembourg"
+  carrier_attribute {
+    mccmnc_tuple: "27001"
+  }
+}
+carrier_id {
+  canonical_id: 896
+  carrier_name: "Tango"
+  carrier_attribute {
+    mccmnc_tuple: "27077"
+  }
+}
+carrier_id {
+  canonical_id: 897
+  carrier_name: "Orange"
+  carrier_attribute {
+    mccmnc_tuple: "27099"
+  }
+}
+carrier_id {
+  canonical_id: 898
+  carrier_name: "LMT/Amigo"
+  carrier_attribute {
+    mccmnc_tuple: "24701"
+  }
+}
+carrier_id {
+  canonical_id: 899
+  carrier_name: "Tele2/ZZ"
+  carrier_attribute {
+    mccmnc_tuple: "24702"
+  }
+}
+carrier_id {
+  canonical_id: 900
+  carrier_name: "Telekom Baltija"
+  carrier_attribute {
+    mccmnc_tuple: "24703"
+  }
+}
+carrier_id {
+  canonical_id: 901
+  carrier_name: "Beta Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "24704"
+  }
+}
+carrier_id {
+  canonical_id: 902
+  carrier_name: "Méditélécom"
+  carrier_attribute {
+    mccmnc_tuple: "60400"
+  }
+}
+carrier_id {
+  canonical_id: 903
+  carrier_name: "Ittissalat Al Maghrid"
+  carrier_attribute {
+    mccmnc_tuple: "60401"
+  }
+}
+carrier_id {
+  canonical_id: 904
+  carrier_name: "Orange Moldova GSM"
+  carrier_attribute {
+    mccmnc_tuple: "25901"
+  }
+}
+carrier_id {
+  canonical_id: 905
+  carrier_name: "Moldcell GSM"
+  carrier_attribute {
+    mccmnc_tuple: "25902"
+  }
+}
+carrier_id {
+  canonical_id: 906
+  carrier_name: "Eventis Mobile GSM"
+  carrier_attribute {
+    mccmnc_tuple: "25904"
+  }
+}
+carrier_id {
+  canonical_id: 941
+  carrier_name: "Mobile Telecommunications Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "64901"
+  }
+}
+carrier_id {
+  canonical_id: 942
+  carrier_name: "Powercom Pty Ltd"
+  carrier_attribute {
+    mccmnc_tuple: "64903"
+  }
+}
+carrier_id {
+  canonical_id: 943
+  carrier_name: "OPT Mobilis"
+  carrier_attribute {
+    mccmnc_tuple: "54601"
+  }
+}
+carrier_id {
+  canonical_id: 945
+  carrier_name: "Celtel"
+  carrier_attribute {
+    mccmnc_tuple: "61402"
+  }
+}
+carrier_id {
+  canonical_id: 958
+  carrier_name: "Telenor"
+  carrier_attribute {
+    mccmnc_tuple: "24201"
+  }
+}
+carrier_id {
+  canonical_id: 959
+  carrier_name: "Netcom GSM AS"
+  carrier_attribute {
+    mccmnc_tuple: "24202"
+  }
+}
+carrier_id {
+  canonical_id: 960
+  carrier_name: "Teletopia Mobile Communications AS"
+  carrier_attribute {
+    mccmnc_tuple: "24203"
+  }
+}
+carrier_id {
+  canonical_id: 961
+  carrier_name: "Tele2 Norge AS"
+  carrier_attribute {
+    mccmnc_tuple: "24204"
+  }
+}
+carrier_id {
+  canonical_id: 962
+  carrier_name: "Nepal Telecommunications"
+  carrier_attribute {
+    mccmnc_tuple: "42901"
+    mccmnc_tuple: "42903"
+  }
+}
+carrier_id {
+  canonical_id: 963
+  carrier_name: "Reserved for AMPS MIN based IMSI's"
+  carrier_attribute {
+    mccmnc_tuple: "53000"
+  }
+}
+carrier_id {
+  canonical_id: 965
+  carrier_name: "Teleom New Zealand CDMA Network"
+  carrier_attribute {
+    mccmnc_tuple: "53002"
+  }
+}
+carrier_id {
+  canonical_id: 966
+  carrier_name: "Woosh Wireless - CDMA Network"
+  carrier_attribute {
+    mccmnc_tuple: "53003"
+  }
+}
+carrier_id {
+  canonical_id: 967
+  carrier_name: "TelstraClear - GSM Network"
+  carrier_attribute {
+    mccmnc_tuple: "53004"
+  }
+}
+carrier_id {
+  canonical_id: 968
+  carrier_name: "Spark NZ"
+  carrier_attribute {
+    mccmnc_tuple: "53005"
+  }
+}
+carrier_id {
+  canonical_id: 969
+  carrier_name: "2degrees"
+  carrier_attribute {
+    mccmnc_tuple: "53024"
+  }
+}
+carrier_id {
+  canonical_id: 970
+  carrier_name: "Omantel"
+  carrier_attribute {
+    mccmnc_tuple: "42202"
+    mccmnc_tuple: "42204"
+  }
+}
+carrier_id {
+  canonical_id: 971
+  carrier_name: "Ooredoo"
+  carrier_attribute {
+    mccmnc_tuple: "42203"
+  }
+}
+carrier_id {
+  canonical_id: 973
+  carrier_name: "Cable & Wireless Panama S.A."
+  carrier_attribute {
+    mccmnc_tuple: "71401"
+  }
+}
+carrier_id {
+  canonical_id: 974
+  carrier_name: "movistar (Telefónica Moviles Panama S.A.)"
+  carrier_attribute {
+    mccmnc_tuple: "71402"
+    mccmnc_tuple: "714020"
+  }
+}
+carrier_id {
+  canonical_id: 1008
+  carrier_name: "Société Réunionnaise du Radiotéléphone"
+  carrier_attribute {
+    mccmnc_tuple: "64710"
+  }
+}
+carrier_id {
+  canonical_id: 1010
+  carrier_name: "Telekom Romania"
+  carrier_attribute {
+    mccmnc_tuple: "22603"
+    mccmnc_tuple: "22606"
+    spn: "TELEKOM.RO"
+    spn: "COSMOTE"
+    spn: "frog"
+    spn: "MTV Mobile"
+  }
+}
+carrier_id {
+  canonical_id: 1011
+  carrier_name: "Orange"
+  carrier_attribute {
+    mccmnc_tuple: "22610"
+  }
+}
+carrier_id {
+  canonical_id: 1012
+  carrier_name: "Telenor d.o.o."
+  carrier_attribute {
+    mccmnc_tuple: "22001"
+    mccmnc_tuple: "22002"
+  }
+}
+carrier_id {
+  canonical_id: 1013
+  carrier_name: "Telekom Srbija a.d."
+  carrier_attribute {
+    mccmnc_tuple: "22003"
+  }
+}
+carrier_id {
+  canonical_id: 1014
+  carrier_name: "Vip mobile d.o.o."
+  carrier_attribute {
+    mccmnc_tuple: "22005"
+  }
+}
+carrier_id {
+  canonical_id: 1016
+  carrier_name: "MegaFon"
+  carrier_attribute {
+    mccmnc_tuple: "25002"
+  }
+}
+carrier_id {
+  canonical_id: 1018
+  carrier_name: "Sibchallenge"
+  carrier_attribute {
+    mccmnc_tuple: "25004"
+  }
+}
+carrier_id {
+  canonical_id: 1020
+  carrier_name: "BM Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "25007"
+  }
+}
+carrier_id {
+  canonical_id: 1021
+  carrier_name: "Don Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "25010"
+  }
+}
+carrier_id {
+  canonical_id: 1022
+  carrier_name: "Orensot"
+  carrier_attribute {
+    mccmnc_tuple: "25011"
+  }
+}
+carrier_id {
+  canonical_id: 1024
+  carrier_name: "Kuban GSM"
+  carrier_attribute {
+    mccmnc_tuple: "25013"
+  }
+}
+carrier_id {
+  canonical_id: 1025
+  carrier_name: "New Telephone Company"
+  carrier_attribute {
+    mccmnc_tuple: "25016"
+  }
+}
+carrier_id {
+  canonical_id: 1027
+  carrier_name: "Volgograd Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "25019"
+  }
+}
+carrier_id {
+  canonical_id: 1028
+  carrier_name: "Tele2"
+  carrier_attribute {
+    mccmnc_tuple: "25003"
+    mccmnc_tuple: "25005"
+    mccmnc_tuple: "25012"
+    mccmnc_tuple: "25017"
+    mccmnc_tuple: "25020"
+    mccmnc_tuple: "25039"
+  }
+}
+carrier_id {
+  canonical_id: 1029
+  carrier_name: "Extel"
+  carrier_attribute {
+    mccmnc_tuple: "25028"
+  }
+}
+carrier_id {
+  canonical_id: 1031
+  carrier_name: "Stuvtelesot"
+  carrier_attribute {
+    mccmnc_tuple: "25044"
+  }
+}
+carrier_id {
+  canonical_id: 1074
+  carrier_name: "Comium (Sierra Leone) Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "61904"
+  }
+}
+carrier_id {
+  canonical_id: 1075
+  carrier_name: "Lintel (Sierra Leone) Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "61905"
+  }
+}
+carrier_id {
+  canonical_id: 1076
+  carrier_name: "Mobitel"
+  carrier_attribute {
+    mccmnc_tuple: "61925"
+  }
+}
+carrier_id {
+  canonical_id: 1077
+  carrier_name: "Datatel (SL) Ltd GSM"
+  carrier_attribute {
+    mccmnc_tuple: "61940"
+  }
+}
+carrier_id {
+  canonical_id: 1082
+  carrier_name: "Golis Telecommunications Company"
+  carrier_attribute {
+    mccmnc_tuple: "63730"
+  }
+}
+carrier_id {
+  canonical_id: 1083
+  carrier_name: "Telesur"
+  carrier_attribute {
+    mccmnc_tuple: "74602"
+  }
+}
+carrier_id {
+  canonical_id: 1084
+  carrier_name: "Digicel"
+  carrier_attribute {
+    mccmnc_tuple: "74603"
+  }
+}
+carrier_id {
+  canonical_id: 1085
+  carrier_name: "Intelsur"
+  carrier_attribute {
+    mccmnc_tuple: "74604"
+  }
+}
+carrier_id {
+  canonical_id: 1086
+  carrier_name: "Companhia Santomese de Telecomunicaçôes"
+  carrier_attribute {
+    mccmnc_tuple: "62601"
+  }
+}
+carrier_id {
+  canonical_id: 1087
+  carrier_name: "Digicel, S.A. de C.V."
+  carrier_attribute {
+    mccmnc_tuple: "70602"
+  }
+}
+carrier_id {
+  canonical_id: 1088
+  carrier_name: "Syriatel"
+  carrier_attribute {
+    mccmnc_tuple: "41701"
+  }
+}
+carrier_id {
+  canonical_id: 1089
+  carrier_name: "Spacetel Syria"
+  carrier_attribute {
+    mccmnc_tuple: "41702"
+  }
+}
+carrier_id {
+  canonical_id: 1090
+  carrier_name: "Syrian Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "41709"
+  }
+}
+carrier_id {
+  canonical_id: 1091
+  carrier_name: "Swazi MTN"
+  carrier_attribute {
+    mccmnc_tuple: "65310"
+  }
+}
+carrier_id {
+  canonical_id: 1092
+  carrier_name: "IslandCom Communications Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "376352"
+  }
+}
+carrier_id {
+  canonical_id: 1093
+  carrier_name: "Celtel"
+  carrier_attribute {
+    mccmnc_tuple: "62201"
+  }
+}
+carrier_id {
+  canonical_id: 1094
+  carrier_name: "Tchad Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "62202"
+  }
+}
+carrier_id {
+  canonical_id: 1095
+  carrier_name: "Togo Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "61501"
+  }
+}
+carrier_id {
+  canonical_id: 1096
+  carrier_name: "CAT CDMA"
+  carrier_attribute {
+    mccmnc_tuple: "52000"
+  }
+}
+carrier_id {
+  canonical_id: 1097
+  carrier_name: "AIS GSM"
+  carrier_attribute {
+    mccmnc_tuple: "52001"
+  }
+}
+carrier_id {
+  canonical_id: 1098
+  carrier_name: "AIS"
+  carrier_attribute {
+    mccmnc_tuple: "52003"
+  }
+}
+carrier_id {
+  canonical_id: 1136
+  carrier_name: "Iridium Satellite, LLC (GMSS)"
+  carrier_attribute {
+    mccmnc_tuple: "90103"
+  }
+}
+carrier_id {
+  canonical_id: 1137
+  carrier_name: "Globalstar"
+  carrier_attribute {
+    mccmnc_tuple: "90104"
+  }
+}
+carrier_id {
+  canonical_id: 1138
+  carrier_name: "Thuraya RMSS Network"
+  carrier_attribute {
+    mccmnc_tuple: "90105"
+  }
+}
+carrier_id {
+  canonical_id: 1139
+  carrier_name: "Thuraya Satellite Telecommunications Company"
+  carrier_attribute {
+    mccmnc_tuple: "90106"
+  }
+}
+carrier_id {
+  canonical_id: 1166
+  carrier_name: "Consolidated Telcom"
+  carrier_attribute {
+    mccmnc_tuple: "310060"
+  }
+}
+carrier_id {
+  canonical_id: 1168
+  carrier_name: "Corr Wireless Communications LLC"
+  carrier_attribute {
+    mccmnc_tuple: "310080"
+  }
+}
+carrier_id {
+  canonical_id: 1170
+  carrier_name: "New Mexico RSA 4 East Ltd. Partnership"
+  carrier_attribute {
+    mccmnc_tuple: "310100"
+  }
+}
+carrier_id {
+  canonical_id: 1183
+  carrier_name: "High Plains Midwest LLC, dba Wetlink Communications"
+  carrier_attribute {
+    mccmnc_tuple: "310340"
+  }
+}
+carrier_id {
+  canonical_id: 1184
+  carrier_name: "Mohave Cellular L.P."
+  carrier_attribute {
+    mccmnc_tuple: "310350"
+  }
+}
+carrier_id {
+  canonical_id: 1185
+  carrier_name: "Cellular Network Partnership dba Pioneer Cellular"
+  carrier_attribute {
+    mccmnc_tuple: "310360"
+  }
+}
+carrier_id {
+  canonical_id: 1186
+  carrier_name: "Guamcell Cellular and Paging"
+  carrier_attribute {
+    mccmnc_tuple: "310370"
+  }
+}
+carrier_id {
+  canonical_id: 1187
+  carrier_name: "AT&T"
+  carrier_attribute {
+    mccmnc_tuple: "310030"
+    mccmnc_tuple: "310070"
+    mccmnc_tuple: "310170"
+    mccmnc_tuple: "310280"
+    mccmnc_tuple: "310380"
+    mccmnc_tuple: "310410"
+    mccmnc_tuple: "310560"
+    mccmnc_tuple: "310680"
+    mccmnc_tuple: "310950"
+    mccmnc_tuple: "311180"
+  }
+}
+carrier_id {
+  canonical_id: 1188
+  carrier_name: "TX-11 Acquistion LLC"
+  carrier_attribute {
+    mccmnc_tuple: "310390"
+  }
+}
+carrier_id {
+  canonical_id: 1189
+  carrier_name: "Wave Runner LLC"
+  carrier_attribute {
+    mccmnc_tuple: "310400"
+    mccmnc_tuple: "311250"
+  }
+}
+carrier_id {
+  canonical_id: 1190
+  carrier_name: "Cincinnati Bell Wireless LLC"
+  carrier_attribute {
+    mccmnc_tuple: "310420"
+  }
+}
+carrier_id {
+  canonical_id: 1191
+  carrier_name: "Alaska Digitel LLC"
+  carrier_attribute {
+    mccmnc_tuple: "310430"
+  }
+}
+carrier_id {
+  canonical_id: 1192
+  carrier_name: "Numerex Corp."
+  carrier_attribute {
+    mccmnc_tuple: "310440"
+  }
+}
+carrier_id {
+  canonical_id: 1193
+  carrier_name: "North East Cellular Inc."
+  carrier_attribute {
+    mccmnc_tuple: "310450"
+  }
+}
+carrier_id {
+  canonical_id: 1194
+  carrier_name: "TMP Corporation"
+  carrier_attribute {
+    mccmnc_tuple: "310460"
+  }
+}
+carrier_id {
+  canonical_id: 1195
+  carrier_name: "Choice Phone LLC"
+  carrier_attribute {
+    mccmnc_tuple: "310480"
+    mccmnc_tuple: "311120"
+  }
+}
+carrier_id {
+  canonical_id: 1196
+  carrier_name: "Public Service Cellular, Inc."
+  carrier_attribute {
+    mccmnc_tuple: "310500"
+  }
+}
+carrier_id {
+  canonical_id: 1197
+  carrier_name: "Airtel Wireless LLC"
+  carrier_attribute {
+    mccmnc_tuple: "310510"
+  }
+}
+carrier_id {
+  canonical_id: 1198
+  carrier_name: "VeriSign"
+  carrier_attribute {
+    mccmnc_tuple: "310520"
+  }
+}
+carrier_id {
+  canonical_id: 1199
+  carrier_name: "Oklahoma Western Telephone Company"
+  carrier_attribute {
+    mccmnc_tuple: "310540"
+  }
+}
+carrier_id {
+  canonical_id: 1234
+  carrier_name: "UBET Wireless"
+  carrier_attribute {
+    mccmnc_tuple: "310960"
+  }
+}
+carrier_id {
+  canonical_id: 1235
+  carrier_name: "Globalstar USA"
+  carrier_attribute {
+    mccmnc_tuple: "310970"
+  }
+}
+carrier_id {
+  canonical_id: 1236
+  carrier_name: "Mid-Tex Cellular Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "311000"
+  }
+}
+carrier_id {
+  canonical_id: 1237
+  carrier_name: "Chariton Valley Communications Corp., Inc."
+  carrier_attribute {
+    mccmnc_tuple: "311010"
+  }
+}
+carrier_id {
+  canonical_id: 1238
+  carrier_name: "Missouri RSA No. 5 Partnership"
+  carrier_attribute {
+    mccmnc_tuple: "311020"
+  }
+}
+carrier_id {
+  canonical_id: 1239
+  carrier_name: "Indigo Wireless, Inc."
+  carrier_attribute {
+    mccmnc_tuple: "311030"
+  }
+}
+carrier_id {
+  canonical_id: 1240
+  carrier_name: "Commet Wireless, LLC"
+  carrier_attribute {
+    mccmnc_tuple: "311040"
+  }
+}
+carrier_id {
+  canonical_id: 1241
+  carrier_name: "Thumb Cellular Limited Partnership"
+  carrier_attribute {
+    mccmnc_tuple: "311050"
+  }
+}
+carrier_id {
+  canonical_id: 1242
+  carrier_name: "Space Data Corporation"
+  carrier_attribute {
+    mccmnc_tuple: "311060"
+  }
+}
+carrier_id {
+  canonical_id: 1243
+  carrier_name: "Easterbrooke Cellular Corporation"
+  carrier_attribute {
+    mccmnc_tuple: "311070"
+  }
+}
+carrier_id {
+  canonical_id: 1244
+  carrier_name: "Pine Telephone Company dba Pine Cellular"
+  carrier_attribute {
+    mccmnc_tuple: "311080"
+  }
+}
+carrier_id {
+  canonical_id: 1245
+  carrier_name: "Siouxland PCS"
+  carrier_attribute {
+    mccmnc_tuple: "311090"
+  }
+}
+carrier_id {
+  canonical_id: 1246
+  carrier_name: "Alltel Communications Inc."
+  carrier_attribute {
+    mccmnc_tuple: "311110"
+    mccmnc_tuple: "311270"
+    mccmnc_tuple: "311271"
+    mccmnc_tuple: "311272"
+    mccmnc_tuple: "311273"
+    mccmnc_tuple: "311274"
+    mccmnc_tuple: "311275"
+    mccmnc_tuple: "311276"
+    mccmnc_tuple: "311277"
+    mccmnc_tuple: "311278"
+    mccmnc_tuple: "311279"
+  }
+}
+carrier_id {
+  canonical_id: 1247
+  carrier_name: "MBO Wireless Inc./Cross Telephone Company"
+  carrier_attribute {
+    mccmnc_tuple: "311140"
+  }
+}
+carrier_id {
+  canonical_id: 1248
+  carrier_name: "Wilkes Cellular Inc."
+  carrier_attribute {
+    mccmnc_tuple: "311150"
+  }
+}
+carrier_id {
+  canonical_id: 1261
+  carrier_name: "Commnet Wireless LLC"
+  carrier_attribute {
+    mccmnc_tuple: "311320"
+  }
+}
+carrier_id {
+  canonical_id: 1262
+  carrier_name: "Bag Tussel Wireless LLC"
+  carrier_attribute {
+    mccmnc_tuple: "311330"
+  }
+}
+carrier_id {
+  canonical_id: 1263
+  carrier_name: "Illinois Valley Cellular"
+  carrier_attribute {
+    mccmnc_tuple: "311340"
+  }
+}
+carrier_id {
+  canonical_id: 1264
+  carrier_name: "Torrestar Networks Inc"
+  carrier_attribute {
+    mccmnc_tuple: "311350"
+  }
+}
+carrier_id {
+  canonical_id: 1265
+  carrier_name: "Stelera Wireless LLC"
+  carrier_attribute {
+    mccmnc_tuple: "311360"
+  }
+}
+carrier_id {
+  canonical_id: 1299
+  carrier_name: "Mobifone"
+  carrier_attribute {
+    mccmnc_tuple: "45201"
+  }
+}
+carrier_id {
+  canonical_id: 1300
+  carrier_name: "Vinaphone"
+  carrier_attribute {
+    mccmnc_tuple: "45202"
+  }
+}
+carrier_id {
+  canonical_id: 1301
+  carrier_name: "SMILE"
+  carrier_attribute {
+    mccmnc_tuple: "54101"
+  }
+}
+carrier_id {
+  canonical_id: 1302
+  carrier_name: "Telecom Samoa Cellular Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "54901"
+  }
+}
+carrier_id {
+  canonical_id: 1307
+  carrier_name: "Sentech (Pty) Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "65506"
+  }
+}
+carrier_id {
+  canonical_id: 1308
+  carrier_name: "Cell C (Pty) Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "65507"
+  }
+}
+carrier_id {
+  canonical_id: 1309
+  carrier_name: "Mobile Telephone Networks"
+  carrier_attribute {
+    mccmnc_tuple: "65510"
+  }
+}
+carrier_id {
+  canonical_id: 1310
+  carrier_name: "SAPS Gauteng"
+  carrier_attribute {
+    mccmnc_tuple: "65511"
+  }
+}
+carrier_id {
+  canonical_id: 1311
+  carrier_name: "Cape Town Metropolitan Council"
+  carrier_attribute {
+    mccmnc_tuple: "65521"
+  }
+}
+carrier_id {
+  canonical_id: 1312
+  carrier_name: "Bokamoso Consortium"
+  carrier_attribute {
+    mccmnc_tuple: "65530"
+  }
+}
+carrier_id {
+  canonical_id: 1313
+  carrier_name: "Karabo Telecoms (Pty) Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "65531"
+  }
+}
+carrier_id {
+  canonical_id: 1314
+  carrier_name: "Ilizwi Telecommunications"
+  carrier_attribute {
+    mccmnc_tuple: "65532"
+  }
+}
+carrier_id {
+  canonical_id: 1315
+  carrier_name: "Thinta Thinta Telecommunications"
+  carrier_attribute {
+    mccmnc_tuple: "65533"
+  }
+}
+carrier_id {
+  canonical_id: 1316
+  carrier_name: "Bokone Telecoms"
+  carrier_attribute {
+    mccmnc_tuple: "65534"
+  }
+}
+carrier_id {
+  canonical_id: 1317
+  carrier_name: "Kingdom Communications"
+  carrier_attribute {
+    mccmnc_tuple: "65535"
+  }
+}
+carrier_id {
+  canonical_id: 1318
+  carrier_name: "Amatole Telecommunication Services"
+  carrier_attribute {
+    mccmnc_tuple: "65536"
+  }
+}
+carrier_id {
+  canonical_id: 1319
+  carrier_name: "Celtel Zambia Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "64501"
+  }
+}
+carrier_id {
+  canonical_id: 1320
+  carrier_name: "Telecel Zambia Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "64502"
+  }
+}
+carrier_id {
+  canonical_id: 1321
+  carrier_name: "Zamtel"
+  carrier_attribute {
+    mccmnc_tuple: "64503"
+  }
+}
+carrier_id {
+  canonical_id: 1322
+  carrier_name: "Net One"
+  carrier_attribute {
+    mccmnc_tuple: "64801"
+  }
+}
+carrier_id {
+  canonical_id: 1325
+  carrier_name: "Cable & Wireless (Antigua)"
+  carrier_attribute {
+    mccmnc_tuple: "344920"
+  }
+}
+carrier_id {
+  canonical_id: 1326
+  carrier_name: "AT&T Wireless (Antigua)"
+  carrier_attribute {
+    mccmnc_tuple: "344930"
+  }
+}
+carrier_id {
+  canonical_id: 1327
+  carrier_name: "Weblinks Limited"
+  carrier_attribute {
+    mccmnc_tuple: "365010"
+  }
+}
+carrier_id {
+  canonical_id: 1328
+  carrier_name: "Telekom Albania"
+  carrier_attribute {
+    mccmnc_tuple: "27601"
+    gid1: "01"
+  }
+}
+carrier_id {
+  canonical_id: 1329
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "27602"
+    spn: "VODAFONE AL"
+  }
+}
+carrier_id {
+  canonical_id: 1330
+  carrier_name: "Eagle Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "27603"
+  }
+}
+carrier_id {
+  canonical_id: 1331
+  carrier_name: "TELCELL GSM"
+  carrier_attribute {
+    mccmnc_tuple: "36251"
+  }
+}
+carrier_id {
+  canonical_id: 1332
+  carrier_name: "CT GSM"
+  carrier_attribute {
+    mccmnc_tuple: "36269"
+  }
+}
+carrier_id {
+  canonical_id: 1333
+  carrier_name: "SETEL GSM"
+  carrier_attribute {
+    mccmnc_tuple: "36291"
+  }
+}
+carrier_id {
+  canonical_id: 1334
+  carrier_name: "Unitel"
+  carrier_attribute {
+    mccmnc_tuple: "63102"
+  }
+}
+carrier_id {
+  canonical_id: 1335
+  carrier_name: "Compañía de Radiocomunicaciones Moviles S.A."
+  carrier_attribute {
+    mccmnc_tuple: "722010"
+  }
+}
+carrier_id {
+  canonical_id: 1336
+  carrier_name: "Nextel Argentina srl"
+  carrier_attribute {
+    mccmnc_tuple: "722020"
+  }
+}
+carrier_id {
+  canonical_id: 1337
+  carrier_name: "Telefonica Comunicaciones Personales S.A."
+  carrier_attribute {
+    mccmnc_tuple: "722070"
+    mccmnc_tuple: "72207"
+  }
+}
+carrier_id {
+  canonical_id: 1338
+  carrier_name: "Claro AR"
+  carrier_attribute {
+    mccmnc_tuple: "722310"
+  }
+}
+carrier_id {
+  canonical_id: 1339
+  carrier_name: "Compañía de Telefonos del Interior Norte S.A."
+  carrier_attribute {
+    mccmnc_tuple: "722320"
+  }
+}
+carrier_id {
+  canonical_id: 1340
+  carrier_name: "Compañía de Telefonos del Interior S.A."
+  carrier_attribute {
+    mccmnc_tuple: "722330"
+  }
+}
+carrier_id {
+  canonical_id: 1341
+  carrier_name: "Telecom Personal S.A."
+  carrier_attribute {
+    mccmnc_tuple: "722340"
+    mccmnc_tuple: "72234"
+    mccmnc_tuple: "722341"
+  }
+}
+carrier_id {
+  canonical_id: 1343
+  carrier_name: "Telefonica Austria"
+  carrier_attribute {
+    mccmnc_tuple: "23208"
+  }
+}
+carrier_id {
+  canonical_id: 1344
+  carrier_name: "Drei"
+  carrier_attribute {
+    mccmnc_tuple: "23205"
+    mccmnc_tuple: "23210"
+  }
+}
+carrier_id {
+  canonical_id: 1345
+  carrier_name: "Telstra"
+  carrier_attribute {
+    mccmnc_tuple: "50501"
+    mccmnc_tuple: "50511"
+    mccmnc_tuple: "50571"
+    mccmnc_tuple: "50572"
+  }
+}
+carrier_id {
+  canonical_id: 1346
+  carrier_name: "Department of Defence"
+  carrier_attribute {
+    mccmnc_tuple: "50504"
+  }
+}
+carrier_id {
+  canonical_id: 1347
+  carrier_name: "The Ozitel Network Pty. Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "50505"
+  }
+}
+carrier_id {
+  canonical_id: 1348
+  carrier_name: "Hutchison 3G Australia Pty. Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "50506"
+  }
+}
+carrier_id {
+  canonical_id: 1349
+  carrier_name: "One.Tel GSM 1800 Pty. Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "50508"
+    mccmnc_tuple: "50599"
+  }
+}
+carrier_id {
+  canonical_id: 1350
+  carrier_name: "Airnet Commercial Australia Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "50509"
+  }
+}
+carrier_id {
+  canonical_id: 1351
+  carrier_name: "Hutchison Telecommunications (Australia) Pty. Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "50512"
+  }
+}
+carrier_id {
+  canonical_id: 1352
+  carrier_name: "AAPT Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "50514"
+  }
+}
+carrier_id {
+  canonical_id: 1353
+  carrier_name: "3GIS Pty Ltd. (Telstra & Hutchison 3G)"
+  carrier_attribute {
+    mccmnc_tuple: "50515"
+  }
+}
+carrier_id {
+  canonical_id: 1354
+  carrier_name: "Azercell Limited Liability Joint Venture"
+  carrier_attribute {
+    mccmnc_tuple: "40001"
+  }
+}
+carrier_id {
+  canonical_id: 1355
+  carrier_name: "Catel JV"
+  carrier_attribute {
+    mccmnc_tuple: "40003"
+  }
+}
+carrier_id {
+  canonical_id: 1356
+  carrier_name: "Azerphone LLC"
+  carrier_attribute {
+    mccmnc_tuple: "40004"
+  }
+}
+carrier_id {
+  canonical_id: 1357
+  carrier_name: "Eronet Mobile Communications Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "21803"
+  }
+}
+carrier_id {
+  canonical_id: 1358
+  carrier_name: "MOBI'S (Mobilina Srpske)"
+  carrier_attribute {
+    mccmnc_tuple: "21805"
+  }
+}
+carrier_id {
+  canonical_id: 1359
+  carrier_name: "GSMBIH"
+  carrier_attribute {
+    mccmnc_tuple: "21890"
+  }
+}
+carrier_id {
+  canonical_id: 1360
+  carrier_name: "Cable & Wireless (Barbados) Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "342600"
+  }
+}
+carrier_id {
+  canonical_id: 1361
+  carrier_name: "Sunbeach Communications"
+  carrier_attribute {
+    mccmnc_tuple: "342820"
+  }
+}
+carrier_id {
+  canonical_id: 1362
+  carrier_name: "GramenPhone"
+  carrier_attribute {
+    mccmnc_tuple: "47001"
+  }
+}
+carrier_id {
+  canonical_id: 1363
+  carrier_name: "Aktel"
+  carrier_attribute {
+    mccmnc_tuple: "47002"
+  }
+}
+carrier_id {
+  canonical_id: 1364
+  carrier_name: "Mobile 2000"
+  carrier_attribute {
+    mccmnc_tuple: "47003"
+  }
+}
+carrier_id {
+  canonical_id: 1365
+  carrier_name: "Proximus"
+  carrier_attribute {
+    mccmnc_tuple: "20601"
+  }
+}
+carrier_id {
+  canonical_id: 1366
+  carrier_name: "Orange"
+  carrier_attribute {
+    mccmnc_tuple: "20610"
+  }
+}
+carrier_id {
+  canonical_id: 1367
+  carrier_name: "BASE"
+  carrier_attribute {
+    mccmnc_tuple: "20620"
+  }
+}
+carrier_id {
+  canonical_id: 1368
+  carrier_name: "Orange"
+  carrier_attribute {
+    mccmnc_tuple: "61302"
+  }
+}
+carrier_id {
+  canonical_id: 1369
+  carrier_name: "Telecel"
+  carrier_attribute {
+    mccmnc_tuple: "61303"
+  }
+}
+carrier_id {
+  canonical_id: 1370
+  carrier_name: "Mtel"
+  carrier_attribute {
+    mccmnc_tuple: "28401"
+  }
+}
+carrier_id {
+  canonical_id: 1371
+  carrier_name: "Telenor"
+  carrier_attribute {
+    mccmnc_tuple: "28405"
+  }
+}
+carrier_id {
+  canonical_id: 1372
+  carrier_name: "Batelco"
+  carrier_attribute {
+    mccmnc_tuple: "42601"
+    mccmnc_tuple: "42605"
+  }
+}
+carrier_id {
+  canonical_id: 1373
+  carrier_name: "Spacetel Burundi"
+  carrier_attribute {
+    mccmnc_tuple: "64201"
+  }
+}
+carrier_id {
+  canonical_id: 1374
+  carrier_name: "Safaris"
+  carrier_attribute {
+    mccmnc_tuple: "64202"
+  }
+}
+carrier_id {
+  canonical_id: 1375
+  carrier_name: "Telecel Burundi Company"
+  carrier_attribute {
+    mccmnc_tuple: "64203"
+  }
+}
+carrier_id {
+  canonical_id: 1376
+  carrier_name: "Libercom"
+  carrier_attribute {
+    mccmnc_tuple: "61601"
+  }
+}
+carrier_id {
+  canonical_id: 1377
+  carrier_name: "Telecel"
+  carrier_attribute {
+    mccmnc_tuple: "61602"
+  }
+}
+carrier_id {
+  canonical_id: 1378
+  carrier_name: "Spacetel Benin"
+  carrier_attribute {
+    mccmnc_tuple: "61603"
+  }
+}
+carrier_id {
+  canonical_id: 1379
+  carrier_name: "DST Com"
+  carrier_attribute {
+    mccmnc_tuple: "52811"
+  }
+}
+carrier_id {
+  canonical_id: 1380
+  carrier_name: "Nuevatel S.A."
+  carrier_attribute {
+    mccmnc_tuple: "73601"
+  }
+}
+carrier_id {
+  canonical_id: 1381
+  carrier_name: "ENTEL S.A."
+  carrier_attribute {
+    mccmnc_tuple: "73602"
+  }
+}
+carrier_id {
+  canonical_id: 1382
+  carrier_name: "Telecel S.A."
+  carrier_attribute {
+    mccmnc_tuple: "73603"
+  }
+}
+carrier_id {
+  canonical_id: 1383
+  carrier_name: "Nextel"
+  carrier_attribute {
+    mccmnc_tuple: "72400"
+    mccmnc_tuple: "72439"
+  }
+}
+carrier_id {
+  canonical_id: 1384
+  carrier_name: "CRT Cellular"
+  carrier_attribute {
+    mccmnc_tuple: "72401"
+  }
+}
+carrier_id {
+  canonical_id: 1385
+  carrier_name: "TIM"
+  carrier_attribute {
+    mccmnc_tuple: "72402"
+    mccmnc_tuple: "72403"
+    mccmnc_tuple: "72404"
+  }
+}
+carrier_id {
+  canonical_id: 1388
+  carrier_name: "Sercontel Cel"
+  carrier_attribute {
+    mccmnc_tuple: "72407"
+  }
+}
+carrier_id {
+  canonical_id: 1389
+  carrier_name: "Oi"
+  carrier_attribute {
+    mccmnc_tuple: "72430"
+    mccmnc_tuple: "72431"
+  }
+}
+carrier_id {
+  canonical_id: 1390
+  carrier_name: "CTBC Celular"
+  carrier_attribute {
+    mccmnc_tuple: "72432"
+    mccmnc_tuple: "72433"
+    mccmnc_tuple: "72434"
+  }
+}
+carrier_id {
+  canonical_id: 1391
+  carrier_name: "Telebahia Cel"
+  carrier_attribute {
+    mccmnc_tuple: "72435"
+  }
+}
+carrier_id {
+  canonical_id: 1392
+  carrier_name: "Telergipe Cel"
+  carrier_attribute {
+    mccmnc_tuple: "72437"
+  }
+}
+carrier_id {
+  canonical_id: 1394
+  carrier_name: "Telpe Cel"
+  carrier_attribute {
+    mccmnc_tuple: "72441"
+  }
+}
+carrier_id {
+  canonical_id: 1395
+  carrier_name: "Telepisa Cel"
+  carrier_attribute {
+    mccmnc_tuple: "72443"
+  }
+}
+carrier_id {
+  canonical_id: 1396
+  carrier_name: "Telpa Cel"
+  carrier_attribute {
+    mccmnc_tuple: "72445"
+  }
+}
+carrier_id {
+  canonical_id: 1397
+  carrier_name: "Telern Cel"
+  carrier_attribute {
+    mccmnc_tuple: "72447"
+  }
+}
+carrier_id {
+  canonical_id: 1398
+  carrier_name: "Teleceara Cel"
+  carrier_attribute {
+    mccmnc_tuple: "72448"
+  }
+}
+carrier_id {
+  canonical_id: 1399
+  carrier_name: "Telma Cel"
+  carrier_attribute {
+    mccmnc_tuple: "72451"
+  }
+}
+carrier_id {
+  canonical_id: 1400
+  carrier_name: "Telepara Cel"
+  carrier_attribute {
+    mccmnc_tuple: "72453"
+  }
+}
+carrier_id {
+  canonical_id: 1401
+  carrier_name: "Teleamazon Cel"
+  carrier_attribute {
+    mccmnc_tuple: "72455"
+  }
+}
+carrier_id {
+  canonical_id: 1402
+  carrier_name: "B-Mobile of Bhutan Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "40217"
+  }
+}
+carrier_id {
+  canonical_id: 1403
+  carrier_name: "Rogers"
+  carrier_attribute {
+    mccmnc_tuple: "302720"
+    mccmnc_tuple: "30272"
+  }
+}
+carrier_id {
+  canonical_id: 1404
+  carrier_name: "TELUS Mobility"
+  carrier_attribute {
+    mccmnc_tuple: "302220"
+    mccmnc_tuple: "302221"
+    mccmnc_tuple: "30222"
+    mccmnc_tuple: "30286"
+  }
+}
+carrier_id {
+  canonical_id: 1405
+  carrier_name: "Vodacom Congo RDC sprl"
+  carrier_attribute {
+    mccmnc_tuple: "63001"
+  }
+}
+carrier_id {
+  canonical_id: 1406
+  carrier_name: "Supercell Sprl"
+  carrier_attribute {
+    mccmnc_tuple: "63005"
+  }
+}
+carrier_id {
+  canonical_id: 1407
+  carrier_name: "Orange RDC"
+  carrier_attribute {
+    mccmnc_tuple: "63086"
+  }
+}
+carrier_id {
+  canonical_id: 1408
+  carrier_name: "Centrafrique Telecom Plus (CTP)"
+  carrier_attribute {
+    mccmnc_tuple: "62301"
+  }
+}
+carrier_id {
+  canonical_id: 1409
+  carrier_name: "Telecel Centrafrique (TC)"
+  carrier_attribute {
+    mccmnc_tuple: "62302"
+  }
+}
+carrier_id {
+  canonical_id: 1410
+  carrier_name: "Orange Centrafricaine"
+  carrier_attribute {
+    mccmnc_tuple: "62303"
+  }
+}
+carrier_id {
+  canonical_id: 1411
+  carrier_name: "Celtel"
+  carrier_attribute {
+    mccmnc_tuple: "62901"
+  }
+}
+carrier_id {
+  canonical_id: 1412
+  carrier_name: "Libertis Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "62910"
+  }
+}
+carrier_id {
+  canonical_id: 1413
+  carrier_name: "Sunrise"
+  carrier_attribute {
+    mccmnc_tuple: "22802"
+    mccmnc_tuple: "22812"
+  }
+}
+carrier_id {
+  canonical_id: 1414
+  carrier_name: "Salt"
+  carrier_attribute {
+    mccmnc_tuple: "22803"
+  }
+}
+carrier_id {
+  canonical_id: 1415
+  carrier_name: "Comfone AG"
+  carrier_attribute {
+    mccmnc_tuple: "22805"
+  }
+}
+carrier_id {
+  canonical_id: 1416
+  carrier_name: "SBB AG"
+  carrier_attribute {
+    mccmnc_tuple: "22806"
+  }
+}
+carrier_id {
+  canonical_id: 1417
+  carrier_name: "IN&Phone SA"
+  carrier_attribute {
+    mccmnc_tuple: "22807"
+  }
+}
+carrier_id {
+  canonical_id: 1418
+  carrier_name: "Tele2 Telecommunications AG"
+  carrier_attribute {
+    mccmnc_tuple: "22808"
+  }
+}
+carrier_id {
+  canonical_id: 1419
+  carrier_name: "Bebbicell AG"
+  carrier_attribute {
+    mccmnc_tuple: "22851"
+  }
+}
+carrier_id {
+  canonical_id: 1420
+  carrier_name: "Atlantique Cellulaire"
+  carrier_attribute {
+    mccmnc_tuple: "61202"
+  }
+}
+carrier_id {
+  canonical_id: 1421
+  carrier_name: "Orange Côte d'Ivoire"
+  carrier_attribute {
+    mccmnc_tuple: "61203"
+  }
+}
+carrier_id {
+  canonical_id: 1422
+  carrier_name: "Comium Côte d'Ivoire"
+  carrier_attribute {
+    mccmnc_tuple: "61204"
+  }
+}
+carrier_id {
+  canonical_id: 1423
+  carrier_name: "Loteny Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "61205"
+  }
+}
+carrier_id {
+  canonical_id: 1424
+  carrier_name: "Oricel Côte d'Ivoire"
+  carrier_attribute {
+    mccmnc_tuple: "61206"
+  }
+}
+carrier_id {
+  canonical_id: 1425
+  carrier_name: "Aircomm Côte d'Ivoire"
+  carrier_attribute {
+    mccmnc_tuple: "61207"
+  }
+}
+carrier_id {
+  canonical_id: 1426
+  carrier_name: "Telecom Cook"
+  carrier_attribute {
+    mccmnc_tuple: "54801"
+  }
+}
+carrier_id {
+  canonical_id: 1427
+  carrier_name: "Entel"
+  carrier_attribute {
+    mccmnc_tuple: "73001"
+    mccmnc_tuple: "73010"
+  }
+}
+carrier_id {
+  canonical_id: 1428
+  carrier_name: "Telefónica Móvil"
+  carrier_attribute {
+    mccmnc_tuple: "73002"
+    mccmnc_tuple: "73007"
+  }
+}
+carrier_id {
+  canonical_id: 1429
+  carrier_name: "Claro CL"
+  carrier_attribute {
+    mccmnc_tuple: "73003"
+  }
+}
+carrier_id {
+  canonical_id: 1430
+  carrier_name: "Centennial Cayman Corp. Chile S.A."
+  carrier_attribute {
+    mccmnc_tuple: "73004"
+  }
+}
+carrier_id {
+  canonical_id: 1431
+  carrier_name: "Multikom S.A."
+  carrier_attribute {
+    mccmnc_tuple: "73005"
+  }
+}
+carrier_id {
+  canonical_id: 1433
+  carrier_name: "Mobile Telephone Networks Cameroon"
+  carrier_attribute {
+    mccmnc_tuple: "62401"
+  }
+}
+carrier_id {
+  canonical_id: 1434
+  carrier_name: "Orange Cameroun"
+  carrier_attribute {
+    mccmnc_tuple: "62402"
+  }
+}
+carrier_id {
+  canonical_id: 1435
+  carrier_name: "China Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "46000"
+    mccmnc_tuple: "46002"
+    mccmnc_tuple: "46007"
+  }
+}
+carrier_id {
+  canonical_id: 1436
+  carrier_name: "China Unicom"
+  carrier_attribute {
+    mccmnc_tuple: "46001"
+    mccmnc_tuple: "46003"
+  }
+}
+carrier_id {
+  canonical_id: 1437
+  carrier_name: "China Satellite Global Star Network"
+  carrier_attribute {
+    mccmnc_tuple: "46004"
+  }
+}
+carrier_id {
+  canonical_id: 1438
+  carrier_name: "Colombia Telecomunicaciones S.A. - Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "732001"
+  }
+}
+carrier_id {
+  canonical_id: 1439
+  carrier_name: "Edatel S.A."
+  carrier_attribute {
+    mccmnc_tuple: "732002"
+  }
+}
+carrier_id {
+  canonical_id: 1440
+  carrier_name: "Emtelsa"
+  carrier_attribute {
+    mccmnc_tuple: "732020"
+  }
+}
+carrier_id {
+  canonical_id: 1441
+  carrier_name: "Emcali"
+  carrier_attribute {
+    mccmnc_tuple: "732099"
+  }
+}
+carrier_id {
+  canonical_id: 1442
+  carrier_name: "Claro CO"
+  carrier_attribute {
+    mccmnc_tuple: "732101"
+  }
+}
+carrier_id {
+  canonical_id: 1443
+  carrier_name: "Bellsouth Colombia S.A."
+  carrier_attribute {
+    mccmnc_tuple: "732102"
+  }
+}
+carrier_id {
+  canonical_id: 1444
+  carrier_name: "ETECSA"
+  carrier_attribute {
+    mccmnc_tuple: "36801"
+  }
+}
+carrier_id {
+  canonical_id: 1445
+  carrier_name: "Cabo Verde Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "62501"
+  }
+}
+carrier_id {
+  canonical_id: 1446
+  carrier_name: "T+Telecomunicaçôes"
+  carrier_attribute {
+    mccmnc_tuple: "62502"
+  }
+}
+carrier_id {
+  canonical_id: 1447
+  carrier_name: "CYTA"
+  carrier_attribute {
+    mccmnc_tuple: "28001"
+  }
+}
+carrier_id {
+  canonical_id: 1448
+  carrier_name: "Scancom (Cyprus) Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "28010"
+  }
+}
+carrier_id {
+  canonical_id: 1449
+  carrier_name: "O2"
+  carrier_attribute {
+    mccmnc_tuple: "23002"
+  }
+}
+carrier_id {
+  canonical_id: 1450
+  carrier_name: "Mobilkom a.s."
+  carrier_attribute {
+    mccmnc_tuple: "23004"
+  }
+}
+carrier_id {
+  canonical_id: 1451
+  carrier_name: "Sprava Zeleznicni Dopravni Cesty"
+  carrier_attribute {
+    mccmnc_tuple: "23098"
+  }
+}
+carrier_id {
+  canonical_id: 1452
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "23003"
+    spn: "Vodafone CZ"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "23099"
+    imsi_prefix_xpattern: "x"
+  }
+}
+carrier_id {
+  canonical_id: 1453
+  carrier_name: "Telefonica"
+  carrier_attribute {
+    mccmnc_tuple: "26203"
+    mccmnc_tuple: "26205"
+    mccmnc_tuple: "26277"
+  }
+}
+carrier_id {
+  canonical_id: 1454
+  carrier_name: "o2"
+  carrier_attribute {
+    mccmnc_tuple: "26207"
+    mccmnc_tuple: "26208"
+  }
+}
+carrier_id {
+  canonical_id: 1455
+  carrier_name: "Arcor AG & Co."
+  carrier_attribute {
+    mccmnc_tuple: "26210"
+  }
+}
+carrier_id {
+  canonical_id: 1456
+  carrier_name: "O2 (Germany) GmbH & Co. OHG"
+  carrier_attribute {
+    mccmnc_tuple: "26211"
+  }
+}
+carrier_id {
+  canonical_id: 1457
+  carrier_name: "Dolphin Telecom (Deutschland) GmbH"
+  carrier_attribute {
+    mccmnc_tuple: "26212"
+  }
+}
+carrier_id {
+  canonical_id: 1458
+  carrier_name: "Mobilcom Multimedia GmbH"
+  carrier_attribute {
+    mccmnc_tuple: "26213"
+  }
+}
+carrier_id {
+  canonical_id: 1459
+  carrier_name: "Group 3G UMTS GmbH (Quam)"
+  carrier_attribute {
+    mccmnc_tuple: "26214"
+  }
+}
+carrier_id {
+  canonical_id: 1460
+  carrier_name: "Airdata AG"
+  carrier_attribute {
+    mccmnc_tuple: "26215"
+  }
+}
+carrier_id {
+  canonical_id: 1461
+  carrier_name: "Siemens AG, ICMNPGUSTA"
+  carrier_attribute {
+    mccmnc_tuple: "26276"
+  }
+}
+carrier_id {
+  canonical_id: 1462
+  carrier_name: "Evatis"
+  carrier_attribute {
+    mccmnc_tuple: "63801"
+  }
+}
+carrier_id {
+  canonical_id: 1463
+  carrier_name: "TDC Mobil"
+  carrier_attribute {
+    mccmnc_tuple: "23801"
+    mccmnc_tuple: "23810"
+  }
+}
+carrier_id {
+  canonical_id: 1464
+  carrier_name: "Telenor"
+  carrier_attribute {
+    mccmnc_tuple: "23802"
+    mccmnc_tuple: "23866"
+  }
+}
+carrier_id {
+  canonical_id: 1465
+  carrier_name: "MIGway A/S"
+  carrier_attribute {
+    mccmnc_tuple: "23803"
+  }
+}
+carrier_id {
+  canonical_id: 1466
+  carrier_name: "3"
+  carrier_attribute {
+    mccmnc_tuple: "23806"
+  }
+}
+carrier_id {
+  canonical_id: 1467
+  carrier_name: "Claro RD"
+  carrier_attribute {
+    mccmnc_tuple: "37002"
+  }
+}
+carrier_id {
+  canonical_id: 1468
+  carrier_name: "Tricom S.A."
+  carrier_attribute {
+    mccmnc_tuple: "37003"
+  }
+}
+carrier_id {
+  canonical_id: 1469
+  carrier_name: "CentennialDominicana"
+  carrier_attribute {
+    mccmnc_tuple: "37004"
+  }
+}
+carrier_id {
+  canonical_id: 1470
+  carrier_name: "Algérie Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "60301"
+  }
+}
+carrier_id {
+  canonical_id: 1471
+  carrier_name: "Orascom Telecom Algérie"
+  carrier_attribute {
+    mccmnc_tuple: "60302"
+  }
+}
+carrier_id {
+  canonical_id: 1472
+  carrier_name: "Movistar (Otecel S.A.)"
+  carrier_attribute {
+    mccmnc_tuple: "74000"
+  }
+}
+carrier_id {
+  canonical_id: 1473
+  carrier_name: "Claro EC"
+  carrier_attribute {
+    mccmnc_tuple: "74001"
+  }
+}
+carrier_id {
+  canonical_id: 1474
+  carrier_name: "Telecsa S.A."
+  carrier_attribute {
+    mccmnc_tuple: "74002"
+  }
+}
+carrier_id {
+  canonical_id: 1475
+  carrier_name: "Elisa Matkapuhelinpalvelut Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "24405"
+  }
+}
+carrier_id {
+  canonical_id: 1476
+  carrier_name: "Finnet Group"
+  carrier_attribute {
+    mccmnc_tuple: "24409"
+  }
+}
+carrier_id {
+  canonical_id: 1477
+  carrier_name: "Alands Mobiltelefon AB"
+  carrier_attribute {
+    mccmnc_tuple: "24414"
+  }
+}
+carrier_id {
+  canonical_id: 1478
+  carrier_name: "Oy Finland Tele2 AB"
+  carrier_attribute {
+    mccmnc_tuple: "24416"
+  }
+}
+carrier_id {
+  canonical_id: 1479
+  carrier_name: "Saunalahti Group Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "24421"
+  }
+}
+carrier_id {
+  canonical_id: 1480
+  carrier_name: "Telia"
+  carrier_attribute {
+    mccmnc_tuple: "24491"
+  }
+}
+carrier_id {
+  canonical_id: 1481
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "54201"
+  }
+}
+carrier_id {
+  canonical_id: 1482
+  carrier_name: "FSM Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "55001"
+  }
+}
+carrier_id {
+  canonical_id: 1483
+  carrier_name: "Faroese Telecom - GSM"
+  carrier_attribute {
+    mccmnc_tuple: "28801"
+  }
+}
+carrier_id {
+  canonical_id: 1484
+  carrier_name: "Kall GSM"
+  carrier_attribute {
+    mccmnc_tuple: "28802"
+  }
+}
+carrier_id {
+  canonical_id: 1486
+  carrier_name: "Globalstar Europe"
+  carrier_attribute {
+    mccmnc_tuple: "20805"
+    mccmnc_tuple: "20806"
+    mccmnc_tuple: "20807"
+  }
+}
+carrier_id {
+  canonical_id: 1487
+  carrier_name: "Bouygues Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "20820"
+    mccmnc_tuple: "20821"
+    mccmnc_tuple: "20888"
+  }
+}
+carrier_id {
+  canonical_id: 1488
+  carrier_name: "Libertis S.A."
+  carrier_attribute {
+    mccmnc_tuple: "62801"
+  }
+}
+carrier_id {
+  canonical_id: 1489
+  carrier_name: "Telecel Gabon S.A."
+  carrier_attribute {
+    mccmnc_tuple: "62802"
+  }
+}
+carrier_id {
+  canonical_id: 1490
+  carrier_name: "Celtel Gabon S.A."
+  carrier_attribute {
+    mccmnc_tuple: "62803"
+  }
+}
+carrier_id {
+  canonical_id: 1491
+  carrier_name: "Mapesbury Communications Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "23401"
+  }
+}
+carrier_id {
+  canonical_id: 1492
+  carrier_name: "O2"
+  carrier_attribute {
+    mccmnc_tuple: "23402"
+    mccmnc_tuple: "23410"
+    mccmnc_tuple: "23411"
+  }
+}
+carrier_id {
+  canonical_id: 1493
+  carrier_name: "Jersey Telenet Ltd"
+  carrier_attribute {
+    mccmnc_tuple: "23403"
+  }
+}
+carrier_id {
+  canonical_id: 1494
+  carrier_name: "FMS Solutions Ltd"
+  carrier_attribute {
+    mccmnc_tuple: "23404"
+  }
+}
+carrier_id {
+  canonical_id: 1495
+  carrier_name: "Colt Mobile Telecommunications Ltd"
+  carrier_attribute {
+    mccmnc_tuple: "23405"
+  }
+}
+carrier_id {
+  canonical_id: 1496
+  carrier_name: "Internet One Ltd"
+  carrier_attribute {
+    mccmnc_tuple: "23406"
+  }
+}
+carrier_id {
+  canonical_id: 1497
+  carrier_name: "Cable and Wireless plc"
+  carrier_attribute {
+    mccmnc_tuple: "23407"
+  }
+}
+carrier_id {
+  canonical_id: 1498
+  carrier_name: "Wire9 Telecom plc"
+  carrier_attribute {
+    mccmnc_tuple: "23409"
+    mccmnc_tuple: "23418"
+  }
+}
+carrier_id {
+  canonical_id: 1500
+  carrier_name: "Ntework Rail Infrastructure Ltd"
+  carrier_attribute {
+    mccmnc_tuple: "23412"
+    mccmnc_tuple: "23413"
+  }
+}
+carrier_id {
+  canonical_id: 1501
+  carrier_name: "Hay Systems Ltd"
+  carrier_attribute {
+    mccmnc_tuple: "23414"
+  }
+}
+carrier_id {
+  canonical_id: 1502
+  carrier_name: "Opal Telecom Ltd"
+  carrier_attribute {
+    mccmnc_tuple: "23416"
+  }
+}
+carrier_id {
+  canonical_id: 1503
+  carrier_name: "Flextel Ltd"
+  carrier_attribute {
+    mccmnc_tuple: "23417"
+  }
+}
+carrier_id {
+  canonical_id: 1504
+  carrier_name: "Teleware plc"
+  carrier_attribute {
+    mccmnc_tuple: "23419"
+  }
+}
+carrier_id {
+  canonical_id: 1505
+  carrier_name: "Three Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "23420"
+  }
+}
+carrier_id {
+  canonical_id: 1506
+  carrier_name: "Jersey Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "23450"
+  }
+}
+carrier_id {
+  canonical_id: 1507
+  carrier_name: "Manx Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "23458"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "23410"
+    gid1: "519"
+  }
+}
+carrier_id {
+  canonical_id: 1508
+  carrier_name: "Inquam Telecom (Holdings) Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "23475"
+  }
+}
+carrier_id {
+  canonical_id: 1509
+  carrier_name: "British Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "23400"
+    mccmnc_tuple: "23476"
+  }
+}
+carrier_id {
+  canonical_id: 1510
+  carrier_name: "Airwave mmO2 Ltd"
+  carrier_attribute {
+    mccmnc_tuple: "23478"
+  }
+}
+carrier_id {
+  canonical_id: 1511
+  carrier_name: "Geocell Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "28201"
+  }
+}
+carrier_id {
+  canonical_id: 1512
+  carrier_name: "Magti GSM Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "28202"
+  }
+}
+carrier_id {
+  canonical_id: 1513
+  carrier_name: "Iberiatel Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "28203"
+  }
+}
+carrier_id {
+  canonical_id: 1514
+  carrier_name: "Mobitel Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "28204"
+  }
+}
+carrier_id {
+  canonical_id: 1515
+  carrier_name: "MTN"
+  carrier_attribute {
+    mccmnc_tuple: "62001"
+  }
+}
+carrier_id {
+  canonical_id: 1516
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "62002"
+  }
+}
+carrier_id {
+  canonical_id: 1517
+  carrier_name: "Mobitel"
+  carrier_attribute {
+    mccmnc_tuple: "62003"
+  }
+}
+carrier_id {
+  canonical_id: 1518
+  carrier_name: "Kasapa Telecom Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "62004"
+  }
+}
+carrier_id {
+  canonical_id: 1519
+  carrier_name: "Telestet"
+  carrier_attribute {
+    mccmnc_tuple: "20210"
+  }
+}
+carrier_id {
+  canonical_id: 1520
+  carrier_name: "Claro GT"
+  carrier_attribute {
+    mccmnc_tuple: "70401"
+  }
+}
+carrier_id {
+  canonical_id: 1521
+  carrier_name: "Comunicaciones Celulares S.A."
+  carrier_attribute {
+    mccmnc_tuple: "70402"
+  }
+}
+carrier_id {
+  canonical_id: 1522
+  carrier_name: "Telefónica Centroamérica Guatemala S.A."
+  carrier_attribute {
+    mccmnc_tuple: "70403"
+    mccmnc_tuple: "704030"
+  }
+}
+carrier_id {
+  canonical_id: 1523
+  carrier_name: "Guinétel S.A."
+  carrier_attribute {
+    mccmnc_tuple: "63201"
+  }
+}
+carrier_id {
+  canonical_id: 1524
+  carrier_name: "Spacetel Guiné-Bissau S.A."
+  carrier_attribute {
+    mccmnc_tuple: "63202"
+  }
+}
+carrier_id {
+  canonical_id: 1525
+  carrier_name: "Cel*Star (Guyana) Inc."
+  carrier_attribute {
+    mccmnc_tuple: "73801"
+  }
+}
+carrier_id {
+  canonical_id: 1526
+  carrier_name: "1O1O / csl"
+  carrier_attribute {
+    mccmnc_tuple: "45400"
+    mccmnc_tuple: "45419"
+  }
+}
+carrier_id {
+  canonical_id: 1527
+  carrier_name: "MVNO/CITIC"
+  carrier_attribute {
+    mccmnc_tuple: "45401"
+  }
+}
+carrier_id {
+  canonical_id: 1528
+  carrier_name: "Celtel"
+  carrier_attribute {
+    mccmnc_tuple: "708002"
+    mccmnc_tuple: "70802"
+    mccmnc_tuple: "708020"
+  }
+}
+carrier_id {
+  canonical_id: 1529
+  carrier_name: "Tele2"
+  carrier_attribute {
+    mccmnc_tuple: "21902"
+  }
+}
+carrier_id {
+  canonical_id: 1530
+  carrier_name: "Vip/Tomato"
+  carrier_attribute {
+    mccmnc_tuple: "21910"
+  }
+}
+carrier_id {
+  canonical_id: 1531
+  carrier_name: "Comcel"
+  carrier_attribute {
+    mccmnc_tuple: "37201"
+  }
+}
+carrier_id {
+  canonical_id: 1532
+  carrier_name: "Digicel"
+  carrier_attribute {
+    mccmnc_tuple: "37202"
+  }
+}
+carrier_id {
+  canonical_id: 1533
+  carrier_name: "Rectel"
+  carrier_attribute {
+    mccmnc_tuple: "37203"
+  }
+}
+carrier_id {
+  canonical_id: 1534
+  carrier_name: "Telenor"
+  carrier_attribute {
+    mccmnc_tuple: "21601"
+  }
+}
+carrier_id {
+  canonical_id: 1535
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "21670"
+    spn: ""
+    spn: "vodafone HU"
+  }
+}
+carrier_id {
+  canonical_id: 1536
+  carrier_name: "PSN"
+  carrier_attribute {
+    mccmnc_tuple: "51000"
+  }
+}
+carrier_id {
+  canonical_id: 1537
+  carrier_name: "Indosat"
+  carrier_attribute {
+    mccmnc_tuple: "51001"
+  }
+}
+carrier_id {
+  canonical_id: 1543
+  carrier_name: "Reliance"
+  carrier_attribute {
+    mccmnc_tuple: "40409"
+    mccmnc_tuple: "40418"
+    mccmnc_tuple: "40436"
+    mccmnc_tuple: "40450"
+    mccmnc_tuple: "40452"
+    mccmnc_tuple: "40467"
+    mccmnc_tuple: "40485"
+  }
+}
+carrier_id {
+  canonical_id: 1545
+  carrier_name: "BPL Mobile Communications Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "40421"
+  }
+}
+carrier_id {
+  canonical_id: 1549
+  carrier_name: "Bharat Sanchar Nigam Ltd. (BSNL)"
+  carrier_attribute {
+    mccmnc_tuple: "40434"
+    mccmnc_tuple: "40438"
+    mccmnc_tuple: "40451"
+    mccmnc_tuple: "40453"
+    mccmnc_tuple: "40454"
+    mccmnc_tuple: "40455"
+    mccmnc_tuple: "40457"
+    mccmnc_tuple: "40458"
+    mccmnc_tuple: "40459"
+    mccmnc_tuple: "40462"
+    mccmnc_tuple: "40464"
+    mccmnc_tuple: "40466"
+    mccmnc_tuple: "40471"
+    mccmnc_tuple: "40472"
+    mccmnc_tuple: "40473"
+    mccmnc_tuple: "40474"
+    mccmnc_tuple: "40475"
+    mccmnc_tuple: "40476"
+    mccmnc_tuple: "40477"
+    mccmnc_tuple: "40480"
+    mccmnc_tuple: "40481"
+  }
+}
+carrier_id {
+  canonical_id: 1550
+  carrier_name: "RPG Cellular"
+  carrier_attribute {
+    mccmnc_tuple: "40441"
+  }
+}
+carrier_id {
+  canonical_id: 1551
+  carrier_name: "Aircel Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "40442"
+  }
+}
+carrier_id {
+  canonical_id: 1556
+  carrier_name: "Mahanagar Telephone Nigam Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "40468"
+    mccmnc_tuple: "40469"
+  }
+}
+carrier_id {
+  canonical_id: 1559
+  carrier_name: "Reliable Internet Services Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "40483"
+  }
+}
+carrier_id {
+  canonical_id: 1562
+  carrier_name: "IR-MCI (Hamrahe Avval)"
+  carrier_attribute {
+    mccmnc_tuple: "43211"
+  }
+}
+carrier_id {
+  canonical_id: 1563
+  carrier_name: "Telecommunication Kish Co. (KIFZO)"
+  carrier_attribute {
+    mccmnc_tuple: "43214"
+  }
+}
+carrier_id {
+  canonical_id: 1564
+  carrier_name: "MTCE (Espadan)"
+  carrier_attribute {
+    mccmnc_tuple: "43219"
+  }
+}
+carrier_id {
+  canonical_id: 1565
+  carrier_name: "Iceland Telecom Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "27401"
+  }
+}
+carrier_id {
+  canonical_id: 1566
+  carrier_name: "Tal hf"
+  carrier_attribute {
+    mccmnc_tuple: "27402"
+  }
+}
+carrier_id {
+  canonical_id: 1567
+  carrier_name: "Islandssimi GSM ehf"
+  carrier_attribute {
+    mccmnc_tuple: "27403"
+  }
+}
+carrier_id {
+  canonical_id: 1568
+  carrier_name: "IMC Islande ehf"
+  carrier_attribute {
+    mccmnc_tuple: "27404"
+  }
+}
+carrier_id {
+  canonical_id: 1569
+  carrier_name: "IceCell ehf"
+  carrier_attribute {
+    mccmnc_tuple: "27407"
+  }
+}
+carrier_id {
+  canonical_id: 1571
+  carrier_name: "Elsacom"
+  carrier_attribute {
+    mccmnc_tuple: "22202"
+  }
+}
+carrier_id {
+  canonical_id: 1572
+  carrier_name: "IPSE 2000"
+  carrier_attribute {
+    mccmnc_tuple: "22277"
+  }
+}
+carrier_id {
+  canonical_id: 1573
+  carrier_name: "Wind"
+  carrier_attribute {
+    mccmnc_tuple: "22288"
+  }
+}
+carrier_id {
+  canonical_id: 1574
+  carrier_name: "Blu"
+  carrier_attribute {
+    mccmnc_tuple: "22298"
+  }
+}
+carrier_id {
+  canonical_id: 1575
+  carrier_name: "3"
+  carrier_attribute {
+    mccmnc_tuple: "22299"
+  }
+}
+carrier_id {
+  canonical_id: 1576
+  carrier_name: "Cable & Wireless Jamaica Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "338020"
+  }
+}
+carrier_id {
+  canonical_id: 1577
+  carrier_name: "Mossel (Jamaica) Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "338050"
+  }
+}
+carrier_id {
+  canonical_id: 1578
+  carrier_name: "Fastlink"
+  carrier_attribute {
+    mccmnc_tuple: "41601"
+  }
+}
+carrier_id {
+  canonical_id: 1579
+  carrier_name: "Xpress"
+  carrier_attribute {
+    mccmnc_tuple: "41602"
+  }
+}
+carrier_id {
+  canonical_id: 1580
+  carrier_name: "Umniah"
+  carrier_attribute {
+    mccmnc_tuple: "41603"
+  }
+}
+carrier_id {
+  canonical_id: 1581
+  carrier_name: "au"
+  carrier_attribute {
+    mccmnc_tuple: "44050"
+    mccmnc_tuple: "44051"
+    mccmnc_tuple: "44052"
+  }
+}
+carrier_id {
+  canonical_id: 1585
+  carrier_name: "Zain"
+  carrier_attribute {
+    mccmnc_tuple: "41902"
+  }
+}
+carrier_id {
+  canonical_id: 1586
+  carrier_name: "Ooredoo"
+  carrier_attribute {
+    mccmnc_tuple: "41903"
+  }
+}
+carrier_id {
+  canonical_id: 1587
+  carrier_name: "Cable & Wireless (Cayman)"
+  carrier_attribute {
+    mccmnc_tuple: "346140"
+  }
+}
+carrier_id {
+  canonical_id: 1588
+  carrier_name: "Beeline"
+  carrier_attribute {
+    mccmnc_tuple: "40101"
+  }
+}
+carrier_id {
+  canonical_id: 1589
+  carrier_name: "TSC Kazak Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "40102"
+  }
+}
+carrier_id {
+  canonical_id: 1590
+  carrier_name: "Lao Telecommunications"
+  carrier_attribute {
+    mccmnc_tuple: "45701"
+  }
+}
+carrier_id {
+  canonical_id: 1591
+  carrier_name: "ETL Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "45702"
+  }
+}
+carrier_id {
+  canonical_id: 1592
+  carrier_name: "Millicom"
+  carrier_attribute {
+    mccmnc_tuple: "45708"
+  }
+}
+carrier_id {
+  canonical_id: 1593
+  carrier_name: "Cellis"
+  carrier_attribute {
+    mccmnc_tuple: "41532"
+    mccmnc_tuple: "41533"
+    mccmnc_tuple: "41534"
+    mccmnc_tuple: "41535"
+  }
+}
+carrier_id {
+  canonical_id: 1594
+  carrier_name: "Libancell"
+  carrier_attribute {
+    mccmnc_tuple: "41536"
+    mccmnc_tuple: "41537"
+    mccmnc_tuple: "41538"
+    mccmnc_tuple: "41539"
+  }
+}
+carrier_id {
+  canonical_id: 1595
+  carrier_name: "Telecom FL AG"
+  carrier_attribute {
+    mccmnc_tuple: "29501"
+  }
+}
+carrier_id {
+  canonical_id: 1596
+  carrier_name: "Viag Europlatform AG"
+  carrier_attribute {
+    mccmnc_tuple: "29502"
+  }
+}
+carrier_id {
+  canonical_id: 1597
+  carrier_name: "Mobilkom (Liechstein) AG"
+  carrier_attribute {
+    mccmnc_tuple: "29505"
+  }
+}
+carrier_id {
+  canonical_id: 1598
+  carrier_name: "Tele2 AG"
+  carrier_attribute {
+    mccmnc_tuple: "29577"
+  }
+}
+carrier_id {
+  canonical_id: 1599
+  carrier_name: "MTN Network Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "41302"
+  }
+}
+carrier_id {
+  canonical_id: 1600
+  carrier_name: "Celtel Lanka Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "41303"
+  }
+}
+carrier_id {
+  canonical_id: 1601
+  carrier_name: "Comium Liberia"
+  carrier_attribute {
+    mccmnc_tuple: "61804"
+  }
+}
+carrier_id {
+  canonical_id: 1602
+  carrier_name: "Vodacom Lesotho (pty) Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "65101"
+  }
+}
+carrier_id {
+  canonical_id: 1603
+  carrier_name: "Econet Ezin-cel"
+  carrier_attribute {
+    mccmnc_tuple: "65102"
+  }
+}
+carrier_id {
+  canonical_id: 1604
+  carrier_name: "MTEL d.o.o. Podgorica"
+  carrier_attribute {
+    mccmnc_tuple: "29703"
+  }
+}
+carrier_id {
+  canonical_id: 1605
+  carrier_name: "MADACOM"
+  carrier_attribute {
+    mccmnc_tuple: "64601"
+  }
+}
+carrier_id {
+  canonical_id: 1606
+  carrier_name: "Orange Madagascar"
+  carrier_attribute {
+    mccmnc_tuple: "64602"
+  }
+}
+carrier_id {
+  canonical_id: 1607
+  carrier_name: "Telecom Malagasy Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "64604"
+  }
+}
+carrier_id {
+  canonical_id: 1608
+  carrier_name: "Cosmofon"
+  carrier_attribute {
+    mccmnc_tuple: "29402"
+  }
+}
+carrier_id {
+  canonical_id: 1609
+  carrier_name: "Nov Operator"
+  carrier_attribute {
+    mccmnc_tuple: "29403"
+  }
+}
+carrier_id {
+  canonical_id: 1610
+  carrier_name: "Malitel"
+  carrier_attribute {
+    mccmnc_tuple: "61001"
+  }
+}
+carrier_id {
+  canonical_id: 1611
+  carrier_name: "Myanmar Post and Telecommunication"
+  carrier_attribute {
+    mccmnc_tuple: "41401"
+  }
+}
+carrier_id {
+  canonical_id: 1612
+  carrier_name: "Mobicom"
+  carrier_attribute {
+    mccmnc_tuple: "42899"
+  }
+}
+carrier_id {
+  canonical_id: 1613
+  carrier_name: "Smartone Mobile Communications (Macao) Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "45500"
+  }
+}
+carrier_id {
+  canonical_id: 1614
+  carrier_name: "CTM GSM"
+  carrier_attribute {
+    mccmnc_tuple: "45501"
+  }
+}
+carrier_id {
+  canonical_id: 1615
+  carrier_name: "Hutchison Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "45503"
+  }
+}
+carrier_id {
+  canonical_id: 1616
+  carrier_name: "Mattel S.A."
+  carrier_attribute {
+    mccmnc_tuple: "60901"
+  }
+}
+carrier_id {
+  canonical_id: 1617
+  carrier_name: "Chinguitel S.A."
+  carrier_attribute {
+    mccmnc_tuple: "60902"
+  }
+}
+carrier_id {
+  canonical_id: 1618
+  carrier_name: "Mauritel Mobiles"
+  carrier_attribute {
+    mccmnc_tuple: "60910"
+  }
+}
+carrier_id {
+  canonical_id: 1619
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "27801"
+  }
+}
+carrier_id {
+  canonical_id: 1620
+  carrier_name: "go mobile"
+  carrier_attribute {
+    mccmnc_tuple: "27821"
+  }
+}
+carrier_id {
+  canonical_id: 1621
+  carrier_name: "Orange Mauritius"
+  carrier_attribute {
+    mccmnc_tuple: "61701"
+  }
+}
+carrier_id {
+  canonical_id: 1622
+  carrier_name: "Mahanagar Telephone (Mauritius) Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "61702"
+  }
+}
+carrier_id {
+  canonical_id: 1623
+  carrier_name: "Emtel"
+  carrier_attribute {
+    mccmnc_tuple: "61710"
+  }
+}
+carrier_id {
+  canonical_id: 1624
+  carrier_name: "DhiMobile"
+  carrier_attribute {
+    mccmnc_tuple: "47201"
+  }
+}
+carrier_id {
+  canonical_id: 1625
+  carrier_name: "Telekom Network Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "65001"
+  }
+}
+carrier_id {
+  canonical_id: 1626
+  carrier_name: "Celtel ltd."
+  carrier_attribute {
+    mccmnc_tuple: "65010"
+  }
+}
+carrier_id {
+  canonical_id: 1627
+  carrier_name: "Art900"
+  carrier_attribute {
+    mccmnc_tuple: "50200"
+  }
+}
+carrier_id {
+  canonical_id: 1628
+  carrier_name: "Maxis/Hotlink/Ookyo"
+  carrier_attribute {
+    mccmnc_tuple: "50212"
+  }
+}
+carrier_id {
+  canonical_id: 1630
+  carrier_name: "Digi"
+  carrier_attribute {
+    mccmnc_tuple: "50210"
+    mccmnc_tuple: "50216"
+  }
+}
+carrier_id {
+  canonical_id: 1631
+  carrier_name: "TimeCel"
+  carrier_attribute {
+    mccmnc_tuple: "50217"
+  }
+}
+carrier_id {
+  canonical_id: 1632
+  carrier_name: "U Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "50218"
+  }
+}
+carrier_id {
+  canonical_id: 1633
+  carrier_name: "Celcom"
+  carrier_attribute {
+    mccmnc_tuple: "50213"
+    mccmnc_tuple: "50219"
+  }
+}
+carrier_id {
+  canonical_id: 1634
+  carrier_name: "T.D.M. GSM"
+  carrier_attribute {
+    mccmnc_tuple: "64301"
+  }
+}
+carrier_id {
+  canonical_id: 1635
+  carrier_name: "Vodacom"
+  carrier_attribute {
+    mccmnc_tuple: "64304"
+  }
+}
+carrier_id {
+  canonical_id: 1636
+  carrier_name: "Sahel.Com"
+  carrier_attribute {
+    mccmnc_tuple: "61401"
+  }
+}
+carrier_id {
+  canonical_id: 1637
+  carrier_name: "Telecel"
+  carrier_attribute {
+    mccmnc_tuple: "61403"
+  }
+}
+carrier_id {
+  canonical_id: 1638
+  carrier_name: "Econet Wireless Nigeria Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "62120"
+  }
+}
+carrier_id {
+  canonical_id: 1639
+  carrier_name: "MTN Nigeria Communications"
+  carrier_attribute {
+    mccmnc_tuple: "62130"
+  }
+}
+carrier_id {
+  canonical_id: 1640
+  carrier_name: "Nigeria Telecommunications Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "62140"
+  }
+}
+carrier_id {
+  canonical_id: 1641
+  carrier_name: "Claro NI"
+  carrier_attribute {
+    mccmnc_tuple: "71021"
+  }
+}
+carrier_id {
+  canonical_id: 1642
+  carrier_name: "Servicios de Comunicaciones, S.A. (SERCOM)"
+  carrier_attribute {
+    mccmnc_tuple: "71073"
+  }
+}
+carrier_id {
+  canonical_id: 1643
+  carrier_name: "Tele2"
+  carrier_attribute {
+    mccmnc_tuple: "20402"
+  }
+}
+carrier_id {
+  canonical_id: 1644
+  carrier_name: "KPN/Telfort"
+  carrier_attribute {
+    mccmnc_tuple: "20408"
+    mccmnc_tuple: "20412"
+  }
+}
+carrier_id {
+  canonical_id: 1646
+  carrier_name: "NS Railinfrabeheer B.V."
+  carrier_attribute {
+    mccmnc_tuple: "20421"
+  }
+}
+carrier_id {
+  canonical_id: 1647
+  carrier_name: "Claro PE"
+  carrier_attribute {
+    mccmnc_tuple: "71610"
+  }
+}
+carrier_id {
+  canonical_id: 1648
+  carrier_name: "Tikiphone"
+  carrier_attribute {
+    mccmnc_tuple: "54720"
+  }
+}
+carrier_id {
+  canonical_id: 1649
+  carrier_name: "Bmobile"
+  carrier_attribute {
+    mccmnc_tuple: "53701"
+  }
+}
+carrier_id {
+  canonical_id: 1650
+  carrier_name: "Greencom"
+  carrier_attribute {
+    mccmnc_tuple: "53702"
+  }
+}
+carrier_id {
+  canonical_id: 1651
+  carrier_name: "Digicel Ltd"
+  carrier_attribute {
+    mccmnc_tuple: "53703"
+  }
+}
+carrier_id {
+  canonical_id: 1652
+  carrier_name: "Islacom"
+  carrier_attribute {
+    mccmnc_tuple: "51501"
+  }
+}
+carrier_id {
+  canonical_id: 1653
+  carrier_name: "Globe Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "51502"
+  }
+}
+carrier_id {
+  canonical_id: 1654
+  carrier_name: "Smart Communications"
+  carrier_attribute {
+    mccmnc_tuple: "51503"
+  }
+}
+carrier_id {
+  canonical_id: 1655
+  carrier_name: "Sun"
+  carrier_attribute {
+    mccmnc_tuple: "51505"
+  }
+}
+carrier_id {
+  canonical_id: 1656
+  carrier_name: "Jazz"
+  carrier_attribute {
+    mccmnc_tuple: "41001"
+  }
+}
+carrier_id {
+  canonical_id: 1657
+  carrier_name: "PAK Telecom Mobile Ltd. (UFONE)"
+  carrier_attribute {
+    mccmnc_tuple: "41003"
+  }
+}
+carrier_id {
+  canonical_id: 1658
+  carrier_name: "Plus"
+  carrier_attribute {
+    mccmnc_tuple: "26001"
+  }
+}
+carrier_id {
+  canonical_id: 1659
+  carrier_name: "Orange"
+  carrier_attribute {
+    mccmnc_tuple: "26003"
+  }
+}
+carrier_id {
+  canonical_id: 1660
+  carrier_name: "Tele2 Polska (Tele2 Polska Sp. Z.o.o.)"
+  carrier_attribute {
+    mccmnc_tuple: "26004"
+  }
+}
+carrier_id {
+  canonical_id: 1661
+  carrier_name: "IDEA (UMTS)/PTK Centertel sp. Z.o.o."
+  carrier_attribute {
+    mccmnc_tuple: "26005"
+  }
+}
+carrier_id {
+  canonical_id: 1662
+  carrier_name: "PLAY"
+  carrier_attribute {
+    mccmnc_tuple: "26006"
+  }
+}
+carrier_id {
+  canonical_id: 1663
+  carrier_name: "Premium internet"
+  carrier_attribute {
+    mccmnc_tuple: "26007"
+  }
+}
+carrier_id {
+  canonical_id: 1664
+  carrier_name: "E-Telko"
+  carrier_attribute {
+    mccmnc_tuple: "26008"
+  }
+}
+carrier_id {
+  canonical_id: 1665
+  carrier_name: "Telekomunikacja Kolejowa (GSM-R)"
+  carrier_attribute {
+    mccmnc_tuple: "26009"
+  }
+}
+carrier_id {
+  canonical_id: 1666
+  carrier_name: "Telefony Opalenickie"
+  carrier_attribute {
+    mccmnc_tuple: "26010"
+  }
+}
+carrier_id {
+  canonical_id: 1667
+  carrier_name: "St. Pierre-et-Miquelon Télécom"
+  carrier_attribute {
+    mccmnc_tuple: "30801"
+  }
+}
+carrier_id {
+  canonical_id: 1668
+  carrier_name: "Optimus - Telecomunicações, S.A."
+  carrier_attribute {
+    mccmnc_tuple: "26803"
+  }
+}
+carrier_id {
+  canonical_id: 1669
+  carrier_name: "Oniway - Inforcomunicaçôes, S.A."
+  carrier_attribute {
+    mccmnc_tuple: "26805"
+  }
+}
+carrier_id {
+  canonical_id: 1670
+  carrier_name: "TMN - Telecomunicações Móveis Nacionais, S.A."
+  carrier_attribute {
+    mccmnc_tuple: "26806"
+  }
+}
+carrier_id {
+  canonical_id: 1671
+  carrier_name: "Palau National Communications Corp. (a.k.a. PNCC)"
+  carrier_attribute {
+    mccmnc_tuple: "55201"
+  }
+}
+carrier_id {
+  canonical_id: 1672
+  carrier_name: "Hola Paraguay S.A."
+  carrier_attribute {
+    mccmnc_tuple: "74401"
+    mccmnc_tuple: "74406"
+  }
+}
+carrier_id {
+  canonical_id: 1673
+  carrier_name: "Claro PY"
+  carrier_attribute {
+    mccmnc_tuple: "74402"
+  }
+}
+carrier_id {
+  canonical_id: 1674
+  carrier_name: "Compañia Privada de Comunicaciones S.A."
+  carrier_attribute {
+    mccmnc_tuple: "74403"
+  }
+}
+carrier_id {
+  canonical_id: 1675
+  carrier_name: "Ooredoo"
+  carrier_attribute {
+    mccmnc_tuple: "42701"
+  }
+}
+carrier_id {
+  canonical_id: 1676
+  carrier_name: "Orange La Réunion"
+  carrier_attribute {
+    mccmnc_tuple: "64700"
+  }
+}
+carrier_id {
+  canonical_id: 1677
+  carrier_name: "Outremer Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "64702"
+  }
+}
+carrier_id {
+  canonical_id: 1678
+  carrier_name: "МТС"
+  carrier_attribute {
+    mccmnc_tuple: "25001"
+  }
+}
+carrier_id {
+  canonical_id: 1679
+  carrier_name: "Printelefone"
+  carrier_attribute {
+    mccmnc_tuple: "25092"
+  }
+}
+carrier_id {
+  canonical_id: 1680
+  carrier_name: "Telecom XXI"
+  carrier_attribute {
+    mccmnc_tuple: "25093"
+  }
+}
+carrier_id {
+  canonical_id: 1681
+  carrier_name: "Билайн"
+  carrier_attribute {
+    mccmnc_tuple: "25099"
+  }
+}
+carrier_id {
+  canonical_id: 1682
+  carrier_name: "MTN Rwandacell"
+  carrier_attribute {
+    mccmnc_tuple: "63510"
+  }
+}
+carrier_id {
+  canonical_id: 1683
+  carrier_name: "STC"
+  carrier_attribute {
+    mccmnc_tuple: "42001"
+  }
+}
+carrier_id {
+  canonical_id: 1684
+  carrier_name: "Mobily"
+  carrier_attribute {
+    mccmnc_tuple: "42003"
+  }
+}
+carrier_id {
+  canonical_id: 1685
+  carrier_name: "Cable & Wireless (Seychelles) Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "63301"
+  }
+}
+carrier_id {
+  canonical_id: 1686
+  carrier_name: "Mediatech International Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "63302"
+  }
+}
+carrier_id {
+  canonical_id: 1687
+  carrier_name: "Telecom (Seychelles) Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "63310"
+  }
+}
+carrier_id {
+  canonical_id: 1688
+  carrier_name: "SD Mobitel"
+  carrier_attribute {
+    mccmnc_tuple: "63401"
+  }
+}
+carrier_id {
+  canonical_id: 1689
+  carrier_name: "Areeba-Sudan"
+  carrier_attribute {
+    mccmnc_tuple: "63402"
+  }
+}
+carrier_id {
+  canonical_id: 1690
+  carrier_name: "Telia Sonera AB"
+  carrier_attribute {
+    mccmnc_tuple: "24001"
+  }
+}
+carrier_id {
+  canonical_id: 1691
+  carrier_name: "3 (Hi3G Access AB)"
+  carrier_attribute {
+    mccmnc_tuple: "24002"
+  }
+}
+carrier_id {
+  canonical_id: 1692
+  carrier_name: "Nordisk Mobiltelefon AS"
+  carrier_attribute {
+    mccmnc_tuple: "24003"
+  }
+}
+carrier_id {
+  canonical_id: 1693
+  carrier_name: "3G Infrastructure Services AB"
+  carrier_attribute {
+    mccmnc_tuple: "24004"
+  }
+}
+carrier_id {
+  canonical_id: 1694
+  carrier_name: "Svenska UMTS-Nät AB"
+  carrier_attribute {
+    mccmnc_tuple: "24005"
+  }
+}
+carrier_id {
+  canonical_id: 1695
+  carrier_name: "Telenor Sverige AB"
+  carrier_attribute {
+    mccmnc_tuple: "24006"
+    mccmnc_tuple: "24008"
+  }
+}
+carrier_id {
+  canonical_id: 1696
+  carrier_name: "Tele2/Comviq Sverige"
+  carrier_attribute {
+    mccmnc_tuple: "24007"
+  }
+}
+carrier_id {
+  canonical_id: 1697
+  carrier_name: "Spring Mobil AB"
+  carrier_attribute {
+    mccmnc_tuple: "24010"
+  }
+}
+carrier_id {
+  canonical_id: 1698
+  carrier_name: "Linholmen Science Park AB"
+  carrier_attribute {
+    mccmnc_tuple: "24011"
+  }
+}
+carrier_id {
+  canonical_id: 1699
+  carrier_name: "Barablu Mobile Scandinavia Ltd"
+  carrier_attribute {
+    mccmnc_tuple: "24012"
+  }
+}
+carrier_id {
+  canonical_id: 1700
+  carrier_name: "Ventelo Sverige AB"
+  carrier_attribute {
+    mccmnc_tuple: "24013"
+  }
+}
+carrier_id {
+  canonical_id: 1701
+  carrier_name: "TDC Mobil A/S"
+  carrier_attribute {
+    mccmnc_tuple: "24014"
+  }
+}
+carrier_id {
+  canonical_id: 1702
+  carrier_name: "Wireless Maingate Nordic AB"
+  carrier_attribute {
+    mccmnc_tuple: "24015"
+  }
+}
+carrier_id {
+  canonical_id: 1703
+  carrier_name: "42IT AB"
+  carrier_attribute {
+    mccmnc_tuple: "24016"
+  }
+}
+carrier_id {
+  canonical_id: 1704
+  carrier_name: "Wireless Maingate Message Services AB"
+  carrier_attribute {
+    mccmnc_tuple: "24020"
+  }
+}
+carrier_id {
+  canonical_id: 1705
+  carrier_name: "Banverket"
+  carrier_attribute {
+    mccmnc_tuple: "24021"
+  }
+}
+carrier_id {
+  canonical_id: 1706
+  carrier_name: "M1"
+  carrier_attribute {
+    mccmnc_tuple: "52503"
+  }
+}
+carrier_id {
+  canonical_id: 1707
+  carrier_name: "StarHub"
+  carrier_attribute {
+    mccmnc_tuple: "52505"
+  }
+}
+carrier_id {
+  canonical_id: 1708
+  carrier_name: "Digital Trunked Radio Network"
+  carrier_attribute {
+    mccmnc_tuple: "52512"
+  }
+}
+carrier_id {
+  canonical_id: 1709
+  carrier_name: "SI Mobil"
+  carrier_attribute {
+    mccmnc_tuple: "29340"
+  }
+}
+carrier_id {
+  canonical_id: 1710
+  carrier_name: "Telekom Slovenije"
+  carrier_attribute {
+    mccmnc_tuple: "29341"
+  }
+}
+carrier_id {
+  canonical_id: 1711
+  carrier_name: "T-2 d.o.o."
+  carrier_attribute {
+    mccmnc_tuple: "29364"
+  }
+}
+carrier_id {
+  canonical_id: 1712
+  carrier_name: "Tusmobil d.o.o."
+  carrier_attribute {
+    mccmnc_tuple: "29370"
+  }
+}
+carrier_id {
+  canonical_id: 1713
+  carrier_name: "Orange"
+  carrier_attribute {
+    mccmnc_tuple: "23101"
+  }
+}
+carrier_id {
+  canonical_id: 1714
+  carrier_name: "Eurotel, UMTS"
+  carrier_attribute {
+    mccmnc_tuple: "23104"
+  }
+}
+carrier_id {
+  canonical_id: 1715
+  carrier_name: "Orange, UMTS"
+  carrier_attribute {
+    mccmnc_tuple: "23105"
+  }
+}
+carrier_id {
+  canonical_id: 1716
+  carrier_name: "Orange"
+  carrier_attribute {
+    mccmnc_tuple: "61901"
+  }
+}
+carrier_id {
+  canonical_id: 1717
+  carrier_name: "Millicom"
+  carrier_attribute {
+    mccmnc_tuple: "61902"
+  }
+}
+carrier_id {
+  canonical_id: 1718
+  carrier_name: "Africell"
+  carrier_attribute {
+    mccmnc_tuple: "61903"
+  }
+}
+carrier_id {
+  canonical_id: 1719
+  carrier_name: "Dtatel (SL) Ltd CDMA"
+  carrier_attribute {
+    mccmnc_tuple: "61950"
+  }
+}
+carrier_id {
+  canonical_id: 1720
+  carrier_name: "SMT - San  Marino Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "29201"
+  }
+}
+carrier_id {
+  canonical_id: 1721
+  carrier_name: "Orange Senegal"
+  carrier_attribute {
+    mccmnc_tuple: "60801"
+  }
+}
+carrier_id {
+  canonical_id: 1722
+  carrier_name: "Sentel GSM"
+  carrier_attribute {
+    mccmnc_tuple: "60802"
+  }
+}
+carrier_id {
+  canonical_id: 1723
+  carrier_name: "ACT Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "52015"
+  }
+}
+carrier_id {
+  canonical_id: 1724
+  carrier_name: "JC Somoncom"
+  carrier_attribute {
+    mccmnc_tuple: "43601"
+  }
+}
+carrier_id {
+  canonical_id: 1725
+  carrier_name: "CJSC Indigo Tajikistan"
+  carrier_attribute {
+    mccmnc_tuple: "43602"
+  }
+}
+carrier_id {
+  canonical_id: 1726
+  carrier_name: "TT mobile"
+  carrier_attribute {
+    mccmnc_tuple: "43603"
+  }
+}
+carrier_id {
+  canonical_id: 1727
+  carrier_name: "Josa Babilon-T"
+  carrier_attribute {
+    mccmnc_tuple: "43604"
+  }
+}
+carrier_id {
+  canonical_id: 1728
+  carrier_name: "CTJTHSC Tajik-tel"
+  carrier_attribute {
+    mccmnc_tuple: "43605"
+  }
+}
+carrier_id {
+  canonical_id: 1729
+  carrier_name: "Barash Communication Technologies (BCTI)"
+  carrier_attribute {
+    mccmnc_tuple: "43801"
+  }
+}
+carrier_id {
+  canonical_id: 1730
+  carrier_name: "TM-Cell"
+  carrier_attribute {
+    mccmnc_tuple: "43802"
+  }
+}
+carrier_id {
+  canonical_id: 1731
+  carrier_name: "Tunisie Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "60502"
+  }
+}
+carrier_id {
+  canonical_id: 1732
+  carrier_name: "Ooredoo Tunisia"
+  carrier_attribute {
+    mccmnc_tuple: "60503"
+  }
+}
+carrier_id {
+  canonical_id: 1733
+  carrier_name: "Tonga Communications Corporation"
+  carrier_attribute {
+    mccmnc_tuple: "53901"
+  }
+}
+carrier_id {
+  canonical_id: 1734
+  carrier_name: "Shoreline Communication"
+  carrier_attribute {
+    mccmnc_tuple: "53943"
+  }
+}
+carrier_id {
+  canonical_id: 1735
+  carrier_name: "Paycell | Turkcell"
+  carrier_attribute {
+    mccmnc_tuple: "28601"
+  }
+}
+carrier_id {
+  canonical_id: 1736
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "28602"
+  }
+}
+carrier_id {
+  canonical_id: 1737
+  carrier_name: "Türk Telekom"
+  carrier_attribute {
+    mccmnc_tuple: "28603"
+    mccmnc_tuple: "28604"
+  }
+}
+carrier_id {
+  canonical_id: 1739
+  carrier_name: "TSTT Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "37412"
+  }
+}
+carrier_id {
+  canonical_id: 1740
+  carrier_name: "Digicel Trinidad and Tobago Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "374130"
+  }
+}
+carrier_id {
+  canonical_id: 1741
+  carrier_name: "LaqTel Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "374140"
+  }
+}
+carrier_id {
+  canonical_id: 1742
+  carrier_name: "TIGO"
+  carrier_attribute {
+    mccmnc_tuple: "64002"
+  }
+}
+carrier_id {
+  canonical_id: 1743
+  carrier_name: "Zantel"
+  carrier_attribute {
+    mccmnc_tuple: "64003"
+  }
+}
+carrier_id {
+  canonical_id: 1744
+  carrier_name: "Vodacom (T) Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "64004"
+  }
+}
+carrier_id {
+  canonical_id: 1745
+  carrier_name: "Celtel (T) Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "64005"
+  }
+}
+carrier_id {
+  canonical_id: 1746
+  carrier_name: "Ukrainian Mobile Communication, UMC"
+  carrier_attribute {
+    mccmnc_tuple: "25501"
+  }
+}
+carrier_id {
+  canonical_id: 1747
+  carrier_name: "Kyivstar GSM"
+  carrier_attribute {
+    mccmnc_tuple: "25503"
+  }
+}
+carrier_id {
+  canonical_id: 1748
+  carrier_name: "International Telecommunications Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "25504"
+  }
+}
+carrier_id {
+  canonical_id: 1749
+  carrier_name: "Golden Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "25505"
+  }
+}
+carrier_id {
+  canonical_id: 1750
+  carrier_name: "Astelit"
+  carrier_attribute {
+    mccmnc_tuple: "25506"
+  }
+}
+carrier_id {
+  canonical_id: 1751
+  carrier_name: "Ukrtelecom"
+  carrier_attribute {
+    mccmnc_tuple: "25507"
+  }
+}
+carrier_id {
+  canonical_id: 1752
+  carrier_name: "CJSC - Telesystems of Ukraine"
+  carrier_attribute {
+    mccmnc_tuple: "25521"
+  }
+}
+carrier_id {
+  canonical_id: 1753
+  carrier_name: "Celtel Uganda"
+  carrier_attribute {
+    mccmnc_tuple: "64101"
+  }
+}
+carrier_id {
+  canonical_id: 1754
+  carrier_name: "MTN Uganda Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "64110"
+  }
+}
+carrier_id {
+  canonical_id: 1755
+  carrier_name: "Uganda Telecom Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "64111"
+  }
+}
+carrier_id {
+  canonical_id: 1756
+  carrier_name: "House of Integrated Technology and Systems Uganda Ltd"
+  carrier_attribute {
+    mccmnc_tuple: "64114"
+  }
+}
+carrier_id {
+  canonical_id: 1757
+  carrier_name: "Warid Telecom Uganda Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "64122"
+  }
+}
+carrier_id {
+  canonical_id: 1758
+  carrier_name: "ICO Global Communications"
+  carrier_attribute {
+    mccmnc_tuple: "90101"
+  }
+}
+carrier_id {
+  canonical_id: 1759
+  carrier_name: "Sense Communications International AS"
+  carrier_attribute {
+    mccmnc_tuple: "90102"
+  }
+}
+carrier_id {
+  canonical_id: 1760
+  carrier_name: "Ellipso"
+  carrier_attribute {
+    mccmnc_tuple: "90107"
+  }
+}
+carrier_id {
+  canonical_id: 1761
+  carrier_name: "Tele1 Europe"
+  carrier_attribute {
+    mccmnc_tuple: "90109"
+  }
+}
+carrier_id {
+  canonical_id: 1762
+  carrier_name: "Asia Cellular Satellite (AceS)"
+  carrier_attribute {
+    mccmnc_tuple: "90110"
+  }
+}
+carrier_id {
+  canonical_id: 1763
+  carrier_name: "Inmarsat Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "90111"
+  }
+}
+carrier_id {
+  canonical_id: 1764
+  carrier_name: "Maritime Communications Partner AS (MCP network)"
+  carrier_attribute {
+    mccmnc_tuple: "90112"
+  }
+}
+carrier_id {
+  canonical_id: 1765
+  carrier_name: "Global Networks, Inc."
+  carrier_attribute {
+    mccmnc_tuple: "90113"
+  }
+}
+carrier_id {
+  canonical_id: 1766
+  carrier_name: "Telenor GSM - services in aircraft"
+  carrier_attribute {
+    mccmnc_tuple: "90114"
+  }
+}
+carrier_id {
+  canonical_id: 1767
+  carrier_name: "SITA GSM services in aircraft (On Air)"
+  carrier_attribute {
+    mccmnc_tuple: "90115"
+  }
+}
+carrier_id {
+  canonical_id: 1768
+  carrier_name: "Jasper Systems, Inc."
+  carrier_attribute {
+    mccmnc_tuple: "90116"
+  }
+}
+carrier_id {
+  canonical_id: 1769
+  carrier_name: "Jersey Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "90117"
+  }
+}
+carrier_id {
+  canonical_id: 1770
+  carrier_name: "AT&T Mobility (Wireless Maritime Services)"
+  carrier_attribute {
+    mccmnc_tuple: "90118"
+  }
+}
+carrier_id {
+  canonical_id: 1771
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "90119"
+  }
+}
+carrier_id {
+  canonical_id: 1772
+  carrier_name: "Intermatica"
+  carrier_attribute {
+    mccmnc_tuple: "90120"
+  }
+}
+carrier_id {
+  canonical_id: 1773
+  carrier_name: "Seanet Maritime Communications"
+  carrier_attribute {
+    mccmnc_tuple: "90121"
+  }
+}
+carrier_id {
+  canonical_id: 1774
+  carrier_name: "Denver Consultants Ltd"
+  carrier_attribute {
+    mccmnc_tuple: "90122"
+  }
+}
+carrier_id {
+  canonical_id: 1775
+  carrier_name: "Telecommunications for Disaster Relief (TDR) (OCHA)"
+  carrier_attribute {
+    mccmnc_tuple: "90188"
+  }
+}
+carrier_id {
+  canonical_id: 1776
+  carrier_name: "MCI"
+  carrier_attribute {
+    mccmnc_tuple: "310010"
+  }
+}
+carrier_id {
+  canonical_id: 1778
+  carrier_name: "Mobile Tel Inc."
+  carrier_attribute {
+    mccmnc_tuple: "310013"
+  }
+}
+carrier_id {
+  canonical_id: 1779
+  carrier_name: "Cricket Wireless"
+  carrier_attribute {
+    mccmnc_tuple: "310016"
+    mccmnc_tuple: "310090"
+  }
+}
+carrier_id {
+  canonical_id: 1780
+  carrier_name: "North Sight Communications Inc."
+  carrier_attribute {
+    mccmnc_tuple: "310017"
+  }
+}
+carrier_id {
+  canonical_id: 1781
+  carrier_name: "Union Telephone Company"
+  carrier_attribute {
+    mccmnc_tuple: "310020"
+  }
+}
+carrier_id {
+  canonical_id: 1783
+  carrier_name: "Nevada Wireless LLC"
+  carrier_attribute {
+    mccmnc_tuple: "310034"
+  }
+}
+carrier_id {
+  canonical_id: 1784
+  carrier_name: "MTA Communications dba MTA Wireless"
+  carrier_attribute {
+    mccmnc_tuple: "310040"
+  }
+}
+carrier_id {
+  canonical_id: 1785
+  carrier_name: "ACS Wireless Inc."
+  carrier_attribute {
+    mccmnc_tuple: "310050"
+  }
+}
+carrier_id {
+  canonical_id: 1787
+  carrier_name: "Pacific Telecom Inc"
+  carrier_attribute {
+    mccmnc_tuple: "310110"
+  }
+}
+carrier_id {
+  canonical_id: 1788
+  carrier_name: "Sprint"
+  carrier_attribute {
+    mccmnc_tuple: "310120"
+    mccmnc_tuple: "312530"
+  }
+}
+carrier_id {
+  canonical_id: 1789
+  carrier_name: "Carolina West Wireless"
+  carrier_attribute {
+    mccmnc_tuple: "310130"
+  }
+}
+carrier_id {
+  canonical_id: 1790
+  carrier_name: "GTA Wireless LLC"
+  carrier_attribute {
+    mccmnc_tuple: "310140"
+  }
+}
+carrier_id {
+  canonical_id: 1792
+  carrier_name: "West Central Wireless"
+  carrier_attribute {
+    mccmnc_tuple: "310180"
+  }
+}
+carrier_id {
+  canonical_id: 1793
+  carrier_name: "Alaska Wireless Communications LLC"
+  carrier_attribute {
+    mccmnc_tuple: "310190"
+  }
+}
+carrier_id {
+  canonical_id: 1795
+  carrier_name: "Nep Cellcorp Inc."
+  carrier_attribute {
+    mccmnc_tuple: "310290"
+  }
+}
+carrier_id {
+  canonical_id: 1796
+  carrier_name: "Smith Bagley Inc, dba Cellular One"
+  carrier_attribute {
+    mccmnc_tuple: "310320"
+  }
+}
+carrier_id {
+  canonical_id: 1797
+  carrier_name: "AN Subsidiary LLC"
+  carrier_attribute {
+    mccmnc_tuple: "310330"
+  }
+}
+carrier_id {
+  canonical_id: 1798
+  carrier_name: "Wireless Solutions International"
+  carrier_attribute {
+    mccmnc_tuple: "310550"
+  }
+}
+carrier_id {
+  canonical_id: 1800
+  carrier_name: "MTPCS LLC"
+  carrier_attribute {
+    mccmnc_tuple: "310570"
+  }
+}
+carrier_id {
+  canonical_id: 1802
+  carrier_name: "New Cell Inc. dba Cellcom"
+  carrier_attribute {
+    mccmnc_tuple: "310600"
+    mccmnc_tuple: "31060"
+  }
+}
+carrier_id {
+  canonical_id: 1803
+  carrier_name: "Elkhart Telephone Co. Inc. dba Epic Touch Co."
+  carrier_attribute {
+    mccmnc_tuple: "310610"
+  }
+}
+carrier_id {
+  canonical_id: 1804
+  carrier_name: "Coleman County Telecommunications Inc. (Trans Texas PCS)"
+  carrier_attribute {
+    mccmnc_tuple: "310620"
+  }
+}
+carrier_id {
+  canonical_id: 1806
+  carrier_name: "Jasper Wireless Inc."
+  carrier_attribute {
+    mccmnc_tuple: "310650"
+  }
+}
+carrier_id {
+  canonical_id: 1807
+  carrier_name: "AT&T Mobility Vanguard Services"
+  carrier_attribute {
+    mccmnc_tuple: "310670"
+  }
+}
+carrier_id {
+  canonical_id: 1808
+  carrier_name: "Conestoga Wireless Company"
+  carrier_attribute {
+    mccmnc_tuple: "310690"
+  }
+}
+carrier_id {
+  canonical_id: 1809
+  carrier_name: "Cross Valiant Cellular Partnership"
+  carrier_attribute {
+    mccmnc_tuple: "310700"
+  }
+}
+carrier_id {
+  canonical_id: 1810
+  carrier_name: "Arctic Slopo Telephone Association Cooperative"
+  carrier_attribute {
+    mccmnc_tuple: "310710"
+  }
+}
+carrier_id {
+  canonical_id: 1811
+  carrier_name: "Wireless Solutions International Inc."
+  carrier_attribute {
+    mccmnc_tuple: "310720"
+  }
+}
+carrier_id {
+  canonical_id: 1812
+  carrier_name: "Sea Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "310730"
+  }
+}
+carrier_id {
+  canonical_id: 1813
+  carrier_name: "East Kentucky Network LLC dba Appalachian Wireless"
+  carrier_attribute {
+    mccmnc_tuple: "310750"
+  }
+}
+carrier_id {
+  canonical_id: 1814
+  carrier_name: "Panhandle Telecommunications Systems Inc."
+  carrier_attribute {
+    mccmnc_tuple: "310760"
+  }
+}
+carrier_id {
+  canonical_id: 1815
+  carrier_name: "Iowa Wireless Services LLC dba I Wireless"
+  carrier_attribute {
+    mccmnc_tuple: "310770"
+  }
+}
+carrier_id {
+  canonical_id: 1816
+  carrier_name: "Connect Net Inc"
+  carrier_attribute {
+    mccmnc_tuple: "310780"
+  }
+}
+carrier_id {
+  canonical_id: 1817
+  carrier_name: "PinPoint Communications Inc."
+  carrier_attribute {
+    mccmnc_tuple: "310790"
+  }
+}
+carrier_id {
+  canonical_id: 1818
+  carrier_name: "Brazos Cellular Communications Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "310810"
+  }
+}
+carrier_id {
+  canonical_id: 1819
+  carrier_name: "South Canaan Cellular Communications Co. LP"
+  carrier_attribute {
+    mccmnc_tuple: "310820"
+  }
+}
+carrier_id {
+  canonical_id: 1820
+  carrier_name: "Caprock Cellular Ltd. Partnership"
+  carrier_attribute {
+    mccmnc_tuple: "310830"
+  }
+}
+carrier_id {
+  canonical_id: 1821
+  carrier_name: "Edge Mobile LLC"
+  carrier_attribute {
+    mccmnc_tuple: "310840"
+  }
+}
+carrier_id {
+  canonical_id: 1822
+  carrier_name: "Aeris Communications, Inc."
+  carrier_attribute {
+    mccmnc_tuple: "310850"
+  }
+}
+carrier_id {
+  canonical_id: 1823
+  carrier_name: "TX RSA 15B2, LP dba Five Star Wireless"
+  carrier_attribute {
+    mccmnc_tuple: "310860"
+  }
+}
+carrier_id {
+  canonical_id: 1824
+  carrier_name: "Kaplan Telephone Company Inc."
+  carrier_attribute {
+    mccmnc_tuple: "310870"
+  }
+}
+carrier_id {
+  canonical_id: 1825
+  carrier_name: "Advantage Cellular Systems, Inc."
+  carrier_attribute {
+    mccmnc_tuple: "310880"
+  }
+}
+carrier_id {
+  canonical_id: 1826
+  carrier_name: "Rural Cellular Corporation"
+  carrier_attribute {
+    mccmnc_tuple: "310890"
+  }
+}
+carrier_id {
+  canonical_id: 1827
+  carrier_name: "Taylor Telecommunications Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "310900"
+  }
+}
+carrier_id {
+  canonical_id: 1828
+  carrier_name: "Southern IL RSA Partnership dba First Cellular of Southern Illinois"
+  carrier_attribute {
+    mccmnc_tuple: "310910"
+  }
+}
+carrier_id {
+  canonical_id: 1829
+  carrier_name: "Get Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "310920"
+  }
+}
+carrier_id {
+  canonical_id: 1830
+  carrier_name: "Copper Valley Wireless"
+  carrier_attribute {
+    mccmnc_tuple: "310930"
+  }
+}
+carrier_id {
+  canonical_id: 1831
+  carrier_name: "PetroCom LLC"
+  carrier_attribute {
+    mccmnc_tuple: "311170"
+  }
+}
+carrier_id {
+  canonical_id: 1833
+  carrier_name: "Cellular Properties Inc."
+  carrier_attribute {
+    mccmnc_tuple: "311190"
+  }
+}
+carrier_id {
+  canonical_id: 1834
+  carrier_name: "ARINC"
+  carrier_attribute {
+    mccmnc_tuple: "311200"
+  }
+}
+carrier_id {
+  canonical_id: 1835
+  carrier_name: "Farmers Cellular Telephone"
+  carrier_attribute {
+    mccmnc_tuple: "311210"
+  }
+}
+carrier_id {
+  canonical_id: 1836
+  carrier_name: "Cellular South Inc."
+  carrier_attribute {
+    mccmnc_tuple: "311230"
+    mccmnc_tuple: "310023"
+  }
+}
+carrier_id {
+  canonical_id: 1837
+  carrier_name: "Cordova Wireless Communications Inc"
+  carrier_attribute {
+    mccmnc_tuple: "311240"
+  }
+}
+carrier_id {
+  canonical_id: 1838
+  carrier_name: "SLO Cellular Inc. dba CellularOne of San Luis Obispo"
+  carrier_attribute {
+    mccmnc_tuple: "311260"
+  }
+}
+carrier_id {
+  canonical_id: 1839
+  carrier_name: "Verizon Wireless"
+  carrier_attribute {
+    mccmnc_tuple: "20404"
+    gid1: "BAE0000000000000"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "310004"
+    mccmnc_tuple: "310012"
+    mccmnc_tuple: "311280"
+    mccmnc_tuple: "311281"
+    mccmnc_tuple: "311282"
+    mccmnc_tuple: "311283"
+    mccmnc_tuple: "311284"
+    mccmnc_tuple: "311285"
+    mccmnc_tuple: "311286"
+    mccmnc_tuple: "311287"
+    mccmnc_tuple: "311288"
+    mccmnc_tuple: "311289"
+    mccmnc_tuple: "311480"
+    mccmnc_tuple: "311481"
+    mccmnc_tuple: "311482"
+    mccmnc_tuple: "311483"
+    mccmnc_tuple: "311484"
+    mccmnc_tuple: "311485"
+    mccmnc_tuple: "311486"
+    mccmnc_tuple: "311487"
+    mccmnc_tuple: "311488"
+    mccmnc_tuple: "311489"
+  }
+}
+carrier_id {
+  canonical_id: 1840
+  carrier_name: "Pinpoint Wireless Inc."
+  carrier_attribute {
+    mccmnc_tuple: "311290"
+  }
+}
+carrier_id {
+  canonical_id: 1841
+  carrier_name: "Rutal Cellular Corporation"
+  carrier_attribute {
+    mccmnc_tuple: "311300"
+  }
+}
+carrier_id {
+  canonical_id: 1842
+  carrier_name: "Leaco Rural Telephone Company Inc"
+  carrier_attribute {
+    mccmnc_tuple: "311310"
+  }
+}
+carrier_id {
+  canonical_id: 1843
+  carrier_name: "GCI Communications Corp."
+  carrier_attribute {
+    mccmnc_tuple: "311370"
+  }
+}
+carrier_id {
+  canonical_id: 1844
+  carrier_name: "GreenFly LLC"
+  carrier_attribute {
+    mccmnc_tuple: "311380"
+  }
+}
+carrier_id {
+  canonical_id: 1845
+  carrier_name: "Midwest Wireless Holdings LLC"
+  carrier_attribute {
+    mccmnc_tuple: "311390"
+  }
+}
+carrier_id {
+  canonical_id: 1846
+  carrier_name: "Iowa RSA No.2 Ltd Partnership"
+  carrier_attribute {
+    mccmnc_tuple: "311410"
+  }
+}
+carrier_id {
+  canonical_id: 1847
+  carrier_name: "Northwest Missouri Cellular Limited Partnership"
+  carrier_attribute {
+    mccmnc_tuple: "311420"
+  }
+}
+carrier_id {
+  canonical_id: 1848
+  carrier_name: "RSA 1 Limited Partnership dba Cellular 29 Plus"
+  carrier_attribute {
+    mccmnc_tuple: "311430"
+  }
+}
+carrier_id {
+  canonical_id: 1849
+  carrier_name: "Bluegrass Cellular LLC"
+  carrier_attribute {
+    mccmnc_tuple: "311440"
+  }
+}
+carrier_id {
+  canonical_id: 1850
+  carrier_name: "Panhandle Telecommunication Systems Inc."
+  carrier_attribute {
+    mccmnc_tuple: "311450"
+  }
+}
+carrier_id {
+  canonical_id: 1851
+  carrier_name: "Fisher Wireless Services Inc"
+  carrier_attribute {
+    mccmnc_tuple: "311460"
+  }
+}
+carrier_id {
+  canonical_id: 1852
+  carrier_name: "Vitelcom Cellular Inc dba Innovative Wireless"
+  carrier_attribute {
+    mccmnc_tuple: "311470"
+  }
+}
+carrier_id {
+  canonical_id: 1853
+  carrier_name: "Virgin Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "311490"
+  }
+}
+carrier_id {
+  canonical_id: 1854
+  carrier_name: "CTC Telecom Inc"
+  carrier_attribute {
+    mccmnc_tuple: "311500"
+  }
+}
+carrier_id {
+  canonical_id: 1855
+  carrier_name: "Benton-Lian Wireless"
+  carrier_attribute {
+    mccmnc_tuple: "311510"
+  }
+}
+carrier_id {
+  canonical_id: 1856
+  carrier_name: "Crossroads Wireless Inc"
+  carrier_attribute {
+    mccmnc_tuple: "311520"
+  }
+}
+carrier_id {
+  canonical_id: 1857
+  carrier_name: "Wireless Communications Venture"
+  carrier_attribute {
+    mccmnc_tuple: "311530"
+  }
+}
+carrier_id {
+  canonical_id: 1858
+  carrier_name: "Keystone Wireless Inc"
+  carrier_attribute {
+    mccmnc_tuple: "311540"
+  }
+}
+carrier_id {
+  canonical_id: 1859
+  carrier_name: "Commnet Midwest LLC"
+  carrier_attribute {
+    mccmnc_tuple: "311550"
+  }
+}
+carrier_id {
+  canonical_id: 1860
+  carrier_name: "Nextel Communications Inc."
+  carrier_attribute {
+    mccmnc_tuple: "316010"
+  }
+}
+carrier_id {
+  canonical_id: 1861
+  carrier_name: "Southern Communications Services Inc."
+  carrier_attribute {
+    mccmnc_tuple: "316011"
+  }
+}
+carrier_id {
+  canonical_id: 1862
+  carrier_name: "Ancel"
+  carrier_attribute {
+    mccmnc_tuple: "74800"
+    mccmnc_tuple: "74801"
+    mccmnc_tuple: "74803"
+  }
+}
+carrier_id {
+  canonical_id: 1863
+  carrier_name: "Movistar"
+  carrier_attribute {
+    mccmnc_tuple: "74807"
+  }
+}
+carrier_id {
+  canonical_id: 1864
+  carrier_name: "Claro UY"
+  carrier_attribute {
+    mccmnc_tuple: "74810"
+  }
+}
+carrier_id {
+  canonical_id: 1865
+  carrier_name: "Buztel"
+  carrier_attribute {
+    mccmnc_tuple: "43401"
+  }
+}
+carrier_id {
+  canonical_id: 1866
+  carrier_name: "Uzmacom"
+  carrier_attribute {
+    mccmnc_tuple: "43402"
+  }
+}
+carrier_id {
+  canonical_id: 1867
+  carrier_name: "Daewoo Unitel"
+  carrier_attribute {
+    mccmnc_tuple: "43404"
+  }
+}
+carrier_id {
+  canonical_id: 1868
+  carrier_name: "Coscom"
+  carrier_attribute {
+    mccmnc_tuple: "43405"
+  }
+}
+carrier_id {
+  canonical_id: 1869
+  carrier_name: "Uzdunrobita"
+  carrier_attribute {
+    mccmnc_tuple: "43407"
+  }
+}
+carrier_id {
+  canonical_id: 1870
+  carrier_name: "Infonet"
+  carrier_attribute {
+    mccmnc_tuple: "73401"
+  }
+}
+carrier_id {
+  canonical_id: 1871
+  carrier_name: "Corporación Digitel"
+  carrier_attribute {
+    mccmnc_tuple: "73402"
+  }
+}
+carrier_id {
+  canonical_id: 1872
+  carrier_name: "Digicel"
+  carrier_attribute {
+    mccmnc_tuple: "73403"
+  }
+}
+carrier_id {
+  canonical_id: 1873
+  carrier_name: "Movistar"
+  carrier_attribute {
+    mccmnc_tuple: "73404"
+  }
+}
+carrier_id {
+  canonical_id: 1874
+  carrier_name: "Telecomunicaciones Movilnet, C.A."
+  carrier_attribute {
+    mccmnc_tuple: "73406"
+  }
+}
+carrier_id {
+  canonical_id: 1875
+  carrier_name: "Caribbean Cellular Telephone, Boatphone Ltd."
+  carrier_attribute {
+    mccmnc_tuple: "348570"
+  }
+}
+carrier_id {
+  canonical_id: 1876
+  carrier_name: "GoMobile SamoaTel Ltd"
+  carrier_attribute {
+    mccmnc_tuple: "54927"
+  }
+}
+carrier_id {
+  canonical_id: 1877
+  carrier_name: "Yemen Mobile Phone Company"
+  carrier_attribute {
+    mccmnc_tuple: "42101"
+  }
+}
+carrier_id {
+  canonical_id: 1878
+  carrier_name: "Spacetel Yemen"
+  carrier_attribute {
+    mccmnc_tuple: "42102"
+  }
+}
+carrier_id {
+  canonical_id: 1879
+  carrier_name: "Telecel"
+  carrier_attribute {
+    mccmnc_tuple: "64803"
+  }
+}
+carrier_id {
+  canonical_id: 1880
+  carrier_name: "Econet"
+  carrier_attribute {
+    mccmnc_tuple: "64804"
+  }
+}
+carrier_id {
+  canonical_id: 1881
+  carrier_name: "遠傳電信 Far EasTone Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "46601"
+    mccmnc_tuple: "46688"
+  }
+}
+carrier_id {
+  canonical_id: 1882
+  carrier_name: "亞太電信 Asia Pacific Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "46602"
+    mccmnc_tuple: "46605"
+  }
+}
+carrier_id {
+  canonical_id: 1883
+  carrier_name: "Tuntex Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "46606"
+  }
+}
+carrier_id {
+  canonical_id: 1884
+  carrier_name: "中華電信_Chunghwa Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "46611"
+    mccmnc_tuple: "46692"
+  }
+}
+carrier_id {
+  canonical_id: 1886
+  carrier_name: "台灣之星 T Star"
+  carrier_attribute {
+    mccmnc_tuple: "46689"
+  }
+}
+carrier_id {
+  canonical_id: 1887
+  carrier_name: "MobiTai Communications"
+  carrier_attribute {
+    mccmnc_tuple: "46693"
+  }
+}
+carrier_id {
+  canonical_id: 1888
+  carrier_name: "台灣大哥大 Taiwan Mobile Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "46697"
+  }
+}
+carrier_id {
+  canonical_id: 1889
+  carrier_name: "TransAsia Telecoms"
+  carrier_attribute {
+    mccmnc_tuple: "46699"
+  }
+}
+carrier_id {
+  canonical_id: 1890
+  carrier_name: "KT"
+  carrier_attribute {
+    mccmnc_tuple: "45002"
+    mccmnc_tuple: "45004"
+    mccmnc_tuple: "45008"
+  }
+}
+carrier_id {
+  canonical_id: 1891
+  carrier_name: "SK Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "45005"
+  }
+}
+carrier_id {
+  canonical_id: 1892
+  carrier_name: "LG U+"
+  carrier_attribute {
+    mccmnc_tuple: "45006"
+    mccmnc_tuple: "450006"
+  }
+}
+carrier_id {
+  canonical_id: 1893
+  carrier_name: "nTelos"
+  carrier_attribute {
+    mccmnc_tuple: "310470"
+  }
+}
+carrier_id {
+  canonical_id: 1894
+  carrier_name: "SoftBank・Y!mobile"
+  carrier_attribute {
+    mccmnc_tuple: "44020"
+  }
+}
+carrier_id {
+  canonical_id: 1895
+  carrier_name: "Freedom Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "302490"
+  }
+}
+carrier_id {
+  canonical_id: 1896
+  carrier_name: "dtac"
+  carrier_attribute {
+    mccmnc_tuple: "52018"
+  }
+}
+carrier_id {
+  canonical_id: 1897
+  carrier_name: "dtac "
+  carrier_attribute {
+    mccmnc_tuple: "52005"
+  }
+}
+carrier_id {
+  canonical_id: 1898
+  carrier_name: "True Move"
+  carrier_attribute {
+    mccmnc_tuple: "52099"
+  }
+}
+carrier_id {
+  canonical_id: 1899
+  carrier_name: "Viettel Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "45204"
+  }
+}
+carrier_id {
+  canonical_id: 1900
+  carrier_name: "OneCall"
+  carrier_attribute {
+    mccmnc_tuple: "24205"
+  }
+}
+carrier_id {
+  canonical_id: 1901
+  carrier_name: "Android Emulator"
+  carrier_attribute {
+    mccmnc_tuple: "310995"
+  }
+}
+carrier_id {
+  canonical_id: 1902
+  carrier_name: "Access Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "27204"
+  }
+}
+carrier_id {
+  canonical_id: 1903
+  carrier_name: "3"
+  carrier_attribute {
+    mccmnc_tuple: "27205"
+  }
+}
+carrier_id {
+  canonical_id: 1904
+  carrier_name: "DNA"
+  carrier_attribute {
+    mccmnc_tuple: "24403"
+    mccmnc_tuple: "24412"
+    mccmnc_tuple: "24413"
+  }
+}
+carrier_id {
+  canonical_id: 1905
+  carrier_name: "Nokia Test Network"
+  carrier_attribute {
+    mccmnc_tuple: "24407"
+  }
+}
+carrier_id {
+  canonical_id: 1906
+  carrier_name: "Unknown"
+  carrier_attribute {
+    mccmnc_tuple: "24408"
+  }
+}
+carrier_id {
+  canonical_id: 1907
+  carrier_name: "TDC"
+  carrier_attribute {
+    mccmnc_tuple: "24410"
+  }
+}
+carrier_id {
+  canonical_id: 1908
+  carrier_name: "Scnl Truphone"
+  carrier_attribute {
+    mccmnc_tuple: "24429"
+  }
+}
+carrier_id {
+  canonical_id: 1909
+  carrier_name: "Euskaltel"
+  carrier_attribute {
+    mccmnc_tuple: "21406"
+    mccmnc_tuple: "21408"
+  }
+}
+carrier_id {
+  canonical_id: 1910
+  carrier_name: "COX"
+  carrier_attribute {
+    mccmnc_tuple: "311600"
+  }
+}
+carrier_id {
+  canonical_id: 1911
+  carrier_name: "Test Network, Used by GSM test equipment"
+  carrier_attribute {
+    mccmnc_tuple: "00101"
+  }
+}
+carrier_id {
+  canonical_id: 1912
+  carrier_name: "AT&T MX"
+  carrier_attribute {
+    mccmnc_tuple: "33401"
+    mccmnc_tuple: "334090"
+    mccmnc_tuple: "33409"
+  }
+}
+carrier_id {
+  canonical_id: 1913
+  carrier_name: "Telcel"
+  carrier_attribute {
+    mccmnc_tuple: "334020"
+    mccmnc_tuple: "33402"
+  }
+}
+carrier_id {
+  canonical_id: 1914
+  carrier_name: "Movistar"
+  carrier_attribute {
+    mccmnc_tuple: "334030"
+    mccmnc_tuple: "33403"
+  }
+}
+carrier_id {
+  canonical_id: 1915
+  carrier_name: "AT&T MX"
+  carrier_attribute {
+    mccmnc_tuple: "334050"
+  }
+}
+carrier_id {
+  canonical_id: 1916
+  carrier_name: "Dauphin Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "34080"
+  }
+}
+carrier_id {
+  canonical_id: 1917
+  carrier_name: "eMobile"
+  carrier_attribute {
+    mccmnc_tuple: "44000"
+  }
+}
+carrier_id {
+  canonical_id: 1918
+  carrier_name: "Hondutel"
+  carrier_attribute {
+    mccmnc_tuple: "70830"
+  }
+}
+carrier_id {
+  canonical_id: 1919
+  carrier_name: "Digicel"
+  carrier_attribute {
+    mccmnc_tuple: "70840"
+  }
+}
+carrier_id {
+  canonical_id: 1920
+  carrier_name: "Alfa"
+  carrier_attribute {
+    mccmnc_tuple: "41501"
+  }
+}
+carrier_id {
+  canonical_id: 1921
+  carrier_name: "MTC Touch"
+  carrier_attribute {
+    mccmnc_tuple: "41503"
+  }
+}
+carrier_id {
+  canonical_id: 1922
+  carrier_name: "Ogero Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "41505"
+  }
+}
+carrier_id {
+  canonical_id: 1923
+  carrier_name: "Ncell"
+  carrier_attribute {
+    mccmnc_tuple: "42902"
+  }
+}
+carrier_id {
+  canonical_id: 1924
+  carrier_name: "Smart Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "42904"
+  }
+}
+carrier_id {
+  canonical_id: 1925
+  carrier_name: "Claro PA"
+  carrier_attribute {
+    mccmnc_tuple: "71403"
+  }
+}
+carrier_id {
+  canonical_id: 1926
+  carrier_name: "Digicel"
+  carrier_attribute {
+    mccmnc_tuple: "71404"
+  }
+}
+carrier_id {
+  canonical_id: 1927
+  carrier_name: "Telecel"
+  carrier_attribute {
+    mccmnc_tuple: "74404"
+  }
+}
+carrier_id {
+  canonical_id: 1928
+  carrier_name: "Personal"
+  carrier_attribute {
+    mccmnc_tuple: "74405"
+  }
+}
+carrier_id {
+  canonical_id: 1929
+  carrier_name: "Telefonica Moviles Peru"
+  carrier_attribute {
+    mccmnc_tuple: "71606"
+  }
+}
+carrier_id {
+  canonical_id: 1930
+  carrier_name: "Nextel"
+  carrier_attribute {
+    mccmnc_tuple: "71617"
+  }
+}
+carrier_id {
+  canonical_id: 1931
+  carrier_name: "Sri Lanka Telecom Mobitel"
+  carrier_attribute {
+    mccmnc_tuple: "41301"
+  }
+}
+carrier_id {
+  canonical_id: 1932
+  carrier_name: "Airtel Lanka"
+  carrier_attribute {
+    mccmnc_tuple: "41305"
+  }
+}
+carrier_id {
+  canonical_id: 1933
+  carrier_name: "Hutchison Telecommunications Lanka"
+  carrier_attribute {
+    mccmnc_tuple: "41308"
+  }
+}
+carrier_id {
+  canonical_id: 1934
+  carrier_name: "Sasatel Tanzania"
+  carrier_attribute {
+    mccmnc_tuple: "64006"
+  }
+}
+carrier_id {
+  canonical_id: 1935
+  carrier_name: "Life Tanzania"
+  carrier_attribute {
+    mccmnc_tuple: "64007"
+  }
+}
+carrier_id {
+  canonical_id: 1936
+  carrier_name: "Orange Tunisie"
+  carrier_attribute {
+    mccmnc_tuple: "60501"
+  }
+}
+carrier_id {
+  canonical_id: 1937
+  carrier_name: "ArmenTel"
+  carrier_attribute {
+    mccmnc_tuple: "28301"
+  }
+}
+carrier_id {
+  canonical_id: 1938
+  carrier_name: "Karabakh Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "28304"
+  }
+}
+carrier_id {
+  canonical_id: 1939
+  carrier_name: "K Telecom CJSC"
+  carrier_attribute {
+    mccmnc_tuple: "28305"
+  }
+}
+carrier_id {
+  canonical_id: 1940
+  carrier_name: "Orange"
+  carrier_attribute {
+    mccmnc_tuple: "28310"
+  }
+}
+carrier_id {
+  canonical_id: 1941
+  carrier_name: "Bahamas Telecommunications"
+  carrier_attribute {
+    mccmnc_tuple: "364390"
+  }
+}
+carrier_id {
+  canonical_id: 1942
+  carrier_name: "Open Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "33000"
+  }
+}
+carrier_id {
+  canonical_id: 1943
+  carrier_name: "Orange Niger"
+  carrier_attribute {
+    mccmnc_tuple: "61404"
+  }
+}
+carrier_id {
+  canonical_id: 1944
+  carrier_name: "Bell Benin Communications"
+  carrier_attribute {
+    mccmnc_tuple: "61604"
+  }
+}
+carrier_id {
+  canonical_id: 1945
+  carrier_name: "Glo Communications Benin"
+  carrier_attribute {
+    mccmnc_tuple: "61605"
+  }
+}
+carrier_id {
+  canonical_id: 1946
+  carrier_name: "Onatal (Telmob)"
+  carrier_attribute {
+    mccmnc_tuple: "61301"
+  }
+}
+carrier_id {
+  canonical_id: 1947
+  carrier_name: "Moov Togo"
+  carrier_attribute {
+    mccmnc_tuple: "61503"
+  }
+}
+carrier_id {
+  canonical_id: 1948
+  carrier_name: "Orange"
+  carrier_attribute {
+    mccmnc_tuple: "63203"
+  }
+}
+carrier_id {
+  canonical_id: 1949
+  carrier_name: "MetroPCS"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "6D38"
+  }
+}
+carrier_id {
+  canonical_id: 1950
+  carrier_name: "life:)"
+  carrier_attribute {
+    mccmnc_tuple: "25704"
+  }
+}
+carrier_id {
+  canonical_id: 1951
+  carrier_name: "Boost Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "311870"
+  }
+}
+carrier_id {
+  canonical_id: 1952
+  carrier_name: "U.S. Cellular"
+  carrier_attribute {
+    mccmnc_tuple: "311580"
+    mccmnc_tuple: "311220"
+  }
+}
+carrier_id {
+  canonical_id: 1953
+  carrier_name: "Claro CR"
+  carrier_attribute {
+    mccmnc_tuple: "71203"
+  }
+}
+carrier_id {
+  canonical_id: 1954
+  carrier_name: "Claro SV"
+  carrier_attribute {
+    mccmnc_tuple: "70601"
+  }
+}
+carrier_id {
+  canonical_id: 1955
+  carrier_name: "Claro PR"
+  carrier_attribute {
+    mccmnc_tuple: "330110"
+    mccmnc_tuple: "33011"
+  }
+}
+carrier_id {
+  canonical_id: 1956
+  carrier_name: "Telenor Sweden (not used)"
+  carrier_attribute {
+    mccmnc_tuple: "24009"
+  }
+}
+carrier_id {
+  canonical_id: 1957
+  carrier_name: "Bluesky"
+  carrier_attribute {
+    mccmnc_tuple: "54411"
+  }
+}
+carrier_id {
+  canonical_id: 1958
+  carrier_name: "Free"
+  carrier_attribute {
+    mccmnc_tuple: "20815"
+  }
+}
+carrier_id {
+  canonical_id: 1959
+  carrier_name: "Digi.Mobil"
+  carrier_attribute {
+    mccmnc_tuple: "22605"
+  }
+}
+carrier_id {
+  canonical_id: 1960
+  carrier_name: "Hutchison 2G"
+  carrier_attribute {
+    mccmnc_tuple: "45404"
+    mccmnc_tuple: "45405"
+  }
+}
+carrier_id {
+  canonical_id: 1961
+  carrier_name: "Airtel"
+  carrier_attribute {
+    mccmnc_tuple: "40402"
+    mccmnc_tuple: "40403"
+    mccmnc_tuple: "40410"
+    mccmnc_tuple: "40416"
+    mccmnc_tuple: "40431"
+    mccmnc_tuple: "40440"
+    mccmnc_tuple: "40445"
+    mccmnc_tuple: "40449"
+    mccmnc_tuple: "40470"
+    mccmnc_tuple: "40490"
+    mccmnc_tuple: "40492"
+    mccmnc_tuple: "40493"
+    mccmnc_tuple: "40494"
+    mccmnc_tuple: "40495"
+    mccmnc_tuple: "40496"
+    mccmnc_tuple: "40497"
+    mccmnc_tuple: "40498"
+    mccmnc_tuple: "40551"
+    mccmnc_tuple: "40552"
+    mccmnc_tuple: "40553"
+    mccmnc_tuple: "40554"
+    mccmnc_tuple: "40555"
+    mccmnc_tuple: "40556"
+  }
+}
+carrier_id {
+  canonical_id: 1962
+  carrier_name: "Fido"
+  carrier_attribute {
+    mccmnc_tuple: "302370"
+    gid1: "DD"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "302370"
+    mccmnc_tuple: "30237"
+  }
+}
+carrier_id {
+  canonical_id: 1963
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "40401"
+    mccmnc_tuple: "40405"
+    mccmnc_tuple: "40411"
+    mccmnc_tuple: "40413"
+    mccmnc_tuple: "40415"
+    mccmnc_tuple: "40420"
+    mccmnc_tuple: "40427"
+    mccmnc_tuple: "40430"
+    mccmnc_tuple: "40443"
+    mccmnc_tuple: "40446"
+    mccmnc_tuple: "40460"
+    mccmnc_tuple: "40484"
+    mccmnc_tuple: "40486"
+    mccmnc_tuple: "40488"
+    mccmnc_tuple: "40566"
+    mccmnc_tuple: "40567"
+    mccmnc_tuple: "405750"
+    mccmnc_tuple: "405751"
+    mccmnc_tuple: "405752"
+    mccmnc_tuple: "405753"
+    mccmnc_tuple: "405754"
+    mccmnc_tuple: "405755"
+    mccmnc_tuple: "405756"
+  }
+}
+carrier_id {
+  canonical_id: 1964
+  carrier_name: "Aio"
+  carrier_attribute {
+    mccmnc_tuple: "310150"
+  }
+}
+carrier_id {
+  canonical_id: 1965
+  carrier_name: "Telkom"
+  carrier_attribute {
+    mccmnc_tuple: "65502"
+  }
+}
+carrier_id {
+  canonical_id: 1966
+  carrier_name: "3"
+  carrier_attribute {
+    mccmnc_tuple: "51089"
+  }
+}
+carrier_id {
+  canonical_id: 1967
+  carrier_name: "Irancell"
+  carrier_attribute {
+    mccmnc_tuple: "43235"
+  }
+}
+carrier_id {
+  canonical_id: 1968
+  carrier_name: "Etisalat"
+  carrier_attribute {
+    mccmnc_tuple: "60203"
+  }
+}
+carrier_id {
+  canonical_id: 1969
+  carrier_name: "Asia Cell"
+  carrier_attribute {
+    mccmnc_tuple: "41805"
+  }
+}
+carrier_id {
+  canonical_id: 1970
+  carrier_name: "du"
+  carrier_attribute {
+    mccmnc_tuple: "42403"
+  }
+}
+carrier_id {
+  canonical_id: 1971
+  carrier_name: "Zain Iraq"
+  carrier_attribute {
+    mccmnc_tuple: "41820"
+    mccmnc_tuple: "41830"
+  }
+}
+carrier_id {
+  canonical_id: 1972
+  carrier_name: "Zain Saudi Arabia"
+  carrier_attribute {
+    mccmnc_tuple: "42004"
+  }
+}
+carrier_id {
+  canonical_id: 1973
+  carrier_name: "Libyana"
+  carrier_attribute {
+    mccmnc_tuple: "60600"
+  }
+}
+carrier_id {
+  canonical_id: 1974
+  carrier_name: "Jazztel"
+  carrier_attribute {
+    mccmnc_tuple: "21421"
+  }
+}
+carrier_id {
+  canonical_id: 1975
+  carrier_name: "Telenor"
+  carrier_attribute {
+    mccmnc_tuple: "41006"
+  }
+}
+carrier_id {
+  canonical_id: 1976
+  carrier_name: "ONO"
+  carrier_attribute {
+    mccmnc_tuple: "21418"
+  }
+}
+carrier_id {
+  canonical_id: 1977
+  carrier_name: "Ooredoo"
+  carrier_attribute {
+    mccmnc_tuple: "60303"
+  }
+}
+carrier_id {
+  canonical_id: 1978
+  carrier_name: "Smartfren"
+  carrier_attribute {
+    mccmnc_tuple: "51009"
+    mccmnc_tuple: "51028"
+  }
+}
+carrier_id {
+  canonical_id: 1979
+  carrier_name: "9Pay"
+  carrier_attribute {
+    mccmnc_tuple: "62160"
+  }
+}
+carrier_id {
+  canonical_id: 1980
+  carrier_name: "Zong"
+  carrier_attribute {
+    mccmnc_tuple: "41004"
+  }
+}
+carrier_id {
+  canonical_id: 1981
+  carrier_name: "WaridTel"
+  carrier_attribute {
+    mccmnc_tuple: "41007"
+  }
+}
+carrier_id {
+  canonical_id: 1982
+  carrier_name: "TATA DOCOMO"
+  carrier_attribute {
+    mccmnc_tuple: "405025"
+    mccmnc_tuple: "405026"
+    mccmnc_tuple: "405027"
+    mccmnc_tuple: "405028"
+    mccmnc_tuple: "405029"
+    mccmnc_tuple: "405030"
+    mccmnc_tuple: "405031"
+    mccmnc_tuple: "405032"
+    mccmnc_tuple: "405033"
+    mccmnc_tuple: "405034"
+    mccmnc_tuple: "405035"
+    mccmnc_tuple: "405036"
+    mccmnc_tuple: "405037"
+    mccmnc_tuple: "405038"
+    mccmnc_tuple: "405039"
+    mccmnc_tuple: "405041"
+    mccmnc_tuple: "405042"
+    mccmnc_tuple: "405043"
+    mccmnc_tuple: "405044"
+    mccmnc_tuple: "405045"
+    mccmnc_tuple: "405046"
+    mccmnc_tuple: "405047"
+  }
+}
+carrier_id {
+  canonical_id: 1983
+  carrier_name: "Korek"
+  carrier_attribute {
+    mccmnc_tuple: "41840"
+  }
+}
+carrier_id {
+  canonical_id: 1984
+  carrier_name: "Virgin"
+  carrier_attribute {
+    mccmnc_tuple: "20823"
+  }
+}
+carrier_id {
+  canonical_id: 1985
+  carrier_name: "Wana"
+  carrier_attribute {
+    mccmnc_tuple: "60402"
+  }
+}
+carrier_id {
+  canonical_id: 1986
+  carrier_name: "Tele2"
+  carrier_attribute {
+    mccmnc_tuple: "40177"
+  }
+}
+carrier_id {
+  canonical_id: 1987
+  carrier_name: "Rightel"
+  carrier_attribute {
+    mccmnc_tuple: "43220"
+  }
+}
+carrier_id {
+  canonical_id: 1988
+  carrier_name: "O2"
+  carrier_attribute {
+    mccmnc_tuple: "23106"
+  }
+}
+carrier_id {
+  canonical_id: 1989
+  carrier_name: "Project Fi"
+  carrier_attribute {
+    mccmnc_tuple: "23420"
+    gid1: "0306"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "45403"
+    gid1: "0206"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "310120"
+    spn: "Fi Network"
+    spn: "nova"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    imsi_prefix_xpattern: "31026097"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "311580"
+    imsi_prefix_xpattern: "3115801"
+  }
+}
+carrier_id {
+  canonical_id: 1990
+  carrier_name: "Golan Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "42508"
+  }
+}
+carrier_id {
+  canonical_id: 1991
+  carrier_name: "Hot Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "42507"
+    mccmnc_tuple: "42577"
+  }
+}
+carrier_id {
+  canonical_id: 1992
+  carrier_name: "Viva"
+  carrier_attribute {
+    mccmnc_tuple: "41904"
+  }
+}
+carrier_id {
+  canonical_id: 1994
+  carrier_name: "Vietnamobile"
+  carrier_attribute {
+    mccmnc_tuple: "45205"
+  }
+}
+carrier_id {
+  canonical_id: 1996
+  carrier_name: "Ooredoo Myanmar"
+  carrier_attribute {
+    mccmnc_tuple: "41405"
+  }
+}
+carrier_id {
+  canonical_id: 1997
+  carrier_name: "TrueMove H 4G LTE"
+  carrier_attribute {
+    mccmnc_tuple: "52004"
+  }
+}
+carrier_id {
+  canonical_id: 1998
+  carrier_name: "Tigo"
+  carrier_attribute {
+    mccmnc_tuple: "70603"
+  }
+}
+carrier_id {
+  canonical_id: 1999
+  carrier_name: "VIVACOM"
+  carrier_attribute {
+    mccmnc_tuple: "28403"
+  }
+}
+carrier_id {
+  canonical_id: 2001
+  carrier_name: "Vodafone"
+  carrier_attribute {
+    mccmnc_tuple: "42702"
+  }
+}
+carrier_id {
+  canonical_id: 2002
+  carrier_name: "Uninor"
+  carrier_attribute {
+    mccmnc_tuple: "405813"
+    mccmnc_tuple: "405814"
+    mccmnc_tuple: "405815"
+    mccmnc_tuple: "405816"
+    mccmnc_tuple: "405817"
+    mccmnc_tuple: "405818"
+    mccmnc_tuple: "405819"
+    mccmnc_tuple: "405820"
+    mccmnc_tuple: "405821"
+    mccmnc_tuple: "405822"
+    mccmnc_tuple: "405844"
+    mccmnc_tuple: "405875"
+    mccmnc_tuple: "405876"
+    mccmnc_tuple: "405877"
+    mccmnc_tuple: "405878"
+    mccmnc_tuple: "405879"
+    mccmnc_tuple: "405880"
+    mccmnc_tuple: "405925"
+    mccmnc_tuple: "405926"
+    mccmnc_tuple: "405927"
+    mccmnc_tuple: "405928"
+    mccmnc_tuple: "405929"
+  }
+}
+carrier_id {
+  canonical_id: 2003
+  carrier_name: "movistar"
+  carrier_attribute {
+    mccmnc_tuple: "71204"
+  }
+}
+carrier_id {
+  canonical_id: 2004
+  carrier_name: "Airtel BD"
+  carrier_attribute {
+    mccmnc_tuple: "47007"
+  }
+}
+carrier_id {
+  canonical_id: 2005
+  carrier_name: "NRJ Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "20826"
+  }
+}
+carrier_id {
+  canonical_id: 2006
+  carrier_name: "Madar"
+  carrier_attribute {
+    mccmnc_tuple: "60601"
+  }
+}
+carrier_id {
+  canonical_id: 2007
+  carrier_name: "Telenor Myanmar"
+  carrier_attribute {
+    mccmnc_tuple: "41406"
+  }
+}
+carrier_id {
+  canonical_id: 2008
+  carrier_name: "Videotron"
+  carrier_attribute {
+    mccmnc_tuple: "302500"
+    mccmnc_tuple: "302510"
+    mccmnc_tuple: "302520"
+  }
+}
+carrier_id {
+  canonical_id: 2009
+  carrier_name: "Movistar (Telefonica Moviles El Salvador)"
+  carrier_attribute {
+    mccmnc_tuple: "70604"
+    mccmnc_tuple: "706040"
+  }
+}
+carrier_id {
+  canonical_id: 2010
+  carrier_name: "Movistar (Telefonía Celular de Nicaragua)"
+  carrier_attribute {
+    mccmnc_tuple: "71030"
+    mccmnc_tuple: "710300"
+  }
+}
+carrier_id {
+  canonical_id: 2011
+  carrier_name: "Inland Cellular"
+  carrier_attribute {
+    mccmnc_tuple: "310580"
+  }
+}
+carrier_id {
+  canonical_id: 2014
+  carrier_name: "Zain Bahrain"
+  carrier_attribute {
+    mccmnc_tuple: "42602"
+  }
+}
+carrier_id {
+  canonical_id: 2015
+  carrier_name: "VIVA"
+  carrier_attribute {
+    mccmnc_tuple: "42604"
+  }
+}
+carrier_id {
+  canonical_id: 2017
+  carrier_name: "Yes 4G"
+  carrier_attribute {
+    mccmnc_tuple: "502152"
+  }
+}
+carrier_id {
+  canonical_id: 2018
+  carrier_name: "Reliance Jio"
+  carrier_attribute {
+    mccmnc_tuple: "405840"
+    mccmnc_tuple: "405854"
+    mccmnc_tuple: "405855"
+    mccmnc_tuple: "405856"
+    mccmnc_tuple: "405857"
+    mccmnc_tuple: "405858"
+    mccmnc_tuple: "405859"
+    mccmnc_tuple: "405860"
+    mccmnc_tuple: "405861"
+    mccmnc_tuple: "405862"
+    mccmnc_tuple: "405863"
+    mccmnc_tuple: "405864"
+    mccmnc_tuple: "405865"
+    mccmnc_tuple: "405866"
+    mccmnc_tuple: "405867"
+    mccmnc_tuple: "405868"
+    mccmnc_tuple: "405869"
+    mccmnc_tuple: "405870"
+    mccmnc_tuple: "405871"
+    mccmnc_tuple: "405872"
+    mccmnc_tuple: "405873"
+    mccmnc_tuple: "405874"
+  }
+}
+carrier_id {
+  canonical_id: 2020
+  carrier_name: "Koodo Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "302220"
+    mccmnc_tuple: "302221"
+    mccmnc_tuple: "30222"
+    mccmnc_tuple: "30286"
+    gid1: "4b4f"
+  }
+}
+carrier_id {
+  canonical_id: 2021
+  carrier_name: "Virgin Mobile Canada"
+  carrier_attribute {
+    mccmnc_tuple: "30264"
+    mccmnc_tuple: "302610"
+    mccmnc_tuple: "302630"
+    mccmnc_tuple: "302640"
+    gid1: "3e"
+  }
+}
+carrier_id {
+  canonical_id: 2022
+  carrier_name: "TracFone"
+  carrier_attribute {
+    mccmnc_tuple: "310410"
+    mccmnc_tuple: "310260"
+    gid1: "DDFF"
+    gid1: "DEFF"
+  }
+}
+carrier_id {
+  canonical_id: 2023
+  carrier_name: "Consumer Cellular"
+  carrier_attribute {
+    mccmnc_tuple: "310410"
+    mccmnc_tuple: "310260"
+    gid1: "2AC9"
+  }
+}
+carrier_id {
+  canonical_id: 2024
+  carrier_name: "Locus Telecom"
+  carrier_attribute {
+    mccmnc_tuple: "310410"
+    gid1: "49FF"
+  }
+}
+carrier_id {
+  canonical_id: 2025
+  carrier_name: "Telrite/Pure Talk"
+  carrier_attribute {
+    mccmnc_tuple: "310410"
+    gid1: "62FF"
+  }
+}
+carrier_id {
+  canonical_id: 2026
+  carrier_name: "Red Pocket"
+  carrier_attribute {
+    mccmnc_tuple: "310410"
+    mccmnc_tuple: "310260"
+    gid1: "4201"
+  }
+}
+carrier_id {
+  canonical_id: 2027
+  carrier_name: "Airvoice"
+  carrier_attribute {
+    mccmnc_tuple: "310410"
+    gid1: "44FF"
+  }
+}
+carrier_id {
+  canonical_id: 2028
+  carrier_name: "Ztar"
+  carrier_attribute {
+    mccmnc_tuple: "310410"
+    gid1: "4102"
+  }
+}
+carrier_id {
+  canonical_id: 2029
+  carrier_name: "Defense Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "310410"
+    gid1: "60FF"
+  }
+}
+carrier_id {
+  canonical_id: 2030
+  carrier_name: "AGMS"
+  carrier_attribute {
+    mccmnc_tuple: "310380"
+    gid1: "50FF"
+  }
+}
+carrier_id {
+  canonical_id: 2031
+  carrier_name: "Kore"
+  carrier_attribute {
+    mccmnc_tuple: "310410"
+    gid1: "69FF"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "4B4F"
+  }
+}
+carrier_id {
+  canonical_id: 2032
+  carrier_name: "XFINITY Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "311480"
+    gid1: "BA01450000000000"
+  }
+}
+carrier_id {
+  canonical_id: 2033
+  carrier_name: "Ooredoo"
+  carrier_attribute {
+    mccmnc_tuple: "47202"
+  }
+}
+carrier_id {
+  canonical_id: 2034
+  carrier_name: "Orange Mali"
+  carrier_attribute {
+    mccmnc_tuple: "61002"
+  }
+}
+carrier_id {
+  canonical_id: 2035
+  carrier_name: "Celcom"
+  carrier_attribute {
+    mccmnc_tuple: "61807"
+  }
+}
+carrier_id {
+  canonical_id: 2036
+  carrier_name: "Tigo"
+  carrier_attribute {
+    mccmnc_tuple: "63089"
+  }
+}
+carrier_id {
+  canonical_id: 2037
+  carrier_name: "Bite"
+  carrier_attribute {
+    mccmnc_tuple: "24705"
+  }
+}
+carrier_id {
+  canonical_id: 2038
+  carrier_name: "unifi mobile"
+  carrier_attribute {
+    mccmnc_tuple: "50211"
+    mccmnc_tuple: "502153"
+  }
+}
+carrier_id {
+  canonical_id: 2052
+  carrier_name: "Virgin"
+  carrier_attribute {
+    mccmnc_tuple: "42005"
+  }
+}
+carrier_id {
+  canonical_id: 2053
+  carrier_name: "PC mobile"
+  carrier_attribute {
+    mccmnc_tuple: "30264"
+    mccmnc_tuple: "302610"
+    mccmnc_tuple: "302630"
+    mccmnc_tuple: "302640"
+    gid1: "40"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "302220"
+    gid1: "5043"
+  }
+}
+carrier_id {
+  canonical_id: 2054
+  carrier_name: "Solo Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "30264"
+    mccmnc_tuple: "302610"
+    mccmnc_tuple: "302630"
+    mccmnc_tuple: "302640"
+    gid1: "3f"
+  }
+}
+carrier_id {
+  canonical_id: 2055
+  carrier_name: "Chatr Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "302720"
+    imsi_prefix_xpattern: "302720x94"
+    gid1: "ff"
+  }
+}
+carrier_id {
+  canonical_id: 2056
+  carrier_name: "Ztar Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "302720"
+    imsi_prefix_xpattern: "302720x98"
+    gid1: "ff"
+  }
+}
+carrier_id {
+  canonical_id: 2057
+  carrier_name: "Cityfone"
+  carrier_attribute {
+    mccmnc_tuple: "302720"
+    imsi_prefix_xpattern: "302720x97"
+    gid1: "cc"
+  }
+}
+carrier_id {
+  canonical_id: 2060
+  carrier_name: "Amaysim (MVNE2)"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "6133"
+  }
+}
+carrier_id {
+  canonical_id: 2062
+  carrier_name: "Bright Spot (Leto)"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "4C45"
+  }
+}
+carrier_id {
+  canonical_id: 2063
+  carrier_name: "Family Mobile (Walmart)"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "4D4B"
+  }
+}
+carrier_id {
+  canonical_id: 2064
+  carrier_name: "GoSmart"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "4753"
+  }
+}
+carrier_id {
+  canonical_id: 2065
+  carrier_name: "IDT"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "AB"
+  }
+}
+carrier_id {
+  canonical_id: 2066
+  carrier_name: "Kajeet (Arterra)"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "6136"
+  }
+}
+carrier_id {
+  canonical_id: 2067
+  carrier_name: "Lyca Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "6C79"
+  }
+}
+carrier_id {
+  canonical_id: 2068
+  carrier_name: "M2M"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "3201"
+  }
+}
+carrier_id {
+  canonical_id: 2069
+  carrier_name: "MVNE2/AMDOCS"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "6132"
+  }
+}
+carrier_id {
+  canonical_id: 2070
+  carrier_name: "Nest"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "4E45"
+  }
+}
+carrier_id {
+  canonical_id: 2071
+  carrier_name: "Plintron"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "7031"
+  }
+}
+carrier_id {
+  canonical_id: 2073
+  carrier_name: "PWG/Cintex"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "4358"
+  }
+}
+carrier_id {
+  canonical_id: 2074
+  carrier_name: "Ready Wireless"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "8234"
+  }
+}
+carrier_id {
+  canonical_id: 2075
+  carrier_name: "Republic Wireless (MVNE2)"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "6134"
+  }
+}
+carrier_id {
+  canonical_id: 2076
+  carrier_name: "Roam Mobility"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "726D"
+    gid1: "524D"
+  }
+}
+carrier_id {
+  canonical_id: 2077
+  carrier_name: "Rock Island"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "6137"
+  }
+}
+carrier_id {
+  canonical_id: 2078
+  carrier_name: "Simple Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "534D"
+  }
+}
+carrier_id {
+  canonical_id: 2079
+  carrier_name: "Solavei"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "7376"
+  }
+}
+carrier_id {
+  canonical_id: 2080
+  carrier_name: "Ting"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "6135"
+  }
+}
+carrier_id {
+  canonical_id: 2081
+  carrier_name: "Twilio M2M"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "7477"
+  }
+}
+carrier_id {
+  canonical_id: 2083
+  carrier_name: "Ultra/Univision"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "354D"
+    gid1: "364D"
+    gid1: "4153"
+    gid1: "554D"
+  }
+}
+carrier_id {
+  canonical_id: 2086
+  carrier_name: "Vodafone US"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "2801"
+  }
+}
+carrier_id {
+  canonical_id: 2087
+  carrier_name: "Wyless"
+  carrier_attribute {
+    mccmnc_tuple: "310260"
+    gid1: "574D"
+  }
+}
+carrier_id {
+  canonical_id: 2088
+  carrier_name: "Crnogorski Telekom"
+  carrier_attribute {
+    mccmnc_tuple: "29702"
+    spn: "Telekom.me"
+  }
+}
+carrier_id {
+  canonical_id: 2089
+  carrier_name: "Public Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "302220"
+    gid1: "4d4f"
+  }
+}
+carrier_id {
+  canonical_id: 2090
+  carrier_name: "Tbaytel"
+  carrier_attribute {
+    mccmnc_tuple: "302720"
+    imsi_prefix_xpattern: "302720x92"
+    gid1: "ba"
+  }
+}
+carrier_id {
+  canonical_id: 2091
+  carrier_name: "Bonbon"
+  carrier_attribute {
+    mccmnc_tuple: "21901"
+    spn: "bonbon"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "21901"
+    gid1: "10"
+    gid1: "11"
+    gid1: "12"
+  }
+}
+carrier_id {
+  canonical_id: 2092
+  carrier_name: "Congstar"
+  carrier_attribute {
+    mccmnc_tuple: "26201"
+    gid1: "44"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "26201"
+    spn: "congstar"
+    spn: "congstar.de"
+  }
+}
+carrier_id {
+  canonical_id: 2093
+  carrier_name: "Tesco"
+  carrier_attribute {
+    mccmnc_tuple: "23410"
+    gid1: "0A"
+    gid1: "0AFFFF"
+    gid1: "0AFFFFFFFFFF544553434F202020202020202020"
+  }
+}
+carrier_id {
+  canonical_id: 2094
+  carrier_name: "tele.ring"
+  carrier_attribute {
+    mccmnc_tuple: "23207"
+    gid1: ""
+    gid1: "07"
+    gid1: "FF"
+  }
+}
+carrier_id {
+  canonical_id: 2095
+  carrier_name: "Ben"
+  carrier_attribute {
+    mccmnc_tuple: "20416"
+    gid1: "5F"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "20416"
+    spn: "Ben NL"
+  }
+}
+carrier_id {
+  canonical_id: 2096
+  carrier_name: "Heyah"
+  carrier_attribute {
+    mccmnc_tuple: "26002"
+    gid1: "23"
+  }
+  carrier_attribute {
+    mccmnc_tuple: "26002"
+    spn: "heyah"
+  }
+}
+carrier_id {
+  canonical_id: 2100
+  carrier_name: "Y!mobile"
+  carrier_attribute {
+    mccmnc_tuple: "44020"
+    imsi_prefix_xpattern: "4402091"
+    gid1: "16ffff"
+  }
+}
+carrier_id {
+  canonical_id: 2101
+  carrier_name: "BT"
+  carrier_attribute {
+    mccmnc_tuple: "23430"
+    gid1: "c3"
+    gid1: "c3ff"
+  }
+}
+carrier_id {
+  canonical_id: 2102
+  carrier_name: "BT Business"
+  carrier_attribute {
+    mccmnc_tuple: "23430"
+    gid1: "b3"
+    gid1: "b3ff"
+  }
+}
+carrier_id {
+  canonical_id: 2103
+  carrier_name: "BT One Phone"
+  carrier_attribute {
+    mccmnc_tuple: "23430"
+    mccmnc_tuple: "23408"
+    gid1: "b2"
+    gid1: "b2ff"
+  }
+}
+carrier_id {
+  canonical_id: 2105
+  carrier_name: "Skinny"
+  carrier_attribute {
+    mccmnc_tuple: "53005"
+    spn: "Skinny"
+  }
+}
+carrier_id {
+  canonical_id: 2106
+  carrier_name: "IIJmio"
+  carrier_attribute {
+    mccmnc_tuple: "44010"
+    preferred_apn: "iijmio.jp"
+  }
+}
+carrier_id {
+  canonical_id: 2107
+  carrier_name: "Telenor"
+  carrier_attribute {
+    mccmnc_tuple: "29701"
+  }
+}
+carrier_id {
+  canonical_id: 2108
+  carrier_name: "Airtel"
+  carrier_attribute {
+    mccmnc_tuple: "62006"
+  }
+}
+carrier_id {
+  canonical_id: 2109
+  carrier_name: "Rakuten Mobile"
+  carrier_attribute {
+    mccmnc_tuple: "44010"
+    preferred_apn: "rmobile.co"
+    preferred_apn: "rmobile.jp"
+  }
+}
+carrier_id {
+  canonical_id: 2110
+  carrier_name: "UQ mobile"
+  carrier_attribute {
+    mccmnc_tuple: "44051"
+    preferred_apn: "uqmobile.jp"
+  }
+}
+carrier_id {
+  canonical_id: 2111
+  carrier_name: "Euskaltel"
+  carrier_attribute {
+    mccmnc_tuple: "21403"
+    mccmnc_tuple: "21408"
+    spn: "EUSKALTEL"
+  }
+}
+carrier_id {
+  canonical_id: 2112
+  carrier_name: "Telecable"
+  carrier_attribute {
+    mccmnc_tuple: "21405"
+    mccmnc_tuple: "21416"
+    spn: "telecable"
+    spn: "Telecable"
+    spn: "TeleCable"
+  }
+}
+carrier_id {
+  canonical_id: 2113
+  carrier_name: "mobilR"
+  carrier_attribute {
+    mccmnc_tuple: "21403"
+    mccmnc_tuple: "21408"
+    mccmnc_tuple: "21417"
+    spn: "mobilR"
+    spn: "mobil R"
+    spn: "Mobil R"
+  }
+}
+carrier_id {
+  canonical_id: 2114
+  carrier_name: "EWE"
+  carrier_attribute {
+    mccmnc_tuple: "26202"
+    spn: "ewe.de"
+  }
+}
+carrier_id {
+  canonical_id: 2115
+  carrier_name: "Glo"
+  carrier_attribute {
+    mccmnc_tuple: "62150"
+  }
+}
+carrier_id {
+  canonical_id: 2116
+  carrier_name: "Ratel"
+  carrier_attribute {
+    mccmnc_tuple: "44010"
+    preferred_apn: "ratel.com"
+  }
+}
+carrier_id {
+  canonical_id: 2117
+  carrier_name: "TalkTalk"
+  carrier_attribute {
+    mccmnc_tuple: "23410"
+    gid1: "537"
+  }
+}
+carrier_id {
+  canonical_id: 2118
+  carrier_name: "giffgaff"
+  carrier_attribute {
+    mccmnc_tuple: "23410"
+    gid1: "508"
+  }
+}
+version: 4
diff --git a/src/com/android/providers/telephony/CarrierDatabaseHelper.java b/src/com/android/providers/telephony/CarrierDatabaseHelper.java
index b654a77..0b97da1 100644
--- a/src/com/android/providers/telephony/CarrierDatabaseHelper.java
+++ b/src/com/android/providers/telephony/CarrierDatabaseHelper.java
@@ -40,6 +40,7 @@
      */
     public CarrierDatabaseHelper(Context context) {
         super(context, DATABASE_NAME, null, DATABASE_VERSION);
+        setWriteAheadLoggingEnabled(false);
     }
 
     public static final String KEY_TYPE = "key_type";
diff --git a/src/com/android/providers/telephony/CarrierIdProvider.java b/src/com/android/providers/telephony/CarrierIdProvider.java
index 1c2e5e5..44f053d 100644
--- a/src/com/android/providers/telephony/CarrierIdProvider.java
+++ b/src/com/android/providers/telephony/CarrierIdProvider.java
@@ -25,11 +25,14 @@
 import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.MatrixCursor;
+import android.database.SQLException;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Environment;
+import android.os.SystemProperties;
 import android.provider.Telephony.CarrierId;
 import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
@@ -74,10 +77,12 @@
     private static final String TAG = CarrierIdProvider.class.getSimpleName();
 
     private static final String DATABASE_NAME = "carrierIdentification.db";
-    private static final int DATABASE_VERSION = 3;
+    private static final int DATABASE_VERSION = 5;
 
     private static final String ASSETS_PB_FILE = "carrier_list.pb";
     private static final String VERSION_KEY = "version";
+    // The version number is offset by SDK level, the MSB 8 bits is reserved for SDK.
+    private static final int VERSION_BITMASK = 0x00FFFFFF;
     private static final String OTA_UPDATED_PB_PATH = "misc/carrierid/" + ASSETS_PB_FILE;
     private static final String PREF_FILE = CarrierIdProvider.class.getSimpleName();
 
@@ -119,10 +124,15 @@
     * index 7: {@link CarrierId.All#ICCID_PREFIX}
     */
     private static final int ICCID_PREFIX_INDEX          = 7;
+
+    /**
+     * index 8: {@link CarrierId.All#PRIVILEGE_ACCESS_RULE}
+     */
+    private static final int PRIVILEGE_ACCESS_RULE       = 8;
     /**
      * ending index of carrier attribute list.
      */
-    private static final int CARRIER_ATTR_END_IDX        = ICCID_PREFIX_INDEX;
+    private static final int CARRIER_ATTR_END_IDX        = PRIVILEGE_ACCESS_RULE;
     /**
      * The authority string for the CarrierIdProvider
      */
@@ -139,15 +149,18 @@
             CarrierId.All.IMSI_PREFIX_XPATTERN,
             CarrierId.All.SPN,
             CarrierId.All.APN,
-            CarrierId.All.ICCID_PREFIX));
+            CarrierId.All.ICCID_PREFIX,
+            CarrierId.All.PRIVILEGE_ACCESS_RULE,
+            CarrierId.PARENT_CARRIER_ID));
 
     private CarrierIdDatabaseHelper mDbHelper;
 
     /**
      * Stores carrier id information for the current active subscriptions.
-     * Key is the active subId and entryValue is a pair of carrier id(int) and Carrier Name(String).
+     * Key is the active subId and entryValue is carrier id(int), mno carrier id (int) and
+     * carrier name(String).
      */
-    private final Map<Integer, Pair<Integer, String>> mCurrentSubscriptionMap =
+    private final Map<Integer, ContentValues> mCurrentSubscriptionMap =
             new ConcurrentHashMap<>();
 
     @VisibleForTesting
@@ -162,8 +175,10 @@
                 + CarrierId.All.SPN + " TEXT,"
                 + CarrierId.All.APN + " TEXT,"
                 + CarrierId.All.ICCID_PREFIX + " TEXT,"
+                + CarrierId.All.PRIVILEGE_ACCESS_RULE + " TEXT,"
                 + CarrierId.CARRIER_NAME + " TEXT,"
                 + CarrierId.CARRIER_ID + " INTEGER DEFAULT -1,"
+                + CarrierId.PARENT_CARRIER_ID + " INTEGER DEFAULT -1,"
                 + "UNIQUE (" + TextUtils.join(", ", CARRIERS_ID_UNIQUE_FIELDS) + "));";
     }
 
@@ -317,6 +332,8 @@
          */
         public CarrierIdDatabaseHelper(Context context) {
             super(context, DATABASE_NAME, null, DATABASE_VERSION);
+            Log.d(TAG, "CarrierIdDatabaseHelper: " + DATABASE_VERSION);
+            setWriteAheadLoggingEnabled(false);
         }
 
         @Override
@@ -341,6 +358,9 @@
             if (oldVersion < DATABASE_VERSION) {
                 dropCarrierTable(db);
                 createCarrierTable(db);
+                // force rewrite carrier id db
+                setAppliedVersion(0);
+                updateDatabaseFromPb(db);
             }
         }
     }
@@ -368,6 +388,11 @@
                     cv = new ContentValues();
                     cv.put(CarrierId.CARRIER_ID, id.canonicalId);
                     cv.put(CarrierId.CARRIER_NAME, id.carrierName);
+                    // 0 is the default proto value. if parentCanonicalId is unset, apply default
+                    // unknown carrier id -1.
+                    if (id.parentCanonicalId > 0) {
+                        cv.put(CarrierId.PARENT_CARRIER_ID, id.parentCanonicalId);
+                    }
                     cvs = new ArrayList<>();
                     convertCarrierAttrToContentValues(cv, cvs, attr, 0);
                     for (ContentValues contentVal : cvs) {
@@ -403,7 +428,9 @@
     private void convertCarrierAttrToContentValues(ContentValues cv, List<ContentValues> cvs,
             CarrierIdProto.CarrierAttribute attr, int index) {
         if (index > CARRIER_ATTR_END_IDX) {
-            cvs.add(new ContentValues(cv));
+            ContentValues carrier = new ContentValues(cv);
+            if (!cvs.contains(carrier))
+            cvs.add(carrier);
             return;
         }
         boolean found = false;
@@ -426,7 +453,7 @@
                 break;
             case GID1_INDEX:
                 for (String str : attr.gid1) {
-                    cv.put(CarrierId.All.GID1, str);
+                    cv.put(CarrierId.All.GID1, str.toLowerCase());
                     convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
                     cv.remove(CarrierId.All.GID1);
                     found = true;
@@ -434,7 +461,7 @@
                 break;
             case GID2_INDEX:
                 for (String str : attr.gid2) {
-                    cv.put(CarrierId.All.GID2, str);
+                    cv.put(CarrierId.All.GID2, str.toLowerCase());
                     convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
                     cv.remove(CarrierId.All.GID2);
                     found = true;
@@ -450,7 +477,7 @@
                 break;
             case SPN_INDEX:
                 for (String str : attr.spn) {
-                    cv.put(CarrierId.All.SPN, str);
+                    cv.put(CarrierId.All.SPN, str.toLowerCase());
                     convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
                     cv.remove(CarrierId.All.SPN);
                     found = true;
@@ -472,6 +499,14 @@
                     found = true;
                 }
                 break;
+            case PRIVILEGE_ACCESS_RULE:
+                for (String str : attr.privilegeAccessRule) {
+                    cv.put(CarrierId.All.PRIVILEGE_ACCESS_RULE, str);
+                    convertCarrierAttrToContentValues(cv, cvs, attr, index + 1);
+                    cv.remove(CarrierId.All.PRIVILEGE_ACCESS_RULE);
+                    found = true;
+                }
+                break;
             default:
                 Log.e(TAG, "unsupported index: " + index);
                 break;
@@ -517,7 +552,10 @@
             carrierList = assets;
             version = assets.version;
         }
-        if (ota != null && ota.version > version) {
+        // bypass version check for ota carrier id test
+        if (ota != null && ((Build.IS_DEBUGGABLE && SystemProperties.getBoolean(
+                "persist.telephony.test.carrierid.ota", false))
+                || (ota.version > version))) {
             carrierList = ota;
             version = ota.version;
         }
@@ -532,6 +570,9 @@
     }
 
     private void setAppliedVersion(int version) {
+        int relative_version = version & VERSION_BITMASK;
+        Log.d(TAG, "update version number: " +  Integer.toHexString(version)
+                + " relative version: " + relative_version);
         final SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE,
                 Context.MODE_PRIVATE);
         SharedPreferences.Editor editor = sp.edit();
@@ -572,7 +613,7 @@
         if (!SubscriptionController.getInstance().isActiveSubId(subId)) {
             // Remove absent subId from the currentSubscriptionMap.
             final List activeSubscriptions = Arrays.asList(SubscriptionController.getInstance()
-                    .getActiveSubIdList());
+                    .getActiveSubIdList(false));
             int count = 0;
             for (int subscription : mCurrentSubscriptionMap.keySet()) {
                 if (!activeSubscriptions.contains(subscription)) {
@@ -584,9 +625,7 @@
             }
             return count;
         } else {
-            mCurrentSubscriptionMap.put(subId,
-                    new Pair(cv.getAsInteger(CarrierId.CARRIER_ID),
-                    cv.getAsString(CarrierId.CARRIER_NAME)));
+            mCurrentSubscriptionMap.put(subId, new ContentValues(cv));
             getContext().getContentResolver().notifyChange(CarrierId.CONTENT_URI, null);
             return 1;
         }
@@ -618,9 +657,9 @@
         for (int i = 0; i < c.getColumnCount(); i++) {
             final String columnName = c.getColumnName(i);
             if (CarrierId.CARRIER_ID.equals(columnName)) {
-                row.add(mCurrentSubscriptionMap.get(subId).first);
+                row.add(mCurrentSubscriptionMap.get(subId).get(CarrierId.CARRIER_ID));
             } else if (CarrierId.CARRIER_NAME.equals(columnName)) {
-                row.add(mCurrentSubscriptionMap.get(subId).second);
+                row.add(mCurrentSubscriptionMap.get(subId).get(CarrierId.CARRIER_NAME));
             } else {
                 throw new IllegalArgumentException("Invalid column " + projectionIn[i]);
             }
diff --git a/src/com/android/providers/telephony/HbpcdLookupDatabaseHelper.java b/src/com/android/providers/telephony/HbpcdLookupDatabaseHelper.java
index 2debc57..0149687 100644
--- a/src/com/android/providers/telephony/HbpcdLookupDatabaseHelper.java
+++ b/src/com/android/providers/telephony/HbpcdLookupDatabaseHelper.java
@@ -102,6 +102,7 @@
         mContext = context;
         // Memory optimization - close idle connections after 30s of inactivity
         setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
+        setWriteAheadLoggingEnabled(false);
     }
 
     @Override
diff --git a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
index 1dc2106..5fc36d2 100644
--- a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
+++ b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
@@ -16,12 +16,15 @@
 
 package com.android.providers.telephony;
 
+import static android.provider.Telephony.RcsColumns.IS_RCS_TABLE_SCHEMA_CODE_COMPLETE;
+
 import android.content.BroadcastReceiver;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.DatabaseErrorHandler;
 import android.database.DefaultDatabaseErrorHandler;
@@ -284,11 +287,13 @@
         }
     }
 
-    private MmsSmsDatabaseHelper(Context context, MmsSmsDatabaseErrorHandler dbErrorHandler) {
+    @VisibleForTesting
+    MmsSmsDatabaseHelper(Context context, MmsSmsDatabaseErrorHandler dbErrorHandler) {
         super(context, DATABASE_NAME, null, DATABASE_VERSION, dbErrorHandler);
         mContext = context;
         // Memory optimization - close idle connections after 30s of inactivity
         setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
+        setWriteAheadLoggingEnabled(false);
         try {
             PhoneFactory.addLocalLog(TAG, 100);
         } catch (IllegalArgumentException e) {
@@ -523,7 +528,7 @@
         localLog("onCreate: Creating all SMS-MMS tables.");
         // if FBE is not supported, or if this onCreate is for CE partition database
         if (!StorageManager.isFileEncryptedNativeOrEmulated()
-                || mContext.isCredentialProtectedStorage()) {
+                || (mContext != null && mContext.isCredentialProtectedStorage())) {
             localLog("onCreate: broadcasting ACTION_SMS_MMS_DB_CREATED");
             // Broadcast ACTION_SMS_MMS_DB_CREATED
             Intent intent = new Intent(Sms.Intents.ACTION_SMS_MMS_DB_CREATED);
@@ -545,6 +550,14 @@
         createMmsTables(db);
         createSmsTables(db);
         createCommonTables(db);
+
+        if (IS_RCS_TABLE_SCHEMA_CODE_COMPLETE) {
+            RcsProviderThreadHelper.createThreadTables(db);
+            RcsProviderParticipantHelper.createParticipantTables(db);
+            RcsProviderMessageHelper.createRcsMessageTables(db);
+            RcsProviderEventHelper.createRcsEventTables(db);
+        }
+
         createCommonTriggers(db);
         createMmsTriggers(db);
         createWordsTables(db);
@@ -708,7 +721,8 @@
         }
     }
 
-    private void createMmsTables(SQLiteDatabase db) {
+    @VisibleForTesting
+    void createMmsTables(SQLiteDatabase db) {
         // N.B.: Whenever the columns here are changed, the columns in
         // {@ref MmsSmsProvider} must be changed to match.
         db.execSQL("CREATE TABLE " + MmsProvider.TABLE_PDU + " (" +
@@ -1030,7 +1044,8 @@
             "message_body TEXT," + // message body
             "display_originating_addr TEXT);";
     // email address if from an email gateway, otherwise same as address
-    private void createSmsTables(SQLiteDatabase db) {
+    @VisibleForTesting
+    void createSmsTables(SQLiteDatabase db) {
         // N.B.: Whenever the columns here are changed, the columns in
         // {@ref MmsSmsProvider} must be changed to match.
         db.execSQL(CREATE_SMS_TABLE_STRING);
@@ -1054,9 +1069,33 @@
                    Sms.TYPE + "=" + Sms.MESSAGE_TYPE_INBOX +
                    " OR " +
                    Sms.TYPE + "=" + Sms.MESSAGE_TYPE_SENT + ";");
+
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+            // Create a table to keep track of changes to SMS table - specifically on update to read
+            // and deletion of msgs
+            db.execSQL("CREATE TABLE sms_changes (" +
+                       "_id INTEGER PRIMARY KEY," +
+                       "orig_rowid INTEGER," +
+                       "sub_id INTEGER," +
+                       "type INTEGER," +
+                       "new_read_status INTEGER" +
+                       ");");
+            db.execSQL("CREATE TRIGGER sms_update_on_read_change_row " +
+                        "AFTER UPDATE OF read ON sms WHEN NEW.read != OLD.read " +
+                        "BEGIN " +
+                        "  INSERT INTO sms_changes VALUES(null, NEW._id, NEW.sub_id, " +
+                        "0, NEW.read); " +
+                        "END;");
+            db.execSQL("CREATE TRIGGER sms_delete_change_row " +
+                       "AFTER DELETE ON sms " +
+                       "BEGIN " +
+                       "  INSERT INTO sms_changes values(null, OLD._id, OLD.sub_id, 1, null); " +
+                       "END;");
+        }
     }
 
-    private void createCommonTables(SQLiteDatabase db) {
+    @VisibleForTesting
+    void createCommonTables(SQLiteDatabase db) {
         // TODO Ensure that each entry is removed when the last use of
         // any address equivalent to its address is removed.
 
@@ -1625,6 +1664,15 @@
             } finally {
                 db.endTransaction();
             }
+            // fall through
+        case 67:
+            if (currentVersion <= 67 || !IS_RCS_TABLE_SCHEMA_CODE_COMPLETE) {
+                return;
+            }
+            RcsProviderThreadHelper.createThreadTables(db);
+            RcsProviderParticipantHelper.createParticipantTables(db);
+            RcsProviderMessageHelper.createRcsMessageTables(db);
+            RcsProviderEventHelper.createRcsEventTables(db);
             return;
         }
 
diff --git a/src/com/android/providers/telephony/MmsSmsProvider.java b/src/com/android/providers/telephony/MmsSmsProvider.java
index 1653cd9..8c3555c 100644
--- a/src/com/android/providers/telephony/MmsSmsProvider.java
+++ b/src/com/android/providers/telephony/MmsSmsProvider.java
@@ -110,7 +110,7 @@
     /**
      * the name of the table that is used to store the canonical addresses for both SMS and MMS.
      */
-    private static final String TABLE_CANONICAL_ADDRESSES = "canonical_addresses";
+    static final String TABLE_CANONICAL_ADDRESSES = "canonical_addresses";
 
     /**
      * the name of the table that is used to store the conversation threads.
@@ -1272,10 +1272,15 @@
 
     @Override
     public Uri insert(Uri uri, ContentValues values) {
-        if (URI_MATCHER.match(uri) == URI_PENDING_MSG) {
-            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        int matchIndex = URI_MATCHER.match(uri);
+
+        if (matchIndex == URI_PENDING_MSG) {
             long rowId = db.insert(TABLE_PENDING_MSG, null, values);
-            return Uri.parse(uri + "/" + rowId);
+            return uri.buildUpon().appendPath(Long.toString(rowId)).build();
+        } else if (matchIndex == URI_CANONICAL_ADDRESS) {
+            long rowId = db.insert(TABLE_CANONICAL_ADDRESSES, null, values);
+            return uri.buildUpon().appendPath(Long.toString(rowId)).build();
         }
         throw new UnsupportedOperationException(NO_DELETES_INSERTS_OR_UPDATES + uri);
     }
diff --git a/src/com/android/providers/telephony/RcsProvider.java b/src/com/android/providers/telephony/RcsProvider.java
new file mode 100644
index 0000000..3235b27
--- /dev/null
+++ b/src/com/android/providers/telephony/RcsProvider.java
@@ -0,0 +1,804 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.providers.telephony;
+
+import static android.provider.Telephony.RcsColumns.RcsThreadColumns.RCS_THREAD_URI_PART;
+import static android.provider.Telephony.RcsColumns.TRANSACTION_FAILED;
+
+import static com.android.providers.telephony.RcsProviderUtil.buildUriWithRowIdAppended;
+import static com.android.providers.telephony.RcsProviderUtil.returnUriAsIsIfSuccessful;
+
+import android.app.AppOpsManager;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Content provider to handle RCS messages. The functionality here is similar to SmsProvider,
+ * MmsProvider etc.
+ *
+ * The provider has constraints around inserting, updating and deleting - the user needs to know
+ * whether they are inserting a message that is incoming/outgoing, or the thread they are inserting
+ * is a group or p2p etc. This is in order to keep the implementation simple and avoid complex
+ * queries.
+ *
+ * @hide
+ */
+public class RcsProvider extends ContentProvider {
+    static final String TAG = "RcsProvider";
+    static final String AUTHORITY = "rcs";
+    private static final String CONTENT_AUTHORITY = "content://" + AUTHORITY;
+
+    private static final Uri PARTICIPANT_URI_PREFIX =
+            Uri.parse(CONTENT_AUTHORITY + "/participant/");
+    private static final Uri P2P_THREAD_URI_PREFIX = Uri.parse(CONTENT_AUTHORITY + "/p2p_thread/");
+    private static final Uri FILE_TRANSFER_PREFIX = Uri.parse(
+            CONTENT_AUTHORITY + "/file_transfer/");
+    static final Uri GROUP_THREAD_URI_PREFIX = Uri.parse(CONTENT_AUTHORITY + "/group_thread/");
+
+    // Rcs table names
+    static final String RCS_THREAD_TABLE = "rcs_thread";
+    static final String RCS_1_TO_1_THREAD_TABLE = "rcs_1_to_1_thread";
+    static final String RCS_GROUP_THREAD_TABLE = "rcs_group_thread";
+    static final String UNIFIED_RCS_THREAD_VIEW = "rcs_unified_rcs_thread_view";
+    static final String RCS_PARTICIPANT_TABLE = "rcs_participant";
+    static final String RCS_PARTICIPANT_THREAD_JUNCTION_TABLE = "rcs_thread_participant";
+    static final String RCS_MESSAGE_TABLE = "rcs_message";
+    static final String RCS_INCOMING_MESSAGE_TABLE = "rcs_incoming_message";
+    static final String RCS_OUTGOING_MESSAGE_TABLE = "rcs_outgoing_message";
+    static final String RCS_MESSAGE_DELIVERY_TABLE = "rcs_message_delivery";
+    static final String UNIFIED_MESSAGE_VIEW = "rcs_unified_message_view";
+    static final String RCS_FILE_TRANSFER_TABLE = "rcs_file_transfer";
+    static final String RCS_THREAD_EVENT_TABLE = "rcs_thread_event";
+    static final String RCS_PARTICIPANT_EVENT_TABLE = "rcs_participant_event";
+    static final String RCS_UNIFIED_EVENT_VIEW = "rcs_unified_event_view";
+
+    private static final UriMatcher URL_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+
+    private static final int UNIFIED_RCS_THREAD = 1;
+    private static final int UNIFIED_RCS_THREAD_WITH_ID = 2;
+    private static final int PARTICIPANT = 3;
+    private static final int PARTICIPANT_WITH_ID = 4;
+    private static final int PARTICIPANT_ALIAS_CHANGE_EVENT = 5;
+    private static final int PARTICIPANT_ALIAS_CHANGE_EVENT_WITH_ID = 6;
+    private static final int P2P_THREAD = 7;
+    private static final int P2P_THREAD_WITH_ID = 8;
+    private static final int P2P_THREAD_PARTICIPANT = 9;
+    private static final int P2P_THREAD_PARTICIPANT_WITH_ID = 10;
+    private static final int GROUP_THREAD = 11;
+    private static final int GROUP_THREAD_WITH_ID = 12;
+    private static final int GROUP_THREAD_PARTICIPANT = 13;
+    private static final int GROUP_THREAD_PARTICIPANT_WITH_ID = 14;
+    private static final int GROUP_THREAD_PARTICIPANT_JOINED_EVENT = 15;
+    private static final int GROUP_THREAD_PARTICIPANT_JOINED_EVENT_WITH_ID = 16;
+    private static final int GROUP_THREAD_PARTICIPANT_LEFT_EVENT = 17;
+    private static final int GROUP_THREAD_PARTICIPANT_LEFT_EVENT_WITH_ID = 18;
+    private static final int GROUP_THREAD_NAME_CHANGE_EVENT = 19;
+    private static final int GROUP_THREAD_NAME_CHANGE_EVENT_WITH_ID = 20;
+    private static final int GROUP_THREAD_ICON_CHANGE_EVENT = 21;
+    private static final int GROUP_THREAD_ICON_CHANGE_EVENT_WITH_ID = 22;
+    private static final int UNIFIED_MESSAGE = 23;
+    private static final int UNIFIED_MESSAGE_WITH_ID = 24;
+    private static final int UNIFIED_MESSAGE_WITH_FILE_TRANSFER = 25;
+    private static final int INCOMING_MESSAGE = 26;
+    private static final int INCOMING_MESSAGE_WITH_ID = 27;
+    private static final int OUTGOING_MESSAGE = 28;
+    private static final int OUTGOING_MESSAGE_WITH_ID = 29;
+    private static final int OUTGOING_MESSAGE_DELIVERY = 30;
+    private static final int OUTGOING_MESSAGE_DELIVERY_WITH_ID = 31;
+    private static final int UNIFIED_MESSAGE_ON_THREAD = 32;
+    private static final int UNIFIED_MESSAGE_ON_THREAD_WITH_ID = 33;
+    private static final int INCOMING_MESSAGE_ON_P2P_THREAD = 34;
+    private static final int INCOMING_MESSAGE_ON_P2P_THREAD_WITH_ID = 35;
+    private static final int OUTGOING_MESSAGE_ON_P2P_THREAD = 36;
+    private static final int OUTGOING_MESSAGE_ON_P2P_THREAD_WITH_ID = 37;
+    private static final int INCOMING_MESSAGE_ON_GROUP_THREAD = 38;
+    private static final int INCOMING_MESSAGE_ON_GROUP_THREAD_WITH_ID = 39;
+    private static final int OUTGOING_MESSAGE_ON_GROUP_THREAD = 40;
+    private static final int OUTGOING_MESSAGE_ON_GROUP_THREAD_WITH_ID = 41;
+    private static final int FILE_TRANSFER_WITH_ID = 42;
+    private static final int EVENT = 43;
+    private static final int CANONICAL_ADDRESS = 44;
+
+    SQLiteOpenHelper mDbOpenHelper;
+
+    @VisibleForTesting
+    RcsProviderThreadHelper mThreadHelper;
+    @VisibleForTesting
+    RcsProviderParticipantHelper mParticipantHelper;
+    @VisibleForTesting
+    RcsProviderMessageHelper mMessageHelper;
+    @VisibleForTesting
+    RcsProviderEventHelper mEventHelper;
+    @VisibleForTesting
+    RcsProviderCanonicalAddressHelper mCanonicalAddressHelper;
+
+    static {
+        // example URI: content://rcs/thread
+        URL_MATCHER.addURI(AUTHORITY, RCS_THREAD_URI_PART, UNIFIED_RCS_THREAD);
+
+        // example URI: content://rcs/thread/4, where 4 is the thread id.
+        URL_MATCHER.addURI(AUTHORITY, "thread/#", UNIFIED_RCS_THREAD_WITH_ID);
+
+        // example URI: content://rcs/participant
+        URL_MATCHER.addURI(AUTHORITY, "participant", PARTICIPANT);
+
+        // example URI: content://rcs/participant/12, where 12 is the participant id
+        URL_MATCHER.addURI(AUTHORITY, "participant/#", PARTICIPANT_WITH_ID);
+
+        // example URI: content://rcs/participant/12/alias_change_event, where 12 is the participant
+        // id.
+        URL_MATCHER.addURI(AUTHORITY, "participant/#/alias_change_event",
+                PARTICIPANT_ALIAS_CHANGE_EVENT);
+
+        // example URI: content://rcs/participant/12/alias_change_event/4, where 12 is the
+        // participant id, and 4 is the event id
+        URL_MATCHER.addURI(AUTHORITY, "participant/#/alias_change_event/#",
+                PARTICIPANT_ALIAS_CHANGE_EVENT_WITH_ID);
+
+        // example URI: content://rcs/p2p_thread
+        URL_MATCHER.addURI(AUTHORITY, "p2p_thread", P2P_THREAD);
+
+        // example URI: content://rcs/p2p_thread/4, where 4 is the thread id
+        URL_MATCHER.addURI(AUTHORITY, "p2p_thread/#", P2P_THREAD_WITH_ID);
+
+        // example URI: content://rcs/p2p_thread/4/participant, where 4 is the thread id
+        URL_MATCHER.addURI(AUTHORITY, "p2p_thread/#/participant", P2P_THREAD_PARTICIPANT);
+
+        // example URI: content://rcs/p2p_thread/9/participant/3", only supports a 1 time insert.
+        // 9 is the thread ID, 3 is the participant ID.
+        URL_MATCHER.addURI(AUTHORITY, "p2p_thread/#/participant/#", P2P_THREAD_PARTICIPANT_WITH_ID);
+
+        // example URI: content://rcs/group_thread
+        URL_MATCHER.addURI(AUTHORITY, "group_thread", GROUP_THREAD);
+
+        // example URI: content://rcs/group_thread/13, where 13 is the _id in rcs_threads table.
+        URL_MATCHER.addURI(AUTHORITY, "group_thread/#", GROUP_THREAD_WITH_ID);
+
+        // example URI: content://rcs/group_thread/13/participant_joined_event. Supports
+        // queries and inserts
+        URL_MATCHER.addURI(AUTHORITY, "group_thread/#/participant_joined_event",
+                GROUP_THREAD_PARTICIPANT_JOINED_EVENT);
+
+        // example URI: content://rcs/group_thread/13/participant_joined_event/3. Supports deletes.
+        URL_MATCHER.addURI(AUTHORITY, "group_thread/#/participant_joined_event/#",
+                GROUP_THREAD_PARTICIPANT_JOINED_EVENT_WITH_ID);
+
+        // example URI: content://rcs/group_thread/13/participant_left_event. Supports queries
+        // and inserts
+        URL_MATCHER.addURI(AUTHORITY, "group_thread/#/participant_left_event",
+                GROUP_THREAD_PARTICIPANT_LEFT_EVENT);
+
+        // example URI: content://rcs/group_thread/13/participant_left_event/5. Supports deletes
+        URL_MATCHER.addURI(AUTHORITY, "group_thread/#/participant_left_event/#",
+                GROUP_THREAD_PARTICIPANT_LEFT_EVENT_WITH_ID);
+
+        // example URI: content://rcs/group_thread/13/name_changed_event. Supports queries and
+        // inserts
+        URL_MATCHER.addURI(AUTHORITY, "group_thread/#/name_changed_event",
+                GROUP_THREAD_NAME_CHANGE_EVENT);
+
+        // example URI: content://rcs/group_thread/13/name_changed_event/7. Supports deletes
+        URL_MATCHER.addURI(AUTHORITY, "group_thread/#/name_changed_event/#",
+                GROUP_THREAD_NAME_CHANGE_EVENT_WITH_ID);
+
+        // example URI: content://rcs/group_thread/13/icon_changed_event. Supports queries and
+        // inserts
+        URL_MATCHER.addURI(AUTHORITY, "group_thread/#/icon_changed_event",
+                GROUP_THREAD_ICON_CHANGE_EVENT);
+
+        // example URI: content://rcs/group_thread/13/icon_changed_event/9. Supports deletes
+        URL_MATCHER.addURI(AUTHORITY, "group_thread/#/icon_changed_event/#",
+                GROUP_THREAD_ICON_CHANGE_EVENT_WITH_ID);
+
+        // example URI: content://rcs/group_thread/18/participant
+        URL_MATCHER.addURI(AUTHORITY, "group_thread/#/participant",
+                GROUP_THREAD_PARTICIPANT);
+
+        // example URI: content://rcs/group_thread/21/participant/4, only supports inserts and
+        // deletes
+        URL_MATCHER.addURI(AUTHORITY, "group_thread/#/participant/#",
+                GROUP_THREAD_PARTICIPANT_WITH_ID);
+
+        // example URI: content://rcs/message
+        URL_MATCHER.addURI(AUTHORITY, "message", UNIFIED_MESSAGE);
+
+        // example URI: content://rcs/message/4, where 4 is the message id.
+        URL_MATCHER.addURI(AUTHORITY, "message/#", UNIFIED_MESSAGE_WITH_ID);
+
+        // example URI: content://rcs/message/4/file_transfer, only supports inserts
+        URL_MATCHER.addURI(AUTHORITY, "message/#/file_transfer",
+                UNIFIED_MESSAGE_WITH_FILE_TRANSFER);
+
+        // example URI: content://rcs/incoming_message
+        URL_MATCHER.addURI(AUTHORITY, "incoming_message", INCOMING_MESSAGE);
+
+        // example URI: content://rcs/incoming_message/45
+        URL_MATCHER.addURI(AUTHORITY, "incoming_message/#", INCOMING_MESSAGE_WITH_ID);
+
+        // example URI: content://rcs/outgoing_message
+        URL_MATCHER.addURI(AUTHORITY, "outgoing_message", OUTGOING_MESSAGE);
+
+        // example URI: content://rcs/outgoing_message/54
+        URL_MATCHER.addURI(AUTHORITY, "outgoing_message/#", OUTGOING_MESSAGE_WITH_ID);
+
+        // example URI: content://rcs/outgoing_message/54/delivery. Only supports queries
+        URL_MATCHER.addURI(AUTHORITY, "outgoing_message/#/delivery", OUTGOING_MESSAGE_DELIVERY);
+
+        // example URI: content://rcs/outgoing_message/9/delivery/4. Does not support queries
+        URL_MATCHER.addURI(AUTHORITY, "outgoing_message/#/delivery/#",
+                OUTGOING_MESSAGE_DELIVERY_WITH_ID);
+
+        // example URI: content://rcs/thread/5/message, only supports querying.
+        URL_MATCHER.addURI(AUTHORITY, "thread/#/message", UNIFIED_MESSAGE_ON_THREAD);
+
+        // example URI: content://rcs/thread/5/message/40, only supports querying.
+        URL_MATCHER.addURI(AUTHORITY, "thread/#/message/#", UNIFIED_MESSAGE_ON_THREAD_WITH_ID);
+
+        // example URI: content://rcs/p2p_thread/3/incoming_message. Only available for inserting
+        // incoming messages onto a 1 to 1 thread.
+        URL_MATCHER.addURI(AUTHORITY, "p2p_thread/#/incoming_message",
+                INCOMING_MESSAGE_ON_P2P_THREAD);
+
+        // example URI: content://rcs/p2p_thread/11/incoming_message/45. Only supports querying
+        URL_MATCHER.addURI(AUTHORITY, "p2p_thread/#/incoming_message/#",
+                INCOMING_MESSAGE_ON_P2P_THREAD_WITH_ID);
+
+        // example URI: content://rcs/p2p_thread/3/outgoing_message. Only available for inserting
+        // outgoing messages onto a 1 to 1 thread.
+        URL_MATCHER.addURI(AUTHORITY, "p2p_thread/#/outgoing_message",
+                OUTGOING_MESSAGE_ON_P2P_THREAD);
+
+        // example URI: content://rcs/p2p_thread/11/outgoing_message/46. Only supports querying
+        URL_MATCHER.addURI(AUTHORITY, "p2p_thread/#/outgoing_message/#",
+                OUTGOING_MESSAGE_ON_P2P_THREAD_WITH_ID);
+
+        // example URI: content://rcs/group_thread/3/incoming_message. Only available for inserting
+        // incoming messages onto a group thread.
+        URL_MATCHER.addURI(AUTHORITY, "group_thread/#/incoming_message",
+                INCOMING_MESSAGE_ON_GROUP_THREAD);
+
+        // example URI: content://rcs/group_thread/3/incoming_message/71. Only supports querying
+        URL_MATCHER.addURI(AUTHORITY, "group_thread/#/incoming_message/#",
+                INCOMING_MESSAGE_ON_GROUP_THREAD_WITH_ID);
+
+        // example URI: content://rcs/group_thread/3/outgoing_message. Only available for inserting
+        // outgoing messages onto a group thread.
+        URL_MATCHER.addURI(AUTHORITY, "group_thread/#/outgoing_message",
+                OUTGOING_MESSAGE_ON_GROUP_THREAD);
+
+        // example URI: content://rcs/group_thread/13/outgoing_message/72. Only supports querying
+        URL_MATCHER.addURI(AUTHORITY, "group_thread/#/outgoing_message/#",
+                OUTGOING_MESSAGE_ON_GROUP_THREAD_WITH_ID);
+
+        // example URI: content://rcs/file_transfer/1. Does not support insertion
+        URL_MATCHER.addURI(AUTHORITY, "file_transfer/#", FILE_TRANSFER_WITH_ID);
+
+        // example URI: content://rcs/event
+        URL_MATCHER.addURI(AUTHORITY, "event", EVENT);
+
+        URL_MATCHER.addURI(AUTHORITY, "canonical-address", CANONICAL_ADDRESS);
+    }
+
+    @Override
+    public boolean onCreate() {
+        // Use the credential encrypted mmssms.db for RCS messages.
+        mDbOpenHelper = MmsSmsDatabaseHelper.getInstanceForCe(getContext());
+        mParticipantHelper = new RcsProviderParticipantHelper(mDbOpenHelper);
+        mThreadHelper = new RcsProviderThreadHelper(mDbOpenHelper);
+        mMessageHelper = new RcsProviderMessageHelper(mDbOpenHelper);
+        mEventHelper = new RcsProviderEventHelper(mDbOpenHelper);
+        mCanonicalAddressHelper = new RcsProviderCanonicalAddressHelper(mDbOpenHelper);
+        return true;
+    }
+
+    /**
+     * ContentResolver has a weird bug that if both query methods are overridden, it will always
+     * pick the bundle one to call, but will still require us to override this one as it is
+     * abstract. Work around by putting parameters in a bundle.
+     */
+    @Override
+    public synchronized Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        Bundle bundle = new Bundle();
+        bundle.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selection);
+        bundle.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs);
+        bundle.putString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER, sortOrder);
+        return query(uri, projection, bundle, null);
+    }
+
+    @Override
+    public synchronized Cursor query(Uri uri, String[] projection, Bundle queryArgs,
+            CancellationSignal unused) {
+        int match = URL_MATCHER.match(uri);
+
+        String selection = null;
+        String[] selectionArgs = null;
+        String sortOrder = null;
+
+        if (queryArgs != null) {
+            selection = queryArgs.getString(ContentResolver.QUERY_ARG_SQL_SELECTION);
+            selectionArgs = queryArgs.getStringArray(
+                    ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS);
+            sortOrder = queryArgs.getString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER);
+        }
+
+        switch (match) {
+            case UNIFIED_RCS_THREAD:
+                return mThreadHelper.queryUnifiedThread(queryArgs);
+            case UNIFIED_RCS_THREAD_WITH_ID:
+                return mThreadHelper.queryUnifiedThreadUsingId(uri, projection);
+            case PARTICIPANT:
+                return mParticipantHelper.queryParticipant(queryArgs);
+            case PARTICIPANT_WITH_ID:
+                return mParticipantHelper.queryParticipantWithId(uri, projection);
+            case PARTICIPANT_ALIAS_CHANGE_EVENT:
+                Log.e(TAG, "Querying individual event types is not supported, uri: " + uri);
+                break;
+            case PARTICIPANT_ALIAS_CHANGE_EVENT_WITH_ID:
+                Log.e(TAG, "Querying participant events with id's is not supported, uri: " + uri);
+                break;
+            case P2P_THREAD:
+                return mThreadHelper.query1to1Thread(projection, selection,
+                        selectionArgs, sortOrder);
+            case P2P_THREAD_WITH_ID:
+                return mThreadHelper.query1To1ThreadUsingId(uri, projection);
+            case P2P_THREAD_PARTICIPANT:
+                return mParticipantHelper.queryParticipantIn1To1Thread(uri);
+            case P2P_THREAD_PARTICIPANT_WITH_ID:
+                Log.e(TAG,
+                        "Querying participants in 1 to 1 threads via id's is not supported, uri: "
+                                + uri);
+                break;
+            case GROUP_THREAD:
+                return mThreadHelper.queryGroupThread(projection, selection,
+                        selectionArgs, sortOrder);
+            case GROUP_THREAD_WITH_ID:
+                return mThreadHelper.queryGroupThreadUsingId(uri, projection);
+            case GROUP_THREAD_PARTICIPANT:
+                return mParticipantHelper.queryParticipantsInGroupThread(uri);
+            case GROUP_THREAD_PARTICIPANT_WITH_ID:
+                return mParticipantHelper.queryParticipantInGroupThreadWithId(uri);
+            case GROUP_THREAD_PARTICIPANT_JOINED_EVENT:
+            case GROUP_THREAD_PARTICIPANT_JOINED_EVENT_WITH_ID:
+            case GROUP_THREAD_PARTICIPANT_LEFT_EVENT:
+            case GROUP_THREAD_PARTICIPANT_LEFT_EVENT_WITH_ID:
+            case GROUP_THREAD_NAME_CHANGE_EVENT:
+            case GROUP_THREAD_NAME_CHANGE_EVENT_WITH_ID:
+            case GROUP_THREAD_ICON_CHANGE_EVENT:
+            case GROUP_THREAD_ICON_CHANGE_EVENT_WITH_ID:
+                Log.e(TAG, "Querying individual event types is not supported, uri: " + uri);
+                break;
+            case UNIFIED_MESSAGE:
+                return mMessageHelper.queryMessages(queryArgs);
+            case UNIFIED_MESSAGE_WITH_ID:
+                return mMessageHelper.queryUnifiedMessageWithId(uri);
+            case UNIFIED_MESSAGE_WITH_FILE_TRANSFER:
+                Log.e(TAG,
+                        "Querying file transfers through messages is not supported, uri: " + uri);
+            case INCOMING_MESSAGE:
+                return mMessageHelper.queryIncomingMessageWithSelection(selection, selectionArgs);
+            case INCOMING_MESSAGE_WITH_ID:
+                return mMessageHelper.queryIncomingMessageWithId(uri);
+            case OUTGOING_MESSAGE:
+                return mMessageHelper.queryOutgoingMessageWithSelection(selection, selectionArgs);
+            case OUTGOING_MESSAGE_WITH_ID:
+                return mMessageHelper.queryOutgoingMessageWithId(uri);
+            case OUTGOING_MESSAGE_DELIVERY:
+                return mMessageHelper.queryOutgoingMessageDeliveries(uri);
+            case OUTGOING_MESSAGE_DELIVERY_WITH_ID:
+                Log.e(TAG,
+                        "Querying deliveries with message and participant ids is not supported, "
+                                + "uri: "
+                                + uri);
+            case UNIFIED_MESSAGE_ON_THREAD:
+                return mMessageHelper.queryAllMessagesOnThread(uri, selection, selectionArgs);
+            case UNIFIED_MESSAGE_ON_THREAD_WITH_ID:
+                return mMessageHelper.queryUnifiedMessageWithIdInThread(uri);
+            case INCOMING_MESSAGE_ON_P2P_THREAD:
+                Log.e(TAG,
+                        "Querying incoming messages on P2P thread with selection is not "
+                                + "supported, uri: "
+                                + uri);
+                break;
+            case INCOMING_MESSAGE_ON_P2P_THREAD_WITH_ID:
+                return mMessageHelper.queryUnifiedMessageWithIdInThread(uri);
+            case OUTGOING_MESSAGE_ON_P2P_THREAD:
+                Log.e(TAG,
+                        "Querying outgoing messages on P2P thread with selection is not "
+                                + "supported, uri: "
+                                + uri);
+                break;
+            case OUTGOING_MESSAGE_ON_P2P_THREAD_WITH_ID:
+                return mMessageHelper.queryUnifiedMessageWithIdInThread(uri);
+            case INCOMING_MESSAGE_ON_GROUP_THREAD:
+                Log.e(TAG,
+                        "Querying incoming messages on group thread with selection is not "
+                                + "supported, uri: "
+                                + uri);
+                break;
+            case INCOMING_MESSAGE_ON_GROUP_THREAD_WITH_ID:
+                return mMessageHelper.queryUnifiedMessageWithIdInThread(uri);
+            case OUTGOING_MESSAGE_ON_GROUP_THREAD:
+                Log.e(TAG,
+                        "Querying outgoing messages on group thread with selection is not "
+                                + "supported, uri: "
+                                + uri);
+                break;
+            case OUTGOING_MESSAGE_ON_GROUP_THREAD_WITH_ID:
+                return mMessageHelper.queryUnifiedMessageWithIdInThread(uri);
+            case FILE_TRANSFER_WITH_ID:
+                return mMessageHelper.queryFileTransfer(uri);
+            case EVENT:
+                return mEventHelper.queryEvents(queryArgs);
+            case CANONICAL_ADDRESS:
+                String canonicalAddress = uri.getQueryParameter("address");
+                return mCanonicalAddressHelper.getOrCreateCanonicalAddress(canonicalAddress);
+            default:
+                Log.e(TAG, "Invalid query: " + uri);
+        }
+
+        return null;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return null;
+    }
+
+    @Override
+    public synchronized Uri insert(Uri uri, ContentValues values) {
+        int match = URL_MATCHER.match(uri);
+        long rowId;
+
+        switch (match) {
+            case UNIFIED_RCS_THREAD:
+            case UNIFIED_RCS_THREAD_WITH_ID:
+                Log.e(TAG, "Inserting into unified thread view is not supported, uri: " + uri);
+                break;
+            case PARTICIPANT:
+                return buildUriWithRowIdAppended(PARTICIPANT_URI_PREFIX,
+                        mParticipantHelper.insertParticipant(values));
+            case PARTICIPANT_WITH_ID:
+                Log.e(TAG, "Inserting participant with a specified ID is not supported, uri: "
+                        + uri);
+                break;
+            case PARTICIPANT_ALIAS_CHANGE_EVENT:
+                return buildUriWithRowIdAppended(uri,
+                        mEventHelper.insertParticipantEvent(uri, values));
+            case PARTICIPANT_ALIAS_CHANGE_EVENT_WITH_ID:
+                Log.e(TAG, "Inserting participant events with id's is not supported, uri: " + uri);
+                break;
+            case P2P_THREAD:
+                return buildUriWithRowIdAppended(P2P_THREAD_URI_PREFIX,
+                        mThreadHelper.insert1To1Thread(values));
+            case P2P_THREAD_WITH_ID:
+                Log.e(TAG, "Inserting a thread with a specified ID is not supported, uri: " + uri);
+                break;
+            case P2P_THREAD_PARTICIPANT:
+                Log.e(TAG,
+                        "Inserting a participant into a thread via content values is not "
+                                + "supported, uri: "
+                                + uri);
+                break;
+            case P2P_THREAD_PARTICIPANT_WITH_ID:
+                Log.e(TAG,
+                        "Inserting participant into a thread via URI is not supported, uri: "
+                                + uri);
+                break;
+            case GROUP_THREAD:
+                return buildUriWithRowIdAppended(GROUP_THREAD_URI_PREFIX,
+                        mThreadHelper.insertGroupThread(values));
+            case GROUP_THREAD_WITH_ID:
+                Log.e(TAG, "Inserting a thread with a specified ID is not supported, uri: " + uri);
+                break;
+            case GROUP_THREAD_PARTICIPANT_JOINED_EVENT:
+                return buildUriWithRowIdAppended(uri,
+                        mEventHelper.insertParticipantJoinedEvent(uri, values));
+            case GROUP_THREAD_PARTICIPANT_JOINED_EVENT_WITH_ID:
+                Log.e(TAG, "Inserting thread events with id's is not supported, uri: " + uri);
+                break;
+            case GROUP_THREAD_PARTICIPANT_LEFT_EVENT:
+                return buildUriWithRowIdAppended(uri,
+                        mEventHelper.insertParticipantLeftEvent(uri, values));
+            case GROUP_THREAD_PARTICIPANT_LEFT_EVENT_WITH_ID:
+                Log.e(TAG, "Inserting thread events with id's is not supported, uri: " + uri);
+                break;
+            case GROUP_THREAD_NAME_CHANGE_EVENT:
+                return buildUriWithRowIdAppended(uri,
+                        mEventHelper.insertThreadNameChangeEvent(uri, values));
+            case GROUP_THREAD_NAME_CHANGE_EVENT_WITH_ID:
+                Log.e(TAG, "Inserting thread events with id's is not supported, uri: " + uri);
+                break;
+            case GROUP_THREAD_ICON_CHANGE_EVENT:
+                return buildUriWithRowIdAppended(uri,
+                        mEventHelper.insertThreadIconChangeEvent(uri, values));
+            case GROUP_THREAD_ICON_CHANGE_EVENT_WITH_ID:
+                Log.e(TAG, "Inserting thread events with id's is not supported, uri: " + uri);
+                break;
+            case GROUP_THREAD_PARTICIPANT:
+                rowId = mParticipantHelper.insertParticipantIntoGroupThread(values);
+                if (rowId == TRANSACTION_FAILED) {
+                    return null;
+                }
+                return mParticipantHelper.getParticipantInThreadUri(values, rowId);
+            case GROUP_THREAD_PARTICIPANT_WITH_ID:
+                return returnUriAsIsIfSuccessful(uri,
+                        mParticipantHelper.insertParticipantIntoGroupThreadWithId(uri));
+            case UNIFIED_MESSAGE:
+            case UNIFIED_MESSAGE_WITH_ID:
+                Log.e(TAG, "Inserting into unified message view is not supported, uri: " + uri);
+                break;
+            case UNIFIED_MESSAGE_WITH_FILE_TRANSFER:
+                return buildUriWithRowIdAppended(FILE_TRANSFER_PREFIX,
+                        mMessageHelper.insertFileTransferToMessage(uri, values));
+            case INCOMING_MESSAGE:
+            case INCOMING_MESSAGE_WITH_ID:
+            case OUTGOING_MESSAGE:
+            case OUTGOING_MESSAGE_WITH_ID:
+                Log.e(TAG, "Inserting a message without a thread is not supported, uri: "
+                        + uri);
+                break;
+            case OUTGOING_MESSAGE_DELIVERY:
+                Log.e(TAG,
+                        "Inserting an outgoing message delivery without a participant is not "
+                                + "supported, uri: "
+                                + uri);
+                break;
+            case OUTGOING_MESSAGE_DELIVERY_WITH_ID:
+                return returnUriAsIsIfSuccessful(uri,
+                        mMessageHelper.insertMessageDelivery(uri, values));
+            case UNIFIED_MESSAGE_ON_THREAD:
+            case UNIFIED_MESSAGE_ON_THREAD_WITH_ID:
+                Log.e(TAG,
+                        "Inserting a message on unified thread view is not supported, uri: " + uri);
+                break;
+            case INCOMING_MESSAGE_ON_P2P_THREAD:
+                return mMessageHelper.insertMessageOnThread(uri, values, /* isIncoming= */
+                        true, /* is1To1 */ true);
+            case OUTGOING_MESSAGE_ON_P2P_THREAD:
+                return mMessageHelper.insertMessageOnThread(uri, values, /* isIncoming= */
+                        false, /* is1To1 */ true);
+            case INCOMING_MESSAGE_ON_GROUP_THREAD:
+                return mMessageHelper.insertMessageOnThread(uri, values, /* isIncoming= */
+                        true, /* is1To1 */ false);
+            case OUTGOING_MESSAGE_ON_GROUP_THREAD:
+                return mMessageHelper.insertMessageOnThread(uri, values, /* isIncoming= */
+                        false, /* is1To1 */ false);
+            case INCOMING_MESSAGE_ON_P2P_THREAD_WITH_ID:
+            case OUTGOING_MESSAGE_ON_P2P_THREAD_WITH_ID:
+            case INCOMING_MESSAGE_ON_GROUP_THREAD_WITH_ID:
+            case OUTGOING_MESSAGE_ON_GROUP_THREAD_WITH_ID:
+                Log.e(TAG, "Inserting a message with a specific id is not supported, uri: " + uri);
+                break;
+            case FILE_TRANSFER_WITH_ID:
+                Log.e(TAG, "Inserting a file transfer without a message is not supported, uri: "
+                        + uri);
+                break;
+            case EVENT:
+                Log.e(TAG,
+                        "Inserting event using unified event query is not supported, uri: " + uri);
+                break;
+            default:
+                Log.e(TAG, "Invalid insert: " + uri);
+        }
+
+        return null;
+    }
+
+    @Override
+    public synchronized int delete(Uri uri, String selection, String[] selectionArgs) {
+        int match = URL_MATCHER.match(uri);
+        int deletedCount = 0;
+
+        switch (match) {
+            case UNIFIED_RCS_THREAD:
+            case UNIFIED_RCS_THREAD_WITH_ID:
+                Log.e(TAG, "Deleting entries from unified view is not allowed: " + uri);
+                break;
+            case PARTICIPANT:
+                Log.e(TAG, "Deleting participant with selection is not allowed: " + uri);
+                break;
+            case PARTICIPANT_WITH_ID:
+                return mParticipantHelper.deleteParticipantWithId(uri);
+            case PARTICIPANT_ALIAS_CHANGE_EVENT:
+                Log.e(TAG, "Deleting participant events without id is not allowed: " + uri);
+                break;
+            case PARTICIPANT_ALIAS_CHANGE_EVENT_WITH_ID:
+                return mEventHelper.deleteParticipantEvent(uri);
+            case P2P_THREAD:
+                return mThreadHelper.delete1To1Thread(selection, selectionArgs);
+            case P2P_THREAD_WITH_ID:
+                return mThreadHelper.delete1To1ThreadWithId(uri);
+            case P2P_THREAD_PARTICIPANT:
+                Log.e(TAG, "Removing participant from 1 to 1 thread is not allowed, uri: " + uri);
+                break;
+            case GROUP_THREAD:
+                return mThreadHelper.deleteGroupThread(selection, selectionArgs);
+            case GROUP_THREAD_WITH_ID:
+                return mThreadHelper.deleteGroupThreadWithId(uri);
+            case GROUP_THREAD_PARTICIPANT_JOINED_EVENT_WITH_ID:
+                return mEventHelper.deleteGroupThreadEvent(uri);
+            case GROUP_THREAD_PARTICIPANT_LEFT_EVENT_WITH_ID:
+                return mEventHelper.deleteGroupThreadEvent(uri);
+            case GROUP_THREAD_NAME_CHANGE_EVENT_WITH_ID:
+                return mEventHelper.deleteGroupThreadEvent(uri);
+            case GROUP_THREAD_PARTICIPANT_JOINED_EVENT:
+            case GROUP_THREAD_PARTICIPANT_LEFT_EVENT:
+            case GROUP_THREAD_NAME_CHANGE_EVENT:
+            case GROUP_THREAD_ICON_CHANGE_EVENT:
+                Log.e(TAG, "Deleting thread events via selection is not allowed, uri: " + uri);
+                break;
+            case GROUP_THREAD_ICON_CHANGE_EVENT_WITH_ID:
+                return mEventHelper.deleteGroupThreadEvent(uri);
+            case GROUP_THREAD_PARTICIPANT:
+                Log.e(TAG,
+                        "Deleting a participant from group thread via selection is not allowed, "
+                                + "uri: "
+                                + uri);
+                break;
+            case GROUP_THREAD_PARTICIPANT_WITH_ID:
+                return mParticipantHelper.deleteParticipantFromGroupThread(uri);
+            case UNIFIED_MESSAGE:
+                Log.e(TAG,
+                        "Deleting message from unified view with selection is not allowed: " + uri);
+                break;
+            case UNIFIED_MESSAGE_WITH_ID:
+                Log.e(TAG, "Deleting message from unified view with id is not allowed: " + uri);
+                break;
+            case UNIFIED_MESSAGE_WITH_FILE_TRANSFER:
+                Log.e(TAG, "Deleting file transfer using message uri is not allowed, uri: " + uri);
+                break;
+            case INCOMING_MESSAGE:
+                return mMessageHelper.deleteIncomingMessageWithSelection(selection, selectionArgs);
+            case INCOMING_MESSAGE_WITH_ID:
+                return mMessageHelper.deleteIncomingMessageWithId(uri);
+            case OUTGOING_MESSAGE:
+                return mMessageHelper.deleteOutgoingMessageWithSelection(selection, selectionArgs);
+            case OUTGOING_MESSAGE_WITH_ID:
+                return mMessageHelper.deleteOutgoingMessageWithId(uri);
+            case OUTGOING_MESSAGE_DELIVERY:
+            case OUTGOING_MESSAGE_DELIVERY_WITH_ID:
+                Log.e(TAG, "Deleting message deliveries is not supported, uri: " + uri);
+                break;
+            case UNIFIED_MESSAGE_ON_THREAD:
+            case UNIFIED_MESSAGE_ON_THREAD_WITH_ID:
+            case INCOMING_MESSAGE_ON_P2P_THREAD:
+            case INCOMING_MESSAGE_ON_P2P_THREAD_WITH_ID:
+            case OUTGOING_MESSAGE_ON_P2P_THREAD:
+            case OUTGOING_MESSAGE_ON_P2P_THREAD_WITH_ID:
+            case INCOMING_MESSAGE_ON_GROUP_THREAD:
+            case INCOMING_MESSAGE_ON_GROUP_THREAD_WITH_ID:
+            case OUTGOING_MESSAGE_ON_GROUP_THREAD:
+            case OUTGOING_MESSAGE_ON_GROUP_THREAD_WITH_ID:
+                Log.e(TAG, "Deleting messages using thread uris is not supported, uri: " + uri);
+                break;
+            case FILE_TRANSFER_WITH_ID:
+                return mMessageHelper.deleteFileTransfer(uri);
+            case EVENT:
+                Log.e(TAG, "Deleting events using unified event uri is not supported, uri: " + uri);
+                break;
+            default:
+                Log.e(TAG, "Invalid delete: " + uri);
+        }
+
+        return deletedCount;
+    }
+
+    @Override
+    public synchronized int update(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs) {
+        int match = URL_MATCHER.match(uri);
+        int updatedCount = 0;
+
+        switch (match) {
+            case UNIFIED_RCS_THREAD:
+            case UNIFIED_RCS_THREAD_WITH_ID:
+                Log.e(TAG, "Updating unified thread view is not supported, uri: " + uri);
+                break;
+            case PARTICIPANT:
+                Log.e(TAG, "Updating participants with selection is not supported, uri: " + uri);
+                break;
+            case PARTICIPANT_WITH_ID:
+                return mParticipantHelper.updateParticipantWithId(values, uri);
+            case PARTICIPANT_ALIAS_CHANGE_EVENT:
+            case PARTICIPANT_ALIAS_CHANGE_EVENT_WITH_ID:
+                Log.e(TAG, "Updating events is not supported, uri: " + uri);
+                break;
+            case P2P_THREAD:
+                return mThreadHelper.update1To1Thread(values, selection, selectionArgs);
+            case P2P_THREAD_WITH_ID:
+                return mThreadHelper.update1To1ThreadWithId(values, uri);
+            case P2P_THREAD_PARTICIPANT:
+                Log.e(TAG, "Updating junction table entries is not supported, uri: " + uri);
+                break;
+            case GROUP_THREAD:
+                return mThreadHelper.updateGroupThread(values, selection, selectionArgs);
+            case GROUP_THREAD_WITH_ID:
+                return mThreadHelper.updateGroupThreadWithId(values, uri);
+            case GROUP_THREAD_PARTICIPANT_JOINED_EVENT:
+            case GROUP_THREAD_PARTICIPANT_JOINED_EVENT_WITH_ID:
+            case GROUP_THREAD_PARTICIPANT_LEFT_EVENT:
+            case GROUP_THREAD_PARTICIPANT_LEFT_EVENT_WITH_ID:
+            case GROUP_THREAD_NAME_CHANGE_EVENT:
+            case GROUP_THREAD_NAME_CHANGE_EVENT_WITH_ID:
+            case GROUP_THREAD_ICON_CHANGE_EVENT:
+            case GROUP_THREAD_ICON_CHANGE_EVENT_WITH_ID:
+                Log.e(TAG, "Updating thread events is not supported, uri: " + uri);
+                break;
+            case GROUP_THREAD_PARTICIPANT:
+            case GROUP_THREAD_PARTICIPANT_WITH_ID:
+                Log.e(TAG, "Updating junction table entries is not supported, uri: " + uri);
+                break;
+            case UNIFIED_MESSAGE:
+            case UNIFIED_MESSAGE_WITH_ID:
+                Log.e(TAG, "Updating unified message view is not supported, uri: " + uri);
+                break;
+            case UNIFIED_MESSAGE_WITH_FILE_TRANSFER:
+                Log.e(TAG,
+                        "Updating file transfer using unified message uri is not supported, uri: "
+                                + uri);
+            case INCOMING_MESSAGE:
+                Log.e(TAG,
+                        "Updating an incoming message via selection is not supported, uri: " + uri);
+                break;
+            case INCOMING_MESSAGE_WITH_ID:
+                return mMessageHelper.updateIncomingMessage(uri, values);
+            case OUTGOING_MESSAGE:
+                Log.e(TAG,
+                        "Updating an outgoing message via selection is not supported, uri: " + uri);
+                break;
+            case OUTGOING_MESSAGE_WITH_ID:
+                return mMessageHelper.updateOutgoingMessage(uri, values);
+            case OUTGOING_MESSAGE_DELIVERY:
+                Log.e(TAG, "Updating message deliveries using message uris is not supported, uri: "
+                        + uri);
+                break;
+            case OUTGOING_MESSAGE_DELIVERY_WITH_ID:
+                return mMessageHelper.updateDelivery(uri, values);
+            case UNIFIED_MESSAGE_ON_THREAD:
+            case UNIFIED_MESSAGE_ON_THREAD_WITH_ID:
+            case INCOMING_MESSAGE_ON_P2P_THREAD:
+            case INCOMING_MESSAGE_ON_P2P_THREAD_WITH_ID:
+            case OUTGOING_MESSAGE_ON_P2P_THREAD:
+            case OUTGOING_MESSAGE_ON_P2P_THREAD_WITH_ID:
+            case INCOMING_MESSAGE_ON_GROUP_THREAD:
+            case INCOMING_MESSAGE_ON_GROUP_THREAD_WITH_ID:
+            case OUTGOING_MESSAGE_ON_GROUP_THREAD:
+            case OUTGOING_MESSAGE_ON_GROUP_THREAD_WITH_ID:
+                Log.e(TAG, "Updating messages using threads uris is not supported, uri: " + uri);
+                break;
+            case FILE_TRANSFER_WITH_ID:
+                return mMessageHelper.updateFileTransfer(uri, values);
+            case EVENT:
+                Log.e(TAG, "Updating events is not supported, uri: " + uri);
+                break;
+            default:
+                Log.e(TAG, "Invalid update: " + uri);
+        }
+
+        return updatedCount;
+    }
+}
diff --git a/src/com/android/providers/telephony/RcsProviderCanonicalAddressHelper.java b/src/com/android/providers/telephony/RcsProviderCanonicalAddressHelper.java
new file mode 100644
index 0000000..496b512
--- /dev/null
+++ b/src/com/android/providers/telephony/RcsProviderCanonicalAddressHelper.java
@@ -0,0 +1,54 @@
+package com.android.providers.telephony;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.provider.BaseColumns;
+import android.provider.Telephony;
+
+public class RcsProviderCanonicalAddressHelper {
+    SQLiteOpenHelper mSQLiteOpenHelper;
+
+    RcsProviderCanonicalAddressHelper(SQLiteOpenHelper sqLiteOpenHelper) {
+        mSQLiteOpenHelper = sqLiteOpenHelper;
+    }
+
+    Cursor getOrCreateCanonicalAddress(String canonicalAddress) {
+        SQLiteDatabase db = mSQLiteOpenHelper.getReadableDatabase();
+
+        Cursor cursor = db.query(
+                MmsSmsProvider.TABLE_CANONICAL_ADDRESSES,
+                new String[]{BaseColumns._ID}, Telephony.CanonicalAddressesColumns.ADDRESS + "=?",
+                new String[]{canonicalAddress}, null, null, null);
+
+        if (cursor != null && cursor.getCount() > 0) {
+            return cursor;
+        }
+
+        if (cursor != null) {
+            cursor.close();
+        }
+
+        return insertCanonicalAddress(canonicalAddress);
+    }
+
+    private Cursor insertCanonicalAddress(String canonicalAddress) {
+        ContentValues contentValues = new ContentValues();
+        contentValues.put(Telephony.CanonicalAddressesColumns.ADDRESS, canonicalAddress);
+
+        SQLiteDatabase db = mSQLiteOpenHelper.getWritableDatabase();
+
+        long id = db.insert(MmsSmsProvider.TABLE_CANONICAL_ADDRESSES, null, contentValues);
+
+        if (id == -1) {
+            return null;
+        }
+
+        MatrixCursor matrixCursor = new MatrixCursor(new String[]{BaseColumns._ID}, 1);
+        matrixCursor.addRow(new Object[]{id});
+
+        return matrixCursor;
+    }
+}
diff --git a/src/com/android/providers/telephony/RcsProviderEventHelper.java b/src/com/android/providers/telephony/RcsProviderEventHelper.java
new file mode 100644
index 0000000..c6a6fc6
--- /dev/null
+++ b/src/com/android/providers/telephony/RcsProviderEventHelper.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.providers.telephony;
+
+import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_ALIAS_CHANGED_EVENT_TYPE;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantEventColumns.NEW_ALIAS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsEventTypes.ICON_CHANGED_EVENT_TYPE;
+import static android.provider.Telephony.RcsColumns.RcsEventTypes.NAME_CHANGED_EVENT_TYPE;
+import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_JOINED_EVENT_TYPE;
+import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_LEFT_EVENT_TYPE;
+import static android.provider.Telephony.RcsColumns.RcsThreadColumns.RCS_THREAD_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.DESTINATION_PARTICIPANT_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.EVENT_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.EVENT_TYPE_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.NEW_ICON_URI_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.NEW_NAME_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.SOURCE_PARTICIPANT_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.TIMESTAMP_COLUMN;
+import static android.provider.Telephony.RcsColumns.TRANSACTION_FAILED;
+import static android.telephony.ims.RcsEventQueryParams.ALL_EVENTS;
+import static android.telephony.ims.RcsEventQueryParams.ALL_GROUP_THREAD_EVENTS;
+import static android.telephony.ims.RcsEventQueryParams.EVENT_QUERY_PARAMETERS_KEY;
+
+import static android.telephony.ims.RcsQueryContinuationToken.EVENT_QUERY_CONTINUATION_TOKEN_TYPE;
+import static android.telephony.ims.RcsQueryContinuationToken.QUERY_CONTINUATION_TOKEN;
+import static com.android.providers.telephony.RcsProvider.RCS_PARTICIPANT_EVENT_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_PARTICIPANT_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_THREAD_EVENT_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_THREAD_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_UNIFIED_EVENT_VIEW;
+import static com.android.providers.telephony.RcsProvider.TAG;
+import static com.android.providers.telephony.RcsProviderThreadHelper.getThreadIdFromUri;
+import static com.android.providers.telephony.RcsProviderUtil.INSERTION_FAILED;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.os.Bundle;
+import android.telephony.ims.RcsEventQueryParams;
+import android.telephony.ims.RcsQueryContinuationToken;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Constants and helpers related to events for {@link RcsProvider} to keep the code clean.
+ *
+ * @hide
+ */
+class RcsProviderEventHelper {
+    private static final int PARTICIPANT_INDEX_IN_EVENT_URI = 1;
+    private static final int EVENT_INDEX_IN_EVENT_URI = 3;
+
+    @VisibleForTesting
+    public static void createRcsEventTables(SQLiteDatabase db) {
+        Log.d(TAG, "Creating event tables");
+
+        // Add the event tables
+        db.execSQL("CREATE TABLE " + RCS_THREAD_EVENT_TABLE + "(" + EVENT_ID_COLUMN
+                + " INTEGER PRIMARY KEY AUTOINCREMENT, " + RCS_THREAD_ID_COLUMN + " INTEGER, "
+                + SOURCE_PARTICIPANT_ID_COLUMN + " INTEGER, " + EVENT_TYPE_COLUMN + " INTEGER, "
+                + TIMESTAMP_COLUMN + " INTEGER, " + DESTINATION_PARTICIPANT_ID_COLUMN + " INTEGER, "
+                + NEW_ICON_URI_COLUMN + " TEXT, " + NEW_NAME_COLUMN + " TEXT, " + " FOREIGN KEY ("
+                + RCS_THREAD_ID_COLUMN + ") REFERENCES " + RCS_THREAD_TABLE + " ("
+                + RCS_THREAD_ID_COLUMN + "), FOREIGN KEY (" + SOURCE_PARTICIPANT_ID_COLUMN
+                + ") REFERENCES " + RCS_PARTICIPANT_TABLE + " (" + RCS_PARTICIPANT_ID_COLUMN
+                + "))");
+
+        db.execSQL("CREATE TABLE " + RCS_PARTICIPANT_EVENT_TABLE + "(" + EVENT_ID_COLUMN
+                + " INTEGER PRIMARY KEY AUTOINCREMENT, " + SOURCE_PARTICIPANT_ID_COLUMN +
+                " INTEGER, " + TIMESTAMP_COLUMN + " INTEGER, "
+                + NEW_ALIAS_COLUMN + " TEXT," + " FOREIGN KEY (" + SOURCE_PARTICIPANT_ID_COLUMN
+                + ") REFERENCES " + RCS_PARTICIPANT_TABLE + " (" + RCS_PARTICIPANT_ID_COLUMN
+                + "))");
+
+        // Add the views
+
+        // The following is a unified event view that puts every entry in both tables into one query
+        db.execSQL("CREATE VIEW " + RCS_UNIFIED_EVENT_VIEW + " AS "
+                + "SELECT " + PARTICIPANT_ALIAS_CHANGED_EVENT_TYPE + " AS " + EVENT_TYPE_COLUMN
+                + ", " + EVENT_ID_COLUMN + ", " + SOURCE_PARTICIPANT_ID_COLUMN + ", "
+                + TIMESTAMP_COLUMN + ", " + NEW_ALIAS_COLUMN + ", NULL as " + RCS_THREAD_ID_COLUMN
+                + ", NULL as " + DESTINATION_PARTICIPANT_ID_COLUMN + ", NULL as "
+                + NEW_ICON_URI_COLUMN + ", NULL as " + NEW_NAME_COLUMN + " "
+                + "FROM " + RCS_PARTICIPANT_EVENT_TABLE + " "
+                + "UNION "
+                + "SELECT " + EVENT_TYPE_COLUMN + ", " + EVENT_ID_COLUMN + ", "
+                + SOURCE_PARTICIPANT_ID_COLUMN + ", " + TIMESTAMP_COLUMN + ", "
+                + "NULL as " + NEW_ALIAS_COLUMN + ", " + RCS_THREAD_ID_COLUMN + ", "
+                + DESTINATION_PARTICIPANT_ID_COLUMN + ", " + NEW_ICON_URI_COLUMN + ", "
+                + NEW_NAME_COLUMN + " "
+                + "FROM " + RCS_THREAD_EVENT_TABLE);
+    }
+
+    private final SQLiteOpenHelper mSqLiteOpenHelper;
+
+    Cursor queryEvents(Bundle bundle) {
+        RcsEventQueryParams queryParameters = null;
+        RcsQueryContinuationToken continuationToken = null;
+
+        if (bundle != null) {
+            queryParameters = bundle.getParcelable(EVENT_QUERY_PARAMETERS_KEY);
+            continuationToken = bundle.getParcelable(QUERY_CONTINUATION_TOKEN);
+        }
+
+        if (continuationToken != null) {
+            return RcsProviderUtil.performContinuationQuery(mSqLiteOpenHelper.getReadableDatabase(),
+                    continuationToken);
+        }
+
+        // if no query parameters were entered, build an empty query parameters object
+        if (queryParameters == null) {
+            queryParameters = new RcsEventQueryParams.Builder().build();
+        }
+
+        return performInitialQuery(queryParameters);
+    }
+
+    private Cursor performInitialQuery(RcsEventQueryParams queryParameters) {
+        SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+        StringBuilder rawQuery = new StringBuilder("SELECT * FROM ").append(RCS_UNIFIED_EVENT_VIEW);
+
+        int eventType = queryParameters.getEventType();
+        if (eventType != ALL_EVENTS) {
+            rawQuery.append(" WHERE ").append(EVENT_TYPE_COLUMN);
+            if (eventType == ALL_GROUP_THREAD_EVENTS) {
+                rawQuery.append(" IN (").append(
+                        PARTICIPANT_JOINED_EVENT_TYPE).append(", ").append(
+                        PARTICIPANT_LEFT_EVENT_TYPE).append(", ").append(
+                        ICON_CHANGED_EVENT_TYPE).append(", ").append(
+                        NAME_CHANGED_EVENT_TYPE).append(
+                        ")");
+            } else {
+                rawQuery.append("=").append(eventType);
+            }
+        }
+
+        rawQuery.append(" ORDER BY ");
+
+        int sortingProperty = queryParameters.getSortingProperty();
+        if (sortingProperty == RcsEventQueryParams.SORT_BY_TIMESTAMP) {
+            rawQuery.append(TIMESTAMP_COLUMN);
+        } else {
+            rawQuery.append(EVENT_ID_COLUMN);
+        }
+
+        rawQuery.append(queryParameters.getSortDirection() ? " ASC " : " DESC ");
+
+        RcsProviderUtil.appendLimit(rawQuery, queryParameters.getLimit());
+        String rawQueryAsString = rawQuery.toString();
+        Cursor cursor = db.rawQuery(rawQueryAsString, null);
+
+        // if the query was paginated, build the next query
+        int limit = queryParameters.getLimit();
+        if (limit > 0) {
+            RcsProviderUtil.createContinuationTokenBundle(cursor,
+                    new RcsQueryContinuationToken(EVENT_QUERY_CONTINUATION_TOKEN_TYPE,
+                        rawQueryAsString, limit, limit), QUERY_CONTINUATION_TOKEN);
+        }
+
+        return cursor;
+    }
+
+    RcsProviderEventHelper(SQLiteOpenHelper sqLiteOpenHelper) {
+        mSqLiteOpenHelper = sqLiteOpenHelper;
+    }
+
+    long insertParticipantEvent(Uri uri, ContentValues values) {
+        String participantId = getParticipantIdFromUri(uri);
+
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+        values.put(SOURCE_PARTICIPANT_ID_COLUMN, participantId);
+        long rowId = db.insert(RCS_PARTICIPANT_EVENT_TABLE, SOURCE_PARTICIPANT_ID_COLUMN, values);
+        values.remove(SOURCE_PARTICIPANT_ID_COLUMN);
+
+        if (rowId == INSERTION_FAILED) {
+            return TRANSACTION_FAILED;
+        }
+
+        return rowId;
+    }
+
+    long insertParticipantJoinedEvent(Uri uri, ContentValues values) {
+        return insertGroupThreadEvent(uri, values, PARTICIPANT_JOINED_EVENT_TYPE);
+    }
+
+    long insertParticipantLeftEvent(Uri uri, ContentValues values) {
+        return insertGroupThreadEvent(uri, values, PARTICIPANT_LEFT_EVENT_TYPE);
+    }
+
+    long insertThreadNameChangeEvent(Uri uri, ContentValues values) {
+        return insertGroupThreadEvent(uri, values, NAME_CHANGED_EVENT_TYPE);
+    }
+
+    long insertThreadIconChangeEvent(Uri uri, ContentValues values) {
+        return insertGroupThreadEvent(uri, values, ICON_CHANGED_EVENT_TYPE);
+    }
+
+    private long insertGroupThreadEvent(Uri uri, ContentValues valuesParameter,
+            int eventType) {
+        String threadId = getThreadIdFromUri(uri);
+
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+        ContentValues values = new ContentValues(valuesParameter);
+        values.put(EVENT_TYPE_COLUMN, eventType);
+        values.put(RCS_THREAD_ID_COLUMN, threadId);
+        long rowId = db.insert(RCS_THREAD_EVENT_TABLE, EVENT_ID_COLUMN, values);
+
+        if (rowId == INSERTION_FAILED) {
+            return TRANSACTION_FAILED;
+        }
+
+        return rowId;
+    }
+
+    int deleteParticipantEvent(Uri uri) {
+        String eventId = getEventIdFromEventUri(uri);
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+
+        return db.delete(RCS_PARTICIPANT_EVENT_TABLE, EVENT_ID_COLUMN + "=?",
+                new String[]{eventId});
+    }
+
+    int deleteGroupThreadEvent(Uri uri) {
+        String eventId = getEventIdFromEventUri(uri);
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+
+        return db.delete(RCS_THREAD_EVENT_TABLE, EVENT_ID_COLUMN + "=?", new String[]{eventId});
+    }
+
+    private String getEventIdFromEventUri(Uri uri) {
+        return uri.getPathSegments().get(EVENT_INDEX_IN_EVENT_URI);
+    }
+
+    private String getParticipantIdFromUri(Uri uri) {
+        return uri.getPathSegments().get(PARTICIPANT_INDEX_IN_EVENT_URI);
+    }
+}
diff --git a/src/com/android/providers/telephony/RcsProviderMessageHelper.java b/src/com/android/providers/telephony/RcsProviderMessageHelper.java
new file mode 100644
index 0000000..670c641
--- /dev/null
+++ b/src/com/android/providers/telephony/RcsProviderMessageHelper.java
@@ -0,0 +1,707 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.providers.telephony;
+
+import static android.provider.Telephony.RcsColumns.CONTENT_AND_AUTHORITY;
+import static android.provider.Telephony.RcsColumns.Rcs1To1ThreadColumns.RCS_1_TO_1_THREAD_URI_PART;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.CONTENT_TYPE_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.CONTENT_URI_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.FILE_SIZE_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.FILE_TRANSFER_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.HEIGHT_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.DURATION_MILLIS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.PREVIEW_TYPE_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.PREVIEW_URI_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.SESSION_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.SUCCESSFULLY_TRANSFERRED_BYTES;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.TRANSFER_STATUS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.WIDTH_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.RCS_GROUP_THREAD_URI_PART;
+import static android.provider.Telephony.RcsColumns.RcsIncomingMessageColumns.ARRIVAL_TIMESTAMP_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsIncomingMessageColumns.INCOMING_MESSAGE_URI_PART;
+import static android.provider.Telephony.RcsColumns.RcsIncomingMessageColumns.SENDER_PARTICIPANT_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.GLOBAL_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.LATITUDE_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.LONGITUDE_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.MESSAGE_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.MESSAGE_TEXT_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.ORIGINATION_TIMESTAMP_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.STATUS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.SUB_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageDeliveryColumns.DELIVERED_TIMESTAMP_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsOutgoingMessageColumns.OUTGOING_MESSAGE_URI_PART;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadColumns.RCS_THREAD_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.MESSAGE_TYPE_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.MESSAGE_TYPE_INCOMING;
+import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.MESSAGE_TYPE_OUTGOING;
+import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.UNIFIED_INCOMING_MESSAGE_VIEW;
+import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.UNIFIED_OUTGOING_MESSAGE_VIEW;
+import static android.provider.Telephony.RcsColumns.TRANSACTION_FAILED;
+import static android.telephony.ims.RcsMessageQueryParams.MESSAGE_QUERY_PARAMETERS_KEY;
+import static android.telephony.ims.RcsMessageQueryParams.THREAD_ID_NOT_SET;
+
+import static android.telephony.ims.RcsQueryContinuationToken.MESSAGE_QUERY_CONTINUATION_TOKEN_TYPE;
+import static android.telephony.ims.RcsQueryContinuationToken.QUERY_CONTINUATION_TOKEN;
+import static com.android.providers.telephony.RcsProvider.RCS_FILE_TRANSFER_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_INCOMING_MESSAGE_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_MESSAGE_DELIVERY_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_MESSAGE_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_OUTGOING_MESSAGE_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_PARTICIPANT_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_PARTICIPANT_THREAD_JUNCTION_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_THREAD_TABLE;
+import static com.android.providers.telephony.RcsProvider.TAG;
+import static com.android.providers.telephony.RcsProvider.UNIFIED_MESSAGE_VIEW;
+import static com.android.providers.telephony.RcsProviderThreadHelper.getThreadIdFromUri;
+import static com.android.providers.telephony.RcsProviderUtil.INSERTION_FAILED;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Telephony.RcsColumns.RcsIncomingMessageColumns;
+import android.provider.Telephony.RcsColumns.RcsMessageDeliveryColumns;
+import android.telephony.ims.RcsMessageQueryParams;
+import android.telephony.ims.RcsQueryContinuationToken;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Constants and helpers related to messages for {@link RcsProvider} to keep the code clean.
+ *
+ * @hide
+ */
+public class RcsProviderMessageHelper {
+    private static final int MESSAGE_ID_INDEX_IN_URI = 1;
+    private static final int MESSAGE_ID_INDEX_IN_THREAD_URI = 3;
+
+    private final SQLiteOpenHelper mSqLiteOpenHelper;
+
+    @VisibleForTesting
+    public static void createRcsMessageTables(SQLiteDatabase db) {
+        Log.d(TAG, "Creating message tables");
+
+        // Add the message tables
+        db.execSQL("CREATE TABLE " + RCS_MESSAGE_TABLE + "(" + MESSAGE_ID_COLUMN
+                + " INTEGER PRIMARY KEY AUTOINCREMENT, " + RCS_THREAD_ID_COLUMN + " INTEGER, "
+                + GLOBAL_ID_COLUMN + " TEXT, " + SUB_ID_COLUMN + " INTEGER, " + MESSAGE_TEXT_COLUMN
+                + " TEXT," + LATITUDE_COLUMN + " REAL, " + LONGITUDE_COLUMN + " REAL, "
+                + STATUS_COLUMN + " INTEGER, " + ORIGINATION_TIMESTAMP_COLUMN
+                + " INTEGER, FOREIGN KEY(" + RCS_THREAD_ID_COLUMN + ") REFERENCES "
+                + RCS_THREAD_TABLE + "(" + RCS_THREAD_ID_COLUMN + "))");
+
+        db.execSQL("CREATE TABLE " + RCS_INCOMING_MESSAGE_TABLE + "(" + MESSAGE_ID_COLUMN
+                + " INTEGER PRIMARY KEY, " + SENDER_PARTICIPANT_ID_COLUMN + " INTEGER, "
+                + ARRIVAL_TIMESTAMP_COLUMN + " INTEGER, "
+                + RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN + " INTEGER, FOREIGN KEY ("
+                + MESSAGE_ID_COLUMN + ") REFERENCES " + RCS_MESSAGE_TABLE + "(" + MESSAGE_ID_COLUMN
+                + "))");
+
+        db.execSQL("CREATE TABLE " + RCS_OUTGOING_MESSAGE_TABLE + "(" + MESSAGE_ID_COLUMN
+                + " INTEGER PRIMARY KEY, FOREIGN KEY (" + MESSAGE_ID_COLUMN + ") REFERENCES "
+                + RCS_MESSAGE_TABLE + "(" + MESSAGE_ID_COLUMN + "))");
+
+        db.execSQL("CREATE TABLE " + RCS_MESSAGE_DELIVERY_TABLE + "(" + MESSAGE_ID_COLUMN
+                + " INTEGER, " + RCS_PARTICIPANT_ID_COLUMN + " INTEGER, "
+                + DELIVERED_TIMESTAMP_COLUMN + " INTEGER, "
+                + RcsMessageDeliveryColumns.SEEN_TIMESTAMP_COLUMN + " INTEGER, "
+                + "CONSTRAINT message_delivery PRIMARY KEY (" + MESSAGE_ID_COLUMN + ", "
+                + RCS_PARTICIPANT_ID_COLUMN + "), FOREIGN KEY (" + MESSAGE_ID_COLUMN
+                + ") REFERENCES " + RCS_MESSAGE_TABLE + "(" + MESSAGE_ID_COLUMN + "), FOREIGN KEY ("
+                + RCS_PARTICIPANT_ID_COLUMN + ") REFERENCES " + RCS_PARTICIPANT_TABLE + "("
+                + RCS_PARTICIPANT_ID_COLUMN + "))");
+
+        db.execSQL("CREATE TABLE " + RCS_FILE_TRANSFER_TABLE + " (" + FILE_TRANSFER_ID_COLUMN
+                + " INTEGER PRIMARY KEY AUTOINCREMENT, " + MESSAGE_ID_COLUMN + " INTEGER, "
+                + SESSION_ID_COLUMN + " TEXT, " + CONTENT_URI_COLUMN + " TEXT, "
+                + CONTENT_TYPE_COLUMN + " TEXT, " + FILE_SIZE_COLUMN + " INTEGER, "
+                + SUCCESSFULLY_TRANSFERRED_BYTES + " INTEGER, " + TRANSFER_STATUS_COLUMN +
+                " INTEGER, " + WIDTH_COLUMN + " INTEGER, " + HEIGHT_COLUMN + " INTEGER, "
+                + DURATION_MILLIS_COLUMN + " INTEGER, " + PREVIEW_URI_COLUMN + " TEXT, "
+                + PREVIEW_TYPE_COLUMN + " TEXT, FOREIGN KEY (" + MESSAGE_ID_COLUMN + ") REFERENCES "
+                + RCS_MESSAGE_TABLE + "(" + MESSAGE_ID_COLUMN + "))");
+
+        // Add the views
+        //
+        // The following view inner joins incoming messages with all messages, inner joins outgoing
+        // messages with all messages, and unions them together, while also adding an is_incoming
+        // column for easily telling where the record came from. This may have been achieved with
+        // an outer join but SQLite doesn't support them.
+        //
+        // CREATE VIEW unified_message_view AS
+        //
+        // SELECT rcs_message.rcs_message_row_id,
+        //        rcs_message.rcs_thread_id,
+        //        rcs_message.rcs_message_global_id,
+        //        rcs_message.sub_id,
+        //        rcs_message.status,
+        //        rcs_message.origination_timestamp,
+        //        rcs_message.rcs_text,
+        //        rcs_message.latitude,
+        //        rcs_message.longitude,
+        //        0 AS sender_participant,
+        //        0 AS arrival_timestamp,
+        //        0 AS seen_timestamp,
+        //        outgoing AS message_type
+        //
+        // FROM rcs_message INNER JOIN rcs_outgoing_message
+        //          ON rcs_message.rcs_message_row_id=rcs_outgoing_message.rcs_message_row_id
+        //
+        // UNION
+        //
+        // SELECT rcs_message.rcs_message_row_id,
+        //        rcs_message.rcs_thread_id,
+        //        rcs_message.rcs_message_global_id,
+        //        rcs_message.sub_id,
+        //        rcs_message.status,
+        //        rcs_message.origination_timestamp,
+        //        rcs_message.rcs_text,
+        //        rcs_message.latitude,
+        //        rcs_message.longitude,
+        //        rcs_incoming_message.sender_participant,
+        //        rcs_incoming_message.arrival_timestamp,
+        //        rcs_incoming_message.seen_timestamp,
+        //        incoming AS message_type
+        //
+        // FROM rcs_message INNER JOIN rcs_incoming_message
+        //          ON rcs_message.rcs_message_row_id=rcs_incoming_message.rcs_message_row_id
+        //
+        db.execSQL("CREATE VIEW " + UNIFIED_MESSAGE_VIEW + " AS SELECT "
+                + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + RCS_THREAD_ID_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + GLOBAL_ID_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + SUB_ID_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + STATUS_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + ORIGINATION_TIMESTAMP_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + MESSAGE_TEXT_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + LATITUDE_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + LONGITUDE_COLUMN + ", "
+                + "0 AS " + SENDER_PARTICIPANT_ID_COLUMN + ", "
+                + "0 AS " + ARRIVAL_TIMESTAMP_COLUMN + ", "
+                + "0 AS " + RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN + ", "
+                + MESSAGE_TYPE_OUTGOING + " AS " + MESSAGE_TYPE_COLUMN
+                + " FROM " + RCS_MESSAGE_TABLE + " INNER JOIN " + RCS_OUTGOING_MESSAGE_TABLE
+                + " ON " + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + "="
+                + RCS_OUTGOING_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN
+                + " UNION SELECT "
+                + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + RCS_THREAD_ID_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + GLOBAL_ID_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + SUB_ID_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + STATUS_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + ORIGINATION_TIMESTAMP_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + MESSAGE_TEXT_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + LATITUDE_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + LONGITUDE_COLUMN + ", "
+                + RCS_INCOMING_MESSAGE_TABLE + "." + SENDER_PARTICIPANT_ID_COLUMN + ", "
+                + RCS_INCOMING_MESSAGE_TABLE + "." + ARRIVAL_TIMESTAMP_COLUMN + ", "
+                + RCS_INCOMING_MESSAGE_TABLE + "." + RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN
+                + ", "
+                + MESSAGE_TYPE_INCOMING + " AS " + MESSAGE_TYPE_COLUMN
+                + " FROM " + RCS_MESSAGE_TABLE + " INNER JOIN " + RCS_INCOMING_MESSAGE_TABLE
+                + " ON " + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + "="
+                + RCS_INCOMING_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN);
+
+        // The following view inner joins incoming messages with all messages
+        //
+        // CREATE VIEW unified_incoming_message_view AS
+        //
+        // SELECT rcs_message.rcs_message_row_id,
+        //        rcs_message.rcs_thread_id,
+        //        rcs_message.rcs_message_global_id,
+        //        rcs_message.sub_id,
+        //        rcs_message.status,
+        //        rcs_message.origination_timestamp,
+        //        rcs_message.rcs_text,
+        //        rcs_message.latitude,
+        //        rcs_message.longitude,
+        //        rcs_incoming_message.sender_participant,
+        //        rcs_incoming_message.arrival_timestamp,
+        //        rcs_incoming_message.seen_timestamp,
+        //
+        // FROM rcs_message INNER JOIN rcs_incoming_message
+        //          ON rcs_message.rcs_message_row_id=rcs_incoming_message.rcs_message_row_id
+
+        db.execSQL("CREATE VIEW " + UNIFIED_INCOMING_MESSAGE_VIEW + " AS SELECT "
+                + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + RCS_THREAD_ID_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + GLOBAL_ID_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + SUB_ID_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + STATUS_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + ORIGINATION_TIMESTAMP_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + MESSAGE_TEXT_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + LATITUDE_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + LONGITUDE_COLUMN + ", "
+                + RCS_INCOMING_MESSAGE_TABLE + "." + SENDER_PARTICIPANT_ID_COLUMN + ", "
+                + RCS_INCOMING_MESSAGE_TABLE + "." + ARRIVAL_TIMESTAMP_COLUMN + ", "
+                + RCS_INCOMING_MESSAGE_TABLE + "." + RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN
+                + " FROM " + RCS_MESSAGE_TABLE + " INNER JOIN " + RCS_INCOMING_MESSAGE_TABLE
+                + " ON " + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + "="
+                + RCS_INCOMING_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN);
+
+        // The following view inner joins outgoing messages with all messages.
+        //
+        // CREATE VIEW unified_outgoing_message AS
+        //
+        // SELECT rcs_message.rcs_message_row_id,
+        //        rcs_message.rcs_thread_id,
+        //        rcs_message.rcs_message_global_id,
+        //        rcs_message.sub_id,
+        //        rcs_message.status,
+        //        rcs_message.origination_timestamp
+        //        rcs_message.rcs_text,
+        //        rcs_message.latitude,
+        //        rcs_message.longitude,
+        //
+        // FROM rcs_message INNER JOIN rcs_outgoing_message
+        //          ON rcs_message.rcs_message_row_id=rcs_outgoing_message.rcs_message_row_id
+
+        db.execSQL("CREATE VIEW " + UNIFIED_OUTGOING_MESSAGE_VIEW + " AS SELECT "
+                + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + RCS_THREAD_ID_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + GLOBAL_ID_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + SUB_ID_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + STATUS_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + ORIGINATION_TIMESTAMP_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + MESSAGE_TEXT_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + LATITUDE_COLUMN + ", "
+                + RCS_MESSAGE_TABLE + "." + LONGITUDE_COLUMN
+                + " FROM " + RCS_MESSAGE_TABLE + " INNER JOIN " + RCS_OUTGOING_MESSAGE_TABLE
+                + " ON " + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + "="
+                + RCS_OUTGOING_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN);
+
+        // Add triggers
+
+        // Delete the corresponding rcs_message row upon deleting a row in rcs_incoming_message
+        //
+        // CREATE TRIGGER delete_common_message_after_incoming
+        //  AFTER DELETE ON rcs_incoming_message
+        // BEGIN
+        //  DELETE FROM rcs_message WHERE rcs_message.rcs_message_row_id=OLD.rcs_message_row_id;
+        // END
+        db.execSQL("CREATE TRIGGER deleteCommonMessageAfterIncoming AFTER DELETE ON "
+                + RCS_INCOMING_MESSAGE_TABLE + " BEGIN DELETE FROM " + RCS_MESSAGE_TABLE
+                + " WHERE " + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + "=OLD."
+                + MESSAGE_ID_COLUMN + "; END");
+
+        // Delete the corresponding rcs_message row upon deleting a row in rcs_outgoing_message
+        //
+        // CREATE TRIGGER delete_common_message_after_outgoing
+        //  AFTER DELETE ON rcs_outgoing_message
+        // BEGIN
+        //  DELETE FROM rcs_message WHERE rcs_message.rcs_message_row_id=OLD.rcs_message_row_id;
+        // END
+        db.execSQL("CREATE TRIGGER deleteCommonMessageAfterOutgoing AFTER DELETE ON "
+                + RCS_OUTGOING_MESSAGE_TABLE + " BEGIN DELETE FROM " + RCS_MESSAGE_TABLE
+                + " WHERE " + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + "=OLD."
+                + MESSAGE_ID_COLUMN + "; END");
+    }
+
+    RcsProviderMessageHelper(SQLiteOpenHelper sqLiteOpenHelper) {
+        mSqLiteOpenHelper = sqLiteOpenHelper;
+    }
+
+    Cursor queryMessages(Bundle bundle) {
+        RcsMessageQueryParams queryParameters = null;
+        RcsQueryContinuationToken continuationToken = null;
+
+        if (bundle != null) {
+            queryParameters = bundle.getParcelable(MESSAGE_QUERY_PARAMETERS_KEY);
+            continuationToken = bundle.getParcelable(QUERY_CONTINUATION_TOKEN);
+        }
+
+        if (continuationToken != null) {
+            return RcsProviderUtil.performContinuationQuery(mSqLiteOpenHelper.getReadableDatabase(),
+                    continuationToken);
+        }
+
+        // if no parameters were entered, build an empty query parameters object
+        if (queryParameters == null) {
+            queryParameters = new RcsMessageQueryParams.Builder().build();
+        }
+
+        return performInitialQuery(queryParameters);
+    }
+
+    private Cursor performInitialQuery(RcsMessageQueryParams queryParameters) {
+        SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+
+        StringBuilder rawQuery = new StringBuilder("SELECT * FROM ").append(UNIFIED_MESSAGE_VIEW);
+
+        int messageType = queryParameters.getMessageType();
+        String messageLike = queryParameters.getMessageLike();
+        int threadId = queryParameters.getThreadId();
+
+        boolean isMessageLikePresent = !TextUtils.isEmpty(messageLike);
+        boolean isMessageTypeFiltered = (messageType == MESSAGE_TYPE_INCOMING)
+                || (messageType == MESSAGE_TYPE_OUTGOING);
+        boolean isThreadFiltered = threadId != THREAD_ID_NOT_SET;
+
+        if (isMessageLikePresent || isMessageTypeFiltered || isThreadFiltered) {
+            rawQuery.append(" WHERE ");
+        }
+
+        if (messageType == MESSAGE_TYPE_INCOMING) {
+            rawQuery.append(MESSAGE_TYPE_COLUMN).append("=").append(MESSAGE_TYPE_INCOMING);
+        } else if (messageType == MESSAGE_TYPE_OUTGOING) {
+            rawQuery.append(MESSAGE_TYPE_COLUMN).append("=").append(MESSAGE_TYPE_OUTGOING);
+        }
+
+        if (isMessageLikePresent) {
+            if (isMessageTypeFiltered) {
+                rawQuery.append(" AND ");
+            }
+            rawQuery.append(MESSAGE_TEXT_COLUMN).append(" LIKE \"").append(messageLike)
+                    .append("\"");
+        }
+
+        if (isThreadFiltered) {
+            if (isMessageLikePresent || isMessageTypeFiltered) {
+                rawQuery.append(" AND ");
+            }
+            rawQuery.append(RCS_THREAD_ID_COLUMN).append("=").append(threadId);
+        }
+
+        // TODO - figure out a way to see if this message has file transfer or not. Ideally we
+        // should join the unified table with file transfer table, but using a trigger to change a
+        // flag on rcs_message would also work
+
+        rawQuery.append(" ORDER BY ");
+
+        int sortingProperty = queryParameters.getSortingProperty();
+        if (sortingProperty == RcsMessageQueryParams.SORT_BY_TIMESTAMP) {
+            rawQuery.append(ORIGINATION_TIMESTAMP_COLUMN);
+        } else {
+            rawQuery.append(MESSAGE_ID_COLUMN);
+        }
+
+        rawQuery.append(queryParameters.getSortDirection() ? " ASC " : " DESC ");
+
+        RcsProviderUtil.appendLimit(rawQuery, queryParameters.getLimit());
+        String rawQueryAsString = rawQuery.toString();
+        Cursor cursor = db.rawQuery(rawQueryAsString, null);
+
+        // If the query was paginated, build the next query
+        int limit = queryParameters.getLimit();
+        if (limit > 0) {
+            RcsProviderUtil.createContinuationTokenBundle(cursor,
+                    new RcsQueryContinuationToken(MESSAGE_QUERY_CONTINUATION_TOKEN_TYPE,
+                            rawQueryAsString, limit, limit), QUERY_CONTINUATION_TOKEN);
+        }
+
+        return cursor;
+    }
+
+    Cursor queryUnifiedMessageWithId(Uri uri) {
+        return queryUnifiedMessageWithSelection(getMessageIdSelection(uri), null);
+    }
+
+    Cursor queryUnifiedMessageWithIdInThread(Uri uri) {
+        return queryUnifiedMessageWithSelection(getMessageIdSelectionInThreadUri(uri), null);
+    }
+
+    Cursor queryUnifiedMessageWithSelection(String selection, String[] selectionArgs) {
+        SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+        return db.query(UNIFIED_MESSAGE_VIEW, null, selection, selectionArgs, null, null, null,
+                null);
+    }
+
+    Cursor queryIncomingMessageWithId(Uri uri) {
+        return queryIncomingMessageWithSelection(getMessageIdSelection(uri), null);
+    }
+
+    Cursor queryIncomingMessageWithSelection(String selection, String[] selectionArgs) {
+        SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+        return db.query(UNIFIED_INCOMING_MESSAGE_VIEW, null, selection, selectionArgs, null, null,
+                null, null);
+    }
+
+    Cursor queryOutgoingMessageWithId(Uri uri) {
+        return queryOutgoingMessageWithSelection(getMessageIdSelection(uri), null);
+    }
+
+    Cursor queryOutgoingMessageWithSelection(String selection, String[] selectionArgs) {
+        SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+        return db.query(UNIFIED_OUTGOING_MESSAGE_VIEW, null, selection, selectionArgs, null, null,
+                null, null);
+    }
+
+    Cursor queryAllMessagesOnThread(Uri uri, String selection, String[] selectionArgs) {
+        SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+
+        String appendedSelection = appendThreadIdToSelection(uri, selection);
+        return db.query(UNIFIED_MESSAGE_VIEW, null, appendedSelection, null, null, null, null);
+    }
+
+    Uri insertMessageOnThread(Uri uri, ContentValues valuesParameter, boolean isIncoming,
+            boolean is1To1) {
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+
+        String threadId = RcsProviderThreadHelper.getThreadIdFromUri(uri);
+        ContentValues values = new ContentValues(valuesParameter);
+        values.put(RCS_THREAD_ID_COLUMN, Integer.parseInt(threadId));
+
+        db.beginTransaction();
+
+        ContentValues subMessageTableValues = new ContentValues();
+        if (isIncoming) {
+            subMessageTableValues = getIncomingMessageValues(values);
+        }
+
+        long rowId;
+        try {
+            rowId = db.insert(RCS_MESSAGE_TABLE, MESSAGE_ID_COLUMN, values);
+            if (rowId == INSERTION_FAILED) {
+                return null;
+            }
+
+            subMessageTableValues.put(MESSAGE_ID_COLUMN, rowId);
+            long tempId = db.insert(
+                    isIncoming ? RCS_INCOMING_MESSAGE_TABLE : RCS_OUTGOING_MESSAGE_TABLE,
+                    MESSAGE_ID_COLUMN, subMessageTableValues);
+            if (tempId == INSERTION_FAILED) {
+                return null;
+            }
+
+            // if the thread is outgoing, insert message deliveries
+            if (!isIncoming && !insertMessageDeliveriesForOutgoingMessageCreation(db, tempId,
+                    threadId)) {
+                return null;
+            }
+
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+
+        values.remove(RCS_THREAD_ID_COLUMN);
+
+        String threadPart =  is1To1 ? RCS_1_TO_1_THREAD_URI_PART : RCS_GROUP_THREAD_URI_PART;
+        String messagePart = isIncoming ? INCOMING_MESSAGE_URI_PART : OUTGOING_MESSAGE_URI_PART;
+
+        return CONTENT_AND_AUTHORITY.buildUpon().appendPath(threadPart).appendPath(threadId).
+                appendPath(messagePart).appendPath(Long.toString(rowId)).build();
+    }
+
+    // Tries to insert deliveries for outgoing message, returns false if it fails.
+    private boolean insertMessageDeliveriesForOutgoingMessageCreation(
+            SQLiteDatabase dbInTransaction, long messageId, String threadId) {
+        try (Cursor participantsInThreadCursor = dbInTransaction.query(
+                RCS_PARTICIPANT_THREAD_JUNCTION_TABLE, null, RCS_THREAD_ID_COLUMN + "=?",
+                new String[]{threadId}, null, null, null)) {
+            if (participantsInThreadCursor == null) {
+                return false;
+            }
+
+            while (participantsInThreadCursor.moveToNext()) {
+                String participantId = participantsInThreadCursor.getString(
+                        participantsInThreadCursor.getColumnIndex(
+                                RCS_PARTICIPANT_ID_COLUMN));
+
+                long insertionRow = insertMessageDelivery(Long.toString(messageId), participantId,
+                        new ContentValues());
+
+                if (insertionRow == INSERTION_FAILED) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    long insertMessageDelivery(Uri uri, ContentValues values) {
+        String messageId = getMessageIdFromUri(uri);
+        String participantId = getParticipantIdFromDeliveryUri(uri);
+        return insertMessageDelivery(messageId, participantId, values);
+    }
+
+    private long insertMessageDelivery(String messageId, String participantId,
+            ContentValues valuesParameter) {
+        ContentValues values = new ContentValues(valuesParameter);
+        values.put(MESSAGE_ID_COLUMN, messageId);
+        values.put(RCS_PARTICIPANT_ID_COLUMN, participantId);
+
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+        return db.insert(RCS_MESSAGE_DELIVERY_TABLE, MESSAGE_ID_COLUMN, values);
+    }
+
+    int updateDelivery(Uri uri, ContentValues contentValues) {
+        String messageId = getMessageIdFromUri(uri);
+        String participantId = getParticipantIdFromDeliveryUri(uri);
+
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+        return db.update(RCS_MESSAGE_DELIVERY_TABLE, contentValues,
+                MESSAGE_ID_COLUMN + "=? AND " + RCS_PARTICIPANT_ID_COLUMN + "=?",
+                new String[]{messageId, participantId});
+    }
+
+    int deleteIncomingMessageWithId(Uri uri) {
+        return deleteIncomingMessageWithSelection(getMessageIdSelection(uri), null);
+    }
+
+    int deleteIncomingMessageWithSelection(String selection, String[] selectionArgs) {
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+        return db.delete(RCS_INCOMING_MESSAGE_TABLE, selection, selectionArgs);
+    }
+
+    int deleteOutgoingMessageWithId(Uri uri) {
+        return deleteOutgoingMessageWithSelection(getMessageIdSelection(uri), null);
+    }
+
+    int deleteOutgoingMessageWithSelection(String selection, String[] selectionArgs) {
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+        return db.delete(RCS_OUTGOING_MESSAGE_TABLE, selection, selectionArgs);
+    }
+
+    int updateIncomingMessage(Uri uri, ContentValues values) {
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+
+        ContentValues incomingMessageValues = getIncomingMessageValues(values);
+
+        int updateCountInIncoming = 0;
+        int updateCountInCommon = 0;
+        db.beginTransaction();
+        if (!incomingMessageValues.isEmpty()) {
+            updateCountInIncoming = db.update(RCS_INCOMING_MESSAGE_TABLE, incomingMessageValues,
+                    getMessageIdSelection(uri), null);
+        }
+        if (!values.isEmpty()) {
+            updateCountInCommon = db.update(RCS_MESSAGE_TABLE, values, getMessageIdSelection(uri),
+                    null);
+        }
+        db.setTransactionSuccessful();
+        db.endTransaction();
+
+        return Math.max(updateCountInIncoming, updateCountInCommon);
+    }
+
+    int updateOutgoingMessage(Uri uri, ContentValues values) {
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+        return db.update(RCS_MESSAGE_TABLE, values, getMessageIdSelection(uri), null);
+    }
+
+    Cursor queryOutgoingMessageDeliveries(Uri uri) {
+        String messageId = getMessageIdFromUri(uri);
+
+        SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+        return db.query(RCS_MESSAGE_DELIVERY_TABLE, null, MESSAGE_ID_COLUMN + "=?",
+                new String[]{messageId}, null, null, null);
+    }
+
+    Cursor queryFileTransfer(Uri uri) {
+        SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+        return db.query(RCS_FILE_TRANSFER_TABLE, null, FILE_TRANSFER_ID_COLUMN + "=?",
+                new String[]{getFileTransferIdFromUri(uri)}, null, null, null, null);
+    }
+
+    long insertFileTransferToMessage(Uri uri, ContentValues values) {
+        String messageId = getMessageIdFromUri(uri);
+        values.put(MESSAGE_ID_COLUMN, messageId);
+
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+        long rowId = db.insert(RCS_FILE_TRANSFER_TABLE, MESSAGE_ID_COLUMN, values);
+        values.remove(MESSAGE_ID_COLUMN);
+
+        if (rowId == INSERTION_FAILED) {
+            rowId = TRANSACTION_FAILED;
+        }
+
+        return rowId;
+    }
+
+    int deleteFileTransfer(Uri uri) {
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+        return db.delete(RCS_FILE_TRANSFER_TABLE, FILE_TRANSFER_ID_COLUMN + "=?",
+                new String[]{getFileTransferIdFromUri(uri)});
+    }
+
+    int updateFileTransfer(Uri uri, ContentValues values) {
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+        return db.update(RCS_FILE_TRANSFER_TABLE, values,
+                FILE_TRANSFER_ID_COLUMN + "=?", new String[]{getFileTransferIdFromUri(uri)});
+    }
+
+    /**
+     * Removes the incoming message values out of all values and returns as a separate content
+     * values object.
+     */
+    private ContentValues getIncomingMessageValues(ContentValues allValues) {
+        ContentValues incomingMessageValues = new ContentValues();
+
+        if (allValues.containsKey(SENDER_PARTICIPANT_ID_COLUMN)) {
+            incomingMessageValues.put(SENDER_PARTICIPANT_ID_COLUMN,
+                    allValues.getAsInteger(SENDER_PARTICIPANT_ID_COLUMN));
+            allValues.remove(SENDER_PARTICIPANT_ID_COLUMN);
+        }
+
+        if (allValues.containsKey(ARRIVAL_TIMESTAMP_COLUMN)) {
+            incomingMessageValues.put(
+                    ARRIVAL_TIMESTAMP_COLUMN, allValues.getAsLong(ARRIVAL_TIMESTAMP_COLUMN));
+            allValues.remove(ARRIVAL_TIMESTAMP_COLUMN);
+        }
+
+        if (allValues.containsKey(RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN)) {
+            incomingMessageValues.put(
+                    RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN,
+                    allValues.getAsLong(RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN));
+            allValues.remove(RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN);
+        }
+
+        return incomingMessageValues;
+    }
+
+    private String appendThreadIdToSelection(Uri uri, String selection) {
+        String threadIdSelection = RCS_THREAD_ID_COLUMN + "=" + getThreadIdFromUri(uri);
+
+        if (TextUtils.isEmpty(selection)) {
+            return threadIdSelection;
+        }
+
+        return "(" + selection + ") AND " + threadIdSelection;
+    }
+
+    private String getMessageIdSelection(Uri uri) {
+        return MESSAGE_ID_COLUMN + "=" + getMessageIdFromUri(uri);
+    }
+
+    private String getMessageIdSelectionInThreadUri(Uri uri) {
+        return MESSAGE_ID_COLUMN + "=" + getMessageIdFromThreadUri(uri);
+    }
+
+    private String getMessageIdFromUri(Uri uri) {
+        return uri.getPathSegments().get(MESSAGE_ID_INDEX_IN_URI);
+    }
+
+    private String getFileTransferIdFromUri(Uri uri) {
+        // this works because messages and file transfer uri's have the same indices.
+        return getMessageIdFromUri(uri);
+    }
+
+    private String getParticipantIdFromDeliveryUri(Uri uri) {
+        // this works because messages in threads and participants in deliveries have the same
+        // indices.
+        return getMessageIdFromThreadUri(uri);
+    }
+
+    private String getMessageIdFromThreadUri(Uri uri) {
+        return uri.getPathSegments().get(MESSAGE_ID_INDEX_IN_THREAD_URI);
+    }
+}
diff --git a/src/com/android/providers/telephony/RcsProviderParticipantHelper.java b/src/com/android/providers/telephony/RcsProviderParticipantHelper.java
new file mode 100644
index 0000000..ab23d6d
--- /dev/null
+++ b/src/com/android/providers/telephony/RcsProviderParticipantHelper.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.providers.telephony;
+
+import static android.provider.Telephony.CanonicalAddressesColumns.ADDRESS;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.CANONICAL_ADDRESS_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_ALIAS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_URI_PART;
+import static android.provider.Telephony.RcsColumns.RcsParticipantHelpers.RCS_PARTICIPANT_WITH_ADDRESS_VIEW;
+import static android.provider.Telephony.RcsColumns.RcsParticipantHelpers.RCS_PARTICIPANT_WITH_THREAD_VIEW;
+import static android.provider.Telephony.RcsColumns.RcsThreadColumns.RCS_THREAD_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.TRANSACTION_FAILED;
+import static android.telephony.ims.RcsParticipantQueryParams.PARTICIPANT_QUERY_PARAMETERS_KEY;
+
+import static android.telephony.ims.RcsQueryContinuationToken.PARTICIPANT_QUERY_CONTINUATION_TOKEN_TYPE;
+import static android.telephony.ims.RcsQueryContinuationToken.QUERY_CONTINUATION_TOKEN;
+import static com.android.providers.telephony.RcsProvider.GROUP_THREAD_URI_PREFIX;
+import static com.android.providers.telephony.RcsProvider.RCS_PARTICIPANT_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_PARTICIPANT_THREAD_JUNCTION_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_THREAD_TABLE;
+import static com.android.providers.telephony.RcsProvider.TAG;
+import static com.android.providers.telephony.RcsProviderThreadHelper.getThreadIdFromUri;
+import static com.android.providers.telephony.RcsProviderUtil.INSERTION_FAILED;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.os.Bundle;
+import android.telephony.ims.RcsParticipantQueryParams;
+import android.telephony.ims.RcsQueryContinuationToken;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Constants and helpers related to participants for {@link RcsProvider} to keep the code clean.
+ *
+ * @hide
+ */
+class RcsProviderParticipantHelper {
+    private static final int PARTICIPANT_ID_INDEX_IN_URI = 1;
+    private static final int PARTICIPANT_ID_INDEX_IN_THREAD_URI = 3;
+
+    @VisibleForTesting
+    public static void createParticipantTables(SQLiteDatabase db) {
+        Log.d(TAG, "Creating participant tables");
+
+        // create participant tables
+        db.execSQL("CREATE TABLE " + RCS_PARTICIPANT_TABLE + " (" +
+                RCS_PARTICIPANT_ID_COLUMN + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
+                CANONICAL_ADDRESS_ID_COLUMN + " INTEGER ," +
+                RCS_ALIAS_COLUMN + " TEXT, " +
+                "FOREIGN KEY(" + CANONICAL_ADDRESS_ID_COLUMN + ") "
+                + "REFERENCES canonical_addresses(_id)" +
+                ");");
+
+        db.execSQL("CREATE TABLE " + RCS_PARTICIPANT_THREAD_JUNCTION_TABLE + " (" +
+                RCS_THREAD_ID_COLUMN + " INTEGER, " +
+                RCS_PARTICIPANT_ID_COLUMN + " INTEGER, " +
+                "CONSTRAINT thread_participant PRIMARY KEY("
+                + RCS_THREAD_ID_COLUMN + ", " + RCS_PARTICIPANT_ID_COLUMN + "), " +
+                "FOREIGN KEY(" + RCS_THREAD_ID_COLUMN
+                + ") REFERENCES " + RCS_THREAD_TABLE + "(" + RCS_THREAD_ID_COLUMN + "), " +
+                "FOREIGN KEY(" + RCS_PARTICIPANT_ID_COLUMN
+                + ") REFERENCES " + RCS_PARTICIPANT_TABLE + "(" + RCS_PARTICIPANT_ID_COLUMN + "))");
+
+        // create views
+
+        // The following view joins rcs_participant table with canonical_addresses table to add the
+        // actual address of a participant in the result.
+        db.execSQL("CREATE VIEW " + RCS_PARTICIPANT_WITH_ADDRESS_VIEW + " AS SELECT "
+                + "rcs_participant.rcs_participant_id, rcs_participant.canonical_address_id, "
+                + "rcs_participant.rcs_alias, canonical_addresses.address FROM rcs_participant "
+                + "LEFT JOIN canonical_addresses ON "
+                + "rcs_participant.canonical_address_id=canonical_addresses._id");
+
+        // The following view is the rcs_participant_with_address_view above, plus the information
+        // on which threads this participant contributes to, to enable getting participants of a
+        // thread
+        db.execSQL("CREATE VIEW " + RCS_PARTICIPANT_WITH_THREAD_VIEW + " AS SELECT "
+                + "rcs_participant.rcs_participant_id, rcs_participant.canonical_address_id, "
+                + "rcs_participant.rcs_alias, canonical_addresses.address, rcs_thread_participant"
+                + ".rcs_thread_id FROM rcs_participant LEFT JOIN canonical_addresses ON "
+                + "rcs_participant.canonical_address_id=canonical_addresses._id LEFT JOIN "
+                + "rcs_thread_participant ON rcs_participant.rcs_participant_id="
+                + "rcs_thread_participant.rcs_participant_id");
+
+        // TODO - create indexes for faster querying
+    }
+
+    private final SQLiteOpenHelper mSqLiteOpenHelper;
+
+    RcsProviderParticipantHelper(SQLiteOpenHelper sqLiteOpenHelper) {
+        mSqLiteOpenHelper = sqLiteOpenHelper;
+    }
+
+    Cursor queryParticipant(Bundle bundle) {
+        RcsParticipantQueryParams queryParameters = null;
+        RcsQueryContinuationToken continuationToken = null;
+
+        if (bundle != null) {
+            queryParameters = bundle.getParcelable(PARTICIPANT_QUERY_PARAMETERS_KEY);
+            continuationToken = bundle.getParcelable(QUERY_CONTINUATION_TOKEN);
+        }
+
+        if (continuationToken != null) {
+            return RcsProviderUtil.performContinuationQuery(mSqLiteOpenHelper.getReadableDatabase(),
+                    continuationToken);
+        }
+
+        if (queryParameters == null) {
+            queryParameters = new RcsParticipantQueryParams.Builder().build();
+        }
+
+        return performInitialQuery(queryParameters);
+    }
+
+    private Cursor performInitialQuery(RcsParticipantQueryParams queryParameters) {
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+
+        StringBuilder rawQuery = buildInitialRawQuery(queryParameters);
+        RcsProviderUtil.appendLimit(rawQuery, queryParameters.getLimit());
+        String rawQueryAsString = rawQuery.toString();
+        Cursor cursor = db.rawQuery(rawQueryAsString, null);
+
+        // If the query was paginated, build the next query
+        int limit = queryParameters.getLimit();
+        if (limit > 0) {
+            RcsProviderUtil.createContinuationTokenBundle(cursor,
+                    new RcsQueryContinuationToken(PARTICIPANT_QUERY_CONTINUATION_TOKEN_TYPE,
+                            rawQueryAsString, limit, limit), QUERY_CONTINUATION_TOKEN);
+        }
+
+        return cursor;
+    }
+
+    private StringBuilder buildInitialRawQuery(RcsParticipantQueryParams queryParameters) {
+        StringBuilder rawQuery = new StringBuilder("SELECT * FROM ");
+
+        boolean isThreadFiltered = queryParameters.getThreadId() > 0;
+
+        if (isThreadFiltered) {
+            rawQuery.append(RCS_PARTICIPANT_WITH_THREAD_VIEW);
+        } else {
+            rawQuery.append(RCS_PARTICIPANT_WITH_ADDRESS_VIEW);
+        }
+
+        boolean isAliasFiltered = !TextUtils.isEmpty(queryParameters.getAliasLike());
+        boolean isCanonicalAddressFiltered = !TextUtils.isEmpty(
+                queryParameters.getCanonicalAddressLike());
+
+        if (isAliasFiltered || isCanonicalAddressFiltered || isThreadFiltered) {
+            rawQuery.append(" WHERE ");
+        }
+
+        if (isAliasFiltered) {
+            rawQuery.append(RCS_ALIAS_COLUMN).append(" LIKE \"").append(
+                    queryParameters.getAliasLike()).append("\"");
+        }
+
+        if (isCanonicalAddressFiltered) {
+            if (isAliasFiltered) {
+                rawQuery.append(" AND ");
+            }
+            rawQuery.append(ADDRESS).append(" LIKE \"").append(
+                    queryParameters.getCanonicalAddressLike()).append("\"");
+        }
+
+        if (isThreadFiltered) {
+            if (isAliasFiltered || isCanonicalAddressFiltered) {
+                rawQuery.append(" AND ");
+            }
+            rawQuery.append(RCS_THREAD_ID_COLUMN).append("=").append(queryParameters.getThreadId());
+        }
+
+        rawQuery.append(" ORDER BY ");
+
+        int sortingProperty = queryParameters.getSortingProperty();
+        if (sortingProperty == RcsParticipantQueryParams.SORT_BY_ALIAS) {
+            rawQuery.append(RCS_ALIAS_COLUMN);
+        } else if (sortingProperty == RcsParticipantQueryParams.SORT_BY_CANONICAL_ADDRESS) {
+            rawQuery.append(ADDRESS);
+        } else {
+            rawQuery.append(RCS_PARTICIPANT_ID_COLUMN);
+        }
+        rawQuery.append(queryParameters.getSortDirection() ? " ASC " : " DESC ");
+
+        return rawQuery;
+    }
+
+    Cursor queryParticipantWithId(Uri uri, String[] projection) {
+        SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+        return db.query(RCS_PARTICIPANT_WITH_ADDRESS_VIEW, projection,
+                getParticipantIdSelection(uri), null, null, null, null);
+    }
+
+    Cursor queryParticipantIn1To1Thread(Uri uri) {
+        String threadId = getThreadIdFromUri(uri);
+        SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+
+        return db.rawQuery(
+                "  SELECT * "
+                        + "FROM rcs_participant "
+                        + "WHERE rcs_participant.rcs_participant_id = ("
+                        + "  SELECT rcs_thread_participant.rcs_participant_id "
+                        + "  FROM rcs_thread_participant "
+                        + "  WHERE rcs_thread_participant.rcs_thread_id=" + threadId + ")", null);
+    }
+
+    Cursor queryParticipantsInGroupThread(Uri uri) {
+        String threadId = getThreadIdFromUri(uri);
+        SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+
+        return db.rawQuery("  SELECT * "
+                + "FROM rcs_participant "
+                + "WHERE rcs_participant.rcs_participant_id = ("
+                + "  SELECT rcs_participant_id "
+                + "  FROM rcs_thread_participant "
+                + "  WHERE rcs_thread_id= " + threadId + ")", null);
+    }
+
+    Cursor queryParticipantInGroupThreadWithId(Uri uri) {
+        String threadId = getThreadIdFromUri(uri);
+        String participantId = getParticipantIdFromUri(uri);
+        SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+
+        return db.rawQuery("  SELECT * "
+                        + "FROM rcs_participant "
+                        + "WHERE rcs_participant.rcs_participant_id = ("
+                        + "  SELECT rcs_participant_id "
+                        + "  FROM rcs_thread_participant "
+                        + "  WHERE rcs_thread_id=? AND rcs_participant_id=?)",
+                new String[]{threadId, participantId});
+    }
+
+    long insertParticipant(ContentValues contentValues) {
+        if (!contentValues.containsKey(CANONICAL_ADDRESS_ID_COLUMN) || TextUtils.isEmpty(
+                contentValues.getAsString(CANONICAL_ADDRESS_ID_COLUMN))) {
+            Log.e(TAG,
+                    "RcsProviderParticipantHelper: Inserting participants without canonical "
+                            + "address is not supported");
+            return TRANSACTION_FAILED;
+        }
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+        long rowId = db.insert(RCS_PARTICIPANT_TABLE, RCS_PARTICIPANT_ID_COLUMN, contentValues);
+        Log.e(TAG, "Inserted participant with rowId=" + rowId);
+        if (rowId < 0) {
+            return TRANSACTION_FAILED;
+        }
+        return rowId;
+    }
+
+    /**
+     * Inserts a participant into group thread. This function returns the participant ID instead of
+     * the row id in the junction table
+     */
+    long insertParticipantIntoGroupThread(ContentValues values) {
+        if (!values.containsKey(RCS_THREAD_ID_COLUMN) || !values.containsKey(
+                RCS_PARTICIPANT_ID_COLUMN)) {
+            Log.e(TAG, "RcsProviderParticipantHelper: Cannot insert participant into group.");
+            return TRANSACTION_FAILED;
+        }
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+        long insertedRowId = db.insert(RCS_PARTICIPANT_THREAD_JUNCTION_TABLE,
+                RCS_PARTICIPANT_ID_COLUMN,
+                values);
+
+        if (insertedRowId == INSERTION_FAILED) {
+            return TRANSACTION_FAILED;
+        }
+
+        return values.getAsLong(RCS_PARTICIPANT_ID_COLUMN);
+    }
+
+    /**
+     * Inserts a participant into group thread. This function returns the participant ID instead of
+     * the row id in the junction table
+     */
+    long insertParticipantIntoGroupThreadWithId(Uri uri) {
+        String threadId = getThreadIdFromUri(uri);
+        String participantId = getParticipantIdFromUri(uri);
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+
+        ContentValues contentValues = new ContentValues(2);
+        contentValues.put(RCS_THREAD_ID_COLUMN, threadId);
+        contentValues.put(RCS_PARTICIPANT_ID_COLUMN, participantId);
+
+        long insertedRowId = db.insert(
+                RCS_PARTICIPANT_THREAD_JUNCTION_TABLE, RCS_PARTICIPANT_ID_COLUMN, contentValues);
+
+        if (insertedRowId == INSERTION_FAILED) {
+            return TRANSACTION_FAILED;
+        }
+
+        return Long.parseLong(participantId);
+    }
+
+    int deleteParticipantWithId(Uri uri) {
+        String participantId = uri.getPathSegments().get(PARTICIPANT_ID_INDEX_IN_URI);
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+
+        // See if this participant is involved in any threads
+        Cursor cursor = db.query(RCS_PARTICIPANT_THREAD_JUNCTION_TABLE, null,
+                RCS_PARTICIPANT_ID_COLUMN + "=?", new String[]{participantId}, null, null, null);
+
+        int participatingThreadCount = 0;
+        if (cursor != null) {
+            participatingThreadCount = cursor.getCount();
+            cursor.close();
+        }
+
+        if (participatingThreadCount > 0) {
+            Log.e(TAG,
+                    "RcsProviderParticipantHelper: Can't delete participant while it is still in "
+                            + "RCS threads, uri:"
+                            + uri);
+            return 0;
+        }
+
+        return db.delete(RCS_PARTICIPANT_TABLE, RCS_PARTICIPANT_ID_COLUMN + "=?",
+                new String[]{participantId});
+    }
+
+    int deleteParticipantFromGroupThread(Uri uri) {
+        String threadId = getThreadIdFromUri(uri);
+        String participantId = getParticipantIdFromUri(uri);
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+        // TODO check to remove owner
+        return db.delete(RCS_PARTICIPANT_THREAD_JUNCTION_TABLE,
+                RCS_THREAD_ID_COLUMN + "=? AND " + RCS_PARTICIPANT_ID_COLUMN + "=?",
+                new String[]{threadId, participantId});
+    }
+
+    int updateParticipant(ContentValues contentValues, String selection, String[] selectionArgs) {
+        if (contentValues.containsKey(RCS_PARTICIPANT_ID_COLUMN)) {
+            Log.e(TAG, "RcsProviderParticipantHelper: Updating participant id is not supported");
+            return 0;
+        }
+
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+        return db.update(RCS_PARTICIPANT_TABLE, contentValues, selection, selectionArgs);
+    }
+
+    int updateParticipantWithId(ContentValues contentValues, Uri uri) {
+        return updateParticipant(contentValues, getParticipantIdSelection(uri), null);
+    }
+
+    private String getParticipantIdSelection(Uri uri) {
+        return RCS_PARTICIPANT_ID_COLUMN + "=" + uri.getPathSegments().get(
+                PARTICIPANT_ID_INDEX_IN_URI);
+    }
+
+    Uri getParticipantInThreadUri(ContentValues values, long rowId) {
+        if (values == null) {
+            return null;
+        }
+        Integer threadId = values.getAsInteger(RCS_THREAD_ID_COLUMN);
+        if (threadId == null) {
+            return null;
+        }
+
+        return GROUP_THREAD_URI_PREFIX.buildUpon().appendPath(
+                Integer.toString(threadId)).appendPath(RCS_PARTICIPANT_URI_PART).appendPath(
+                Long.toString(rowId)).build();
+    }
+
+    private String getParticipantIdFromUri(Uri uri) {
+        return uri.getPathSegments().get(PARTICIPANT_ID_INDEX_IN_THREAD_URI);
+    }
+}
diff --git a/src/com/android/providers/telephony/RcsProviderThreadHelper.java b/src/com/android/providers/telephony/RcsProviderThreadHelper.java
new file mode 100644
index 0000000..c1a35ef
--- /dev/null
+++ b/src/com/android/providers/telephony/RcsProviderThreadHelper.java
@@ -0,0 +1,503 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.providers.telephony;
+
+import static android.provider.Telephony.RcsColumns.Rcs1To1ThreadColumns.FALLBACK_THREAD_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.CONFERENCE_URI_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.GROUP_ICON_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.GROUP_NAME_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.OWNER_PARTICIPANT_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.MESSAGE_TEXT_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.ORIGINATION_TIMESTAMP_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.STATUS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadColumns.RCS_THREAD_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsUnifiedThreadColumns.THREAD_TYPE_COLUMN;
+import static android.provider.Telephony.RcsColumns.TRANSACTION_FAILED;
+import static android.telephony.ims.RcsQueryContinuationToken.QUERY_CONTINUATION_TOKEN;
+import static android.telephony.ims.RcsQueryContinuationToken.THREAD_QUERY_CONTINUATION_TOKEN_TYPE;
+import static android.telephony.ims.RcsThreadQueryParams.THREAD_QUERY_PARAMETERS_KEY;
+
+import static com.android.providers.telephony.RcsProvider.RCS_1_TO_1_THREAD_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_GROUP_THREAD_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_MESSAGE_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_PARTICIPANT_THREAD_JUNCTION_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_THREAD_TABLE;
+import static com.android.providers.telephony.RcsProvider.TAG;
+import static com.android.providers.telephony.RcsProvider.UNIFIED_RCS_THREAD_VIEW;
+import static com.android.providers.telephony.RcsProviderUtil.INSERTION_FAILED;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.BaseColumns;
+import android.telephony.ims.RcsQueryContinuationToken;
+import android.telephony.ims.RcsThreadQueryParams;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Constants and helpers related to threads for {@link RcsProvider} to keep the code clean.
+ *
+ * @hide
+ */
+class RcsProviderThreadHelper {
+    private static final int THREAD_ID_INDEX_IN_URI = 1;
+
+    @VisibleForTesting
+    public static void createThreadTables(SQLiteDatabase db) {
+        Log.d(TAG, "Creating thread tables");
+
+        // Add the thread tables
+        db.execSQL("CREATE TABLE " + RCS_THREAD_TABLE + " (" +
+                RCS_THREAD_ID_COLUMN + " INTEGER PRIMARY KEY AUTOINCREMENT);");
+
+        db.execSQL("CREATE TABLE " + RCS_1_TO_1_THREAD_TABLE + " (" +
+                RCS_THREAD_ID_COLUMN + " INTEGER PRIMARY KEY, " +
+                FALLBACK_THREAD_ID_COLUMN + " INTEGER, " +
+                "FOREIGN KEY(" + RCS_THREAD_ID_COLUMN
+                + ") REFERENCES " + RCS_THREAD_TABLE + "(" + RCS_THREAD_ID_COLUMN + ")," +
+                "FOREIGN KEY(" + FALLBACK_THREAD_ID_COLUMN
+                + ") REFERENCES threads( " + BaseColumns._ID + "))");
+
+        db.execSQL("CREATE TABLE " + RCS_GROUP_THREAD_TABLE + " (" +
+                RCS_THREAD_ID_COLUMN + " INTEGER PRIMARY KEY, " +
+                OWNER_PARTICIPANT_COLUMN + " INTEGER, " +
+                GROUP_NAME_COLUMN + " TEXT, " +
+                GROUP_ICON_COLUMN + " TEXT, " +
+                CONFERENCE_URI_COLUMN + " TEXT, " +
+                "FOREIGN KEY(" + RCS_THREAD_ID_COLUMN
+                + ") REFERENCES " + RCS_THREAD_TABLE + "(" + RCS_THREAD_ID_COLUMN + "))");
+
+        // Add the views
+
+        // The following is a unified thread view. Since SQLite does not support right or full
+        // joins, we are using a union with null values for unused variables for each thread type.
+        // The thread_type column is an easy way to figure out whether the entry came from a 1 to 1
+        // thread or a group thread. The last message in each thread is appended to the table
+        // entries to figure out the latest threads and snippet text. We use COALESCE so that MAX()
+        // can take null values into account in order to have threads with no messages still
+        // represented here
+        //
+        // SELECT <1 to 1 thread and first message>
+        // FROM (
+        //     SELECT *
+        //     FROM rcs_1_to_1_thread LEFT JOIN rcs_message
+        //         ON rcs_1_to_1_thread.rcs_thread_id=rcs_message.rcs_thread_id)
+        // GROUP BY rcs_thread_id
+        // HAVING MAX(COALESCE(origination_timestamp,1))
+        //
+        // UNION
+        // SELECT <group thread and first message>
+        // FROM (
+        //     SELECT *
+        //     FROM rcs_group_thread LEFT JOIN rcs_message
+        //         ON rcs_group_thread.rcs_thread_id=rcs_message.rcs_thread_id)
+        // GROUP BY rcs_thread_id
+        // HAVING MAX(COALESCE(origination_timestamp,1))
+
+        db.execSQL("CREATE VIEW " + UNIFIED_RCS_THREAD_VIEW + " AS "
+                + "SELECT "
+                + RCS_THREAD_ID_COLUMN + ", "
+                + FALLBACK_THREAD_ID_COLUMN + ", "
+                + "null AS " + OWNER_PARTICIPANT_COLUMN + ", "
+                + "null AS " + GROUP_NAME_COLUMN + ", "
+                + "null AS " + GROUP_ICON_COLUMN + ", "
+                + "null AS " + CONFERENCE_URI_COLUMN + ", "
+                + "0 AS " + THREAD_TYPE_COLUMN + ", "
+                + ORIGINATION_TIMESTAMP_COLUMN + ", "
+                + MESSAGE_TEXT_COLUMN + ", "
+                + STATUS_COLUMN
+                + " FROM (SELECT * FROM "
+                + RCS_1_TO_1_THREAD_TABLE + " LEFT JOIN " + RCS_MESSAGE_TABLE
+                + " ON "
+                + RCS_1_TO_1_THREAD_TABLE + "." + RCS_THREAD_ID_COLUMN + "="
+                + RCS_MESSAGE_TABLE + "." + RCS_THREAD_ID_COLUMN + ")"
+                + " GROUP BY " + RCS_THREAD_ID_COLUMN
+                + " HAVING MAX(COALESCE("
+                + ORIGINATION_TIMESTAMP_COLUMN + ", 1))"
+                + " UNION SELECT "
+                + RCS_THREAD_ID_COLUMN + ", "
+                + "null AS " + FALLBACK_THREAD_ID_COLUMN + ", "
+                + OWNER_PARTICIPANT_COLUMN + ", "
+                + GROUP_NAME_COLUMN + ", "
+                + GROUP_ICON_COLUMN + ", "
+                + CONFERENCE_URI_COLUMN + ", "
+                + "1 AS " + THREAD_TYPE_COLUMN + ", "
+                + ORIGINATION_TIMESTAMP_COLUMN + ", "
+                + MESSAGE_TEXT_COLUMN + ", "
+                + STATUS_COLUMN
+                + " FROM (SELECT * FROM "
+                + RCS_GROUP_THREAD_TABLE + " LEFT JOIN " + RCS_MESSAGE_TABLE
+                + " ON "
+                + RCS_GROUP_THREAD_TABLE + "." + RCS_THREAD_ID_COLUMN + "="
+                + RCS_MESSAGE_TABLE + "." + RCS_THREAD_ID_COLUMN + ")"
+                + " GROUP BY " + RCS_THREAD_ID_COLUMN
+                + " HAVING MAX(COALESCE("
+                + ORIGINATION_TIMESTAMP_COLUMN + ", 1))");
+
+        // Add the triggers
+
+        // Delete the corresponding rcs_thread row upon deleting a row in rcs_1_to_1_thread
+        //
+        // CREATE TRIGGER deleteRcsThreadAfter1to1
+        //  AFTER DELETE ON rcs_1_to_1_thread
+        // BEGIN
+        //	DELETE FROM rcs_thread WHERE rcs_thread._id=OLD.rcs_thread_id;
+        // END
+        db.execSQL("CREATE TRIGGER deleteRcsThreadAfter1to1 AFTER DELETE ON "
+                + RCS_1_TO_1_THREAD_TABLE + " BEGIN DELETE FROM " + RCS_THREAD_TABLE + " WHERE "
+                + RCS_THREAD_TABLE + "." + RCS_THREAD_ID_COLUMN + "=OLD." + RCS_THREAD_ID_COLUMN
+                + "; END");
+
+        // Delete the corresponding rcs_thread row upon deleting a row in rcs_group_thread
+        //
+        // CREATE TRIGGER deleteRcsThreadAfter1to1
+        //  AFTER DELETE ON rcs_1_to_1_thread
+        // BEGIN
+        //	DELETE FROM rcs_thread WHERE rcs_thread._id=OLD.rcs_thread_id;
+        // END
+        db.execSQL("CREATE TRIGGER deleteRcsThreadAfterGroup AFTER DELETE ON "
+                + RCS_GROUP_THREAD_TABLE + " BEGIN DELETE FROM " + RCS_THREAD_TABLE + " WHERE "
+                + RCS_THREAD_TABLE + "." + RCS_THREAD_ID_COLUMN + "=OLD." + RCS_THREAD_ID_COLUMN
+                + "; END");
+
+        // Delete the junction table entries upon deleting a 1 to 1 thread
+        //
+        // CREATE TRIGGER delete1To1JunctionEntries
+        // AFTER
+        //  DELETE ON rcs_1_to_1_thread
+        // BEGIN
+        //  DELETE FROM
+        //   rcs_thread_participant
+        //  WHERE
+        //   rcs_thread_participant.rcs_thread_id = OLD.rcs_thread_id;
+        // END
+        db.execSQL("CREATE TRIGGER delete1To1JunctionEntries AFTER DELETE ON "
+                + RCS_1_TO_1_THREAD_TABLE + " BEGIN DELETE FROM "
+                + RCS_PARTICIPANT_THREAD_JUNCTION_TABLE + " WHERE "
+                + RCS_PARTICIPANT_THREAD_JUNCTION_TABLE + "." + RCS_THREAD_ID_COLUMN + "=OLD."
+                + RCS_THREAD_ID_COLUMN + "; END");
+
+        // Delete the junction table entries upon deleting a group thread
+        //
+        // CREATE TRIGGER delete1To1JunctionEntries
+        // AFTER
+        //  DELETE ON rcs_1_to_1_thread
+        // BEGIN
+        //  DELETE FROM
+        //   rcs_thread_participant
+        //  WHERE
+        //   rcs_thread_participant.rcs_thread_id = OLD.rcs_thread_id;
+        // END
+        db.execSQL("CREATE TRIGGER deleteGroupJunctionEntries AFTER DELETE ON "
+                + RCS_GROUP_THREAD_TABLE + " BEGIN DELETE FROM "
+                + RCS_PARTICIPANT_THREAD_JUNCTION_TABLE + " WHERE "
+                + RCS_PARTICIPANT_THREAD_JUNCTION_TABLE + "." + RCS_THREAD_ID_COLUMN + "=OLD."
+                +RCS_THREAD_ID_COLUMN + "; END");
+
+        // TODO - delete all messages in a thread after deleting a thread
+
+        // TODO - create indexes for faster querying
+    }
+
+    private final SQLiteOpenHelper mSqLiteOpenHelper;
+
+    RcsProviderThreadHelper(SQLiteOpenHelper sqLiteOpenHelper) {
+        mSqLiteOpenHelper = sqLiteOpenHelper;
+    }
+
+    Cursor queryUnifiedThread(Bundle bundle) {
+        RcsThreadQueryParams queryParameters = null;
+        RcsQueryContinuationToken continuationToken = null;
+        if (bundle != null) {
+            queryParameters = bundle.getParcelable(
+                    THREAD_QUERY_PARAMETERS_KEY);
+            continuationToken = bundle.getParcelable(QUERY_CONTINUATION_TOKEN);
+        }
+
+        if (continuationToken != null) {
+            return RcsProviderUtil.performContinuationQuery(mSqLiteOpenHelper.getReadableDatabase(),
+                    continuationToken);
+        }
+
+        if (queryParameters == null) {
+            queryParameters = new RcsThreadQueryParams.Builder().build();
+        }
+
+        return performInitialQuery(queryParameters);
+    }
+
+    private Cursor performInitialQuery(RcsThreadQueryParams queryParameters) {
+        if (queryParameters == null) {
+            // return everything for test purposes
+            queryParameters = new RcsThreadQueryParams.Builder().build();
+        }
+
+        SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+        StringBuilder rawQuery = new StringBuilder("SELECT * FROM ").append(
+                UNIFIED_RCS_THREAD_VIEW);
+
+        if (queryParameters.getThreadType() == RcsThreadQueryParams.THREAD_TYPE_1_TO_1) {
+            rawQuery.append(" WHERE ").append(THREAD_TYPE_COLUMN).append("=0");
+        } else if (queryParameters.getThreadType() == RcsThreadQueryParams.THREAD_TYPE_GROUP) {
+            rawQuery.append(" WHERE ").append(THREAD_TYPE_COLUMN).append("=1");
+        }
+
+        rawQuery.append(" ORDER BY ");
+
+        if (queryParameters.getSortingProperty() == RcsThreadQueryParams.SORT_BY_TIMESTAMP) {
+            rawQuery.append(ORIGINATION_TIMESTAMP_COLUMN);
+        } else {
+            rawQuery.append(RCS_THREAD_ID_COLUMN);
+        }
+
+        rawQuery.append(queryParameters.getSortDirection() ? " ASC " : " DESC ");
+        RcsProviderUtil.appendLimit(rawQuery, queryParameters.getLimit());
+
+        String rawQueryAsString = rawQuery.toString();
+        Cursor cursor = db.rawQuery(rawQueryAsString, null);
+
+        // If this is a paginated query, build the next query and return as a Cursor extra. Only do
+        // this if the current query returned a result.
+        int limit = queryParameters.getLimit();
+        if (limit > 0) {
+            RcsProviderUtil.createContinuationTokenBundle(cursor,
+                    new RcsQueryContinuationToken(THREAD_QUERY_CONTINUATION_TOKEN_TYPE,
+                            rawQueryAsString, limit, limit), QUERY_CONTINUATION_TOKEN);
+        }
+
+        return cursor;
+    }
+
+    Cursor queryUnifiedThreadUsingId(Uri uri, String[] projection) {
+        SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+        String threadId = getThreadIdFromUri(uri);
+
+        return db.query(UNIFIED_RCS_THREAD_VIEW, projection, RCS_THREAD_ID_COLUMN + "=?",
+                new String[]{threadId},
+                null, null, null);
+    }
+
+    Cursor query1to1Thread(String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+        return db.query(RCS_1_TO_1_THREAD_TABLE, projection, selection, selectionArgs, null,
+                null, sortOrder);
+    }
+
+    Cursor query1To1ThreadUsingId(Uri uri, String[] projection) {
+        return query1to1Thread(projection, getThreadIdSelection(uri), null, null);
+    }
+
+    Cursor queryGroupThread(String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+        return db.query(RCS_GROUP_THREAD_TABLE, projection, selection, selectionArgs, null,
+                null, sortOrder);
+    }
+
+    Cursor queryGroupThreadUsingId(Uri uri, String[] projection) {
+        return queryGroupThread(projection, getThreadIdSelection(uri), null, null);
+    }
+
+    /**
+     * @param contentValues should contain the participant ID of the other participant under key
+     *                      {@link RCS_PARTICIPANT_ID_COLUMN}
+     */
+    long insert1To1Thread(ContentValues contentValues) {
+        if (contentValues.containsKey(RCS_THREAD_ID_COLUMN)) {
+            Log.e(RcsProvider.TAG,
+                    "RcsProviderThreadHelper: inserting threads with IDs is not supported");
+            return TRANSACTION_FAILED;
+        }
+
+        Long participantId = contentValues.getAsLong(RCS_PARTICIPANT_ID_COLUMN);
+        if (participantId == null) {
+            Log.e(RcsProvider.TAG,
+                    "inserting threads without participant IDs is not supported");
+            return TRANSACTION_FAILED;
+        }
+
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+        try {
+            db.beginTransaction();
+
+            if (hasExisting1To1ThreadForParticipant(db, participantId)) {
+                return TRANSACTION_FAILED;
+            }
+
+            long threadId = insertIntoCommonRcsThreads(db);
+            if (threadId == -1) {
+                return TRANSACTION_FAILED;
+            }
+
+            if (insertP2pThread(db, threadId) == -1) {
+                return TRANSACTION_FAILED;
+            }
+
+            if (insertParticipantIntoP2pThread(db, threadId, participantId) == -1) {
+                return TRANSACTION_FAILED;
+            }
+
+            db.setTransactionSuccessful();
+
+            return threadId;
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    private long insertP2pThread(SQLiteDatabase db, long threadId) {
+        ContentValues contentValues = new ContentValues(1);
+        contentValues.put(RCS_THREAD_ID_COLUMN, threadId);
+
+        return db.insert(RCS_1_TO_1_THREAD_TABLE, RCS_THREAD_ID_COLUMN, contentValues);
+    }
+
+    private long insertParticipantIntoP2pThread(
+            SQLiteDatabase db, long threadId, long participantId) {
+        ContentValues contentValues = new ContentValues(2);
+        contentValues.put(RCS_THREAD_ID_COLUMN, threadId);
+        contentValues.put(RCS_PARTICIPANT_ID_COLUMN, participantId);
+
+        return db.insert(
+                RCS_PARTICIPANT_THREAD_JUNCTION_TABLE, RCS_PARTICIPANT_ID_COLUMN, contentValues);
+    }
+
+    private boolean hasExisting1To1ThreadForParticipant(SQLiteDatabase db, long participantId) {
+        String table = joinOnColumn(
+                RCS_PARTICIPANT_THREAD_JUNCTION_TABLE,
+                RCS_1_TO_1_THREAD_TABLE,
+                RCS_THREAD_ID_COLUMN);
+
+        try (Cursor cursor = db.query(
+                table,
+                null,
+                RCS_PARTICIPANT_ID_COLUMN + "=?",
+                new String[]{Long.toString(participantId)},
+                null,
+                null,
+                null)) {
+            if (cursor == null || cursor.getCount() == 0) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private String joinOnColumn(String t1, String t2, String col) {
+        return t1 + " JOIN " + t2 + " ON (" + t1 + "." + col + "=" + t2 + "." + col + ")";
+    }
+
+    long insertGroupThread(ContentValues contentValues) {
+        long returnValue = TRANSACTION_FAILED;
+        if (contentValues.containsKey(RCS_THREAD_ID_COLUMN)) {
+            Log.e(RcsProvider.TAG,
+                    "RcsProviderThreadHelper: inserting threads with IDs is not supported");
+            return returnValue;
+        }
+
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+        try {
+            db.beginTransaction();
+
+            // Insert into the common rcs_threads table
+            long rowId = insertIntoCommonRcsThreads(db);
+            if (rowId == INSERTION_FAILED) {
+                return returnValue;
+            }
+
+            // Add the rowId in rcs_threads table as a foreign key in rcs_group_table
+            contentValues.put(RCS_THREAD_ID_COLUMN, rowId);
+            db.insert(RCS_GROUP_THREAD_TABLE, RCS_THREAD_ID_COLUMN, contentValues);
+            contentValues.remove(RCS_THREAD_ID_COLUMN);
+
+            db.setTransactionSuccessful();
+            returnValue = rowId;
+        } finally {
+            db.endTransaction();
+        }
+        return returnValue;
+    }
+
+    private long insertIntoCommonRcsThreads(SQLiteDatabase db) {
+        return db.insert(RCS_THREAD_TABLE, RCS_THREAD_ID_COLUMN, new ContentValues());
+    }
+
+    int delete1To1Thread(String selection, String[] selectionArgs) {
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+        return db.delete(RCS_1_TO_1_THREAD_TABLE, selection, selectionArgs);
+    }
+
+    int delete1To1ThreadWithId(Uri uri) {
+        return delete1To1Thread(getThreadIdSelection(uri), null);
+    }
+
+    int deleteGroupThread(String selection, String[] selectionArgs) {
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+        return db.delete(RCS_GROUP_THREAD_TABLE, selection, selectionArgs);
+    }
+
+    int deleteGroupThreadWithId(Uri uri) {
+        return deleteGroupThread(getThreadIdSelection(uri), null);
+    }
+
+    int update1To1Thread(ContentValues values, String selection, String[] selectionArgs) {
+        if (values.containsKey(RCS_THREAD_ID_COLUMN)) {
+            Log.e(TAG,
+                    "RcsProviderThreadHelper: updating thread id for 1 to 1 threads is not "
+                            + "allowed");
+            return 0;
+        }
+
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+        return db.update(RCS_1_TO_1_THREAD_TABLE, values, selection, selectionArgs);
+    }
+
+    int update1To1ThreadWithId(ContentValues values, Uri uri) {
+        return update1To1Thread(values, getThreadIdSelection(uri), null);
+    }
+
+    int updateGroupThread(ContentValues values, String selection, String[] selectionArgs) {
+        if (values.containsKey(RCS_THREAD_ID_COLUMN)) {
+            Log.e(TAG,
+                    "RcsProviderThreadHelper: updating thread id for group threads is not "
+                            + "allowed");
+            return 0;
+        }
+
+        SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+        return db.update(RCS_GROUP_THREAD_TABLE, values, selection, selectionArgs);
+    }
+
+    int updateGroupThreadWithId(ContentValues values, Uri uri) {
+        return updateGroupThread(values, getThreadIdSelection(uri), null);
+    }
+
+    private String getThreadIdSelection(Uri uri) {
+        return RCS_THREAD_ID_COLUMN + "=" + getThreadIdFromUri(uri);
+    }
+
+    static String getThreadIdFromUri(Uri uri) {
+        return uri.getPathSegments().get(THREAD_ID_INDEX_IN_URI);
+    }
+}
diff --git a/src/com/android/providers/telephony/RcsProviderUtil.java b/src/com/android/providers/telephony/RcsProviderUtil.java
new file mode 100644
index 0000000..38f7be1
--- /dev/null
+++ b/src/com/android/providers/telephony/RcsProviderUtil.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.providers.telephony;
+
+import static android.provider.Telephony.RcsColumns.TRANSACTION_FAILED;
+
+import static android.telephony.ims.RcsQueryContinuationToken.QUERY_CONTINUATION_TOKEN;
+import static com.android.providers.telephony.RcsProvider.TAG;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.telephony.ims.RcsQueryContinuationToken;
+import android.text.TextUtils;
+import android.util.Log;
+
+class RcsProviderUtil {
+    /**
+     * The value returned when database insertion fails,
+     * @see SQLiteDatabase#insert(String, String, ContentValues)
+     */
+    static final int INSERTION_FAILED = -1;
+
+    static void appendLimit(StringBuilder stringBuilder, int limit) {
+        if (limit > 0) {
+            stringBuilder.append(" LIMIT ").append(limit);
+        }
+    }
+
+    static void createContinuationTokenBundle(Cursor cursor, Parcelable continuationToken,
+            String tokenKey) {
+        if (cursor.getCount() > 0) {
+            Bundle bundle = new Bundle();
+            bundle.putParcelable(tokenKey, continuationToken);
+            cursor.setExtras(bundle);
+        }
+    }
+
+    /**
+     * This method gets a token with raw query, performs the query, increments the token's offset
+     * and returns it as a cursor extra
+     */
+    static Cursor performContinuationQuery(SQLiteDatabase db,
+            RcsQueryContinuationToken continuationToken) {
+        String rawQuery = continuationToken.getRawQuery();
+        int offset = continuationToken.getOffset();
+
+        if (offset <= 0 || TextUtils.isEmpty(rawQuery)) {
+            Log.e(TAG, "RcsProviderUtil: Invalid continuation token");
+            return null;
+        }
+
+        String continuationQuery = rawQuery + " OFFSET " + offset;
+
+        Cursor cursor = db.rawQuery(continuationQuery, null);
+        if (cursor.getCount() > 0) {
+            continuationToken.incrementOffset();
+            Bundle bundle = new Bundle();
+            bundle.putParcelable(QUERY_CONTINUATION_TOKEN, continuationToken);
+            cursor.setExtras(bundle);
+        }
+
+        return cursor;
+    }
+
+    static Uri buildUriWithRowIdAppended(Uri prefix, long rowId) {
+        if (rowId == TRANSACTION_FAILED) {
+            return null;
+        }
+        return Uri.withAppendedPath(prefix, Long.toString(rowId));
+    }
+
+    static Uri returnUriAsIsIfSuccessful(Uri uri, long rowId) {
+        if (rowId == TRANSACTION_FAILED) {
+            return null;
+        }
+        return uri;
+    }
+}
diff --git a/src/com/android/providers/telephony/ServiceStateProvider.java b/src/com/android/providers/telephony/ServiceStateProvider.java
index e589171..afd8fa1 100644
--- a/src/com/android/providers/telephony/ServiceStateProvider.java
+++ b/src/com/android/providers/telephony/ServiceStateProvider.java
@@ -17,6 +17,36 @@
 
 package com.android.providers.telephony;
 
+import static android.provider.Telephony.ServiceStateTable;
+import static android.provider.Telephony.ServiceStateTable.CDMA_DEFAULT_ROAMING_INDICATOR;
+import static android.provider.Telephony.ServiceStateTable.CDMA_ERI_ICON_INDEX;
+import static android.provider.Telephony.ServiceStateTable.CDMA_ERI_ICON_MODE;
+import static android.provider.Telephony.ServiceStateTable.CDMA_ROAMING_INDICATOR;
+import static android.provider.Telephony.ServiceStateTable.CONTENT_URI;
+import static android.provider.Telephony.ServiceStateTable.CSS_INDICATOR;
+import static android.provider.Telephony.ServiceStateTable.DATA_OPERATOR_ALPHA_LONG;
+import static android.provider.Telephony.ServiceStateTable.DATA_OPERATOR_ALPHA_SHORT;
+import static android.provider.Telephony.ServiceStateTable.DATA_OPERATOR_NUMERIC;
+import static android.provider.Telephony.ServiceStateTable.DATA_REG_STATE;
+import static android.provider.Telephony.ServiceStateTable.DATA_ROAMING_TYPE;
+import static android.provider.Telephony.ServiceStateTable.IS_EMERGENCY_ONLY;
+import static android.provider.Telephony.ServiceStateTable.IS_MANUAL_NETWORK_SELECTION;
+import static android.provider.Telephony.ServiceStateTable.IS_USING_CARRIER_AGGREGATION;
+import static android.provider.Telephony.ServiceStateTable.NETWORK_ID;
+import static android.provider.Telephony.ServiceStateTable.OPERATOR_ALPHA_LONG_RAW;
+import static android.provider.Telephony.ServiceStateTable.OPERATOR_ALPHA_SHORT_RAW;
+import static android.provider.Telephony.ServiceStateTable.RIL_DATA_RADIO_TECHNOLOGY;
+import static android.provider.Telephony.ServiceStateTable.RIL_VOICE_RADIO_TECHNOLOGY;
+import static android.provider.Telephony.ServiceStateTable.SERVICE_STATE;
+import static android.provider.Telephony.ServiceStateTable.SYSTEM_ID;
+import static android.provider.Telephony.ServiceStateTable.VOICE_OPERATOR_ALPHA_LONG;
+import static android.provider.Telephony.ServiceStateTable.VOICE_OPERATOR_ALPHA_SHORT;
+import static android.provider.Telephony.ServiceStateTable.VOICE_OPERATOR_NUMERIC;
+import static android.provider.Telephony.ServiceStateTable.VOICE_REG_STATE;
+import static android.provider.Telephony.ServiceStateTable.VOICE_ROAMING_TYPE;
+import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionId;
+import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionIdAndField;
+
 import android.content.ContentProvider;
 import android.content.ContentValues;
 import android.content.Context;
@@ -24,6 +54,7 @@
 import android.database.MatrixCursor;
 import android.database.MatrixCursor.RowBuilder;
 import android.net.Uri;
+import android.os.Parcel;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.util.Log;
@@ -31,39 +62,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.SubscriptionController;
 
-import java.lang.NumberFormatException;
 import java.util.HashMap;
 
-import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionId;
-import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionIdAndField;
-
-import static android.provider.Telephony.ServiceStateTable;
-import static android.provider.Telephony.ServiceStateTable.CONTENT_URI;
-
-import static android.provider.Telephony.ServiceStateTable.VOICE_REG_STATE;
-import static android.provider.Telephony.ServiceStateTable.DATA_REG_STATE;
-import static android.provider.Telephony.ServiceStateTable.VOICE_ROAMING_TYPE;
-import static android.provider.Telephony.ServiceStateTable.DATA_ROAMING_TYPE;
-import static android.provider.Telephony.ServiceStateTable.VOICE_OPERATOR_ALPHA_LONG;
-import static android.provider.Telephony.ServiceStateTable.VOICE_OPERATOR_ALPHA_SHORT;
-import static android.provider.Telephony.ServiceStateTable.VOICE_OPERATOR_NUMERIC;
-import static android.provider.Telephony.ServiceStateTable.DATA_OPERATOR_ALPHA_LONG;
-import static android.provider.Telephony.ServiceStateTable.DATA_OPERATOR_ALPHA_SHORT;
-import static android.provider.Telephony.ServiceStateTable.DATA_OPERATOR_NUMERIC;
-import static android.provider.Telephony.ServiceStateTable.IS_MANUAL_NETWORK_SELECTION;
-import static android.provider.Telephony.ServiceStateTable.RIL_VOICE_RADIO_TECHNOLOGY;
-import static android.provider.Telephony.ServiceStateTable.RIL_DATA_RADIO_TECHNOLOGY;
-import static android.provider.Telephony.ServiceStateTable.CSS_INDICATOR;
-import static android.provider.Telephony.ServiceStateTable.NETWORK_ID;
-import static android.provider.Telephony.ServiceStateTable.SYSTEM_ID;
-import static android.provider.Telephony.ServiceStateTable.CDMA_ROAMING_INDICATOR;
-import static android.provider.Telephony.ServiceStateTable.CDMA_DEFAULT_ROAMING_INDICATOR;
-import static android.provider.Telephony.ServiceStateTable.CDMA_ERI_ICON_INDEX;
-import static android.provider.Telephony.ServiceStateTable.CDMA_ERI_ICON_MODE;
-import static android.provider.Telephony.ServiceStateTable.IS_EMERGENCY_ONLY;
-import static android.provider.Telephony.ServiceStateTable.IS_DATA_ROAMING_FROM_REGISTRATION;
-import static android.provider.Telephony.ServiceStateTable.IS_USING_CARRIER_AGGREGATION;
-
 
 public class ServiceStateProvider extends ContentProvider {
     private static final String TAG = "ServiceStateProvider";
@@ -94,8 +94,9 @@
         CDMA_ERI_ICON_INDEX,
         CDMA_ERI_ICON_MODE,
         IS_EMERGENCY_ONLY,
-        IS_DATA_ROAMING_FROM_REGISTRATION,
         IS_USING_CARRIER_AGGREGATION,
+        OPERATOR_ALPHA_LONG_RAW,
+        OPERATOR_ALPHA_SHORT_RAW,
     };
 
     @Override
@@ -131,31 +132,13 @@
                 subId = getDefaultSubId();
             }
 
+            final Parcel p = Parcel.obtain();
+            final byte[] rawBytes = values.getAsByteArray(SERVICE_STATE);
+            p.unmarshall(rawBytes, 0, rawBytes.length);
+            p.setDataPosition(0);
+
             // create the new service state
-            ServiceState newSS = new ServiceState();
-            newSS.setVoiceRegState(values.getAsInteger(VOICE_REG_STATE));
-            newSS.setDataRegState(values.getAsInteger(DATA_REG_STATE));
-            newSS.setVoiceOperatorName(values.getAsString(VOICE_OPERATOR_ALPHA_LONG),
-                        values.getAsString(VOICE_OPERATOR_ALPHA_SHORT),
-                        values.getAsString(VOICE_OPERATOR_NUMERIC));
-            newSS.setDataOperatorName(values.getAsString(DATA_OPERATOR_ALPHA_LONG),
-                    values.getAsString(DATA_OPERATOR_ALPHA_SHORT),
-                    values.getAsString(DATA_OPERATOR_NUMERIC));
-            newSS.setIsManualSelection(values.getAsBoolean(IS_MANUAL_NETWORK_SELECTION));
-            newSS.setRilVoiceRadioTechnology(values.getAsInteger(RIL_VOICE_RADIO_TECHNOLOGY));
-            newSS.setRilDataRadioTechnology(values.getAsInteger(RIL_DATA_RADIO_TECHNOLOGY));
-            newSS.setCssIndicator(values.getAsInteger(CSS_INDICATOR));
-            newSS.setCdmaSystemAndNetworkId(values.getAsInteger(SYSTEM_ID),
-                    values.getAsInteger(NETWORK_ID));
-            newSS.setCdmaRoamingIndicator(values.getAsInteger(CDMA_ROAMING_INDICATOR));
-            newSS.setCdmaDefaultRoamingIndicator(
-                    values.getAsInteger(CDMA_DEFAULT_ROAMING_INDICATOR));
-            newSS.setCdmaEriIconIndex(values.getAsInteger(CDMA_ERI_ICON_INDEX));
-            newSS.setCdmaEriIconMode(values.getAsInteger(CDMA_ERI_ICON_MODE));
-            newSS.setEmergencyOnly(values.getAsBoolean(IS_EMERGENCY_ONLY));
-            newSS.setDataRoamingFromRegistration(
-                    values.getAsBoolean(IS_DATA_ROAMING_FROM_REGISTRATION));
-            newSS.setIsUsingCarrierAggregation(values.getAsBoolean(IS_USING_CARRIER_AGGREGATION));
+            final ServiceState newSS = ServiceState.CREATOR.createFromParcel(p);
 
             // notify listeners
             // if ss is null (e.g. first service state update) we will notify for all fields
@@ -235,9 +218,9 @@
             final int cdma_eri_icon_index = ss.getCdmaEriIconIndex();
             final int cdma_eri_icon_mode = ss.getCdmaEriIconMode();
             final int is_emergency_only = (ss.isEmergencyOnly()) ? 1 : 0;
-            final int is_data_roaming_from_registration =
-                    (ss.getDataRoamingFromRegistration()) ? 1 : 0;
             final int is_using_carrier_aggregation = (ss.isUsingCarrierAggregation()) ? 1 : 0;
+            final String operator_alpha_long_raw = ss.getOperatorAlphaLongRaw();
+            final String operator_alpha_short_raw = ss.getOperatorAlphaShortRaw();
 
             return buildSingleRowResult(projection, sColumns, new Object[] {
                         voice_reg_state,
@@ -261,8 +244,9 @@
                         cdma_eri_icon_index,
                         cdma_eri_icon_mode,
                         is_emergency_only,
-                        is_data_roaming_from_registration,
                         is_using_carrier_aggregation,
+                        operator_alpha_long_raw,
+                        operator_alpha_short_raw,
             });
         }
     }
@@ -348,10 +332,10 @@
      *
      * Apps which want to wake when any field in the ServiceState has changed can use
      * JobScheduler's TriggerContentUri.  This replaces the waking functionality of the implicit
-     * broadcast of ACTION_SERVICE_STATE_CHANGED for apps targetting version O.
+     * broadcast of ACTION_SERVICE_STATE_CHANGED for apps targeting version O.
      *
      * We will only notify for certain fields. This is an intentional change from the behavior of
-     * the broadcast. Listeners will be notified when the voice or data registration state or
+     * the broadcast. Listeners will only be notified when the voice/data registration state or
      * roaming type changes.
      */
     @VisibleForTesting
diff --git a/src/com/android/providers/telephony/SmsChangesProvider.java b/src/com/android/providers/telephony/SmsChangesProvider.java
new file mode 100644
index 0000000..3f14e2b
--- /dev/null
+++ b/src/com/android/providers/telephony/SmsChangesProvider.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.telephony;
+
+import android.app.AppOpsManager;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+import android.content.pm.PackageManager;
+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.util.Log;
+
+/**
+ * This is the ContentProvider for the table sms_changes.
+ * This provider is applicable only for Android Auto builds as
+ * this table exists only in Android Auto environment.
+ *
+ * This provider does not notify of changes.
+ * Interested observers should instead listen to notification on sms table, instead.
+ */
+public class SmsChangesProvider extends ContentProvider {
+    private final static String TAG = "SmsChangesProvider";
+
+    private static final String TABLE_SMS_CHANGES = "sms_changes";
+
+    // Db open helper for tables stored in CE(Credential Encrypted) storage.
+    private SQLiteOpenHelper mCeOpenHelper;
+
+    @Override
+    public String getType(Uri url) {
+        return null;
+    }
+
+    @Override
+    public boolean onCreate() {
+        setAppOps(AppOpsManager.OP_READ_SMS, AppOpsManager.OP_WRITE_SMS);
+        mCeOpenHelper = MmsSmsDatabaseHelper.getInstanceForCe(getContext());
+        return true;
+    }
+
+
+    @Override
+    public Cursor query(Uri url, String[] projectionIn, String selection,
+            String[] selectionArgs, String sort) {
+        // return if FEATURE_AUTOMOTIVE is not set
+        if (!isAutoFeatureSet()) {
+            return null;
+        }
+
+        // Only support one type of query
+        //  Caller sends content://mms-sms and other params
+        if (!isUrlSupported(url)) {
+            return null;
+        }
+
+        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+        qb.setTables(TABLE_SMS_CHANGES);
+        SQLiteDatabase db = mCeOpenHelper.getReadableDatabase();
+        return qb.query(db, projectionIn, selection, selectionArgs,
+                null /* groupBy */, null /* having */,
+                null /* sortOrder */);
+    }
+
+    @Override
+    public Uri insert(Uri url, ContentValues initialValues) {
+        return null;
+    }
+
+    @Override
+    public int delete(Uri url, String where, String[] whereArgs) {
+        // return if FEATURE_AUTOMOTIVE is not set
+        if (!isAutoFeatureSet()) {
+            return 0;
+        }
+
+        // only support deletion of all data from the table
+        if (!isUrlSupported(url)) return 0;
+
+        return mCeOpenHelper.getWritableDatabase().delete(TABLE_SMS_CHANGES,
+                null /* whereClause */, null /* whereArgs */);
+    }
+
+    private boolean isUrlSupported(Uri url) {
+        if (sURLMatcher.match(url) != SMSCHANGES_URL) {
+            Log.e(TAG, "Invalid or Unsupported request: " + url);
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int update(Uri url, ContentValues values, String where, String[] whereArgs) {
+        return 0;
+    }
+
+    private boolean isAutoFeatureSet() {
+        return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+    }
+
+    private static final int SMSCHANGES_URL = 0;
+
+    private static final UriMatcher sURLMatcher =
+            new UriMatcher(UriMatcher.NO_MATCH);
+
+    static {
+        sURLMatcher.addURI("sms-changes", null, SMSCHANGES_URL);
+    }
+}
diff --git a/src/com/android/providers/telephony/SmsProvider.java b/src/com/android/providers/telephony/SmsProvider.java
index 2b40d7e..e2e6d10 100644
--- a/src/com/android/providers/telephony/SmsProvider.java
+++ b/src/com/android/providers/telephony/SmsProvider.java
@@ -317,7 +317,7 @@
     private Cursor getSingleMessageFromIcc(String messageIndexString) {
         int messageIndex = -1;
         try {
-            Integer.parseInt(messageIndexString);
+            messageIndex = Integer.parseInt(messageIndexString);
         } catch (NumberFormatException exception) {
             throw new IllegalArgumentException("Bad SMS ICC ID: " + messageIndexString);
         }
@@ -464,7 +464,6 @@
 
         int match = sURLMatcher.match(url);
         String table = TABLE_SMS;
-        boolean notifyIfNotDefault = true;
 
         switch (match) {
             case SMS_ALL:
@@ -503,10 +502,6 @@
 
             case SMS_RAW_MESSAGE:
                 table = "raw";
-                // The raw table is used by the telephony layer for storing an sms before
-                // sending out a notification that an sms has arrived. We don't want to notify
-                // the default sms app of changes to this table.
-                notifyIfNotDefault = false;
                 break;
 
             case SMS_STATUS_PENDING:
diff --git a/src/com/android/providers/telephony/TelephonyBackupAgent.java b/src/com/android/providers/telephony/TelephonyBackupAgent.java
index 5f6eb10..437b5a2 100644
--- a/src/com/android/providers/telephony/TelephonyBackupAgent.java
+++ b/src/com/android/providers/telephony/TelephonyBackupAgent.java
@@ -16,11 +16,6 @@
 
 package com.android.providers.telephony;
 
-import com.google.android.mms.ContentType;
-import com.google.android.mms.pdu.CharacterSets;
-
-import com.android.internal.annotations.VisibleForTesting;
-
 import android.annotation.TargetApi;
 import android.app.AlarmManager;
 import android.app.IntentService;
@@ -53,6 +48,11 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.android.mms.ContentType;
+import com.google.android.mms.pdu.CharacterSets;
+
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileDescriptor;
@@ -319,7 +319,7 @@
         final SubscriptionManager subscriptionManager = SubscriptionManager.from(this);
         if (subscriptionManager != null) {
             final List<SubscriptionInfo> subInfo =
-                    subscriptionManager.getActiveSubscriptionInfoList();
+                    subscriptionManager.getActiveSubscriptionInfoList(/* userVisibleonly */false);
             if (subInfo != null) {
                 for (SubscriptionInfo sub : subInfo) {
                     final String phoneNumber = getNormalizedNumber(sub);
@@ -1155,8 +1155,12 @@
             values.put(Telephony.Mms.Part.NAME, srcName);
             values.put(Telephony.Mms.Part.CONTENT_ID, "<"+srcName+">");
             values.put(Telephony.Mms.Part.CONTENT_LOCATION, srcName);
-            values.put(Telephony.Mms.Part.CHARSET, mms.body.charSet);
-            values.put(Telephony.Mms.Part.TEXT, mms.body.text);
+
+            values.put(
+                    Telephony.Mms.Part.CHARSET,
+                    mms.body == null ? CharacterSets.DEFAULT_CHARSET : mms.body.charSet);
+            values.put(Telephony.Mms.Part.TEXT, mms.body == null ? "" : mms.body.text);
+
             if (mContentResolver.insert(partUri, values) == null) {
                 if (DEBUG) {
                     Log.e(TAG, "Could not insert body part");
diff --git a/src/com/android/providers/telephony/TelephonyProvider.java b/src/com/android/providers/telephony/TelephonyProvider.java
index 8963559..d9e368d 100644
--- a/src/com/android/providers/telephony/TelephonyProvider.java
+++ b/src/com/android/providers/telephony/TelephonyProvider.java
@@ -26,28 +26,29 @@
 import static android.provider.Telephony.Carriers.CARRIER_DELETED_BUT_PRESENT_IN_XML;
 import static android.provider.Telephony.Carriers.CARRIER_EDITED;
 import static android.provider.Telephony.Carriers.CARRIER_ENABLED;
+import static android.provider.Telephony.Carriers.CARRIER_ID;
 import static android.provider.Telephony.Carriers.CONTENT_URI;
 import static android.provider.Telephony.Carriers.CURRENT;
 import static android.provider.Telephony.Carriers.DEFAULT_SORT_ORDER;
-import static android.provider.Telephony.Carriers.EDITED;
-import static android.provider.Telephony.Carriers.MAX_CONNS;
-import static android.provider.Telephony.Carriers.MAX_CONNS_TIME;
+import static android.provider.Telephony.Carriers.EDITED_STATUS;
+import static android.provider.Telephony.Carriers.MAX_CONNECTIONS;
+import static android.provider.Telephony.Carriers.TIME_LIMIT_FOR_MAX_CONNECTIONS;
 import static android.provider.Telephony.Carriers.MCC;
 import static android.provider.Telephony.Carriers.MMSC;
 import static android.provider.Telephony.Carriers.MMSPORT;
 import static android.provider.Telephony.Carriers.MMSPROXY;
 import static android.provider.Telephony.Carriers.MNC;
-import static android.provider.Telephony.Carriers.MODEM_COGNITIVE;
+import static android.provider.Telephony.Carriers.MODEM_PERSIST;
 import static android.provider.Telephony.Carriers.MTU;
 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.NO_SET_SET;
+import static android.provider.Telephony.Carriers.NO_APN_SET_ID;
 import static android.provider.Telephony.Carriers.NUMERIC;
 import static android.provider.Telephony.Carriers.OWNED_BY;
-import static android.provider.Telephony.Carriers.OWNED_BY_OTHERS;
 import static android.provider.Telephony.Carriers.OWNED_BY_DPC;
+import static android.provider.Telephony.Carriers.OWNED_BY_OTHERS;
 import static android.provider.Telephony.Carriers.PASSWORD;
 import static android.provider.Telephony.Carriers.PORT;
 import static android.provider.Telephony.Carriers.PROFILE_ID;
@@ -55,6 +56,8 @@
 import static android.provider.Telephony.Carriers.PROXY;
 import static android.provider.Telephony.Carriers.ROAMING_PROTOCOL;
 import static android.provider.Telephony.Carriers.SERVER;
+import static android.provider.Telephony.Carriers.SKIP_464XLAT;
+import static android.provider.Telephony.Carriers.SKIP_464XLAT_DEFAULT;
 import static android.provider.Telephony.Carriers.SUBSCRIPTION_ID;
 import static android.provider.Telephony.Carriers.TYPE;
 import static android.provider.Telephony.Carriers.UNEDITED;
@@ -64,11 +67,12 @@
 import static android.provider.Telephony.Carriers.USER_EDITABLE;
 import static android.provider.Telephony.Carriers.USER_EDITED;
 import static android.provider.Telephony.Carriers.USER_VISIBLE;
-import static android.provider.Telephony.Carriers.WAIT_TIME;
+import static android.provider.Telephony.Carriers.WAIT_TIME_RETRY;
 import static android.provider.Telephony.Carriers._ID;
 
 import android.content.ComponentName;
 import android.content.ContentProvider;
+import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
@@ -95,10 +99,12 @@
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.provider.Telephony;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
@@ -109,7 +115,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IApnSourceService;
 import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.dataconnection.ApnSetting;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.dataconnection.ApnSettingUtils;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.internal.util.XmlUtils;
@@ -117,16 +124,21 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
-import java.util.function.Consumer;
+import java.util.Set;
+import java.util.zip.CRC32;
 
 public class TelephonyProvider extends ContentProvider
 {
@@ -135,7 +147,7 @@
     private static final boolean DBG = true;
     private static final boolean VDBG = false; // STOPSHIP if true
 
-    private static final int DATABASE_VERSION = 26 << 16;
+    private static final int DATABASE_VERSION = 41 << 16;
     private static final int URL_UNKNOWN = 0;
     private static final int URL_TELEPHONY = 1;
     private static final int URL_CURRENT = 2;
@@ -159,7 +171,11 @@
     private static final int URL_ENFORCE_MANAGED = 20;
     private static final int URL_PREFERAPNSET = 21;
     private static final int URL_PREFERAPNSET_USING_SUBID = 22;
-
+    private static final int URL_SIM_APN_LIST = 23;
+    private static final int URL_SIM_APN_LIST_ID = 24;
+    private static final int URL_FILTERED_USING_SUBID = 25;
+    private static final int URL_SIM_APN_LIST_FILTERED = 26;
+    private static final int URL_SIM_APN_LIST_FILTERED_ID = 27;
 
     private static final String TAG = "TelephonyProvider";
     private static final String CARRIERS_TABLE = "carriers";
@@ -190,6 +206,7 @@
 
     private static final String DEFAULT_PROTOCOL = "IP";
     private static final String DEFAULT_ROAMING_PROTOCOL = "IP";
+
     // Used to check if certain queries contain subqueries that may attempt to access sensitive
     // fields in the carriers db.
     private static final String SQL_SELECT_TOKEN = "select";
@@ -199,24 +216,24 @@
     private static final ContentValues s_currentNullMap;
     private static final ContentValues s_currentSetMap;
 
-    private static final String IS_UNEDITED = EDITED + "=" + UNEDITED;
-    private static final String IS_EDITED = EDITED + "!=" + UNEDITED;
-    private static final String IS_USER_EDITED = EDITED + "=" + USER_EDITED;
-    private static final String IS_NOT_USER_EDITED = EDITED + "!=" + USER_EDITED;
-    private static final String IS_USER_DELETED = EDITED + "=" + USER_DELETED;
-    private static final String IS_NOT_USER_DELETED = EDITED + "!=" + USER_DELETED;
+    private static final String IS_UNEDITED = EDITED_STATUS + "=" + UNEDITED;
+    private static final String IS_EDITED = EDITED_STATUS + "!=" + UNEDITED;
+    private static final String IS_USER_EDITED = EDITED_STATUS + "=" + USER_EDITED;
+    private static final String IS_NOT_USER_EDITED = EDITED_STATUS + "!=" + USER_EDITED;
+    private static final String IS_USER_DELETED = EDITED_STATUS + "=" + USER_DELETED;
+    private static final String IS_NOT_USER_DELETED = EDITED_STATUS + "!=" + USER_DELETED;
     private static final String IS_USER_DELETED_BUT_PRESENT_IN_XML =
-            EDITED + "=" + USER_DELETED_BUT_PRESENT_IN_XML;
+            EDITED_STATUS + "=" + USER_DELETED_BUT_PRESENT_IN_XML;
     private static final String IS_NOT_USER_DELETED_BUT_PRESENT_IN_XML =
-            EDITED + "!=" + USER_DELETED_BUT_PRESENT_IN_XML;
-    private static final String IS_CARRIER_EDITED = EDITED + "=" + CARRIER_EDITED;
-    private static final String IS_NOT_CARRIER_EDITED = EDITED + "!=" + CARRIER_EDITED;
-    private static final String IS_CARRIER_DELETED = EDITED + "=" + CARRIER_DELETED;
-    private static final String IS_NOT_CARRIER_DELETED = EDITED + "!=" + CARRIER_DELETED;
+            EDITED_STATUS + "!=" + USER_DELETED_BUT_PRESENT_IN_XML;
+    private static final String IS_CARRIER_EDITED = EDITED_STATUS + "=" + CARRIER_EDITED;
+    private static final String IS_NOT_CARRIER_EDITED = EDITED_STATUS + "!=" + CARRIER_EDITED;
+    private static final String IS_CARRIER_DELETED = EDITED_STATUS + "=" + CARRIER_DELETED;
+    private static final String IS_NOT_CARRIER_DELETED = EDITED_STATUS + "!=" + CARRIER_DELETED;
     private static final String IS_CARRIER_DELETED_BUT_PRESENT_IN_XML =
-            EDITED + "=" + CARRIER_DELETED_BUT_PRESENT_IN_XML;
+            EDITED_STATUS + "=" + CARRIER_DELETED_BUT_PRESENT_IN_XML;
     private static final String IS_NOT_CARRIER_DELETED_BUT_PRESENT_IN_XML =
-            EDITED + "!=" + CARRIER_DELETED_BUT_PRESENT_IN_XML;
+            EDITED_STATUS + "!=" + CARRIER_DELETED_BUT_PRESENT_IN_XML;
     private static final String IS_OWNED_BY_DPC = OWNED_BY + "=" + OWNED_BY_DPC;
     private static final String IS_NOT_OWNED_BY_DPC = OWNED_BY + "!=" + OWNED_BY_DPC;
 
@@ -225,6 +242,7 @@
 
     private static final int INVALID_APN_ID = -1;
     private static final List<String> CARRIERS_UNIQUE_FIELDS = new ArrayList<String>();
+    private static final Set<String> CARRIERS_BOOLEAN_FIELDS = new HashSet<String>();
     private static final Map<String, String> CARRIERS_UNIQUE_FIELDS_DEFAULTS = new HashMap();
 
     @VisibleForTesting
@@ -240,7 +258,8 @@
     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, network_type_bitmask
+        // wait_time, max_conns_time, mtu, bearer_bitmask, user_visible, network_type_bitmask,
+        // skip_464xlat
         CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(NUMERIC, "");
         CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(MCC, "");
         CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(MNC, "");
@@ -259,9 +278,19 @@
         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_DEFAULTS.put(APN_SET_ID, String.valueOf(NO_SET_SET));
+        CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(APN_SET_ID, String.valueOf(NO_APN_SET_ID));
+        CARRIERS_UNIQUE_FIELDS_DEFAULTS.put(CARRIER_ID,
+                String.valueOf(TelephonyManager.UNKNOWN_CARRIER_ID));
 
         CARRIERS_UNIQUE_FIELDS.addAll(CARRIERS_UNIQUE_FIELDS_DEFAULTS.keySet());
+
+        // SQLite databases store bools as ints but the ContentValues objects passed in through
+        // queries use bools. As a result there is some special handling of boolean fields within
+        // the TelephonyProvider.
+        CARRIERS_BOOLEAN_FIELDS.add(CARRIER_ENABLED);
+        CARRIERS_BOOLEAN_FIELDS.add(MODEM_PERSIST);
+        CARRIERS_BOOLEAN_FIELDS.add(USER_VISIBLE);
+        CARRIERS_BOOLEAN_FIELDS.add(USER_EDITABLE);
     }
 
     @VisibleForTesting
@@ -272,6 +301,7 @@
                 NUMERIC + " TEXT DEFAULT ''," +
                 MCC + " TEXT DEFAULT ''," +
                 MNC + " TEXT DEFAULT ''," +
+                CARRIER_ID + " INTEGER DEFAULT " + TelephonyManager.UNKNOWN_CARRIER_ID  + "," +
                 APN + " TEXT DEFAULT ''," +
                 USER + " TEXT DEFAULT ''," +
                 SERVER + " TEXT DEFAULT ''," +
@@ -286,31 +316,32 @@
                 CURRENT + " INTEGER," +
                 PROTOCOL + " TEXT DEFAULT " + DEFAULT_PROTOCOL + "," +
                 ROAMING_PROTOCOL + " TEXT DEFAULT " + DEFAULT_ROAMING_PROTOCOL + "," +
-                CARRIER_ENABLED + " BOOLEAN DEFAULT 1," +
+                CARRIER_ENABLED + " BOOLEAN DEFAULT 1," + // SQLite databases store bools as ints
                 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 "
-                + SubscriptionManager.INVALID_SUBSCRIPTION_ID + "," +
+                SUBSCRIPTION_ID + " INTEGER DEFAULT " +
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID + "," +
                 PROFILE_ID + " INTEGER DEFAULT 0," +
-                MODEM_COGNITIVE + " BOOLEAN DEFAULT 0," +
-                MAX_CONNS + " INTEGER DEFAULT 0," +
-                WAIT_TIME + " INTEGER DEFAULT 0," +
-                MAX_CONNS_TIME + " INTEGER DEFAULT 0," +
+                MODEM_PERSIST + " BOOLEAN DEFAULT 0," +
+                MAX_CONNECTIONS + " INTEGER DEFAULT 0," +
+                WAIT_TIME_RETRY + " INTEGER DEFAULT 0," +
+                TIME_LIMIT_FOR_MAX_CONNECTIONS + " INTEGER DEFAULT 0," +
                 MTU + " INTEGER DEFAULT 0," +
-                EDITED + " INTEGER DEFAULT " + UNEDITED + "," +
+                EDITED_STATUS + " INTEGER DEFAULT " + UNEDITED + "," +
                 USER_VISIBLE + " BOOLEAN DEFAULT 1," +
                 USER_EDITABLE + " BOOLEAN DEFAULT 1," +
                 OWNED_BY + " INTEGER DEFAULT " + OWNED_BY_OTHERS + "," +
-                APN_SET_ID + " INTEGER DEFAULT " + NO_SET_SET + "," +
+                APN_SET_ID + " INTEGER DEFAULT " + NO_APN_SET_ID + "," +
+                SKIP_464XLAT + " INTEGER DEFAULT " + SKIP_464XLAT_DEFAULT + "," +
                 // Uniqueness collisions are used to trigger merge code so if a field is listed
                 // 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,
-                // network_type_bitmask.
+                // network_type_bitmask, skip_464xlat.
                 "UNIQUE (" + TextUtils.join(", ", CARRIERS_UNIQUE_FIELDS) + "));";
     }
 
@@ -335,6 +366,10 @@
                 + " INTEGER DEFAULT " + SubscriptionManager.DATA_ROAMING_DEFAULT + ","
                 + SubscriptionManager.MCC + " INTEGER DEFAULT 0,"
                 + SubscriptionManager.MNC + " INTEGER DEFAULT 0,"
+                + SubscriptionManager.MCC_STRING + " TEXT,"
+                + SubscriptionManager.MNC_STRING + " TEXT,"
+                + SubscriptionManager.EHPLMNS + " TEXT,"
+                + SubscriptionManager.HPLMNS + " TEXT,"
                 + SubscriptionManager.SIM_PROVISIONING_STATUS
                 + " INTEGER DEFAULT " + SubscriptionManager.SIM_PROVISIONED + ","
                 + SubscriptionManager.IS_EMBEDDED + " INTEGER DEFAULT 0,"
@@ -358,7 +393,20 @@
                 + SubscriptionManager.WFC_IMS_ENABLED + " INTEGER DEFAULT -1,"
                 + SubscriptionManager.WFC_IMS_MODE + " INTEGER DEFAULT -1,"
                 + SubscriptionManager.WFC_IMS_ROAMING_MODE + " INTEGER DEFAULT -1,"
-                + SubscriptionManager.WFC_IMS_ROAMING_ENABLED + " INTEGER DEFAULT -1"
+                + SubscriptionManager.WFC_IMS_ROAMING_ENABLED + " INTEGER DEFAULT -1,"
+                + SubscriptionManager.IS_OPPORTUNISTIC + " INTEGER DEFAULT 0,"
+                + SubscriptionManager.GROUP_UUID + " TEXT,"
+                + SubscriptionManager.IS_METERED + " INTEGER DEFAULT 1,"
+                + SubscriptionManager.ISO_COUNTRY_CODE + " TEXT,"
+                + SubscriptionManager.CARRIER_ID + " INTEGER DEFAULT -1,"
+                + SubscriptionManager.PROFILE_CLASS + " INTEGER DEFAULT "
+                + SubscriptionManager.PROFILE_CLASS_DEFAULT + ","
+                + SubscriptionManager.SUBSCRIPTION_TYPE + " INTEGER DEFAULT "
+                + SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM + ","
+                + SubscriptionManager.WHITE_LISTED_APN_DATA + " INTEGER DEFAULT 0,"
+                + SubscriptionManager.GROUP_OWNER + " TEXT,"
+                + SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES + " TEXT,"
+                + SubscriptionManager.IMSI + " TEXT"
                 + ");";
     }
 
@@ -372,6 +420,7 @@
         s_urlMatcher.addURI("telephony", "carriers/preferapnset", URL_PREFERAPNSET);
 
         s_urlMatcher.addURI("telephony", "siminfo", URL_SIMINFO);
+        s_urlMatcher.addURI("telephony", "siminfo/#", URL_SIMINFO_USING_SUBID);
 
         s_urlMatcher.addURI("telephony", "carriers/subId/*", URL_TELEPHONY_USING_SUBID);
         s_urlMatcher.addURI("telephony", "carriers/current/subId/*", URL_CURRENT_USING_SUBID);
@@ -395,8 +444,17 @@
         // Only called by Settings app, DcTracker and other telephony components to get a
         // single APN according to whether DPC records are enforced.
         s_urlMatcher.addURI("telephony", "carriers/filtered/#", URL_FILTERED_ID);
+        // Used by DcTracker to pass a subId.
+        s_urlMatcher.addURI("telephony", "carriers/filtered/subId/*", URL_FILTERED_USING_SUBID);
+
         // Only Called by DevicePolicyManager to enforce DPC records.
         s_urlMatcher.addURI("telephony", "carriers/enforce_managed", URL_ENFORCE_MANAGED);
+        s_urlMatcher.addURI("telephony", "carriers/sim_apn_list", URL_SIM_APN_LIST);
+        s_urlMatcher.addURI("telephony", "carriers/sim_apn_list/#", URL_SIM_APN_LIST_ID);
+        s_urlMatcher.addURI("telephony", "carriers/sim_apn_list/filtered",
+            URL_SIM_APN_LIST_FILTERED);
+        s_urlMatcher.addURI("telephony", "carriers/sim_apn_list/filtered/subId/*",
+                URL_SIM_APN_LIST_FILTERED_ID);
 
         s_currentNullMap = new ContentValues(1);
         s_currentNullMap.put(CURRENT, "0");
@@ -424,7 +482,8 @@
         mInjector = injector;
     }
 
-    private static class DatabaseHelper extends SQLiteOpenHelper {
+    @VisibleForTesting
+    public static class DatabaseHelper extends SQLiteOpenHelper {
         // Context to access resources with
         private Context mContext;
 
@@ -438,12 +497,18 @@
             mContext = context;
             // Memory optimization - close idle connections after 30s of inactivity
             setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
+            setWriteAheadLoggingEnabled(false);
         }
 
-        private static int getVersion(Context context) {
+        @VisibleForTesting
+        public static int getVersion(Context context) {
             if (VDBG) log("getVersion:+");
             // Get the database version, combining a static schema version and the XML version
             Resources r = context.getResources();
+            if (r == null) {
+                loge("resources=null, return version=" + Integer.toHexString(DATABASE_VERSION));
+                return DATABASE_VERSION;
+            }
             XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
             try {
                 XmlUtils.beginDocument(parser, "apns");
@@ -523,9 +588,32 @@
             } catch (IOException e) {
                 loge("IOException for " + file.getAbsolutePath() + ":" + e);
             }
+
+            // The RRO may have been updated in a firmware upgrade. Add checksum for the
+            // resources to the total checksum so that apns in an RRO update is not missed.
+            try (InputStream inputStream = mContext.getResources().
+                        openRawResource(com.android.internal.R.xml.apns)) {
+                byte[] array = toByteArray(inputStream);
+                CRC32 c = new CRC32();
+                c.update(array);
+                checksum += c.getValue();
+                if (DBG) log("Checksum after adding resource is " + checksum);
+            } catch (IOException | Resources.NotFoundException e) {
+                loge("Exception when calculating checksum for internal apn resources: " + e);
+            }
             return checksum;
         }
 
+        private static byte[] toByteArray(InputStream input) throws IOException {
+            byte[] buffer = new byte[128];
+            int bytesRead;
+            ByteArrayOutputStream output = new ByteArrayOutputStream();
+            while ((bytesRead = input.read(buffer)) != -1) {
+                output.write(buffer, 0, bytesRead);
+            }
+            return output.toByteArray();
+        }
+
         private long getApnConfChecksum() {
             SharedPreferences sp = mContext.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
             return sp.getLong(APN_CONF_CHECKSUM, -1);
@@ -543,8 +631,10 @@
             File confFile = new File(Environment.getRootDirectory(), PARTNER_APNS_PATH);
             File oemConfFile =  new File(Environment.getOemDirectory(), OEM_APNS_PATH);
             File updatedConfFile = new File(Environment.getDataDirectory(), OTA_UPDATED_APNS_PATH);
-            confFile = getNewerFile(confFile, oemConfFile);
-            confFile = getNewerFile(confFile, updatedConfFile);
+            File productConfFile = new File(Environment.getProductDirectory(), PARTNER_APNS_PATH);
+            confFile = pickSecondIfExists(confFile, oemConfFile);
+            confFile = pickSecondIfExists(confFile, productConfFile);
+            confFile = pickSecondIfExists(confFile, updatedConfFile);
             return confFile;
         }
 
@@ -575,16 +665,20 @@
             if (VDBG) log("dbh.initDatabase:+ db=" + db);
             // Read internal APNS data
             Resources r = mContext.getResources();
-            XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
             int publicversion = -1;
-            try {
-                XmlUtils.beginDocument(parser, "apns");
-                publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
-                loadApns(db, parser);
-            } catch (Exception e) {
-                loge("Got exception while loading APN database." + e);
-            } finally {
-                parser.close();
+            if (r != null) {
+                XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
+                try {
+                    XmlUtils.beginDocument(parser, "apns");
+                    publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
+                    loadApns(db, parser);
+                } catch (Exception e) {
+                    loge("Got exception while loading APN database." + e);
+                } finally {
+                    parser.close();
+                }
+            } else {
+                loge("initDatabase: resources=null");
             }
 
             // Read external APNS data (partner-provided)
@@ -627,12 +721,12 @@
 
                 // Change USER_DELETED_BUT_PRESENT_IN_XML to USER_DELETED
                 ContentValues cv = new ContentValues();
-                cv.put(EDITED, USER_DELETED);
+                cv.put(EDITED_STATUS, USER_DELETED);
                 db.update(CARRIERS_TABLE, cv, IS_USER_DELETED_BUT_PRESENT_IN_XML, null);
 
                 // Change CARRIER_DELETED_BUT_PRESENT_IN_XML to CARRIER_DELETED
                 cv = new ContentValues();
-                cv.put(EDITED, CARRIER_DELETED);
+                cv.put(EDITED_STATUS, CARRIER_DELETED);
                 db.update(CARRIERS_TABLE, cv, IS_CARRIER_DELETED_BUT_PRESENT_IN_XML, null);
 
                 if (confreader != null) {
@@ -650,26 +744,16 @@
 
         }
 
-        private File getNewerFile(File sysApnFile, File altApnFile) {
+        private File pickSecondIfExists(File sysApnFile, File altApnFile) {
             if (altApnFile.exists()) {
-                // Alternate file exists. Use the newer one.
-                long altFileTime = altApnFile.lastModified();
-                long currFileTime = sysApnFile.lastModified();
-                if (DBG) log("APNs Timestamp: altFileTime = " + altFileTime + " currFileTime = "
-                        + currFileTime);
-
-                // To get the latest version from OEM or System image
-                if (altFileTime > currFileTime) {
-                    if (DBG) log("APNs Timestamp: Alternate image " + altApnFile.getPath() +
-                            " is greater than System image");
-                    return altApnFile;
-                }
+                if (DBG) log("Load APNs from " + altApnFile.getPath() +
+                        " instead of " + sysApnFile.getPath());
+                return altApnFile;
             } else {
-                // No Apn in alternate image, so load it from system image.
-                if (DBG) log("No APNs in OEM image = " + altApnFile.getPath() +
-                        " Load APNs from system image");
+                if (DBG) log("Load APNs from " + sysApnFile.getPath() +
+                        " instead of " + altApnFile.getPath());
+                return sysApnFile;
             }
-            return sysApnFile;
         }
 
         @Override
@@ -989,7 +1073,7 @@
             if (oldVersion < (24 << 16 | 6)) {
                 Cursor c = null;
                 String[] proj = {"_id"};
-                recreateDB(c, db, proj, /* version */24);
+                recreateDB(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());
@@ -1029,7 +1113,7 @@
                 // Carriers.NO_SET_SET by default.
                 try {
                     db.execSQL("ALTER TABLE " + CARRIERS_TABLE + " ADD COLUMN " +
-                            APN_SET_ID + " INTEGER DEFAULT " + NO_SET_SET + ";");
+                            APN_SET_ID + " INTEGER DEFAULT " + NO_APN_SET_ID + ";");
                 } catch (SQLiteException e) {
                     if (DBG) {
                         log("onUpgrade skipping " + CARRIERS_TABLE + " upgrade. " +
@@ -1038,9 +1122,243 @@
                 }
                 oldVersion = 26 << 16 | 6;
             }
+
+            if (oldVersion < (27 << 16 | 6)) {
+                // Add the new MCC_STRING and MNC_STRING columns into the subscription table,
+                // and attempt to populate them.
+                try {
+                    // Try to update the siminfo table. It might not be there.
+                    db.execSQL("ALTER TABLE " + SIMINFO_TABLE +
+                            " ADD COLUMN " + SubscriptionManager.MCC_STRING + " TEXT;");
+                    db.execSQL("ALTER TABLE " + SIMINFO_TABLE +
+                            " ADD COLUMN " + SubscriptionManager.MNC_STRING + " TEXT;");
+                } catch (SQLiteException e) {
+                    if (DBG) {
+                        log("onUpgrade skipping " + SIMINFO_TABLE + " upgrade. " +
+                                " The table will get created in onOpen.");
+                    }
+                }
+                // Migrate the old integer values over to strings
+                String[] proj = {SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID,
+                        SubscriptionManager.MCC, SubscriptionManager.MNC};
+                try (Cursor c = db.query(SIMINFO_TABLE, proj, null, null, null, null, null)) {
+                    while (c.moveToNext()) {
+                        fillInMccMncStringAtCursor(mContext, db, c);
+                    }
+                }
+                oldVersion = 27 << 16 | 6;
+            }
+
+            if (oldVersion < (28 << 16 | 6)) {
+                try {
+                    // Try to update the siminfo table. It might not be there.
+                    db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
+                            + SubscriptionManager.IS_OPPORTUNISTIC + " INTEGER DEFAULT 0;");
+                } catch (SQLiteException e) {
+                    if (DBG) {
+                        log("onUpgrade skipping " + SIMINFO_TABLE + " upgrade. " +
+                                "The table will get created in onOpen.");
+                    }
+                }
+                oldVersion = 28 << 16 | 6;
+            }
+
+            if (oldVersion < (29 << 16 | 6)) {
+                try {
+                    // Add a new column Telephony.CARRIER_ID into the database and add UNIQUE
+                    // constraint into table. However, sqlite cannot add constraints to an existing
+                    // table, so recreate the table.
+                    String[] proj = {"_id"};
+                    recreateDB(db, proj,  /* version */29);
+                } catch (SQLiteException e) {
+                    if (DBG) {
+                        log("onUpgrade skipping " + CARRIERS_TABLE + " upgrade. " +
+                                "The table will get created in onOpen.");
+                    }
+                }
+                oldVersion = 29 << 16 | 6;
+            }
+
+            if (oldVersion < (30 << 16 | 6)) {
+                try {
+                    // Try to update the siminfo table. It might not be there.
+                    db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
+                        + SubscriptionManager.GROUP_UUID + " TEXT;");
+                } catch (SQLiteException e) {
+                    if (DBG) {
+                        log("onUpgrade skipping " + SIMINFO_TABLE + " upgrade. " +
+                            "The table will get created in onOpen.");
+                    }
+                }
+                oldVersion = 30 << 16 | 6;
+            }
+
+            if (oldVersion < (31 << 16 | 6)) {
+                try {
+                    // Try to update the siminfo table. It might not be there.
+                    db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
+                            + SubscriptionManager.IS_METERED + " INTEGER DEFAULT 1;");
+                } catch (SQLiteException e) {
+                    if (DBG) {
+                        log("onUpgrade skipping " + SIMINFO_TABLE + " upgrade. " +
+                                "The table will get created in onOpen.");
+                    }
+                }
+                oldVersion = 31 << 16 | 6;
+            }
+
+            if (oldVersion < (32 << 16 | 6)) {
+                try {
+                    // Try to update the siminfo table. It might not be there.
+                    db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
+                            + SubscriptionManager.ISO_COUNTRY_CODE + " TEXT;");
+                } catch (SQLiteException e) {
+                    if (DBG) {
+                        log("onUpgrade skipping " + SIMINFO_TABLE + " upgrade. " +
+                                "The table will get created in onOpen.");
+                    }
+                }
+                oldVersion = 32 << 16 | 6;
+            }
+
+            if (oldVersion < (33 << 16 | 6)) {
+                try {
+                    // Try to update the siminfo table. It might not be there.
+                    db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
+                            + SubscriptionManager.CARRIER_ID + " INTEGER DEFAULT -1;");
+                } catch (SQLiteException e) {
+                    if (DBG) {
+                        log("onUpgrade skipping " + SIMINFO_TABLE + " upgrade. " +
+                                "The table will get created in onOpen.");
+                    }
+                }
+                oldVersion = 33 << 16 | 6;
+            }
+
+            if (oldVersion < (34 << 16 | 6)) {
+                try {
+                    // Try to update the siminfo table. It might not be there.
+                    db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN " +
+                            SubscriptionManager.PROFILE_CLASS + " INTEGER DEFAULT " +
+                            SubscriptionManager.PROFILE_CLASS_DEFAULT + ";");
+                } catch (SQLiteException e) {
+                    if (DBG) {
+                        log("onUpgrade skipping " + SIMINFO_TABLE + " upgrade. " +
+                                "The table will get created in onOpen.");
+                    }
+                }
+                oldVersion = 34 << 16 | 6;
+            }
+
+            if (oldVersion < (35 << 16 | 6)) {
+                try {
+                    // Try to update the siminfo table. It might not be there.
+                    db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
+                        + SubscriptionManager.SUBSCRIPTION_TYPE + " INTEGER DEFAULT "
+                        + SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM + ";");
+                } catch (SQLiteException e) {
+                    if (DBG) {
+                        log("onUpgrade skipping " + SIMINFO_TABLE + " upgrade. " +
+                            "The table will get created in onOpen.");
+                    }
+                }
+                oldVersion = 35 << 16 | 6;
+            }
+
+            if (oldVersion < (36 << 16 | 6)) {
+                // Add a new column Carriers.SKIP_464XLAT into the database and set the value to
+                // SKIP_464XLAT_DEFAULT.
+                try {
+                    db.execSQL("ALTER TABLE " + CARRIERS_TABLE + " ADD COLUMN " +
+                            SKIP_464XLAT + " INTEGER DEFAULT " + SKIP_464XLAT_DEFAULT + ";");
+                } catch (SQLiteException e) {
+                    if (DBG) {
+                        log("onUpgrade skipping " + CARRIERS_TABLE + " upgrade. " +
+                                "The table will get created in onOpen.");
+                    }
+                }
+                oldVersion = 36 << 16 | 6;
+            }
+
+            if (oldVersion < (37 << 16 | 6)) {
+                // Add new columns SubscriptionManager.EHPLMNS and SubscriptionManager.HPLMNS into
+                // the database.
+                try {
+                    db.execSQL("ALTER TABLE " + SIMINFO_TABLE +
+                            " ADD COLUMN " + SubscriptionManager.EHPLMNS + " TEXT;");
+                    db.execSQL("ALTER TABLE " + SIMINFO_TABLE +
+                            " ADD COLUMN " + SubscriptionManager.HPLMNS + " TEXT;");
+                } catch (SQLiteException e) {
+                    if (DBG) {
+                        log("onUpgrade skipping " + SIMINFO_TABLE + " upgrade for ehplmns. " +
+                                "The table will get created in onOpen.");
+                    }
+                }
+                oldVersion = 37 << 16 | 6;
+            }
+
+            if (oldVersion < (38 << 16 | 6)) {
+                try {
+                    // Try to update the siminfo table. It might not be there.
+                    db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
+                            + SubscriptionManager.WHITE_LISTED_APN_DATA + " INTEGER DEFAULT 0;");
+                } catch (SQLiteException e) {
+                    if (DBG) {
+                        log("onUpgrade skipping " + SIMINFO_TABLE + " upgrade. " +
+                                "The table will get created in onOpen.");
+                    }
+                }
+                oldVersion = 38 << 16 | 6;
+            }
+
+            if (oldVersion < (39 << 16 | 6)) {
+                try {
+                    // Try to update the siminfo table. It might not be there.
+                    db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
+                            + SubscriptionManager.GROUP_OWNER + " TEXT;");
+                } catch (SQLiteException e) {
+                    if (DBG) {
+                        log("onUpgrade skipping " + SIMINFO_TABLE + " upgrade. " +
+                                "The table will get created in onOpen.");
+                    }
+                }
+                oldVersion = 39 << 16 | 6;
+            }
+
+            if (oldVersion < (40 << 16 | 6)) {
+                try {
+                    // Try to update the siminfo table. It might not be there.
+                    db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
+                            + SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES + " TEXT;");
+                } catch (SQLiteException e) {
+                    if (DBG) {
+                        log("onUpgrade skipping " + SIMINFO_TABLE + " upgrade. " +
+                                "The table will get created in onOpen.");
+                    }
+                }
+                oldVersion = 40 << 16 | 6;
+            }
+
+            if (oldVersion < (41 << 16 | 6)) {
+                try {
+                    // Try to update the siminfo table. It might not be there.
+                    db.execSQL("ALTER TABLE " + SIMINFO_TABLE + " ADD COLUMN "
+                            + SubscriptionManager.IMSI + " TEXT;");
+                } catch (SQLiteException e) {
+                    if (DBG) {
+                        log("onUpgrade skipping " + SIMINFO_TABLE + " upgrade. " +
+                                "The table will get created in onOpen.");
+                    }
+                }
+                oldVersion = 41 << 16 | 6;
+            }
+
+
             if (DBG) {
                 log("dbh.onUpgrade:- db=" + db + " oldV=" + oldVersion + " newV=" + newVersion);
             }
+            // when adding fields to onUpgrade, also add a unit test to TelephonyDatabaseHelperTest
+            // and update the DATABASE_VERSION field and add a column in copyAllApnValues
         }
 
         private void recreateSimInfoDB(Cursor c, SQLiteDatabase db, String[] proj) {
@@ -1148,12 +1466,13 @@
             }
         }
 
-        private void recreateDB(Cursor c, SQLiteDatabase db, String[] proj, int version) {
+        private void recreateDB(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.
+            Cursor c;
             if (VDBG) {
                 c = db.query(CARRIERS_TABLE, proj, null, null, null, null, null);
                 log("dbh.onUpgrade:- before upgrading total number of rows: " + c.getCount());
@@ -1171,7 +1490,7 @@
 
             createCarriersTable(db, CARRIERS_TABLE_TMP);
 
-            copyDataToTmpTable(db, c);
+            copyDataToTmpTable(db, c, version);
             c.close();
 
             db.execSQL("DROP TABLE IF EXISTS " + CARRIERS_TABLE);
@@ -1276,10 +1595,10 @@
                     queryValOrNull(MVNO_TYPE) +
                     queryValOrNull(MVNO_MATCH_DATA) +
                     queryValOrNull(PROFILE_ID) +
-                    queryVal2OrNull(MODEM_COGNITIVE) +
-                    queryValOrNull(MAX_CONNS) +
-                    queryValOrNull(WAIT_TIME) +
-                    queryValOrNull(MAX_CONNS_TIME) +
+                    queryVal2OrNull(MODEM_PERSIST) +
+                    queryValOrNull(MAX_CONNECTIONS) +
+                    queryValOrNull(WAIT_TIME_RETRY) +
+                    queryValOrNull(TIME_LIMIT_FOR_MAX_CONNECTIONS) +
                     queryValOrNull(MTU);
             String[] whereArgs = new String[29];
             int i = 0;
@@ -1314,15 +1633,13 @@
             whereArgs[i++] = values.containsKey(ROAMING_PROTOCOL) ?
                     values.getAsString(ROAMING_PROTOCOL) : DEFAULT_ROAMING_PROTOCOL;
 
-            if (values.containsKey(CARRIER_ENABLED) &&
-                    (values.getAsString(CARRIER_ENABLED).
-                            equalsIgnoreCase("false") ||
-                            values.getAsString(CARRIER_ENABLED).equals("0"))) {
-                whereArgs[i++] = "false";
-                whereArgs[i++] = "0";
+            if (values.containsKey(CARRIER_ENABLED)) {
+                whereArgs[i++] = convertStringToBoolString(values.getAsString(CARRIER_ENABLED));
+                whereArgs[i++] = convertStringToIntString(values.getAsString(CARRIER_ENABLED));
             } else {
-                whereArgs[i++] = "true";
-                whereArgs[i++] = "1";
+                String defaultIntString = CARRIERS_UNIQUE_FIELDS_DEFAULTS.get(CARRIER_ENABLED);
+                whereArgs[i++] = convertStringToBoolString(defaultIntString);
+                whereArgs[i++] = defaultIntString;
             }
 
             whereArgs[i++] = values.containsKey(BEARER) ?
@@ -1334,10 +1651,10 @@
             whereArgs[i++] = values.containsKey(PROFILE_ID) ?
                     values.getAsString(PROFILE_ID) : "0";
 
-            if (values.containsKey(MODEM_COGNITIVE) &&
-                    (values.getAsString(MODEM_COGNITIVE).
+            if (values.containsKey(MODEM_PERSIST) &&
+                    (values.getAsString(MODEM_PERSIST).
                             equalsIgnoreCase("true") ||
-                            values.getAsString(MODEM_COGNITIVE).equals("1"))) {
+                            values.getAsString(MODEM_PERSIST).equals("1"))) {
                 whereArgs[i++] = "true";
                 whereArgs[i++] = "1";
             } else {
@@ -1345,12 +1662,12 @@
                 whereArgs[i++] = "0";
             }
 
-            whereArgs[i++] = values.containsKey(MAX_CONNS) ?
-                    values.getAsString(MAX_CONNS) : "0";
-            whereArgs[i++] = values.containsKey(WAIT_TIME) ?
-                    values.getAsString(WAIT_TIME) : "0";
-            whereArgs[i++] = values.containsKey(MAX_CONNS_TIME) ?
-                    values.getAsString(MAX_CONNS_TIME) : "0";
+            whereArgs[i++] = values.containsKey(MAX_CONNECTIONS) ?
+                    values.getAsString(MAX_CONNECTIONS) : "0";
+            whereArgs[i++] = values.containsKey(WAIT_TIME_RETRY) ?
+                    values.getAsString(WAIT_TIME_RETRY) : "0";
+            whereArgs[i++] = values.containsKey(TIME_LIMIT_FOR_MAX_CONNECTIONS) ?
+                    values.getAsString(TIME_LIMIT_FOR_MAX_CONNECTIONS) : "0";
             whereArgs[i++] = values.containsKey(MTU) ?
                     values.getAsString(MTU) : "0";
 
@@ -1367,14 +1684,16 @@
             db.delete(CARRIERS_TABLE, where, whereArgs);
         }
 
-        private void copyDataToTmpTable(SQLiteDatabase db, Cursor c) {
+        private void copyDataToTmpTable(SQLiteDatabase db, Cursor c, int version) {
             // Move entries from CARRIERS_TABLE to CARRIERS_TABLE_TMP
             if (c != null) {
                 while (c.moveToNext()) {
                     ContentValues cv = new ContentValues();
-                    copyApnValuesV17(cv, c);
-                    // Sync bearer bitmask and network type bitmask
-                    getNetworkTypeBitmaskFromCursor(cv, c);
+                    copyAllApnValues(cv, c);
+                    if (version == 24) {
+                        // Sync bearer bitmask and network type bitmask
+                        getNetworkTypeBitmaskFromCursor(cv, c);
+                    }
                     try {
                         db.insertWithOnConflict(CARRIERS_TABLE_TMP, null, cv,
                                 SQLiteDatabase.CONFLICT_ABORT);
@@ -1422,95 +1741,142 @@
             getIntValueFromCursor(cv, c, BEARER);
             getIntValueFromCursor(cv, c, SUBSCRIPTION_ID);
             getIntValueFromCursor(cv, c, PROFILE_ID);
-            getIntValueFromCursor(cv, c, MODEM_COGNITIVE);
-            getIntValueFromCursor(cv, c, MAX_CONNS);
-            getIntValueFromCursor(cv, c, WAIT_TIME);
-            getIntValueFromCursor(cv, c, MAX_CONNS_TIME);
+            getIntValueFromCursor(cv, c, MODEM_PERSIST);
+            getIntValueFromCursor(cv, c, MAX_CONNECTIONS);
+            getIntValueFromCursor(cv, c, WAIT_TIME_RETRY);
+            getIntValueFromCursor(cv, c, TIME_LIMIT_FOR_MAX_CONNECTIONS);
             getIntValueFromCursor(cv, c, MTU);
             getIntValueFromCursor(cv, c, BEARER_BITMASK);
-            getIntValueFromCursor(cv, c, EDITED);
+            getIntValueFromCursor(cv, c, EDITED_STATUS);
             getIntValueFromCursor(cv, c, USER_VISIBLE);
         }
 
+        private void copyAllApnValues(ContentValues cv, Cursor c) {
+            // String vals
+            getStringValueFromCursor(cv, c, NAME);
+            getStringValueFromCursor(cv, c, NUMERIC);
+            getStringValueFromCursor(cv, c, MCC);
+            getStringValueFromCursor(cv, c, MNC);
+            getStringValueFromCursor(cv, c, APN);
+            getStringValueFromCursor(cv, c, USER);
+            getStringValueFromCursor(cv, c, SERVER);
+            getStringValueFromCursor(cv, c, PASSWORD);
+            getStringValueFromCursor(cv, c, PROXY);
+            getStringValueFromCursor(cv, c, PORT);
+            getStringValueFromCursor(cv, c, MMSPROXY);
+            getStringValueFromCursor(cv, c, MMSPORT);
+            getStringValueFromCursor(cv, c, MMSC);
+            getStringValueFromCursor(cv, c, TYPE);
+            getStringValueFromCursor(cv, c, PROTOCOL);
+            getStringValueFromCursor(cv, c, ROAMING_PROTOCOL);
+            getStringValueFromCursor(cv, c, MVNO_TYPE);
+            getStringValueFromCursor(cv, c, MVNO_MATCH_DATA);
+
+            // bool/int vals
+            getIntValueFromCursor(cv, c, AUTH_TYPE);
+            getIntValueFromCursor(cv, c, CURRENT);
+            getIntValueFromCursor(cv, c, CARRIER_ENABLED);
+            getIntValueFromCursor(cv, c, BEARER);
+            getIntValueFromCursor(cv, c, SUBSCRIPTION_ID);
+            getIntValueFromCursor(cv, c, PROFILE_ID);
+            getIntValueFromCursor(cv, c, MODEM_PERSIST);
+            getIntValueFromCursor(cv, c, MAX_CONNECTIONS);
+            getIntValueFromCursor(cv, c, WAIT_TIME_RETRY);
+            getIntValueFromCursor(cv, c, TIME_LIMIT_FOR_MAX_CONNECTIONS);
+            getIntValueFromCursor(cv, c, MTU);
+            getIntValueFromCursor(cv, c, NETWORK_TYPE_BITMASK);
+            getIntValueFromCursor(cv, c, BEARER_BITMASK);
+            getIntValueFromCursor(cv, c, EDITED_STATUS);
+            getIntValueFromCursor(cv, c, USER_VISIBLE);
+            getIntValueFromCursor(cv, c, USER_EDITABLE);
+            getIntValueFromCursor(cv, c, OWNED_BY);
+            getIntValueFromCursor(cv, c, APN_SET_ID);
+            getIntValueFromCursor(cv, c, SKIP_464XLAT);
+        }
 
         private void copyPreservedApnsToNewTable(SQLiteDatabase db, Cursor c) {
             // Move entries from CARRIERS_TABLE to CARRIERS_TABLE_TMP
-            if (c != null) {
-                String[] persistApnsForPlmns = mContext.getResources().getStringArray(
-                        R.array.persist_apns_for_plmn);
-                while (c.moveToNext()) {
-                    ContentValues cv = new ContentValues();
-                    String val;
-                    // Using V17 copy function for V15 upgrade. This should be fine since it handles
-                    // columns that may not exist properly (getStringValueFromCursor() and
-                    // getIntValueFromCursor() handle column index -1)
-                    copyApnValuesV17(cv, c);
-                    // Change bearer to a bitmask
-                    String bearerStr = c.getString(c.getColumnIndex(BEARER));
-                    if (!TextUtils.isEmpty(bearerStr)) {
-                        int bearer_bitmask = ServiceState.getBitmaskForTech(
-                                Integer.parseInt(bearerStr));
-                        cv.put(BEARER_BITMASK, bearer_bitmask);
+            if (c != null && mContext.getResources() != null) {
+                try {
+                    String[] persistApnsForPlmns = mContext.getResources().getStringArray(
+                            R.array.persist_apns_for_plmn);
+                    while (c.moveToNext()) {
+                        ContentValues cv = new ContentValues();
+                        String val;
+                        // Using V17 copy function for V15 upgrade. This should be fine since it handles
+                        // columns that may not exist properly (getStringValueFromCursor() and
+                        // getIntValueFromCursor() handle column index -1)
+                        copyApnValuesV17(cv, c);
+                        // Change bearer to a bitmask
+                        String bearerStr = c.getString(c.getColumnIndex(BEARER));
+                        if (!TextUtils.isEmpty(bearerStr)) {
+                            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");
-                    if (userEditedColumnIdx != -1) {
-                        String user_edited = c.getString(userEditedColumnIdx);
-                        if (!TextUtils.isEmpty(user_edited)) {
-                            cv.put(EDITED, new Integer(user_edited));
+                            int networkTypeBitmask = ServiceState.getBitmaskForTech(
+                                    ServiceState.rilRadioTechnologyToNetworkType(
+                                            Integer.parseInt(bearerStr)));
+                            cv.put(NETWORK_TYPE_BITMASK, networkTypeBitmask);
                         }
-                    } else {
-                        cv.put(EDITED, CARRIER_EDITED);
-                    }
 
-                    // New EDITED column. Default value (UNEDITED) will
-                    // be used for all rows except for non-mvno entries for plmns indicated
-                    // by resource: those will be set to CARRIER_EDITED to preserve
-                    // their current values
-                    val = c.getString(c.getColumnIndex(NUMERIC));
-                    for (String s : persistApnsForPlmns) {
-                        if (!TextUtils.isEmpty(val) && val.equals(s) &&
-                                (!cv.containsKey(MVNO_TYPE) ||
-                                        TextUtils.isEmpty(cv.getAsString(MVNO_TYPE)))) {
-                            if (userEditedColumnIdx == -1) {
-                                cv.put(EDITED, CARRIER_EDITED);
-                            } else { // if (oldVersion == 14) -- if db had user_edited column
-                                if (cv.getAsInteger(EDITED) == USER_EDITED) {
-                                    cv.put(EDITED, CARRIER_EDITED);
-                                }
+                        int userEditedColumnIdx = c.getColumnIndex("user_edited");
+                        if (userEditedColumnIdx != -1) {
+                            String user_edited = c.getString(userEditedColumnIdx);
+                            if (!TextUtils.isEmpty(user_edited)) {
+                                cv.put(EDITED_STATUS, new Integer(user_edited));
                             }
+                        } else {
+                            cv.put(EDITED_STATUS, CARRIER_EDITED);
+                        }
 
-                            break;
+                        // New EDITED column. Default value (UNEDITED) will
+                        // be used for all rows except for non-mvno entries for plmns indicated
+                        // by resource: those will be set to CARRIER_EDITED to preserve
+                        // their current values
+                        val = c.getString(c.getColumnIndex(NUMERIC));
+                        for (String s : persistApnsForPlmns) {
+                            if (!TextUtils.isEmpty(val) && val.equals(s) &&
+                                    (!cv.containsKey(MVNO_TYPE) ||
+                                            TextUtils.isEmpty(cv.getAsString(MVNO_TYPE)))) {
+                                if (userEditedColumnIdx == -1) {
+                                    cv.put(EDITED_STATUS, CARRIER_EDITED);
+                                } else { // if (oldVersion == 14) -- if db had user_edited column
+                                    if (cv.getAsInteger(EDITED_STATUS) == USER_EDITED) {
+                                        cv.put(EDITED_STATUS, CARRIER_EDITED);
+                                    }
+                                }
+
+                                break;
+                            }
+                        }
+
+                        try {
+                            db.insertWithOnConflict(CARRIERS_TABLE_TMP, null, cv,
+                                    SQLiteDatabase.CONFLICT_ABORT);
+                            if (VDBG) {
+                                log("dbh.copyPreservedApnsToNewTable: db.insert returned >= 0; " +
+                                        "insert successful for cv " + cv);
+                            }
+                        } catch (SQLException e) {
+                            if (VDBG)
+                                log("dbh.copyPreservedApnsToNewTable insertWithOnConflict exception " +
+                                        e + " for cv " + cv);
+                            // Insertion failed which could be due to a conflict. Check if that is
+                            // the case and merge the entries
+                            Cursor oldRow = DatabaseHelper.selectConflictingRow(db,
+                                    CARRIERS_TABLE_TMP, cv);
+                            if (oldRow != null) {
+                                ContentValues mergedValues = new ContentValues();
+                                mergeFieldsAndUpdateDb(db, CARRIERS_TABLE_TMP, oldRow, cv,
+                                        mergedValues, true, mContext);
+                                oldRow.close();
+                            }
                         }
                     }
-
-                    try {
-                        db.insertWithOnConflict(CARRIERS_TABLE_TMP, null, cv,
-                                SQLiteDatabase.CONFLICT_ABORT);
-                        if (VDBG) {
-                            log("dbh.copyPreservedApnsToNewTable: db.insert returned >= 0; " +
-                                    "insert successful for cv " + cv);
-                        }
-                    } catch (SQLException e) {
-                        if (VDBG)
-                            log("dbh.copyPreservedApnsToNewTable insertWithOnConflict exception " +
-                                    e + " for cv " + cv);
-                        // Insertion failed which could be due to a conflict. Check if that is
-                        // the case and merge the entries
-                        Cursor oldRow = DatabaseHelper.selectConflictingRow(db,
-                                CARRIERS_TABLE_TMP, cv);
-                        if (oldRow != null) {
-                            ContentValues mergedValues = new ContentValues();
-                            mergeFieldsAndUpdateDb(db, CARRIERS_TABLE_TMP, oldRow, cv,
-                                    mergedValues, true, mContext);
-                            oldRow.close();
-                        }
-                    }
+                } catch (Resources.NotFoundException e) {
+                    loge("array.persist_apns_for_plmn is not found");
+                    return;
                 }
             }
         }
@@ -1626,15 +1992,16 @@
             addIntAttribute(parser, "authtype", map, AUTH_TYPE);
             addIntAttribute(parser, "bearer", map, BEARER);
             addIntAttribute(parser, "profile_id", map, PROFILE_ID);
-            addIntAttribute(parser, "max_conns", map, MAX_CONNS);
-            addIntAttribute(parser, "wait_time", map, WAIT_TIME);
-            addIntAttribute(parser, "max_conns_time", map, MAX_CONNS_TIME);
+            addIntAttribute(parser, "max_conns", map, MAX_CONNECTIONS);
+            addIntAttribute(parser, "wait_time", map, WAIT_TIME_RETRY);
+            addIntAttribute(parser, "max_conns_time", map, TIME_LIMIT_FOR_MAX_CONNECTIONS);
             addIntAttribute(parser, "mtu", map, MTU);
             addIntAttribute(parser, "apn_set_id", map, APN_SET_ID);
-
+            addIntAttribute(parser, "carrier_id", map, CARRIER_ID);
+            addIntAttribute(parser, "skip_464xlat", map, SKIP_464XLAT);
 
             addBoolAttribute(parser, "carrier_enabled", map, CARRIER_ENABLED);
-            addBoolAttribute(parser, "modem_cognitive", map, MODEM_COGNITIVE);
+            addBoolAttribute(parser, "modem_cognitive", map, MODEM_PERSIST);
             addBoolAttribute(parser, "user_visible", map, USER_VISIBLE);
             addBoolAttribute(parser, "user_editable", map, USER_EDITABLE);
 
@@ -1756,7 +2123,7 @@
                 if (oldRow != null) {
                     // Update the row
                     ContentValues mergedValues = new ContentValues();
-                    int edited = oldRow.getInt(oldRow.getColumnIndex(EDITED));
+                    int edited = oldRow.getInt(oldRow.getColumnIndex(EDITED_STATUS));
                     int old_edited = edited;
                     if (edited != UNEDITED) {
                         if (edited == USER_DELETED) {
@@ -1768,7 +2135,7 @@
                             // by user but present in apn xml file.
                             edited = CARRIER_DELETED_BUT_PRESENT_IN_XML;
                         }
-                        mergedValues.put(EDITED, edited);
+                        mergedValues.put(EDITED_STATUS, edited);
                     }
 
                     mergeFieldsAndUpdateDb(db, CARRIERS_TABLE, oldRow, row, mergedValues, false,
@@ -1865,16 +2232,16 @@
 
             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 (newRow.containsKey(EDITED_STATUS)) {
+                    int oldEdited = oldRow.getInt(oldRow.getColumnIndex(EDITED_STATUS));
+                    int newEdited = newRow.getAsInteger(EDITED_STATUS);
                     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);
+                        newRow.remove(EDITED_STATUS);
                     }
                 }
                 mergedValues.putAll(newRow);
@@ -1898,13 +2265,17 @@
             boolean match = false;
 
             // Check if APN falls under persist_apns_for_plmn
-            String[] persistApnsForPlmns = context.getResources().getStringArray(
-                    R.array.persist_apns_for_plmn);
-            for (String s : persistApnsForPlmns) {
-                if (s.equalsIgnoreCase(newRow.getAsString(NUMERIC))) {
-                    match = true;
-                    break;
+            if (context.getResources() != null) {
+                String[] persistApnsForPlmns = context.getResources().getStringArray(
+                        R.array.persist_apns_for_plmn);
+                for (String s : persistApnsForPlmns) {
+                    if (s.equalsIgnoreCase(newRow.getAsString(NUMERIC))) {
+                        match = true;
+                        break;
+                    }
                 }
+            } else {
+                loge("separateRowsNeeded: resources=null");
             }
 
             if (!match) return false;
@@ -1991,7 +2362,7 @@
 
             String[] columns = { "_id",
                     TYPE,
-                    EDITED,
+                    EDITED_STATUS,
                     BEARER_BITMASK,
                     NETWORK_TYPE_BITMASK,
                     PROFILE_ID };
@@ -1999,15 +2370,16 @@
             int i = 0;
             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);
+                if (!row.containsKey(field)) {
+                    selectionArgs[i++] = CARRIERS_UNIQUE_FIELDS_DEFAULTS.get(field);
                 } else {
-                    selectionArgs[i++] = row.containsKey(field) ?
-                            row.getAsString(field) : CARRIERS_UNIQUE_FIELDS_DEFAULTS.get(field);
+                    if (CARRIERS_BOOLEAN_FIELDS.contains(field)) {
+                        // for boolean fields we overwrite the strings "true" and "false" with "1"
+                        // and "0"
+                        selectionArgs[i++] = convertStringToIntString(row.getAsString(field));
+                    } else {
+                        selectionArgs[i++] = row.getAsString(field);
+                    }
                 }
             }
 
@@ -2037,6 +2409,24 @@
     }
 
     /**
+     * Convert "true" and "false" to "1" and "0".
+     * If the passed in string is already "1" or "0" returns the passed in string.
+     */
+    private static String convertStringToIntString(String boolString) {
+        if ("0".equals(boolString) || "false".equalsIgnoreCase(boolString)) return "0";
+        return "1";
+    }
+
+    /**
+     * Convert "1" and "0" to "true" and "false".
+     * If the passed in string is already "true" or "false" returns the passed in string.
+     */
+    private static String convertStringToBoolString(String intString) {
+        if ("0".equals(intString) || "false".equalsIgnoreCase(intString)) return "false";
+        return "true";
+    }
+
+    /**
      * These methods can be overridden in a subclass for testing TelephonyProvider using an
      * in-memory database.
      */
@@ -2072,7 +2462,7 @@
         return s_apnSourceServiceExists;
     }
 
-    private void restoreApnsWithService() {
+    private void restoreApnsWithService(int subId) {
         Context context = getContext();
         Resources r = context.getResources();
         ServiceConnection connection = new ServiceConnection() {
@@ -2100,7 +2490,8 @@
                 r.getString(R.string.apn_source_service)));
         log("binding to service to restore apns, intent=" + intent);
         try {
-            if (context.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
+            if (context.bindService(intent, connection, Context.BIND_IMPORTANT |
+                        Context.BIND_AUTO_CREATE)) {
                 synchronized (mLock) {
                     while (mIApnSourceService == null) {
                         try {
@@ -2110,7 +2501,7 @@
                         }
                     }
                     try {
-                        ContentValues[] values = mIApnSourceService.getApns();
+                        ContentValues[] values = mIApnSourceService.getApns(subId);
                         if (values != null) {
                             // we use the unsynchronized insert because this function is called
                             // within the syncrhonized function delete()
@@ -2141,46 +2532,57 @@
     public boolean onCreate() {
         mOpenHelper = new DatabaseHelper(getContext());
 
-        if (!apnSourceServiceExists(getContext())) {
-            // Call getReadableDatabase() to make sure onUpgrade is called
-            if (VDBG) log("onCreate: calling getReadableDatabase to trigger onUpgrade");
-            SQLiteDatabase db = getReadableDatabase();
+        try {
+            PhoneFactory.addLocalLog(TAG, 100);
+        } catch (IllegalArgumentException e) {
+            // ignore
+        }
 
-            // Update APN db on build update
-            String newBuildId = SystemProperties.get("ro.build.id", null);
-            if (!TextUtils.isEmpty(newBuildId)) {
-                // Check if build id has changed
-                SharedPreferences sp = getContext().getSharedPreferences(BUILD_ID_FILE,
-                        Context.MODE_PRIVATE);
-                String oldBuildId = sp.getString(RO_BUILD_ID, "");
-                if (!newBuildId.equals(oldBuildId)) {
-                    if (DBG) log("onCreate: build id changed from " + oldBuildId + " to " +
-                            newBuildId);
+        boolean isNewBuild = false;
+        String newBuildId = SystemProperties.get("ro.build.id", null);
+        if (!TextUtils.isEmpty(newBuildId)) {
+            // Check if build id has changed
+            SharedPreferences sp = getContext().getSharedPreferences(BUILD_ID_FILE,
+                    Context.MODE_PRIVATE);
+            String oldBuildId = sp.getString(RO_BUILD_ID, "");
+            if (!newBuildId.equals(oldBuildId)) {
+                localLog("onCreate: build id changed from " + oldBuildId + " to " + newBuildId);
+                isNewBuild = true;
+            } else {
+                if (VDBG) log("onCreate: build id did not change: " + oldBuildId);
+            }
+            sp.edit().putString(RO_BUILD_ID, newBuildId).apply();
+        } else {
+            if (VDBG) log("onCreate: newBuildId is empty");
+        }
 
-                    // Get rid of old preferred apn shared preferences
-                    SubscriptionManager sm = SubscriptionManager.from(getContext());
-                    if (sm != null) {
-                        List<SubscriptionInfo> subInfoList = sm.getAllSubscriptionInfoList();
-                        for (SubscriptionInfo subInfo : subInfoList) {
-                            SharedPreferences spPrefFile = getContext().getSharedPreferences(
-                                    PREF_FILE_APN + subInfo.getSubscriptionId(), Context.MODE_PRIVATE);
-                            if (spPrefFile != null) {
-                                SharedPreferences.Editor editor = spPrefFile.edit();
-                                editor.clear();
-                                editor.apply();
-                            }
+        if (isNewBuild) {
+            if (!apnSourceServiceExists(getContext())) {
+                // Call getReadableDatabase() to make sure onUpgrade is called
+                if (VDBG) log("onCreate: calling getReadableDatabase to trigger onUpgrade");
+                SQLiteDatabase db = getReadableDatabase();
+
+                // Get rid of old preferred apn shared preferences
+                SubscriptionManager sm = SubscriptionManager.from(getContext());
+                if (sm != null) {
+                    List<SubscriptionInfo> subInfoList = sm.getAllSubscriptionInfoList();
+                    for (SubscriptionInfo subInfo : subInfoList) {
+                        SharedPreferences spPrefFile = getContext().getSharedPreferences(
+                                PREF_FILE_APN + subInfo.getSubscriptionId(), Context.MODE_PRIVATE);
+                        if (spPrefFile != null) {
+                            SharedPreferences.Editor editor = spPrefFile.edit();
+                            editor.clear();
+                            editor.apply();
                         }
                     }
-
-                    // Update APN DB
-                    updateApnDb();
-                } else {
-                    if (VDBG) log("onCreate: build id did not change: " + oldBuildId);
                 }
-                sp.edit().putString(RO_BUILD_ID, newBuildId).apply();
-            } else {
-                if (VDBG) log("onCreate: newBuildId is empty");
+
+                // Update APN DB
+                updateApnDb();
             }
+
+            // Add all APN related shared prefs to local log for dumpsys
+            if (DBG) addAllApnSharedPrefToLocalLog();
         }
 
         SharedPreferences sp = getContext().getSharedPreferences(ENFORCED_FILE,
@@ -2192,6 +2594,38 @@
         return true;
     }
 
+    private void addAllApnSharedPrefToLocalLog() {
+        localLog("addAllApnSharedPrefToLocalLog");
+        SharedPreferences spApn = getContext().getSharedPreferences(PREF_FILE_APN,
+                Context.MODE_PRIVATE);
+
+        Map<String, ?> allPrefApnId = spApn.getAll();
+        for (String key : allPrefApnId.keySet()) {
+            try {
+                localLog(key + ":" + allPrefApnId.get(key).toString());
+            } catch (Exception e) {
+                localLog("Skipping over key " + key + " due to exception " + e);
+            }
+        }
+
+        SharedPreferences spFullApn = getContext().getSharedPreferences(PREF_FILE_FULL_APN,
+                Context.MODE_PRIVATE);
+
+        Map<String, ?> allPrefFullApn = spFullApn.getAll();
+        for (String key : allPrefFullApn.keySet()) {
+            try {
+                localLog(key + ":" + allPrefFullApn.get(key).toString());
+            } catch (Exception e) {
+                localLog("Skipping over key " + key + " due to exception " + e);
+            }
+        }
+    }
+
+    private static void localLog(String logMsg) {
+        Log.d(TAG, logMsg);
+        PhoneFactory.localLog(TAG, logMsg);
+    }
+
     private synchronized boolean isManagedApnEnforced() {
         return mManagedApnEnforced;
     }
@@ -2212,9 +2646,12 @@
                 Context.MODE_PRIVATE);
         SharedPreferences.Editor editor = sp.edit();
         editor.putLong(COLUMN_APN_ID + subId, id != null ? id : INVALID_APN_ID);
+        localLog("setPreferredApnId: " + COLUMN_APN_ID + subId + ":"
+                + (id != null ? id : INVALID_APN_ID));
         // This is for debug purposes. It indicates if this APN was set by DcTracker or user (true)
         // or if this was restored from APN saved in PREF_FILE_FULL_APN (false).
         editor.putBoolean(EXPLICIT_SET_CALLED + subId, saveApn);
+        localLog("setPreferredApnId: " + EXPLICIT_SET_CALLED + subId + ":" + saveApn);
         editor.apply();
         if (id == null || id.longValue() == INVALID_APN_ID) {
             deletePreferredApn(subId);
@@ -2246,7 +2683,7 @@
         try {
             return Integer.parseInt(sp.getString(APN_SET_ID + subId, null));
         } catch (NumberFormatException e) {
-            return NO_SET_SET;
+            return NO_APN_SET_ID;
         }
     }
 
@@ -2279,7 +2716,7 @@
     }
 
     private void setPreferredApn(Long id, int subId) {
-        log("setPreferredApn: _id " + id + " subId " + subId);
+        localLog("setPreferredApn: _id " + id + " subId " + subId);
         SQLiteDatabase db = getWritableDatabase();
         // query all unique fields from id
         String[] proj = CARRIERS_UNIQUE_FIELDS.toArray(new String[CARRIERS_UNIQUE_FIELDS.size()]);
@@ -2294,9 +2731,12 @@
                 // store values of all unique fields to SP
                 for (String key : CARRIERS_UNIQUE_FIELDS) {
                     editor.putString(key + subId, c.getString(c.getColumnIndex(key)));
+                    localLog("setPreferredApn: " + key + subId + ":"
+                            + c.getString(c.getColumnIndex(key)));
                 }
                 // also store the version number
                 editor.putString(DB_VERSION_KEY + subId, "" + DATABASE_VERSION);
+                localLog("setPreferredApn: " + DB_VERSION_KEY + subId + ":" + DATABASE_VERSION);
                 editor.apply();
             } else {
                 log("setPreferredApn: # matching APNs found " + c.getCount());
@@ -2309,7 +2749,7 @@
 
     private long getPreferredApnIdFromApn(int subId) {
         log("getPreferredApnIdFromApn: for subId " + subId);
-        SQLiteDatabase db = getWritableDatabase();
+        SQLiteDatabase db = getReadableDatabase();
         String where = TextUtils.join("=? and ", CARRIERS_UNIQUE_FIELDS) + "=?";
         String[] whereArgs = new String[CARRIERS_UNIQUE_FIELDS.size()];
         SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE_FULL_APN,
@@ -2370,7 +2810,7 @@
     public synchronized Cursor query(Uri url, String[] projectionIn, String selection,
             String[] selectionArgs, String sort) {
         if (VDBG) log("query: url=" + url + ", projectionIn=" + projectionIn + ", selection="
-            + selection + "selectionArgs=" + selectionArgs + ", sort=" + sort);
+                + selection + "selectionArgs=" + selectionArgs + ", sort=" + sort);
         TelephonyManager mTelephonyManager =
                 (TelephonyManager)getContext().getSystemService(Context.TELEPHONY_SERVICE);
         int subId = SubscriptionManager.getDefaultSubscriptionId();
@@ -2382,6 +2822,7 @@
         List<String> constraints = new ArrayList<String>();
 
         int match = s_urlMatcher.match(url);
+        checkQueryPermission(match, projectionIn, selection, sort);
         switch (match) {
             case URL_TELEPHONY_USING_SUBID: {
                 subIdString = url.getLastPathSegment();
@@ -2464,7 +2905,7 @@
             // intentional fall through from above case
             case URL_PREFERAPNSET: {
                 final int set = getPreferredApnSetId(subId);
-                if (set != NO_SET_SET) {
+                if (set != NO_APN_SET_ID) {
                     constraints.add(APN_SET_ID + "=" + set);
                 }
                 break;
@@ -2477,12 +2918,25 @@
                 break;
             }
 
-            case URL_FILTERED_ID: {
-                constraints.add("_id = " + url.getLastPathSegment());
+            case URL_FILTERED_ID:
+            case URL_FILTERED_USING_SUBID: {
+                String idString = url.getLastPathSegment();
+                if (match == URL_FILTERED_ID) {
+                    constraints.add("_id = " + idString);
+                } else {
+                    try {
+                        subId = Integer.parseInt(idString);
+                        // TODO b/74213956 turn this back on once insertion includes correct sub id
+                        // constraints.add(SUBSCRIPTION_ID + "=" + subIdString);
+                    } catch (NumberFormatException e) {
+                        loge("NumberFormatException" + e);
+                        return null;
+                    }
+                }
             }
             //intentional fall through from above case
             case URL_FILTERED: {
-                if(isManagedApnEnforced()) {
+                if (isManagedApnEnforced()) {
                     // If enforced, return DPC records only.
                     constraints.add(IS_OWNED_BY_DPC);
                 } else {
@@ -2504,6 +2958,43 @@
                 qb.setTables(SIMINFO_TABLE);
                 break;
             }
+            case URL_SIM_APN_LIST_ID: {
+                subIdString = url.getLastPathSegment();
+                try {
+                    subId = Integer.parseInt(subIdString);
+                } catch (NumberFormatException e) {
+                    loge("NumberFormatException" + e);
+                    return null;
+                }
+            }
+            //intentional fall through from above case
+            case URL_SIM_APN_LIST: {
+                qb.appendWhere(IS_NOT_OWNED_BY_DPC);
+                return getSubscriptionMatchingAPNList(qb, projectionIn, selection, selectionArgs,
+                        sort, subId);
+            }
+
+            case URL_SIM_APN_LIST_FILTERED_ID: {
+                subIdString = url.getLastPathSegment();
+                try {
+                    subId = Integer.parseInt(subIdString);
+                } catch (NumberFormatException e) {
+                    loge("NumberFormatException" + e);
+                    return null;
+                }
+            }
+            //intentional fall through from above case
+            case URL_SIM_APN_LIST_FILTERED: {
+                if (isManagedApnEnforced()) {
+                    // If enforced, return DPC records only.
+                    qb.appendWhereStandalone(IS_OWNED_BY_DPC);
+                } else {
+                    // Otherwise return non-DPC records only.
+                    qb.appendWhereStandalone(IS_NOT_OWNED_BY_DPC);
+                }
+                return getSubscriptionMatchingAPNList(qb, projectionIn, selection, selectionArgs,
+                    sort, subId);
+            }
 
             default: {
                 return null;
@@ -2515,48 +3006,6 @@
             qb.appendWhere(TextUtils.join(" AND ", constraints));
         }
 
-        // Determine if we need to do a check for fields in the selection
-        boolean selectionOrSortContainsSensitiveFields;
-        try {
-            selectionOrSortContainsSensitiveFields = containsSensitiveFields(selection);
-            selectionOrSortContainsSensitiveFields |= containsSensitiveFields(sort);
-        } catch (Exception e) {
-            // Malformed sql, check permission anyway.
-            selectionOrSortContainsSensitiveFields = true;
-        }
-
-        if (selectionOrSortContainsSensitiveFields) {
-            try {
-                checkPermission();
-            } catch (SecurityException e) {
-                EventLog.writeEvent(0x534e4554, "124107808", Binder.getCallingUid());
-                throw e;
-            }
-        }
-
-        if (match != URL_SIMINFO) {
-            if (projectionIn != null) {
-                for (String column : projectionIn) {
-                    if (TYPE.equals(column) ||
-                            MMSC.equals(column) ||
-                            MMSPROXY.equals(column) ||
-                            MMSPORT.equals(column) ||
-                            APN.equals(column)) {
-                        // noop
-                    } else {
-                        checkPermission();
-                        break;
-                    }
-                }
-            } else {
-                // null returns all columns, so need permission check
-                checkPermission();
-            }
-        } else {
-            // For the sim_info table, we only require READ_PHONE_STATE
-            checkReadSimInfoPermission();
-        }
-
         SQLiteDatabase db = getReadableDatabase();
         Cursor ret = null;
         try {
@@ -2582,6 +3031,53 @@
         return ret;
     }
 
+    private void checkQueryPermission(int match, String[] projectionIn, String selection,
+            String sort) {
+        // Determine if we need to do a check for fields in the selection
+        boolean selectionOrSortContainsSensitiveFields;
+        try {
+            selectionOrSortContainsSensitiveFields = containsSensitiveFields(selection);
+            selectionOrSortContainsSensitiveFields |= containsSensitiveFields(sort);
+        } catch (IllegalArgumentException e) {
+            // Malformed sql, check permission anyway and return.
+            checkPermission();
+            return;
+        }
+
+        if (selectionOrSortContainsSensitiveFields) {
+            try {
+                checkPermission();
+            } catch (SecurityException e) {
+                EventLog.writeEvent(0x534e4554, "124107808", Binder.getCallingUid());
+                throw e;
+            }
+        }
+
+        if (match != URL_SIMINFO && match != URL_SIMINFO_USING_SUBID) {
+            if (projectionIn != null) {
+                for (String column : projectionIn) {
+                    if (TYPE.equals(column) ||
+                            MMSC.equals(column) ||
+                            MMSPROXY.equals(column) ||
+                            MMSPORT.equals(column) ||
+                            MVNO_TYPE.equals(column) ||
+                            MVNO_MATCH_DATA.equals(column) ||
+                            APN.equals(column)) {
+                    } else {
+                        checkPermission();
+                        break;
+                    }
+                }
+            } else {
+                // null returns all columns, so need permission check
+                checkPermission();
+            }
+        } else {
+            // if querying siminfo, caller should have read privilege permissions
+            checkPhonePrivilegePermission();
+        }
+    }
+
     private boolean containsSensitiveFields(String sqlStatement) {
         try {
             SqlTokenFinder.findTokens(sqlStatement, s -> {
@@ -2598,6 +3094,91 @@
         return false;
     }
 
+    /**
+     * To find the current sim APN. Query APN based on {MCC, MNC, MVNO} to support backward
+     * compatibility but will move to carrier id in the future.
+     *
+     * There has three steps:
+     * 1. Query the APN based on { MCC, MNC, MVNO }.
+     * 2. If can't find the current APN, then query the parent APN. Query based on { MCC, MNC }.
+     * 3. else return empty cursor
+     *
+     */
+    private Cursor getSubscriptionMatchingAPNList(SQLiteQueryBuilder qb, String[] projectionIn,
+            String selection, String[] selectionArgs, String sort, int subId) {
+        Cursor ret;
+        final TelephonyManager tm = ((TelephonyManager)
+                getContext().getSystemService(Context.TELEPHONY_SERVICE))
+                .createForSubscriptionId(subId);
+        SQLiteDatabase db = getReadableDatabase();
+        String mccmnc = tm.getSimOperator();
+
+        qb.appendWhereStandalone(IS_NOT_USER_DELETED + " and " +
+                IS_NOT_USER_DELETED_BUT_PRESENT_IN_XML + " and " +
+                IS_NOT_CARRIER_DELETED + " and " +
+                IS_NOT_CARRIER_DELETED_BUT_PRESENT_IN_XML);
+
+        // For query db one time, append step 1 and step 2 condition in one selection and
+        // separate results after the query is completed. Because IMSI has special match rule,
+        // so just query the MCC / MNC and filter the MVNO by ourselves
+        qb.appendWhereStandalone(NUMERIC + " = '" + mccmnc + "' ");
+
+        ret = qb.query(db, null, selection, selectionArgs, null, null, sort);
+        if (ret == null) {
+            loge("query current APN but cursor is null.");
+            return null;
+        }
+
+        if (DBG) log("match current APN size:  " + ret.getCount());
+
+        String[] coulmnNames = projectionIn != null ? projectionIn : ret.getColumnNames();
+        MatrixCursor currentCursor = new MatrixCursor(coulmnNames);
+        MatrixCursor parentCursor = new MatrixCursor(coulmnNames);
+
+        int numericIndex = ret.getColumnIndex(NUMERIC);
+        int mvnoIndex = ret.getColumnIndex(MVNO_TYPE);
+        int mvnoDataIndex = ret.getColumnIndex(MVNO_MATCH_DATA);
+
+        IccRecords iccRecords = UiccController.getInstance().getIccRecords(
+                SubscriptionManager.getPhoneId(subId), UiccController.APP_FAM_3GPP);
+        if (iccRecords == null) {
+            loge("iccRecords is null");
+            return null;
+        }
+
+        //Separate the result into MatrixCursor
+        while (ret.moveToNext()) {
+            List<String> data = new ArrayList<>();
+            for (String column : coulmnNames) {
+                data.add(ret.getString(ret.getColumnIndex(column)));
+            }
+
+            if (!TextUtils.isEmpty(ret.getString(numericIndex)) &&
+                    ApnSettingUtils.mvnoMatches(iccRecords,
+                            ApnSetting.getMvnoTypeIntFromString(ret.getString(mvnoIndex)),
+                            ret.getString(mvnoDataIndex))) {
+                // 1. APN query result based on legacy SIM MCC/MCC and MVNO
+                currentCursor.addRow(data);
+            } else if (!TextUtils.isEmpty(ret.getString(numericIndex)) &&
+                    TextUtils.isEmpty(ret.getString(mvnoIndex))) {
+                // 2. APN query result based on SIM MCC/MNC
+                parentCursor.addRow(data);
+            }
+        }
+        ret.close();
+
+        if (currentCursor.getCount() > 0) {
+            if (DBG) log("match MVNO APN: " + currentCursor.getCount());
+            return currentCursor;
+        } else if (parentCursor.getCount() > 0) {
+            if (DBG) log("match MNO APN: " + parentCursor.getCount());
+            return parentCursor;
+        } else {
+            if (DBG) log("APN no match");
+            return new MatrixCursor(coulmnNames);
+        }
+    }
+
     @Override
     public String getType(Uri url)
     {
@@ -2608,6 +3189,7 @@
 
         case URL_ID:
         case URL_FILTERED_ID:
+        case URL_FILTERED_USING_SUBID:
             return "vnd.android.cursor.item/telephony-carrier";
 
         case URL_PREFERAPN_USING_SUBID:
@@ -2736,8 +3318,8 @@
                 }
 
                 values = DatabaseHelper.setDefaultValue(values);
-                if (!values.containsKey(EDITED)) {
-                    values.put(EDITED, CARRIER_EDITED);
+                if (!values.containsKey(EDITED_STATUS)) {
+                    values.put(EDITED_STATUS, CARRIER_EDITED);
                 }
                 // Owned_by should be others if inserted via general uri.
                 values.put(OWNED_BY, OWNED_BY_OTHERS);
@@ -2855,7 +3437,7 @@
                 IS_NOT_CARRIER_EDITED + ")";
         String unedited = ") and " + IS_UNEDITED;
         ContentValues cv = new ContentValues();
-        cv.put(EDITED, USER_DELETED);
+        cv.put(EDITED_STATUS, USER_DELETED);
 
         checkPermission();
 
@@ -3042,8 +3624,8 @@
 
             case URL_TELEPHONY:
             {
-                if (!values.containsKey(EDITED)) {
-                    values.put(EDITED, CARRIER_EDITED);
+                if (!values.containsKey(EDITED_STATUS)) {
+                    values.put(EDITED_STATUS, CARRIER_EDITED);
                 }
 
                 // Replace on conflict so that if same APN is present in db with edited
@@ -3071,8 +3653,8 @@
 
             case URL_CURRENT:
             {
-                if (!values.containsKey(EDITED)) {
-                    values.put(EDITED, CARRIER_EDITED);
+                if (!values.containsKey(EDITED_STATUS)) {
+                    values.put(EDITED_STATUS, CARRIER_EDITED);
                 }
                 // Replace on conflict so that if same APN is present in db with edited
                 // as UNEDITED or USER/CARRIER_DELETED, it is replaced with
@@ -3090,8 +3672,8 @@
                     throw new UnsupportedOperationException(
                             "Cannot update URL " + url + " with a where clause");
                 }
-                if (!values.containsKey(EDITED)) {
-                    values.put(EDITED, CARRIER_EDITED);
+                if (!values.containsKey(EDITED_STATUS)) {
+                    values.put(EDITED_STATUS, CARRIER_EDITED);
                 }
 
                 try {
@@ -3169,6 +3751,24 @@
                 break;
             }
 
+            case URL_SIMINFO_USING_SUBID:
+                String subIdString = url.getLastPathSegment();
+                try {
+                    subId = Integer.parseInt(subIdString);
+                } catch (NumberFormatException e) {
+                    loge("NumberFormatException" + e);
+                    throw new IllegalArgumentException("Invalid subId " + url);
+                }
+                if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
+                if (where != null || whereArgs != null) {
+                    throw new UnsupportedOperationException(
+                            "Cannot update URL " + url + " with a where clause");
+                }
+                count = db.update(SIMINFO_TABLE, values, _ID + "=?",
+                        new String[] { subIdString});
+                uriType = URL_SIMINFO_USING_SUBID;
+                break;
+
             case URL_SIMINFO: {
                 count = db.update(SIMINFO_TABLE, values, where, whereArgs);
                 uriType = URL_SIMINFO;
@@ -3181,10 +3781,52 @@
         }
 
         if (count > 0) {
+            boolean usingSubId = false;
             switch (uriType) {
+                case URL_SIMINFO_USING_SUBID:
+                    usingSubId = true;
+                    // intentional fall through from above case
                 case URL_SIMINFO:
+                    // skip notifying descendant URLs to avoid unneccessary wake up.
+                    // If not set, any change to SIMINFO will notify observers which listens to
+                    // specific field of SIMINFO.
                     getContext().getContentResolver().notifyChange(
-                            SubscriptionManager.CONTENT_URI, null, true, UserHandle.USER_ALL);
+                            SubscriptionManager.CONTENT_URI, null,
+                            ContentResolver.NOTIFY_SYNC_TO_NETWORK
+                                    | ContentResolver.NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS,
+                            UserHandle.USER_ALL);
+                    // notify observers on specific user settings changes.
+                    if (values.containsKey(SubscriptionManager.WFC_IMS_ENABLED)) {
+                        getContext().getContentResolver().notifyChange(
+                                getNotifyContentUri(SubscriptionManager.WFC_ENABLED_CONTENT_URI,
+                                        usingSubId, subId), null, true, UserHandle.USER_ALL);
+                    }
+                    if (values.containsKey(SubscriptionManager.ENHANCED_4G_MODE_ENABLED)) {
+                        getContext().getContentResolver().notifyChange(
+                                getNotifyContentUri(SubscriptionManager
+                                                .ADVANCED_CALLING_ENABLED_CONTENT_URI,
+                                        usingSubId, subId), null, true, UserHandle.USER_ALL);
+                    }
+                    if (values.containsKey(SubscriptionManager.VT_IMS_ENABLED)) {
+                        getContext().getContentResolver().notifyChange(
+                                getNotifyContentUri(SubscriptionManager.VT_ENABLED_CONTENT_URI,
+                                        usingSubId, subId), null, true, UserHandle.USER_ALL);
+                    }
+                    if (values.containsKey(SubscriptionManager.WFC_IMS_MODE)) {
+                        getContext().getContentResolver().notifyChange(
+                                getNotifyContentUri(SubscriptionManager.WFC_MODE_CONTENT_URI,
+                                        usingSubId, subId), null, true, UserHandle.USER_ALL);
+                    }
+                    if (values.containsKey(SubscriptionManager.WFC_IMS_ROAMING_MODE)) {
+                        getContext().getContentResolver().notifyChange(getNotifyContentUri(
+                                SubscriptionManager.WFC_ROAMING_MODE_CONTENT_URI,
+                                usingSubId, subId), null, true, UserHandle.USER_ALL);
+                    }
+                    if (values.containsKey(SubscriptionManager.WFC_IMS_ROAMING_ENABLED)) {
+                        getContext().getContentResolver().notifyChange(getNotifyContentUri(
+                                SubscriptionManager.WFC_ROAMING_ENABLED_CONTENT_URI,
+                                usingSubId, subId), null, true, UserHandle.USER_ALL);
+                    }
                     break;
                 default:
                     getContext().getContentResolver().notifyChange(
@@ -3195,6 +3837,10 @@
         return count;
     }
 
+    private static Uri getNotifyContentUri(Uri uri, boolean usingSubId, int subId) {
+        return (usingSubId) ? Uri.withAppendedPath(uri, "" + subId) : uri;
+    }
+
     private void checkPermission() {
         int status = getContext().checkCallingOrSelfPermission(
                 "android.permission.WRITE_APN_SETTINGS");
@@ -3208,7 +3854,7 @@
         TelephonyManager telephonyManager =
                 (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
         for (String pkg : packages) {
-            if (telephonyManager.checkCarrierPrivilegesForPackage(pkg) ==
+            if (telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(pkg) ==
                     TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
                 return;
             }
@@ -3216,21 +3862,13 @@
         throw new SecurityException("No permission to write APN settings");
     }
 
-    private void checkReadSimInfoPermission() {
-        try {
-            // Even if the caller doesn't have READ_PHONE_STATE, we'll let them access sim_info as
-            // long as they have the more restrictive write_apn_settings or carrier priv.
-            checkPermission();
+    private void checkPhonePrivilegePermission() {
+        int status = getContext().checkCallingOrSelfPermission(
+                "android.permission.READ_PRIVILEGED_PHONE_STATE");
+        if (status == PackageManager.PERMISSION_GRANTED) {
             return;
-        } catch (SecurityException e) {
-            int status = getContext().checkCallingOrSelfPermission(
-                    "android.permission.READ_PHONE_STATE");
-            if (status == PackageManager.PERMISSION_GRANTED) {
-                return;
-            }
-            EventLog.writeEvent(0x534e4554, "124107808", Binder.getCallingUid());
-            throw new SecurityException("No READ_PHONE_STATE permission");
         }
+        throw new SecurityException("No phone privilege permission");
     }
 
     private DatabaseHelper mOpenHelper;
@@ -3269,7 +3907,7 @@
         editorApn.apply();
 
         if (apnSourceServiceExists(getContext())) {
-            restoreApnsWithService();
+            restoreApnsWithService(subId);
         } else {
             initDatabaseWithDatabaseHelper(db);
         }
@@ -3293,7 +3931,8 @@
                 String mvnoType = cursor.getString(0 /* MVNO_TYPE index */);
                 String mvnoMatchData = cursor.getString(1 /* MVNO_MATCH_DATA index */);
                 if (!TextUtils.isEmpty(mvnoType) && !TextUtils.isEmpty(mvnoMatchData)
-                        && ApnSetting.mvnoMatches(iccRecords, mvnoType, mvnoMatchData)) {
+                        && ApnSettingUtils.mvnoMatches(iccRecords,
+                        ApnSetting.getMvnoTypeIntFromString(mvnoType), mvnoMatchData)) {
                     where = NUMERIC + "='" + simOperator + "'"
                             + " AND " + MVNO_TYPE + "='" + mvnoType + "'"
                             + " AND " + MVNO_MATCH_DATA + "='" + mvnoMatchData + "'"
@@ -3349,12 +3988,60 @@
 
         initDatabaseWithDatabaseHelper(db);
 
-        // Notify listereners of DB change since DB has been updated
+        // Notify listeners of DB change since DB has been updated
         getContext().getContentResolver().notifyChange(
                 CONTENT_URI, null, true, UserHandle.USER_ALL);
 
     }
 
+    public static void fillInMccMncStringAtCursor(Context context, SQLiteDatabase db, Cursor c) {
+        int mcc, mnc;
+        String subId;
+        try {
+            mcc = c.getInt(c.getColumnIndexOrThrow(SubscriptionManager.MCC));
+            mnc = c.getInt(c.getColumnIndexOrThrow(SubscriptionManager.MNC));
+            subId = c.getString(c.getColumnIndexOrThrow(
+                    SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID));
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Possible database corruption -- some columns not found.");
+            return;
+        }
+
+        String mccString = String.format(Locale.getDefault(), "%03d", mcc);
+        String mncString = getBestStringMnc(context, mccString, mnc);
+        ContentValues cv = new ContentValues(2);
+        cv.put(SubscriptionManager.MCC_STRING, mccString);
+        cv.put(SubscriptionManager.MNC_STRING, mncString);
+        db.update(SIMINFO_TABLE, cv,
+                SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=?",
+                new String[]{subId});
+    }
+
+    /*
+     * Find the best string-form mnc by looking up possibilities in the carrier id db.
+     * Default to the three-digit version if neither/both are valid.
+     */
+    private static String getBestStringMnc(Context context, String mcc, int mnc) {
+        if (mnc >= 100 && mnc <= 999) {
+            return String.valueOf(mnc);
+        }
+        String twoDigitMnc = String.format(Locale.getDefault(), "%02d", mnc);
+        String threeDigitMnc = "0" + twoDigitMnc;
+
+        try (
+                Cursor twoDigitMncCursor = context.getContentResolver().query(
+                        Telephony.CarrierId.All.CONTENT_URI,
+                        /* projection */ null,
+                        /* selection */ Telephony.CarrierId.All.MCCMNC + "=?",
+                        /* selectionArgs */ new String[]{mcc + twoDigitMnc}, null)
+        ) {
+            if (twoDigitMncCursor.getCount() > 0) {
+                return twoDigitMnc;
+            }
+            return threeDigitMnc;
+        }
+    }
+
     /**
      * Sync the bearer bitmask and network type bitmask when inserting and updating.
      * Since bearerBitmask is deprecating, map the networkTypeBitmask to bearerBitmask if
diff --git a/tests/Android.bp b/tests/Android.bp
new file mode 100644
index 0000000..f1be84b
--- /dev/null
+++ b/tests/Android.bp
@@ -0,0 +1,20 @@
+android_test {
+    name: "TelephonyProviderTests",
+    static_libs: [
+        "mockito-target",
+        "compatibility-device-util-axt",
+        "androidx.test.rules",
+        "truth-prebuilt",
+    ],
+    libs: [
+        "android.test.runner",
+        "telephony-common",
+        "android.test.base",
+        "android.test.mock",
+    ],
+    srcs: ["src/**/*.java"],
+    platform_apis: true,
+    test_suites: ["device-tests"],
+    certificate: "platform",
+    instrumentation_for: "TelephonyProvider",
+}
diff --git a/tests/Android.mk b/tests/Android.mk
deleted file mode 100644
index 14f528f..0000000
--- a/tests/Android.mk
+++ /dev/null
@@ -1,26 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_STATIC_JAVA_LIBRARIES := mockito-target \
-                               compatibility-device-util \
-                               android-support-test
-
-LOCAL_JAVA_LIBRARIES := \
-    android.test.runner \
-    telephony-common \
-    android.test.base \
-    android.test.mock \
-
-
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-
-LOCAL_PACKAGE_NAME := TelephonyProviderTests
-LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
-
-LOCAL_INSTRUMENTATION_FOR := TelephonyProvider
-
-include $(BUILD_PACKAGE)
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 91e8953..59fd8bd 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -21,7 +21,7 @@
         <uses-library android:name="android.test.runner" />
     </application>
 
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
         android:targetPackage="com.android.providers.telephony"
         android:label="Tests for TelephonyProvider">
     </instrumentation>
diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml
index 129f4d9..23310f9 100644
--- a/tests/AndroidTest.xml
+++ b/tests/AndroidTest.xml
@@ -22,7 +22,7 @@
     <option name="test-tag" value="TelephonyProviderTests" />
     <test class="com.android.tradefed.testtype.InstrumentationTest" >
         <option name="package" value="com.android.providers.telephony.tests" />
-        <option name="runner" value="android.test.InstrumentationTestRunner" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="hidden-api-checks" value="false"/>
     </test>
 </configuration>
diff --git a/tests/src/com/android/providers/telephony/CarrierIdProviderTest.java b/tests/src/com/android/providers/telephony/CarrierIdProviderTest.java
index 4adcf43..bc39260 100644
--- a/tests/src/com/android/providers/telephony/CarrierIdProviderTest.java
+++ b/tests/src/com/android/providers/telephony/CarrierIdProviderTest.java
@@ -68,6 +68,8 @@
     private static final String dummy_apn = "APN_DUMMY";
     private static final String dummy_iccid_prefix = "ICCID_PREFIX_DUMMY";
     private static final String dummy_name = "NAME_DUMMY";
+    private static final String dummy_access_rule =
+            "B9CFCE1C47A6AC713442718F15EF55B00B3A6D1A6D48CB46249FA8EB51465350";
     private static final int dummy_cid = 0;
 
     private MockContextWithProvider mContext;
@@ -104,14 +106,10 @@
     private class MockContextWithProvider extends MockContext {
         private final MockContentResolver mResolver;
 
-        public MockContextWithProvider(CarrierIdProvider carrierIdProvider) {
+        public MockContextWithProvider(CarrierIdProviderTestable carrierIdProvider) {
             mResolver = new FakeContentResolver();
 
-            ProviderInfo providerInfo = new ProviderInfo();
-            providerInfo.authority = CarrierIdProvider.AUTHORITY;
-
-            // Add context to given carrierIdProvider
-            carrierIdProvider.attachInfoForTesting(this, providerInfo);
+            carrierIdProvider.initializeForTesting(this);
             Log.d(TAG, "MockContextWithProvider: carrierIdProvider.getContext(): "
                     + carrierIdProvider.getContext());
 
@@ -150,6 +148,9 @@
         mContext = new MockContextWithProvider(mCarrierIdProviderTestable);
         mContentResolver = mContext.getContentResolver();
         mContentObserver = new FakeContentObserver(null);
+
+        doReturn("").when(mSubController).getDataEnabledOverrideRules(anyInt());
+
         Field field = SubscriptionController.class.getDeclaredField("sInstance");
         field.setAccessible(true);
         field.set(null, mSubController);
@@ -437,6 +438,7 @@
         contentValues.put(CarrierId.All.ICCID_PREFIX, dummy_iccid_prefix);
         contentValues.put(CarrierId.CARRIER_NAME, dummy_name);
         contentValues.put(CarrierId.CARRIER_ID, dummy_cid);
+        contentValues.put(CarrierId.All.PRIVILEGE_ACCESS_RULE, dummy_access_rule);
         return contentValues;
     }
 }
diff --git a/tests/src/com/android/providers/telephony/CarrierIdProviderTestable.java b/tests/src/com/android/providers/telephony/CarrierIdProviderTestable.java
index 8468801..9ecd93a 100644
--- a/tests/src/com/android/providers/telephony/CarrierIdProviderTestable.java
+++ b/tests/src/com/android/providers/telephony/CarrierIdProviderTestable.java
@@ -15,6 +15,8 @@
  */
 package com.android.providers.telephony;
 
+import android.content.Context;
+import android.content.pm.ProviderInfo;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.util.Log;
@@ -51,6 +53,14 @@
         return mDbHelper.getWritableDatabase();
     }
 
+    void initializeForTesting(Context context) {
+        ProviderInfo providerInfo = new ProviderInfo();
+        providerInfo.authority = CarrierIdProvider.AUTHORITY;
+
+        // Add context to given carrierIdProvider
+        attachInfoForTesting(context, providerInfo);
+    }
+
     /**
      * An in memory DB for CarrierIdProviderTestable to use
      */
diff --git a/tests/src/com/android/providers/telephony/RcsProviderDeleteTest.java b/tests/src/com/android/providers/telephony/RcsProviderDeleteTest.java
new file mode 100644
index 0000000..c0cef0a
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/RcsProviderDeleteTest.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.providers.telephony;
+
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.GROUP_NAME_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.OWNER_PARTICIPANT_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.CANONICAL_ADDRESS_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_ALIAS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantEventColumns.NEW_ALIAS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.NEW_NAME_COLUMN;
+
+import static com.android.providers.telephony.RcsProvider.RCS_MESSAGE_TABLE;
+import static com.android.providers.telephony.RcsProviderHelper.setup1To1Thread;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.test.mock.MockContentResolver;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class RcsProviderDeleteTest {
+    private MockContentResolver mContentResolver;
+    private RcsProviderTestable mRcsProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mRcsProvider = new RcsProviderTestable();
+        RcsProviderTestable.MockContextWithProvider
+                context = new RcsProviderTestable.MockContextWithProvider(mRcsProvider);
+        mContentResolver = context.getContentResolver();
+
+        // insert a participant
+        //  first into the MmsSmsProvider
+        mRcsProvider.getWritableDatabase().execSQL(
+                "INSERT INTO canonical_addresses VALUES (1, \"+15551234567\")");
+
+        //  then into the RcsProvider
+        ContentValues participantValues = new ContentValues();
+        participantValues.put(RCS_ALIAS_COLUMN, "Bob");
+        participantValues.put(CANONICAL_ADDRESS_ID_COLUMN, 1);
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/participant"),
+                participantValues)).isEqualTo(Uri.parse("content://rcs/participant/1"));
+
+        setup1To1Thread(mContentResolver);
+
+        // insert one group thread
+        ContentValues groupContentValues = new ContentValues();
+        groupContentValues.put(OWNER_PARTICIPANT_COLUMN, 1);
+        groupContentValues.put(GROUP_NAME_COLUMN, "name");
+        Uri groupThreadUri = Uri.parse("content://rcs/group_thread");
+        assertThat(mContentResolver.insert(groupThreadUri, groupContentValues)).isEqualTo(
+                Uri.parse("content://rcs/group_thread/2"));
+
+        Uri addParticipantToGroupThread = Uri.parse("content://rcs/group_thread/2/participant/1");
+        assertThat(mContentResolver.insert(addParticipantToGroupThread, null)).isEqualTo(
+                addParticipantToGroupThread);
+
+        // add incoming and outgoing messages to both threads
+        ContentValues messageValues = new ContentValues();
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/1/incoming_message"),
+                messageValues)).isEqualTo(
+                Uri.parse("content://rcs/p2p_thread/1/incoming_message/1"));
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/1/outgoing_message"),
+                messageValues)).isEqualTo(
+                Uri.parse("content://rcs/p2p_thread/1/outgoing_message/2"));
+        assertThat(
+                mContentResolver.insert(Uri.parse("content://rcs/group_thread/2/incoming_message"),
+                        messageValues)).isEqualTo(
+                Uri.parse("content://rcs/group_thread/2/incoming_message/3"));
+        assertThat(
+                mContentResolver.insert(Uri.parse("content://rcs/group_thread/2/outgoing_message"),
+                        messageValues)).isEqualTo(
+                Uri.parse("content://rcs/group_thread/2/outgoing_message/4"));
+
+        // add a file transfer to a message
+        ContentValues fileTransferValues = new ContentValues();
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/message/3/file_transfer"),
+                fileTransferValues)).isEqualTo(Uri.parse("content://rcs/file_transfer/1"));
+
+        // insert an alias change event
+        ContentValues eventValues = new ContentValues();
+        eventValues.put(NEW_ALIAS_COLUMN, "new alias");
+        assertThat(
+                mContentResolver.insert(Uri.parse("content://rcs/participant/1/alias_change_event"),
+                        eventValues)).isEqualTo(Uri.parse(
+                "content://rcs/participant/1/alias_change_event/1"));
+
+        // create a group name change event
+        eventValues.clear();
+        eventValues.put(NEW_NAME_COLUMN, "new name");
+        assertThat(mContentResolver.insert(
+                Uri.parse("content://rcs/group_thread/2/name_changed_event"),
+                eventValues)).isEqualTo(Uri.parse(
+                "content://rcs/group_thread/2/name_changed_event/1"));
+    }
+
+    @After
+    public void tearDown() {
+        mRcsProvider.tearDown();
+    }
+
+    @Test
+    public void testDelete1To1ThreadWithId() {
+        assertThat(mContentResolver.delete(Uri.parse("content://rcs/p2p_thread/1"), null,
+                null)).isEqualTo(1);
+        assertDeletionViaQuery("content://rcs/p2p_thread/1");
+    }
+
+    @Test
+    public void testDelete1To1ThreadWithSelection() {
+        assertThat(mContentResolver.delete(Uri.parse("content://rcs/p2p_thread"),
+                "rcs_fallback_thread_id=1", null)).isEqualTo(1);
+        assertDeletionViaQuery("content://rcs/p2p_thread/1");
+    }
+
+    @Test
+    public void testDeleteGroupThreadWithId() {
+        assertThat(mContentResolver.delete(Uri.parse("content://rcs/group_thread/2"), null,
+                null)).isEqualTo(1);
+        assertDeletionViaQuery("content://rcs/group_thread/2");
+    }
+
+    @Test
+    public void testDeleteGroupThreadWithSelection() {
+        assertThat(mContentResolver.delete(Uri.parse("content://rcs/group_thread"),
+                "group_name=\"name\"", null)).isEqualTo(1);
+        assertDeletionViaQuery("content://rcs/group_thread/1");
+    }
+
+    @Test
+    public void testDeleteParticipantWithIdWhileParticipatingInAThread() {
+        assertThat(mContentResolver.delete(Uri.parse("content://rcs/participant/1"), null,
+                null)).isEqualTo(0);
+    }
+
+    @Test
+    public void testDeleteParticipantAfterLeavingThreads() {
+        // leave the first thread
+        assertThat(mContentResolver.delete(Uri.parse("content://rcs/group_thread/2/participant/1"),
+                null, null)).isEqualTo(1);
+
+        // try deleting the participant. It should fail
+        assertThat(mContentResolver.delete(Uri.parse("content://rcs/participant/1"), null,
+                null)).isEqualTo(0);
+
+        // delete the p2p thread
+        assertThat(mContentResolver.delete(Uri.parse("content://rcs/p2p_thread/1"), null,
+                null)).isEqualTo(1);
+
+        // try deleting the participant. It should succeed
+        assertThat(mContentResolver.delete(Uri.parse("content://rcs/participant/1"), null,
+                null)).isEqualTo(1);
+        assertDeletionViaQuery("content://rcs/participant/1");
+    }
+
+    @Test
+    public void testDeleteParticipantWithSelectionFails() {
+        assertThat(
+                mContentResolver.delete(Uri.parse("content://rcs/participant"), "rcs_alias=\"Bob\"",
+                        null)).isEqualTo(0);
+    }
+
+    @Test
+    public void testDeleteParticipantFrom1To1ThreadFails() {
+        assertThat(
+                mContentResolver.delete(Uri.parse("content://rcs/p2p_thread/1/participant/1"), null,
+                        null)).isEqualTo(0);
+    }
+
+    @Test
+    public void testDeleteParticipantFromGroupThread() {
+        assertThat(mContentResolver.delete(Uri.parse("content://rcs/group_thread/2/participant/1"),
+                null, null)).isEqualTo(1);
+        assertDeletionViaQuery("content://rcs/group_thread/2/participant");
+    }
+
+    @Test
+    public void testDeleteParticipantFromGroupThreadWithSelectionFails() {
+        assertThat(mContentResolver.delete(Uri.parse("content://rcs/group_thread/2/participant"),
+                "rcs_alias=?", new String[]{"Bob"})).isEqualTo(0);
+    }
+
+    @Test
+    public void testDeleteMessagesUsingUnifiedMessageViewFails() {
+        assertThat(mContentResolver.delete(Uri.parse("content://rcs/message/1"), null,
+                null)).isEqualTo(0);
+    }
+
+    @Test
+    public void testDeleteMessagesUsingThreadUrisFails() {
+        assertThat(mContentResolver.delete(Uri.parse("content://rcs/p2p_thread/1/message/1"), null,
+                null)).isEqualTo(0);
+        assertThat(
+                mContentResolver.delete(Uri.parse("content://rcs/p2p_thread/1/incoming_message/1"),
+                        null, null)).isEqualTo(0);
+    }
+
+    @Test
+    @Ignore // TODO: fix and un-ignore
+    public void testDeleteMessage() {
+        // verify there exists 4 messages
+        Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/message"), null, null,
+                null);
+        assertThat(cursor.getCount()).isEqualTo(4);
+        cursor.close();
+
+        // delete 2 of them
+        assertThat(mContentResolver.delete(Uri.parse("content://rcs/incoming_message/1"), null,
+                null)).isEqualTo(1);
+        assertThat(mContentResolver.delete(Uri.parse("content://rcs/outgoing_message/4"), null,
+                null)).isEqualTo(1);
+
+        // verify that only 2 messages are left
+        cursor = mContentResolver.query(Uri.parse("content://rcs/message"), null, null, null);
+        assertThat(cursor.getCount()).isEqualTo(2);
+        cursor.close();
+
+        // verify that entries in common table is deleted and only messages with id's 2 and 3 remain
+        SQLiteDatabase db = mRcsProvider.getWritableDatabase();
+        cursor = db.query(RCS_MESSAGE_TABLE, null, null, null, null, null, null);
+        assertThat(cursor.getCount()).isEqualTo(2);
+
+        cursor.moveToNext();
+        assertThat(cursor.getInt(0)).isEqualTo(2);
+        cursor.moveToNext();
+        assertThat(cursor.getInt(0)).isEqualTo(3);
+        cursor.close();
+    }
+
+    @Test
+    public void testDeleteFileTransfer() {
+        assertThat(mContentResolver.delete(Uri.parse("content://rcs/file_transfer/1"), null,
+                null)).isEqualTo(1);
+        assertDeletionViaQuery("content://rcs/file_transfer/1");
+    }
+
+    @Test
+    public void testDeleteParticipantEvent() {
+        assertThat(mContentResolver.delete(Uri.parse(
+                "content://rcs/participant/1/alias_change_event/1"), null, null)).isEqualTo(1);
+
+        // try deleting again and verify nothing is deleted
+        // TODO - convert to query once querying is in place
+        assertThat(mContentResolver.delete(Uri.parse(
+                "content://rcs/participant/1/alias_change_event/1"), null, null)).isEqualTo(0);
+    }
+
+    @Test
+    public void testDeleteGroupThreadEvent() {
+        assertThat(mContentResolver.delete(Uri.parse(
+                "content://rcs/group_thread/2/name_changed_event/1"), null, null)).isEqualTo(1);
+
+        // try deleting again and verify nothing is deleted
+        // TODO - convert to query once querying is in place
+        assertThat(mContentResolver.delete(Uri.parse(
+                "content://rcs/group_thread/2/name_changed_event/1"), null, null)).isEqualTo(0);
+    }
+
+    private void assertDeletionViaQuery(String queryUri) {
+        Cursor cursor = mContentResolver.query(Uri.parse(queryUri), null, null, null);
+        assertThat(cursor.getCount()).isEqualTo(0);
+        cursor.close();
+    }
+}
diff --git a/tests/src/com/android/providers/telephony/RcsProviderHelper.java b/tests/src/com/android/providers/telephony/RcsProviderHelper.java
new file mode 100644
index 0000000..5a7f9a0
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/RcsProviderHelper.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.providers.telephony;
+
+import static android.provider.Telephony.RcsColumns.Rcs1To1ThreadColumns.FALLBACK_THREAD_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_ID_COLUMN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.net.Uri;
+
+class RcsProviderHelper {
+    /**
+     * Sets up a 1-to-1 thread with a participant ID of 1 and a fallback thread ID of 1.
+     *
+     * @return the {@link Uri} of the newly inserted row
+     */
+    static Uri setup1To1Thread(ContentResolver contentResolver) {
+        return setup1To1Thread(contentResolver, 1, 1);
+    }
+
+    static Uri setup1To1Thread(
+            ContentResolver contentResolver, int participantId, int fallbackThreadId) {
+        ContentValues insertValues = new ContentValues();
+        Uri insertionUri = Uri.parse("content://rcs/p2p_thread");
+        insertValues.put(RCS_PARTICIPANT_ID_COLUMN, participantId);
+
+        Uri rowUri = contentResolver.insert(insertionUri, insertValues);
+        assertThat(rowUri).isNotNull();
+
+        ContentValues updateValues = new ContentValues();
+        updateValues.put(FALLBACK_THREAD_ID_COLUMN, fallbackThreadId);
+        assertThat(contentResolver.update(rowUri, updateValues, null, null))
+                .isEqualTo(1);
+
+        return rowUri;
+    }
+
+}
diff --git a/tests/src/com/android/providers/telephony/RcsProviderInsertTest.java b/tests/src/com/android/providers/telephony/RcsProviderInsertTest.java
new file mode 100644
index 0000000..1a48a49
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/RcsProviderInsertTest.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.providers.telephony;
+
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.CONFERENCE_URI_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.GROUP_ICON_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.GROUP_NAME_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.GLOBAL_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.CANONICAL_ADDRESS_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_ALIAS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantEventColumns.NEW_ALIAS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.NEW_NAME_COLUMN;
+
+import static com.android.providers.telephony.RcsProviderHelper.setup1To1Thread;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.test.mock.MockContentResolver;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class RcsProviderInsertTest {
+    private MockContentResolver mContentResolver;
+    private RcsProviderTestable mRcsProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mRcsProvider = new RcsProviderTestable();
+        RcsProviderTestable.MockContextWithProvider
+                context = new RcsProviderTestable.MockContextWithProvider(mRcsProvider);
+        mContentResolver = context.getContentResolver();
+    }
+
+    @After
+    public void tearDown() {
+        mRcsProvider.tearDown();
+    }
+
+    @Test
+    public void testInsertUnifiedThreadFails() {
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/thread"), null)).isNull();
+    }
+
+    @Test
+    public void testDuplicate1To1ThreadInsertion() {
+        Uri uri = setup1To1Thread(mContentResolver);
+
+        assertThat(mContentResolver.insert(uri, null)).isNull();
+    }
+
+    @Test
+    public void testInsertGroupThread() {
+        ContentValues contentValues = new ContentValues(3);
+        contentValues.put(CONFERENCE_URI_COLUMN, "conference uri");
+        contentValues.put(GROUP_NAME_COLUMN, "group name");
+        contentValues.put(GROUP_ICON_COLUMN, "groupIcon");
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/group_thread"),
+                contentValues)).isEqualTo(Uri.parse("content://rcs/group_thread/1"));
+    }
+
+    @Test
+    public void testInsertParticipant() {
+        ContentValues contentValues = new ContentValues();
+        contentValues.put(CANONICAL_ADDRESS_ID_COLUMN, 6);
+        contentValues.put(RCS_ALIAS_COLUMN, "Alias");
+
+        Uri uri = mContentResolver.insert(Uri.parse("content://rcs/participant"), contentValues);
+        assertThat(uri).isEqualTo(Uri.parse("content://rcs/participant/1"));
+    }
+
+    @Test
+    public void testInsertParticipantIntoGroupThread() {
+        // create a participant
+        ContentValues contentValues = new ContentValues();
+        contentValues.put(CANONICAL_ADDRESS_ID_COLUMN, 23);
+        mContentResolver.insert(Uri.parse("content://rcs/participant"), contentValues);
+
+        // create a thread
+        ContentValues values = new ContentValues(1);
+        values.put(GROUP_NAME_COLUMN, "Group");
+        mContentResolver.insert(Uri.parse("content://rcs/group_thread"), values);
+
+        // add participant to the thread
+        Uri uri = Uri.parse("content://rcs/group_thread/1/participant/1");
+        assertThat(mContentResolver.insert(uri, null)).isEqualTo(uri);
+
+        // assert that adding again fails
+        assertThat(mContentResolver.insert(uri, null)).isNull();
+    }
+
+    @Test
+    public void testInsertMessageFails() {
+        ContentValues contentValues = new ContentValues();
+        contentValues.put(GLOBAL_ID_COLUMN, "global RCS id");
+
+        // try inserting messages without threads
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/message"),
+                contentValues)).isNull();
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/message/6"),
+                contentValues)).isNull();
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/incoming_message"),
+                contentValues)).isNull();
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/incoming_message/12"),
+                contentValues)).isNull();
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/outgoing_message"),
+                contentValues)).isNull();
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/outgoing_message/18"),
+                contentValues)).isNull();
+
+        // try inserting into unified thread view
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/thread/5/incoming_message"),
+                contentValues)).isNull();
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/thread/5/outgoing_message"),
+                contentValues)).isNull();
+    }
+
+    @Test
+    @Ignore // TODO: fix and un-ignore
+    public void testInsertMessageIntoThread() {
+        // create two threads
+        setup1To1Thread(mContentResolver);
+        ContentValues values = new ContentValues();
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/group_thread"),
+                values)).isNotNull();
+
+        // add messages to threads
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/1/incoming_message"),
+                values)).isEqualTo(Uri.parse("content://rcs/p2p_thread/1/incoming_message/1"));
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/1/outgoing_message"),
+                values)).isEqualTo(Uri.parse("content://rcs/p2p_thread/1/outgoing_message/2"));
+        assertThat(
+                mContentResolver.insert(Uri.parse("content://rcs/group_thread/2/incoming_message"),
+                        values)).isEqualTo(
+                Uri.parse("content://rcs/group_thread/2/incoming_message/3"));
+        assertThat(
+                mContentResolver.insert(Uri.parse("content://rcs/group_thread/2/outgoing_message"),
+                        values)).isEqualTo(
+                Uri.parse("content://rcs/group_thread/2/outgoing_message/4"));
+
+        // assert that they are accessible in messages table
+        Cursor messageCursor = mContentResolver.query(Uri.parse("content://rcs/message"), null,
+                null, null, null);
+        assertThat(messageCursor.getCount()).isEqualTo(4);
+    }
+
+    @Test
+    public void testInsertMessageDelivery() {
+        setup1To1Thread(mContentResolver);
+
+        ContentValues values = new ContentValues();
+
+        // add an outgoing message to the thread
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/1/outgoing_message"),
+                values)).isEqualTo(Uri.parse("content://rcs/p2p_thread/1/outgoing_message/1"));
+
+        // add a delivery to the outgoing message
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/outgoing_message/1/delivery/1"),
+                values)).isEqualTo(Uri.parse("content://rcs/outgoing_message/1/delivery/1"));
+    }
+
+    @Test
+    public void testInsertFileTransfer() {
+        setup1To1Thread(mContentResolver);
+
+        ContentValues values = new ContentValues();
+
+        // add an outgoing message to the thread
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/1/outgoing_message"),
+                values)).isEqualTo(Uri.parse("content://rcs/p2p_thread/1/outgoing_message/1"));
+
+        // add a file transfer to the message
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/message/1/file_transfer"),
+                values)).isEqualTo(Uri.parse("content://rcs/file_transfer/1"));
+    }
+
+    @Test
+    public void testInsertParticipantEvent() {
+        // create a participant
+        ContentValues contentValues = new ContentValues();
+        contentValues.put(CANONICAL_ADDRESS_ID_COLUMN, 23);
+        mContentResolver.insert(Uri.parse("content://rcs/participant"), contentValues);
+
+        // insert an alias change event
+        ContentValues eventValues = new ContentValues();
+        eventValues.put(NEW_ALIAS_COLUMN, "new alias");
+        assertThat(
+                mContentResolver.insert(Uri.parse("content://rcs/participant/1/alias_change_event"),
+                        eventValues)).isEqualTo(Uri.parse(
+                "content://rcs/participant/1/alias_change_event/1"));
+    }
+
+    @Test
+    public void testInsertGroupThreadEvent() {
+        // create a group thread
+        ContentValues contentValues = new ContentValues();
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/group_thread"),
+                contentValues)).isEqualTo(Uri.parse("content://rcs/group_thread/1"));
+
+        // create a group name change event
+        ContentValues eventValues = new ContentValues();
+        eventValues.put(NEW_NAME_COLUMN, "new name");
+        assertThat(mContentResolver.insert(
+                Uri.parse("content://rcs/group_thread/1/name_changed_event"),
+                eventValues)).isEqualTo(Uri.parse(
+                "content://rcs/group_thread/1/name_changed_event/1"));
+    }
+}
diff --git a/tests/src/com/android/providers/telephony/RcsProviderQueryTest.java b/tests/src/com/android/providers/telephony/RcsProviderQueryTest.java
new file mode 100644
index 0000000..f0579ea
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/RcsProviderQueryTest.java
@@ -0,0 +1,582 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.providers.telephony;
+
+import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_JOINED_EVENT_TYPE;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.FILE_SIZE_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.SESSION_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.GROUP_NAME_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.MESSAGE_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.MESSAGE_TEXT_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.MESSAGE_TYPE_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.ORIGINATION_TIMESTAMP_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.CANONICAL_ADDRESS_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_ALIAS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadColumns.RCS_THREAD_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.DESTINATION_PARTICIPANT_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.EVENT_TYPE_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.NEW_NAME_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.SOURCE_PARTICIPANT_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsUnifiedThreadColumns.THREAD_TYPE_COLUMN;
+import static android.telephony.ims.RcsEventQueryParams.EVENT_QUERY_PARAMETERS_KEY;
+import static android.telephony.ims.RcsEventQueryParams.GROUP_THREAD_NAME_CHANGED_EVENT;
+import static android.telephony.ims.RcsMessageQueryParams.MESSAGE_QUERY_PARAMETERS_KEY;
+import static android.telephony.ims.RcsParticipantQueryParams.PARTICIPANT_QUERY_PARAMETERS_KEY;
+import static android.telephony.ims.RcsQueryContinuationToken.QUERY_CONTINUATION_TOKEN;
+import static android.telephony.ims.RcsThreadQueryParams.THREAD_QUERY_PARAMETERS_KEY;
+
+import static com.android.providers.telephony.RcsProviderHelper.setup1To1Thread;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.telephony.ims.RcsEventQueryParams;
+import android.telephony.ims.RcsGroupThread;
+import android.telephony.ims.RcsMessageQueryParams;
+import android.telephony.ims.RcsParticipantQueryParams;
+import android.telephony.ims.RcsQueryContinuationToken;
+import android.telephony.ims.RcsThreadQueryParams;
+import android.test.mock.MockContentResolver;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.providers.telephony.RcsProviderTestable.MockContextWithProvider;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class RcsProviderQueryTest {
+    private MockContentResolver mContentResolver;
+    private RcsProviderTestable mRcsProvider;
+
+    private static final String GROUP_NAME = "group name";
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mRcsProvider = new RcsProviderTestable();
+        MockContextWithProvider context = new MockContextWithProvider(mRcsProvider);
+        mContentResolver = context.getContentResolver();
+
+        // insert two participants
+        Uri participantUri = Uri.parse("content://rcs/participant");
+        ContentValues contentValues = new ContentValues();
+        contentValues.put(CANONICAL_ADDRESS_ID_COLUMN, 99);
+        contentValues.put(RCS_ALIAS_COLUMN, "Some alias");
+        mContentResolver.insert(participantUri, contentValues);
+
+        contentValues.clear();
+        contentValues.put(CANONICAL_ADDRESS_ID_COLUMN, 100);
+        contentValues.put(RCS_ALIAS_COLUMN, "Some other alias");
+        mContentResolver.insert(participantUri, contentValues);
+
+        // insert two 1 to 1 threads
+        setup1To1Thread(mContentResolver, 1, 1);
+        setup1To1Thread(mContentResolver, 2, 2);
+
+        // insert one group thread
+        ContentValues groupContentValues = new ContentValues(1);
+        groupContentValues.put(GROUP_NAME_COLUMN, GROUP_NAME);
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/group_thread"),
+                groupContentValues)).isEqualTo(Uri.parse("content://rcs/group_thread/3"));
+
+        // put participants into the group
+        mContentResolver.insert(Uri.parse("content://rcs/group_thread/3/participant/1"), null);
+        mContentResolver.insert(Uri.parse("content://rcs/group_thread/3/participant/2"), null);
+
+        // insert two messages into first thread, leave the second one empty, insert one into group
+        // thread
+        ContentValues messageValues = new ContentValues();
+
+        messageValues.put(ORIGINATION_TIMESTAMP_COLUMN, 300);
+        messageValues.put(MESSAGE_TEXT_COLUMN, "Old message");
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/1/incoming_message"),
+                messageValues)).isEqualTo(
+                Uri.parse("content://rcs/p2p_thread/1/incoming_message/1"));
+
+        messageValues.clear();
+        messageValues.put(ORIGINATION_TIMESTAMP_COLUMN, 400);
+        messageValues.put(MESSAGE_TEXT_COLUMN, "New message");
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/1/outgoing_message"),
+                messageValues)).isEqualTo(
+                Uri.parse("content://rcs/p2p_thread/1/outgoing_message/2"));
+
+        messageValues.clear();
+        messageValues.put(ORIGINATION_TIMESTAMP_COLUMN, 200);
+        messageValues.put(MESSAGE_TEXT_COLUMN, "Group message");
+        assertThat(
+                mContentResolver.insert(Uri.parse("content://rcs/group_thread/3/incoming_message"),
+                        messageValues)).isEqualTo(
+                Uri.parse("content://rcs/group_thread/3/incoming_message/3"));
+
+        // Add two events to the group thread
+        ContentValues eventValues = new ContentValues();
+        eventValues.put(NEW_NAME_COLUMN, "New group name");
+        assertThat(mContentResolver.insert(
+                Uri.parse("content://rcs/group_thread/3/name_changed_event"),
+                eventValues)).isEqualTo(
+                Uri.parse("content://rcs/group_thread/3/name_changed_event/1"));
+
+        eventValues.clear();
+        eventValues.put(SOURCE_PARTICIPANT_ID_COLUMN, 1);
+        eventValues.put(DESTINATION_PARTICIPANT_ID_COLUMN, 2);
+        assertThat(mContentResolver.insert(
+                Uri.parse("content://rcs/group_thread/3/participant_joined_event"),
+                eventValues)).isEqualTo(
+                Uri.parse("content://rcs/group_thread/3/participant_joined_event/2"));
+    }
+
+    @After
+    public void tearDown() {
+        mRcsProvider.tearDown();
+    }
+
+    @Test
+    public void testCanQueryUnifiedThreads() {
+        RcsThreadQueryParams queryParameters = new RcsThreadQueryParams.Builder().build();
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(THREAD_QUERY_PARAMETERS_KEY, queryParameters);
+
+        Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/thread"),
+                null, bundle, null);
+        assertThat(cursor.getCount()).isEqualTo(3);
+
+        cursor.moveToNext();
+        assertThat(cursor.getInt(cursor.getColumnIndex(THREAD_TYPE_COLUMN))).isEqualTo(1);
+        assertThat(cursor.getString(cursor.getColumnIndex(GROUP_NAME_COLUMN))).isEqualTo(
+                GROUP_NAME);
+        assertThat(cursor.getInt(cursor.getColumnIndex(ORIGINATION_TIMESTAMP_COLUMN))).isEqualTo(
+                200);
+        assertThat(cursor.getString(cursor.getColumnIndex(MESSAGE_TEXT_COLUMN))).isEqualTo(
+                "Group message");
+        cursor.moveToNext();
+        assertThat(cursor.getInt(cursor.getColumnIndex(THREAD_TYPE_COLUMN))).isEqualTo(0);
+        assertThat(cursor.getString(cursor.getColumnIndex(GROUP_NAME_COLUMN))).isEqualTo(null);
+        assertThat(cursor.getInt(cursor.getColumnIndex(ORIGINATION_TIMESTAMP_COLUMN))).isEqualTo(0);
+        assertThat(cursor.getString(cursor.getColumnIndex(MESSAGE_TEXT_COLUMN))).isEqualTo(null);
+        cursor.moveToNext();
+        assertThat(cursor.getInt(cursor.getColumnIndex(THREAD_TYPE_COLUMN))).isEqualTo(0);
+        assertThat(cursor.getString(cursor.getColumnIndex(GROUP_NAME_COLUMN))).isEqualTo(null);
+        assertThat(cursor.getInt(cursor.getColumnIndex(ORIGINATION_TIMESTAMP_COLUMN))).isEqualTo(
+                400);
+        assertThat(cursor.getString(cursor.getColumnIndex(MESSAGE_TEXT_COLUMN))).isEqualTo(
+                "New message");
+    }
+
+    @Test
+    public void testCanQueryUnifiedThreadsWithLimitAndSorting() {
+        RcsThreadQueryParams queryParameters = new RcsThreadQueryParams.Builder()
+                .setThreadType(RcsThreadQueryParams.THREAD_TYPE_1_TO_1).setResultLimit(1)
+                .setSortProperty(RcsThreadQueryParams.SORT_BY_TIMESTAMP).setSortDirection(true)
+                .build();
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(THREAD_QUERY_PARAMETERS_KEY, queryParameters);
+
+        Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/thread"),
+                null, bundle, null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+
+        cursor.moveToNext();
+        assertThat(cursor.getInt(cursor.getColumnIndex(THREAD_TYPE_COLUMN))).isEqualTo(0);
+        assertThat(cursor.getString(cursor.getColumnIndex(GROUP_NAME_COLUMN))).isEqualTo(null);
+        assertThat(cursor.getInt(cursor.getColumnIndex(ORIGINATION_TIMESTAMP_COLUMN))).isEqualTo(0);
+        assertThat(cursor.getString(cursor.getColumnIndex(MESSAGE_TEXT_COLUMN))).isEqualTo(null);
+    }
+
+    @Test
+    public void testCanContinueThreadQuery() {
+        // Limit results to 1.
+        RcsThreadQueryParams queryParameters =
+                new RcsThreadQueryParams.Builder().setResultLimit(1).build();
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(THREAD_QUERY_PARAMETERS_KEY, queryParameters);
+
+        // Perform an initial query, verify first thread is returned
+        Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/thread"), null, bundle,
+                null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+
+        cursor.moveToNext();
+        assertThat(cursor.getInt(cursor.getColumnIndex(THREAD_TYPE_COLUMN))).isEqualTo(1);
+        assertThat(cursor.getString(cursor.getColumnIndex(GROUP_NAME_COLUMN))).isEqualTo(
+                GROUP_NAME);
+        assertThat(cursor.getInt(cursor.getColumnIndex(ORIGINATION_TIMESTAMP_COLUMN))).isEqualTo(
+                200);
+        assertThat(cursor.getString(cursor.getColumnIndex(MESSAGE_TEXT_COLUMN))).isEqualTo(
+                "Group message");
+
+        // Put the continuation token in the bundle to do a follow up query
+        RcsQueryContinuationToken continuationToken = cursor.getExtras().getParcelable(
+                QUERY_CONTINUATION_TOKEN);
+        bundle.clear();
+        bundle.putParcelable(QUERY_CONTINUATION_TOKEN, continuationToken);
+        cursor.close();
+
+        cursor = mContentResolver.query(Uri.parse("content://rcs/thread"), null, bundle,
+                null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToNext();
+        assertThat(cursor.getInt(cursor.getColumnIndex(THREAD_TYPE_COLUMN))).isEqualTo(0);
+        assertThat(cursor.getString(cursor.getColumnIndex(GROUP_NAME_COLUMN))).isEqualTo(null);
+        assertThat(cursor.getInt(cursor.getColumnIndex(ORIGINATION_TIMESTAMP_COLUMN))).isEqualTo(0);
+        assertThat(cursor.getString(cursor.getColumnIndex(MESSAGE_TEXT_COLUMN))).isEqualTo(null);
+        cursor.close();
+
+        // Put the continuation token in the bundle to do a follow up query again, verify third
+        // thread is returned
+        continuationToken = cursor.getExtras().getParcelable(QUERY_CONTINUATION_TOKEN);
+        bundle.clear();
+        bundle.putParcelable(QUERY_CONTINUATION_TOKEN, continuationToken);
+        cursor = mContentResolver.query(Uri.parse("content://rcs/thread"), null, bundle,
+                null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToNext();
+        assertThat(cursor.getInt(cursor.getColumnIndex(THREAD_TYPE_COLUMN))).isEqualTo(0);
+        assertThat(cursor.getString(cursor.getColumnIndex(GROUP_NAME_COLUMN))).isEqualTo(null);
+        assertThat(cursor.getInt(cursor.getColumnIndex(ORIGINATION_TIMESTAMP_COLUMN))).isEqualTo(
+                400);
+        assertThat(cursor.getString(cursor.getColumnIndex(MESSAGE_TEXT_COLUMN))).isEqualTo(
+                "New message");
+        cursor.close();
+    }
+
+    @Test
+    public void testQuery1To1Threads() {
+        // verify two threads are returned in the query
+        Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/p2p_thread"),
+                new String[]{RCS_THREAD_ID_COLUMN}, null, null, null);
+        assertThat(cursor.getCount()).isEqualTo(2);
+    }
+
+    @Test
+    public void testQueryGroupThreads() {
+        // verify one thread is returned in the query
+        Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/group_thread"),
+                new String[]{GROUP_NAME_COLUMN}, null, null, null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+
+        cursor.moveToNext();
+        assertThat(cursor.getString(0)).isEqualTo(GROUP_NAME);
+    }
+
+    @Test
+    public void testQueryParticipant() {
+        RcsParticipantQueryParams queryParameters = new RcsParticipantQueryParams.Builder()
+                .build();
+
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(PARTICIPANT_QUERY_PARAMETERS_KEY, queryParameters);
+
+        Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/participant"), null, bundle,
+                null);
+        assertThat(cursor.getCount()).isEqualTo(2);
+
+        cursor.moveToNext();
+        assertThat(cursor.getInt(cursor.getColumnIndex(RCS_PARTICIPANT_ID_COLUMN))).isEqualTo(2);
+        assertThat(cursor.getInt(cursor.getColumnIndex(CANONICAL_ADDRESS_ID_COLUMN))).isEqualTo(
+                100);
+        assertThat(cursor.getString(cursor.getColumnIndex(RCS_ALIAS_COLUMN))).isEqualTo(
+                "Some other alias");
+
+        cursor.moveToNext();
+        assertThat(cursor.getInt(cursor.getColumnIndex(RCS_PARTICIPANT_ID_COLUMN))).isEqualTo(1);
+        assertThat(cursor.getInt(cursor.getColumnIndex(CANONICAL_ADDRESS_ID_COLUMN))).isEqualTo(99);
+        assertThat(cursor.getString(cursor.getColumnIndex(RCS_ALIAS_COLUMN))).isEqualTo(
+                "Some alias");
+    }
+
+    @Test
+    public void testQueryParticipantWithContinuation() {
+        Uri participantUri = Uri.parse("content://rcs/participant");
+
+        // Perform the initial query
+        RcsParticipantQueryParams queryParameters =
+                new RcsParticipantQueryParams.Builder().setAliasLike("%ali%").setSortProperty(
+                        RcsParticipantQueryParams.SORT_BY_ALIAS).setSortDirection(true)
+                        .setResultLimit(1).build();
+
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(PARTICIPANT_QUERY_PARAMETERS_KEY, queryParameters);
+
+        Cursor cursor = mContentResolver.query(participantUri, null, bundle, null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+
+        cursor.moveToNext();
+        assertThat(cursor.getInt(0)).isEqualTo(1);
+        assertThat(cursor.getInt(1)).isEqualTo(99);
+        assertThat(cursor.getString(2)).isEqualTo("Some alias");
+
+        // Perform the continuation query
+        RcsQueryContinuationToken continuationToken = cursor.getExtras().getParcelable(
+                QUERY_CONTINUATION_TOKEN);
+        bundle.clear();
+        bundle.putParcelable(QUERY_CONTINUATION_TOKEN, continuationToken);
+
+        cursor = mContentResolver.query(participantUri, null, bundle, null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+
+        cursor.moveToNext();
+        assertThat(cursor.getInt(0)).isEqualTo(2);
+        assertThat(cursor.getInt(1)).isEqualTo(100);
+        assertThat(cursor.getString(2)).isEqualTo("Some other alias");
+
+        // Perform the continuation query to verify no entries left
+        continuationToken = cursor.getExtras().getParcelable(QUERY_CONTINUATION_TOKEN);
+        bundle.clear();
+        bundle.putParcelable(QUERY_CONTINUATION_TOKEN, continuationToken);
+
+        cursor = mContentResolver.query(participantUri, null, bundle, null);
+        assertThat(cursor.getCount()).isEqualTo(0);
+        continuationToken = cursor.getExtras().getParcelable(QUERY_CONTINUATION_TOKEN);
+        assertThat(continuationToken).isNull();
+    }
+
+    @Test
+    public void testQueryGroupParticipants() {
+        // TODO - implement
+    }
+
+    @Test
+    @Ignore // TODO: fix and un-ignore
+    public void testQueryEvents() {
+        RcsEventQueryParams queryParameters = new RcsEventQueryParams.Builder().build();
+
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(EVENT_QUERY_PARAMETERS_KEY, queryParameters);
+
+        Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/event"), null, bundle,
+                null);
+        assertThat(cursor.getCount()).isEqualTo(2);
+
+        cursor.moveToNext();
+        assertThat(cursor.getInt(cursor.getColumnIndex(EVENT_TYPE_COLUMN))).isEqualTo(
+                PARTICIPANT_JOINED_EVENT_TYPE);
+        assertThat(cursor.getInt(cursor.getColumnIndex(SOURCE_PARTICIPANT_ID_COLUMN))).isEqualTo(
+                1);
+        assertThat(cursor.getInt(cursor.getColumnIndex(DESTINATION_PARTICIPANT_ID_COLUMN))).isEqualTo(
+                2);
+
+        cursor.moveToNext();
+        assertThat(cursor.getInt(cursor.getColumnIndex(EVENT_TYPE_COLUMN))).isEqualTo(
+                GROUP_THREAD_NAME_CHANGED_EVENT);
+        assertThat(cursor.getString(cursor.getColumnIndex(NEW_NAME_COLUMN))).isEqualTo(
+                "New group name");
+    }
+
+    @Test
+    @Ignore // TODO: fix and un-ignore
+    public void testQueryEventsWithContinuation() {
+        RcsEventQueryParams queryParameters =
+                new RcsEventQueryParams.Builder().setResultLimit(1).setSortDirection(true)
+                        .build();
+
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(EVENT_QUERY_PARAMETERS_KEY, queryParameters);
+
+        Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/event"), null, bundle,
+                null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+
+        cursor.moveToNext();
+        assertThat(cursor.getInt(cursor.getColumnIndex(EVENT_TYPE_COLUMN))).isEqualTo(
+                GROUP_THREAD_NAME_CHANGED_EVENT);
+        assertThat(cursor.getString(cursor.getColumnIndex(NEW_NAME_COLUMN))).isEqualTo(
+                "New group name");
+    }
+
+    @Test
+    @Ignore // TODO: fix and un-ignore
+    public void testQueryEventsWithTypeLimitation() {
+        RcsEventQueryParams queryParameters =
+                new RcsEventQueryParams.Builder().setEventType(
+                        GROUP_THREAD_NAME_CHANGED_EVENT).build();
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(EVENT_QUERY_PARAMETERS_KEY, queryParameters);
+
+        Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/event"), null, bundle,
+                null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+
+        cursor.moveToNext();
+        assertThat(cursor.getInt(cursor.getColumnIndex(EVENT_TYPE_COLUMN))).isEqualTo(
+                GROUP_THREAD_NAME_CHANGED_EVENT);
+        assertThat(cursor.getString(cursor.getColumnIndex(NEW_NAME_COLUMN))).isEqualTo(
+                "New group name");
+    }
+
+    @Test
+    @Ignore // TODO: fix and un-ignore
+    public void testQueryMessages() {
+        RcsMessageQueryParams queryParameters = new RcsMessageQueryParams.Builder().build();
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(MESSAGE_QUERY_PARAMETERS_KEY, queryParameters);
+
+        Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/message"), null, bundle,
+                null);
+
+        assertThat(cursor.getCount()).isEqualTo(3);
+        cursor.moveToNext();
+        assertThat(cursor.getInt(cursor.getColumnIndex(MESSAGE_ID_COLUMN))).isEqualTo(3);
+        assertThat(cursor.getInt(cursor.getColumnIndex(RCS_THREAD_ID_COLUMN))).isEqualTo(3);
+
+        cursor.moveToNext();
+        assertThat(cursor.getInt(cursor.getColumnIndex(MESSAGE_ID_COLUMN))).isEqualTo(2);
+        assertThat(cursor.getInt(cursor.getColumnIndex(RCS_THREAD_ID_COLUMN))).isEqualTo(1);
+
+        cursor.moveToNext();
+        assertThat(cursor.getInt(cursor.getColumnIndex(MESSAGE_ID_COLUMN))).isEqualTo(1);
+        assertThat(cursor.getInt(cursor.getColumnIndex(RCS_THREAD_ID_COLUMN))).isEqualTo(1);
+    }
+
+    @Test
+    @Ignore // TODO: fix and un-ignore
+    public void testQueryMessagesWithContinuation() {
+        RcsMessageQueryParams queryParameters =
+                new RcsMessageQueryParams.Builder().setMessageLike("%o%message").setResultLimit(
+                        1).setSortProperty(RcsMessageQueryParams.SORT_BY_TIMESTAMP)
+                        .setSortDirection(true).build();
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(MESSAGE_QUERY_PARAMETERS_KEY, queryParameters);
+
+        // Perform the initial query
+        Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/message"), null, bundle,
+                null);
+
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToNext();
+        assertThat(cursor.getInt(cursor.getColumnIndex(MESSAGE_ID_COLUMN))).isEqualTo(3);
+        assertThat(cursor.getInt(cursor.getColumnIndex(RCS_THREAD_ID_COLUMN))).isEqualTo(3);
+        assertThat(cursor.getInt(cursor.getColumnIndex(ORIGINATION_TIMESTAMP_COLUMN))).isEqualTo(
+                200);
+        assertThat(cursor.getString(cursor.getColumnIndex(MESSAGE_TYPE_COLUMN))).isEqualTo(
+                "Group message");
+
+        // Perform the continuation query
+        RcsQueryContinuationToken continuationToken = cursor.getExtras().getParcelable(
+                QUERY_CONTINUATION_TOKEN);
+        assertThat(continuationToken).isNotNull();
+        bundle.clear();
+        bundle.putParcelable(QUERY_CONTINUATION_TOKEN, continuationToken);
+
+        cursor = mContentResolver.query(Uri.parse("content://rcs/message"), null, bundle, null);
+
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToNext();
+        assertThat(cursor.getInt(cursor.getColumnIndex(MESSAGE_ID_COLUMN))).isEqualTo(1);
+        assertThat(cursor.getInt(cursor.getColumnIndex(RCS_THREAD_ID_COLUMN))).isEqualTo(1);
+        assertThat(cursor.getInt(cursor.getColumnIndex(ORIGINATION_TIMESTAMP_COLUMN))).isEqualTo(
+                300);
+        assertThat(cursor.getString(cursor.getColumnIndex(MESSAGE_TEXT_COLUMN))).isEqualTo(
+                "Old message");
+    }
+
+    @Test
+    @Ignore // TODO: fix and un-ignore
+    public void testQueryMessagesWithThreadFilter() {
+        RcsMessageQueryParams queryParameters =
+                new RcsMessageQueryParams.Builder().setThread(new RcsGroupThread(null, 3))
+                        .build();
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(MESSAGE_QUERY_PARAMETERS_KEY, queryParameters);
+
+        // Perform the initial query
+        Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/message"), null, bundle,
+                null);
+
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToNext();
+        assertThat(cursor.getInt(cursor.getColumnIndex(MESSAGE_ID_COLUMN))).isEqualTo(3);
+        assertThat(cursor.getInt(cursor.getColumnIndex(RCS_THREAD_ID_COLUMN))).isEqualTo(3);
+        assertThat(cursor.getInt(cursor.getColumnIndex(ORIGINATION_TIMESTAMP_COLUMN))).isEqualTo(
+                200);
+        assertThat(cursor.getString(cursor.getColumnIndex(MESSAGE_TEXT_COLUMN))).isEqualTo(
+                "Group message");
+
+    }
+
+    @Test
+    public void testQueryParticipantOf1To1Thread() {
+        // query the participant back
+        Uri queryUri = Uri.parse("content://rcs/p2p_thread/1/participant");
+        Cursor cursor = mContentResolver.query(queryUri, null, null, null, null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToNext();
+
+        assertThat(cursor.getInt(1)).isEqualTo(99);
+        assertThat(cursor.getString(2)).isEqualTo("Some alias");
+    }
+
+    @Test
+    public void testQueryParticipantOfGroupThread() {
+        // query all the participants in this thread
+        Uri queryUri = Uri.parse("content://rcs/group_thread/3/participant");
+        Cursor cursor = mContentResolver.query(queryUri, null, null, null, null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToNext();
+
+        assertThat(cursor.getInt(cursor.getColumnIndex(RCS_PARTICIPANT_ID_COLUMN))).isEqualTo(1);
+        assertThat(cursor.getInt(cursor.getColumnIndex(CANONICAL_ADDRESS_ID_COLUMN))).isEqualTo(99);
+        assertThat(cursor.getString(cursor.getColumnIndex(RCS_ALIAS_COLUMN))).isEqualTo(
+                "Some alias");
+    }
+
+    @Test
+    public void testQueryParticipantOfGroupThreadWithId() {
+        Cursor cursor = mContentResolver.query(
+                Uri.parse("content://rcs/group_thread/3/participant/1"), null, null, null, null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToNext();
+
+        assertThat(cursor.getInt(cursor.getColumnIndex(RCS_PARTICIPANT_ID_COLUMN))).isEqualTo(1);
+        assertThat(cursor.getInt(cursor.getColumnIndex(CANONICAL_ADDRESS_ID_COLUMN))).isEqualTo(99);
+        assertThat(cursor.getString(cursor.getColumnIndex(RCS_ALIAS_COLUMN))).isEqualTo(
+                "Some alias");
+    }
+
+    @Test
+    public void testQueryFileTransfer() {
+        ContentValues values = new ContentValues();
+        // add an incoming message to the thread 2
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/2/incoming_message"),
+                values)).isEqualTo(Uri.parse("content://rcs/p2p_thread/2/incoming_message/4"));
+
+        // add a file transfer
+        values.put(SESSION_ID_COLUMN, "session_id");
+        values.put(FILE_SIZE_COLUMN, 1234567890);
+        assertThat(
+                mContentResolver.insert(Uri.parse("content://rcs/message/4/file_transfer"),
+                        values)).isEqualTo(Uri.parse("content://rcs/file_transfer/1"));
+
+        // query the file transfer back
+        Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/file_transfer/1"), null,
+                null, null, null, null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+
+        cursor.moveToNext();
+        assertThat(cursor.getInt(0)).isEqualTo(1);
+        assertThat(cursor.getInt(1)).isEqualTo(4);
+        assertThat(cursor.getString(2)).isEqualTo("session_id");
+        assertThat(cursor.getLong(5)).isEqualTo(1234567890);
+    }
+}
diff --git a/tests/src/com/android/providers/telephony/RcsProviderTestable.java b/tests/src/com/android/providers/telephony/RcsProviderTestable.java
new file mode 100644
index 0000000..569fcdb
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/RcsProviderTestable.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.providers.telephony;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.telephony.TelephonyManager;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockContext;
+
+import org.mockito.Mockito;
+
+/**
+ * A subclass of RcsProvider used for testing on an in-memory database
+ */
+public class RcsProviderTestable extends RcsProvider {
+    private MockContextWithProvider mockContextWithProvider;
+
+    @Override
+    public boolean onCreate() {
+        mockContextWithProvider = new MockContextWithProvider(this);
+        mDbOpenHelper = new InMemoryRcsDatabase();
+        mParticipantHelper = new RcsProviderParticipantHelper(mDbOpenHelper);
+        mThreadHelper = new RcsProviderThreadHelper(mDbOpenHelper);
+        mMessageHelper = new RcsProviderMessageHelper(mDbOpenHelper);
+        mEventHelper = new RcsProviderEventHelper(mDbOpenHelper);
+        return true;
+    }
+
+    protected void tearDown() {
+        mDbOpenHelper.close();
+    }
+
+    public SQLiteDatabase getWritableDatabase() {
+        return mDbOpenHelper.getWritableDatabase();
+    }
+
+    class InMemoryRcsDatabase extends SQLiteOpenHelper {
+        InMemoryRcsDatabase() {
+            super(null,        // no context is needed for in-memory db
+                    null,      // db file name is null for in-memory db
+                    null,      // CursorFactory is null by default
+                    1);        // db version is no-op for tests
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            MmsSmsDatabaseHelper mmsSmsDatabaseHelper = new MmsSmsDatabaseHelper(
+                mockContextWithProvider, null);
+            mmsSmsDatabaseHelper.createMmsTables(db);
+            mmsSmsDatabaseHelper.createSmsTables(db);
+            mmsSmsDatabaseHelper.createCommonTables(db);
+
+            RcsProviderThreadHelper.createThreadTables(db);
+            RcsProviderParticipantHelper.createParticipantTables(db);
+            RcsProviderMessageHelper.createRcsMessageTables(db);
+            RcsProviderEventHelper.createRcsEventTables(db);
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            // no-op
+        }
+    }
+
+    static class MockContextWithProvider extends MockContext {
+        private final MockContentResolver mResolver;
+
+        MockContextWithProvider(RcsProvider rcsProvider) {
+            mResolver = new MockContentResolver();
+
+            // Add authority="rcs" to given smsProvider
+            ProviderInfo providerInfo = new ProviderInfo();
+            providerInfo.authority = RcsProvider.AUTHORITY;
+            rcsProvider.attachInfoForTesting(this, providerInfo);
+            mResolver.addProvider(RcsProvider.AUTHORITY, rcsProvider);
+        }
+
+        @Override
+        public MockContentResolver getContentResolver() {
+            return mResolver;
+        }
+
+        @Override
+        public PackageManager getPackageManager() {
+            return Mockito.mock(PackageManager.class);
+        }
+
+        @Override
+        public Object getSystemService(String name) {
+            switch (name) {
+                case Context.APP_OPS_SERVICE:
+                    return Mockito.mock(AppOpsManager.class);
+                case Context.TELEPHONY_SERVICE:
+                    return Mockito.mock(TelephonyManager.class);
+                default:
+                    return null;
+            }
+        }
+
+        @Override
+        public boolean isCredentialProtectedStorage() {
+            return false;
+        }
+    }
+}
diff --git a/tests/src/com/android/providers/telephony/RcsProviderUpdateTest.java b/tests/src/com/android/providers/telephony/RcsProviderUpdateTest.java
new file mode 100644
index 0000000..4ba7c1c
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/RcsProviderUpdateTest.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.providers.telephony;
+
+import static android.provider.Telephony.RcsColumns.Rcs1To1ThreadColumns.FALLBACK_THREAD_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.HEIGHT_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.WIDTH_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.GROUP_NAME_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.OWNER_PARTICIPANT_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsIncomingMessageColumns.ARRIVAL_TIMESTAMP_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.ORIGINATION_TIMESTAMP_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageDeliveryColumns.DELIVERED_TIMESTAMP_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.CANONICAL_ADDRESS_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_ALIAS_COLUMN;
+
+import static com.android.providers.telephony.RcsProviderHelper.setup1To1Thread;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.Telephony.RcsColumns.RcsMessageDeliveryColumns;
+import android.test.mock.MockContentResolver;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class RcsProviderUpdateTest {
+    private MockContentResolver mContentResolver;
+    private RcsProviderTestable mRcsProvider;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mRcsProvider = new RcsProviderTestable();
+        RcsProviderTestable.MockContextWithProvider
+                context = new RcsProviderTestable.MockContextWithProvider(mRcsProvider);
+        mContentResolver = context.getContentResolver();
+
+        // insert a participant
+        //  first into the MmsSmsProvider
+        mRcsProvider.getWritableDatabase().execSQL(
+                "INSERT INTO canonical_addresses VALUES (1, \"+15551234567\")");
+
+        //  then into the RcsProvider
+        ContentValues participantValues = new ContentValues();
+        participantValues.put(RCS_ALIAS_COLUMN, "Bob");
+        participantValues.put(CANONICAL_ADDRESS_ID_COLUMN, 1);
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/participant"),
+                participantValues)).isEqualTo(Uri.parse("content://rcs/participant/1"));
+
+        // insert fallback threads
+        mRcsProvider.getWritableDatabase().execSQL("INSERT INTO threads(_id) VALUES (1)");
+        mRcsProvider.getWritableDatabase().execSQL("INSERT INTO threads(_id) VALUES (2)");
+
+        setup1To1Thread(mContentResolver);
+
+        // insert one group thread
+        ContentValues groupContentValues = new ContentValues();
+        groupContentValues.put(OWNER_PARTICIPANT_COLUMN, 1);
+        groupContentValues.put(GROUP_NAME_COLUMN, "Name");
+        Uri groupThreadUri = Uri.parse("content://rcs/group_thread");
+        assertThat(mContentResolver.insert(groupThreadUri, groupContentValues)).isEqualTo(
+                Uri.parse("content://rcs/group_thread/2"));
+
+        Uri groupInsertionUri = Uri.parse("content://rcs/group_thread/2/participant/1");
+        assertThat(mContentResolver.insert(groupInsertionUri, null)).isEqualTo(groupInsertionUri);
+
+        // add incoming and outgoing messages to both threads
+        ContentValues messageValues = new ContentValues();
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/1/incoming_message"),
+                messageValues)).isEqualTo(
+                Uri.parse("content://rcs/p2p_thread/1/incoming_message/1"));
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/1/outgoing_message"),
+                messageValues)).isEqualTo(
+                Uri.parse("content://rcs/p2p_thread/1/outgoing_message/2"));
+        assertThat(
+                mContentResolver.insert(Uri.parse("content://rcs/group_thread/2/incoming_message"),
+                        messageValues)).isEqualTo(
+                Uri.parse("content://rcs/group_thread/2/incoming_message/3"));
+        assertThat(
+                mContentResolver.insert(Uri.parse("content://rcs/group_thread/2/outgoing_message"),
+                        messageValues)).isEqualTo(
+                Uri.parse("content://rcs/group_thread/2/outgoing_message/4"));
+
+        // add message delivery to the outgoing messages
+        ContentValues deliveryValues = new ContentValues();
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/outgoing_message/2/delivery/1"),
+                deliveryValues)).isEqualTo(
+                Uri.parse("content://rcs/outgoing_message/2/delivery/1"));
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/outgoing_message/4/delivery/1"),
+                deliveryValues)).isEqualTo(
+                Uri.parse("content://rcs/outgoing_message/4/delivery/1"));
+
+        // add a file transfer to an incoming message
+        ContentValues fileTransferValues = new ContentValues();
+        assertThat(mContentResolver.insert(Uri.parse("content://rcs/message/3/file_transfer"),
+                fileTransferValues)).isEqualTo(Uri.parse("content://rcs/file_transfer/1"));
+    }
+
+    @After
+    public void tearDown() {
+        mRcsProvider.tearDown();
+    }
+
+    @Test
+    public void testUpdate1To1ThreadWithSelection() {
+        // update the fallback thread id
+        ContentValues contentValues = new ContentValues(1);
+        contentValues.put(FALLBACK_THREAD_ID_COLUMN, 2);
+        Uri p2pThreadUri = Uri.parse("content://rcs/p2p_thread");
+
+        assertThat(mContentResolver.update(p2pThreadUri, contentValues, "rcs_fallback_thread_id=1",
+                null)).isEqualTo(1);
+
+        // verify the thread is actually updated
+        Cursor cursor = mContentResolver.query(p2pThreadUri,
+                new String[]{FALLBACK_THREAD_ID_COLUMN}, "rcs_fallback_thread_id=2", null, null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+
+        cursor.moveToNext();
+        assertThat(cursor.getInt(0)).isEqualTo(2);
+    }
+
+    @Test
+    public void testUpdate1To1ThreadWithId() {
+        // update the fallback thread id
+        ContentValues contentValues = new ContentValues(1);
+        contentValues.put(FALLBACK_THREAD_ID_COLUMN, 2);
+        Uri p2pThreadUri = Uri.parse("content://rcs/p2p_thread/1");
+        assertThat(mContentResolver.update(p2pThreadUri, contentValues, null, null)).isEqualTo(1);
+
+        // verify the thread is actually updated
+        Cursor cursor = mContentResolver.query(p2pThreadUri,
+                new String[]{FALLBACK_THREAD_ID_COLUMN}, null, null, null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+
+        cursor.moveToNext();
+        assertThat(cursor.getInt(0)).isEqualTo(2);
+    }
+
+    @Test
+    public void testUpdateGroupThreadWithSelection() {
+        // update the group name
+        ContentValues contentValues = new ContentValues(1);
+        contentValues.put(GROUP_NAME_COLUMN, "New name");
+        Uri groupThreadUri = Uri.parse("content://rcs/group_thread");
+        assertThat(mContentResolver.update(groupThreadUri, contentValues, "group_name=\"Name\"",
+                null)).isEqualTo(1);
+
+        // verify the thread is actually updated
+        Cursor cursor = mContentResolver.query(groupThreadUri, new String[]{GROUP_NAME_COLUMN},
+                "group_name=\"New name\"", null, null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+
+        cursor.moveToNext();
+        assertThat(cursor.getString(0)).isEqualTo("New name");
+    }
+
+    @Test
+    public void testUpdateGroupThreadWithId() {
+        // update the group name
+        ContentValues contentValues = new ContentValues(1);
+        contentValues.put(GROUP_NAME_COLUMN, "New name");
+        Uri groupThreadUri = Uri.parse("content://rcs/group_thread/2");
+        assertThat(mContentResolver.update(groupThreadUri, contentValues, null, null)).isEqualTo(1);
+
+        // verify the thread is actually updated
+        Cursor cursor = mContentResolver.query(groupThreadUri, new String[]{GROUP_NAME_COLUMN},
+                null, null, null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToNext();
+        assertThat(cursor.getString(0)).isEqualTo("New name");
+    }
+
+    @Test
+    public void testUpdateParticipantWithId() {
+        // change the participant name from Bob to Bobby
+        ContentValues contentValues = new ContentValues(1);
+        contentValues.put(RCS_ALIAS_COLUMN, "Bobby");
+
+        Uri participantUri = Uri.parse("content://rcs/participant/1");
+
+        assertThat(mContentResolver.update(participantUri, contentValues, null, null)).isEqualTo(1);
+
+        // verify participant is actually updated
+        Cursor cursor = mContentResolver.query(participantUri, new String[]{RCS_ALIAS_COLUMN}, null,
+                null, null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToNext();
+        assertThat(cursor.getString(0)).isEqualTo("Bobby");
+    }
+
+    @Test
+    public void testUpdate1To1ThreadParticipantFails() {
+        assertThat(
+                mContentResolver.update(Uri.parse("content://rcs/p2p_thread/1/participant/1"), null,
+                        null, null)).isEqualTo(0);
+    }
+
+    @Test
+    public void testUpdateGroupParticipantFails() {
+        assertThat(mContentResolver.update(Uri.parse("content://rcs/group_thread/2/participant/1"),
+                null, null, null)).isEqualTo(0);
+    }
+
+    @Test
+    public void testUpdateUnifiedMessageViewFails() {
+        ContentValues updateValues = new ContentValues();
+        updateValues.put(ORIGINATION_TIMESTAMP_COLUMN, 1234567890);
+
+        assertThat(mContentResolver.update(Uri.parse("content://rcs/message"), updateValues, null,
+                null)).isEqualTo(0);
+        assertThat(mContentResolver.update(Uri.parse("content://rcs/message/1"), updateValues, null,
+                null)).isEqualTo(0);
+    }
+
+    @Test
+    public void testUpdateMessageOnThreadFails() {
+        ContentValues updateValues = new ContentValues();
+        updateValues.put(ORIGINATION_TIMESTAMP_COLUMN, 1234567890);
+
+        assertThat(mContentResolver.update(Uri.parse("content://rcs/p2p_thread/1/incoming_message"),
+                updateValues, null, null)).isEqualTo(0);
+        assertThat(
+                mContentResolver.update(Uri.parse("content://rcs/p2p_thread/1/incoming_message/1"),
+                        updateValues, null, null)).isEqualTo(0);
+        assertThat(
+                mContentResolver.update(Uri.parse("content://rcs/group_thread/2/outgoing_message"),
+                        updateValues, null, null)).isEqualTo(0);
+        assertThat(mContentResolver.update(
+                Uri.parse("content://rcs/groupp_thread/2/outgoing_message/1"), updateValues, null,
+                null)).isEqualTo(0);
+    }
+
+    @Test
+    public void testUpdateMessage() {
+        // update the message
+        ContentValues updateValues = new ContentValues(1);
+        updateValues.put(ORIGINATION_TIMESTAMP_COLUMN, 1234567890);
+        assertThat(
+                mContentResolver.update(Uri.parse("content://rcs/outgoing_message/2"), updateValues,
+                        null, null)).isEqualTo(1);
+
+        // verify the value is actually updated
+        Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/outgoing_message/2"), null,
+                null, null, null, null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToNext();
+        assertThat(cursor.getLong(5)).isEqualTo(1234567890);
+        cursor.close();
+    }
+
+    @Test
+    @Ignore // TODO: fix and un-ignore
+    public void testUpdateIncomingMessageSpecificColumn() {
+        // update the message
+        ContentValues updateValues = new ContentValues(1);
+        updateValues.put(ARRIVAL_TIMESTAMP_COLUMN, 987654321);
+        assertThat(
+                mContentResolver.update(Uri.parse("content://rcs/incoming_message/3"), updateValues,
+                        null, null)).isEqualTo(1);
+
+        // verify the value is actually updated
+        Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/incoming_message/3"), null,
+                null, null, null, null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToNext();
+        assertThat(cursor.getLong(7)).isEqualTo(987654321);
+        cursor.close();
+    }
+
+    @Test
+    public void testUpdateMessageDelivery() {
+        ContentValues updateValues = new ContentValues();
+        updateValues.put(DELIVERED_TIMESTAMP_COLUMN, 12345);
+        updateValues.put(RcsMessageDeliveryColumns.SEEN_TIMESTAMP_COLUMN, 54321);
+
+        assertThat(mContentResolver.update(Uri.parse("content://rcs/outgoing_message/2/delivery/1"),
+                updateValues, null, null)).isEqualTo(1);
+
+        // verify the value is actually updated
+        Cursor cursor = mContentResolver.query(
+                Uri.parse("content://rcs/outgoing_message/2/delivery"), null, null, null, null,
+                null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToNext();
+        assertThat(cursor.getInt(0)).isEqualTo(2);
+        assertThat(cursor.getInt(1)).isEqualTo(1);
+        assertThat(cursor.getLong(2)).isEqualTo(12345);
+        assertThat(cursor.getLong(3)).isEqualTo(54321);
+    }
+
+    @Test
+    public void testUpdateFileTransfer() {
+        ContentValues updateValues = new ContentValues();
+        updateValues.put(WIDTH_COLUMN, 640);
+        updateValues.put(HEIGHT_COLUMN, 480);
+
+        assertThat(mContentResolver.update(Uri.parse("content://rcs/file_transfer/1"), updateValues,
+                null, null)).isEqualTo(1);
+
+        // verify that the values are actually updated
+        Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/file_transfer/1"), null,
+                null, null, null, null);
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToNext();
+        assertThat(cursor.getInt(8)).isEqualTo(640);
+        assertThat(cursor.getInt(9)).isEqualTo(480);
+    }
+}
diff --git a/tests/src/com/android/providers/telephony/ServiceStateProviderTest.java b/tests/src/com/android/providers/telephony/ServiceStateProviderTest.java
index fd15d01..b6f5508 100644
--- a/tests/src/com/android/providers/telephony/ServiceStateProviderTest.java
+++ b/tests/src/com/android/providers/telephony/ServiceStateProviderTest.java
@@ -16,24 +16,6 @@
 
 package com.android.providers.telephony;
 
-import android.content.Context;
-import android.content.pm.ProviderInfo;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.net.Uri;
-import android.support.test.InstrumentationRegistry;
-import android.telephony.ServiceState;
-import android.telephony.SubscriptionManager;
-import android.test.mock.MockContentResolver;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
 import static android.provider.Telephony.ServiceStateTable;
 import static android.provider.Telephony.ServiceStateTable.getUriForSubscriptionId;
 
@@ -41,13 +23,23 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 
+import android.content.Context;
+import android.content.pm.ProviderInfo;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.test.mock.MockContentResolver;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
 /**
  * Tests for simple queries of ServiceStateProvider.
  *
@@ -87,8 +79,9 @@
         ServiceStateTable.CDMA_ERI_ICON_INDEX,
         ServiceStateTable.CDMA_ERI_ICON_MODE,
         ServiceStateTable.IS_EMERGENCY_ONLY,
-        ServiceStateTable.IS_DATA_ROAMING_FROM_REGISTRATION,
         ServiceStateTable.IS_USING_CARRIER_AGGREGATION,
+        ServiceStateTable.OPERATOR_ALPHA_LONG_RAW,
+        ServiceStateTable.OPERATOR_ALPHA_SHORT_RAW,
     };
 
     @Before
@@ -188,8 +181,9 @@
         final int cdmaEriIconIndex = ss.getCdmaEriIconIndex();
         final int cdmaEriIconMode = ss.getCdmaEriIconMode();
         final int isEmergencyOnly = (ss.isEmergencyOnly()) ? 1 : 0;
-        final int isDataRoamingFromRegistration = (ss.getDataRoamingFromRegistration()) ? 1 : 0;
         final int isUsingCarrierAggregation = (ss.isUsingCarrierAggregation()) ? 1 : 0;
+        final String operatorAlphaLongRaw = ss.getOperatorAlphaLongRaw();
+        final String operatorAlphaShortRaw = ss.getOperatorAlphaShortRaw();
 
         assertEquals(voiceRegState, cursor.getInt(0));
         assertEquals(dataRegState, cursor.getInt(1));
@@ -210,8 +204,9 @@
         assertEquals(cdmaEriIconIndex, cursor.getInt(16));
         assertEquals(cdmaEriIconMode, cursor.getInt(17));
         assertEquals(isEmergencyOnly, cursor.getInt(18));
-        assertEquals(isDataRoamingFromRegistration, cursor.getInt(19));
-        assertEquals(isUsingCarrierAggregation, cursor.getInt(20));
+        assertEquals(isUsingCarrierAggregation, cursor.getInt(19));
+        assertEquals(operatorAlphaLongRaw, cursor.getString(20));
+        assertEquals(operatorAlphaShortRaw, cursor.getString(21));
     }
 
     /**
diff --git a/tests/src/com/android/providers/telephony/SmsProviderTest.java b/tests/src/com/android/providers/telephony/SmsProviderTest.java
index ba63203..2bc5f0f 100644
--- a/tests/src/com/android/providers/telephony/SmsProviderTest.java
+++ b/tests/src/com/android/providers/telephony/SmsProviderTest.java
@@ -52,7 +52,7 @@
  *                 --test-method testInsertUri
  */
 public class SmsProviderTest extends TestCase {
-    private static final String TAG = "TelephonyProviderTest";
+    private static final String TAG = "SmsProviderTest";
 
     private MockContextWithProvider mContext;
     private MockContentResolver mContentResolver;
diff --git a/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java b/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
index 49106ee..37cf6e3 100644
--- a/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
+++ b/tests/src/com/android/providers/telephony/TelephonyBackupAgentTest.java
@@ -40,6 +40,8 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.google.android.mms.pdu.CharacterSets;
+
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -74,6 +76,8 @@
     private final List<ContentValues> mMmsTable = new ArrayList<>();
     /* Table contains parts, addresses of mms */
     private final List<ContentValues> mMmsAllContentValues = new ArrayList<>();
+    /* Table contains parts, addresses of mms for null body test case */
+    private final List<ContentValues> mMmsNullBodyContentValues = new ArrayList<>();
     /* Cursors being used to access sms, mms tables */
     private FakeCursor mSmsCursor, mMmsCursor;
     /* Test data with sms and mms */
@@ -81,7 +85,7 @@
     /* Json representation for the test data */
     private String[] mSmsJson, mMmsJson, mMmsAttachmentJson;
     /* sms, mms json concatenated as json array */
-    private String mAllSmsJson, mAllMmsJson, mMmsAllAttachmentJson;
+    private String mAllSmsJson, mAllMmsJson, mMmsAllAttachmentJson, mMmsAllNullBodyJson;
 
     private StringWriter mStringWriter;
 
@@ -169,7 +173,7 @@
                 new String[]{"+111 (111) 11111111", "+11121212", "example@example.com",
                         "+999999999"} /*addresses*/,
                 3 /*threadId*/, false /*read*/, null /*smil*/, null /*attachmentTypes*/,
-                null /*attachmentFilenames*/);
+                null /*attachmentFilenames*/, mMmsAllContentValues);
 
         mMmsJson[0] = "{\"self_phone\":\"+111111111111111\",\"sub\":\"Subject 1\"," +
                 "\"date\":\"111111\",\"date_sent\":\"111112\",\"m_type\":\"3\",\"v\":\"17\"," +
@@ -191,7 +195,7 @@
                 121 /*body charset*/,
                 new String[]{"+7 (333) ", "example@example.com", "+999999999"} /*addresses*/,
                 4 /*threadId*/, true /*read*/, null /*smil*/, null /*attachmentTypes*/,
-                null /*attachmentFilenames*/);
+                null /*attachmentFilenames*/, mMmsAllContentValues);
         mMmsJson[1] = "{\"date\":\"111122\",\"date_sent\":\"1111112\",\"m_type\":\"4\"," +
                 "\"v\":\"18\",\"msg_box\":\"222\",\"ct_l\":\"location 2\"," +
                 "\"recipients\":[\"example@example.com\",\"+999999999\"]," +
@@ -210,7 +214,7 @@
                 131 /*body charset*/,
                 new String[]{"333 333333333333", "+1232132214124"} /*addresses*/,
                 1 /*threadId*/, false /*read*/, null /*smil*/, null /*attachmentTypes*/,
-                null /*attachmentFilenames*/);
+                null /*attachmentFilenames*/, mMmsAllContentValues);
 
         mMmsJson[2] = "{\"self_phone\":\"+333333333333333\",\"sub\":\"Subject 10\"," +
                 "\"date\":\"111133\",\"date_sent\":\"1111132\",\"m_type\":\"5\",\"v\":\"19\"," +
@@ -239,7 +243,7 @@
                         + " width='100%'/></layout></head><body><par dur='5000ms'>"
                         + "<img src='image000000.jpg' region='Image' /></par></body></smil>",
                 new String[] {"image/jpg"} /*attachmentTypes*/,
-                new String[] {"GreatPict.jpg"}  /*attachmentFilenames*/);
+                new String[] {"GreatPict.jpg"}  /*attachmentFilenames*/, mMmsAllContentValues);
 
         mMmsAttachmentJson[0] = "{\"self_phone\":\"+111111111111111\",\"sub\":\"Subject 1\"," +
                 "\"date\":\"111111\",\"date_sent\":\"111112\",\"m_type\":\"3\",\"v\":\"17\"," +
@@ -255,6 +259,20 @@
 
         mMmsAllAttachmentJson = makeJsonArray(mMmsAttachmentJson);
 
+        createMmsRow(10 /*id*/, 1 /*subid*/, "Subject 1" /*subject*/,
+                100 /*subcharset*/, 111111 /*date*/, 111112 /*datesent*/, 3 /*type*/,
+                17 /*version*/, 0 /*textonly*/,
+                11 /*msgBox*/, "location 1" /*contentLocation*/, "" /*body*/,
+                CharacterSets.DEFAULT_CHARSET /*body charset*/, new String[] {} /*addresses*/,
+                3 /*threadId*/, false /*read*/, null /*smil*/, null /*attachmentTypes*/,
+                null /*attachmentFilenames*/, mMmsNullBodyContentValues);
+
+        mMmsAllNullBodyJson = makeJsonArray(new String[] {"{\"self_phone\":\"+111111111111111\"," +
+                "\"sub\":\"Subject 1\",\"date\":\"111111\",\"date_sent\":\"111112\",\"m_type\":" +
+                "\"3\",\"v\":\"17\",\"msg_box\":\"11\",\"ct_l\":\"location 1\"," +
+                "\"recipients\":[\"+11121212\",\"example@example.com\",\"+999999999\"]," +
+                "\"read\":\"0\", \"mms_addresses\":[],\"mms_charset\":111,\"sub_cs\":\"100\"}"});
+
 
         ContentProvider contentProvider = new MockContentProvider() {
             @Override
@@ -341,7 +359,8 @@
                                        String contentLocation, String body,
                                        int bodyCharset, String[] addresses, long threadId,
                                        boolean read, String smil, String[] attachmentTypes,
-                                       String[] attachmentFilenames) {
+                                       String[] attachmentFilenames,
+                                       List<ContentValues> rowsContainer) {
         ContentValues mmsRow = new ContentValues();
         mmsRow.put(Telephony.Mms._ID, id);
         mmsRow.put(Telephony.Mms.SUBSCRIPTION_ID, subId);
@@ -364,8 +383,8 @@
         final Uri partUri = Telephony.Mms.CONTENT_URI.buildUpon().appendPath(String.valueOf(id)).
                 appendPath("part").build();
         mCursors.put(partUri, createBodyCursor(body, bodyCharset, smil, attachmentTypes,
-                attachmentFilenames));
-        mMmsAllContentValues.add(mmsRow);
+                attachmentFilenames, rowsContainer));
+        rowsContainer.add(mmsRow);
 
         final Uri addrUri = Telephony.Mms.CONTENT_URI.buildUpon().appendPath(String.valueOf(id)).
                 appendPath("addr").build();
@@ -380,7 +399,8 @@
 
     // Cursor with parts of Mms.
     private FakeCursor createBodyCursor(String body, int charset, String existingSmil,
-            String[] attachmentTypes, String[] attachmentFilenames) {
+            String[] attachmentTypes, String[] attachmentFilenames,
+            List<ContentValues> rowsContainer) {
         List<ContentValues> table = new ArrayList<>();
         final String srcName = String.format("text.%06d.txt", 0);
         final String smilBody = TextUtils.isEmpty(existingSmil) ?
@@ -395,7 +415,7 @@
         smilPart.put(Telephony.Mms.Part.CONTENT_ID, "<smil>");
         smilPart.put(Telephony.Mms.Part.CONTENT_LOCATION, "smil.xml");
         smilPart.put(Telephony.Mms.Part.TEXT, smil);
-        mMmsAllContentValues.add(smilPart);
+        rowsContainer.add(smilPart);
 
         // Text part
         final ContentValues bodyPart = new ContentValues();
@@ -407,7 +427,7 @@
         bodyPart.put(Telephony.Mms.Part.CHARSET, charset);
         bodyPart.put(Telephony.Mms.Part.TEXT, body);
         table.add(bodyPart);
-        mMmsAllContentValues.add(bodyPart);
+        rowsContainer.add(bodyPart);
 
         // Attachments
         if (attachmentTypes != null) {
@@ -421,7 +441,7 @@
                 attachmentPart.put(Telephony.Mms.Part.CONTENT_ID, "<"+attachmentFilename+">");
                 attachmentPart.put(Telephony.Mms.Part.CONTENT_LOCATION, attachmentFilename);
                 table.add(attachmentPart);
-                mMmsAllContentValues.add(attachmentPart);
+                rowsContainer.add(attachmentPart);
             }
         }
 
@@ -622,6 +642,17 @@
         assertEquals(7, mmsProvider.getRowsAdded());
     }
 
+    public void testRestoreMmsWithNullBody() throws Exception {
+        JsonReader jsonReader = new JsonReader
+                (new StringReader(addRandomDataToJson(mMmsAllNullBodyJson)));
+        FakeMmsProvider mmsProvider = new FakeMmsProvider(mMmsNullBodyContentValues);
+        mMockContentResolver.addProvider("mms", mmsProvider);
+
+        mTelephonyBackupAgent.putMmsMessagesToProvider(jsonReader);
+
+        assertEquals(3, mmsProvider.getRowsAdded());
+    }
+
     /**
      * Test with quota exceeded. Checking size of the backup before it hits quota and after.
      * It still backs up more than a quota since there is meta-info which matters with small amounts
diff --git a/tests/src/com/android/providers/telephony/TelephonyDatabaseHelperTest.java b/tests/src/com/android/providers/telephony/TelephonyDatabaseHelperTest.java
new file mode 100644
index 0000000..8287352
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/TelephonyDatabaseHelperTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.providers.telephony;
+
+import static android.provider.Telephony.Carriers;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * To run this test, run the following from the dir: packages/providers/TelephonyProvider
+ *    atest TelephonyProviderTests:TelephonyDatabaseHelperTest
+ * Or
+ *    runtest --path tests/src/com/android/providers/telephony/TelephonyDatabaseHelperTest.java
+ */
+@RunWith(JUnit4.class)
+public final class TelephonyDatabaseHelperTest {
+
+    private final static String TAG = TelephonyDatabaseHelperTest.class.getSimpleName();
+
+    private Context mContext;
+    private TelephonyProvider.DatabaseHelper mHelper; // the actual class being tested
+    private SQLiteOpenHelper mInMemoryDbHelper; // used to give us an in-memory db
+
+    @Before
+    public void setUp() {
+        Log.d(TAG, "setUp() +");
+        mContext = InstrumentationRegistry.getContext();
+        mHelper = new TelephonyProvider.DatabaseHelper(mContext);
+        mInMemoryDbHelper = new InMemoryTelephonyProviderV5DbHelper();
+        Log.d(TAG, "setUp() -");
+    }
+
+    @Test
+    public void databaseHelperOnUpgrade_hasApnSetIdField() {
+        Log.d(TAG, "databaseHelperOnUpgrade_hasApnSetIdField");
+        // (5 << 16 | 6) is the first upgrade trigger in onUpgrade
+        SQLiteDatabase db = mInMemoryDbHelper.getWritableDatabase();
+        mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.DatabaseHelper.getVersion(mContext));
+
+        // the upgraded db must have the APN_SET_ID field
+        Cursor cursor = db.query("carriers", null, null, null, null, null, null);
+        String[] upgradedColumns = cursor.getColumnNames();
+        Log.d(TAG, "carriers columns: " + Arrays.toString(upgradedColumns));
+
+        assertTrue(Arrays.asList(upgradedColumns).contains(Carriers.APN_SET_ID));
+    }
+
+    @Test
+    public void databaseHelperOnUpgrade_hasCarrierIdField() {
+        Log.d(TAG, "databaseHelperOnUpgrade_hasSubscriptionTypeField");
+        // (5 << 16 | 6) is the first upgrade trigger in onUpgrade
+        SQLiteDatabase db = mInMemoryDbHelper.getWritableDatabase();
+        mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.DatabaseHelper.getVersion(mContext));
+
+        // the upgraded db must have the Telephony.Carriers.CARRIER_ID field
+        Cursor cursor = db.query("carriers", null, null, null, null, null, null);
+        String[] upgradedColumns = cursor.getColumnNames();
+        Log.d(TAG, "carriers columns: " + Arrays.toString(upgradedColumns));
+        assertTrue(Arrays.asList(upgradedColumns).contains(Carriers.CARRIER_ID));
+    }
+
+    @Test
+    public void databaseHelperOnUpgrade_hasCountryIsoField() {
+        Log.d(TAG, "databaseHelperOnUpgrade_hasCountryIsoField");
+        SQLiteDatabase db = mInMemoryDbHelper.getWritableDatabase();
+        mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.DatabaseHelper.getVersion(mContext));
+
+        // the upgraded db must have the Telephony.Carriers.CARRIER_ID field
+        Cursor cursor = db.query("simInfo", null, null, null, null, null, null);
+        String[] upgradedColumns = cursor.getColumnNames();
+        Log.d(TAG, "iso columns: " + Arrays.toString(upgradedColumns));
+        assertTrue(Arrays.asList(upgradedColumns).contains(SubscriptionManager.ISO_COUNTRY_CODE));
+    }
+
+    @Test
+    public void databaseHelperOnUpgrade_hasProfileClassField() {
+        Log.d(TAG, "databaseHelperOnUpgrade_hasProfileClassField");
+        SQLiteDatabase db = mInMemoryDbHelper.getWritableDatabase();
+        mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.DatabaseHelper.getVersion(mContext));
+
+        // the upgraded db must have the PROFILE_CLASS field
+        Cursor cursor = db.query("siminfo", null, null, null, null, null, null);
+        String[] upgradedColumns = cursor.getColumnNames();
+        Log.d(TAG, "profile class columns: " + Arrays.toString(upgradedColumns));
+        assertTrue(Arrays.asList(upgradedColumns).contains(SubscriptionManager.PROFILE_CLASS));
+    }
+
+    @Test
+    public void databaseHelperOnUpgrade_hasSkip464XlatField() {
+        Log.d(TAG, "databaseHelperOnUpgrade_hasSkip464XlatField");
+        // (5 << 16 | 6) is the first upgrade trigger in onUpgrade
+        SQLiteDatabase db = mInMemoryDbHelper.getWritableDatabase();
+        mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.DatabaseHelper.getVersion(mContext));
+
+        // the upgraded db must have the Telephony.Carriers.CARRIER_ID field
+        Cursor cursor = db.query("carriers", null, null, null, null, null, null);
+        String[] upgradedColumns = cursor.getColumnNames();
+        Log.d(TAG, "carriers columns: " + Arrays.toString(upgradedColumns));
+        assertTrue(Arrays.asList(upgradedColumns).contains(Carriers.SKIP_464XLAT));
+    }
+
+    @Test
+    public void databaseHelperOnUpgrade_columnsMatchNewlyCreatedDb() {
+        Log.d(TAG, "databaseHelperOnUpgrade_columnsMatchNewlyCreatedDb");
+        // (5 << 16 | 6) is the first upgrade trigger in onUpgrade
+        SQLiteDatabase db = mInMemoryDbHelper.getWritableDatabase();
+        mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.DatabaseHelper.getVersion(mContext));
+
+        // compare upgraded carriers table to a carriers table created from scratch
+        db.execSQL(TelephonyProvider.getStringForCarrierTableCreation("carriers_full"));
+
+        Cursor cursor = db.query("carriers", null, null, null, null, null, null);
+        String[] upgradedColumns = cursor.getColumnNames();
+        Log.d(TAG, "carriers columns: " + Arrays.toString(upgradedColumns));
+
+        cursor = db.query("carriers_full", null, null, null, null, null, null);
+        String[] fullColumns = cursor.getColumnNames();
+        Log.d(TAG, "carriers_full colunmns: " + Arrays.toString(fullColumns));
+
+        assertArrayEquals("Carriers table from onUpgrade doesn't match full table",
+                fullColumns, upgradedColumns);
+
+        // compare upgraded siminfo table to siminfo table created from scratch
+        db.execSQL(TelephonyProvider.getStringForSimInfoTableCreation("siminfo_full"));
+
+        cursor = db.query("siminfo", null, null, null, null, null, null);
+        upgradedColumns = cursor.getColumnNames();
+        Log.d(TAG, "siminfo columns: " + Arrays.toString(upgradedColumns));
+
+        cursor = db.query("siminfo_full", null, null, null, null, null, null);
+        fullColumns = cursor.getColumnNames();
+        Log.d(TAG, "siminfo_full colunmns: " + Arrays.toString(fullColumns));
+
+        assertArrayEquals("Siminfo table from onUpgrade doesn't match full table",
+                fullColumns, upgradedColumns);
+    }
+
+    @Test
+    public void databaseHelperOnUpgrade_hasSubscriptionTypeField() {
+        Log.d(TAG, "databaseHelperOnUpgrade_hasSubscriptionTypeField");
+        // (5 << 16 | 6) is the first upgrade trigger in onUpgrade
+        SQLiteDatabase db = mInMemoryDbHelper.getWritableDatabase();
+        mHelper.onUpgrade(db, (4 << 16), TelephonyProvider.DatabaseHelper.getVersion(mContext));
+
+        // the upgraded db must have the SubscriptionManager.SUBSCRIPTION_TYPE field
+        Cursor cursor = db.query("siminfo", null, null, null, null, null, null);
+        String[] upgradedColumns = cursor.getColumnNames();
+        Log.d(TAG, "siminfo columns: " + Arrays.toString(upgradedColumns));
+
+        assertTrue(Arrays.asList(upgradedColumns).contains(SubscriptionManager.SUBSCRIPTION_TYPE));
+    }
+
+    /**
+     * Helper for an in memory DB used to test the TelephonyProvider#DatabaseHelper.
+     *
+     * We pass this in-memory db to DatabaseHelper#onUpgrade so we can use the actual function
+     * without using the actual telephony db.
+     */
+    private static class InMemoryTelephonyProviderV5DbHelper extends SQLiteOpenHelper {
+
+        public InMemoryTelephonyProviderV5DbHelper() {
+            super(InstrumentationRegistry.getContext(),
+                    null,    // db file name is null for in-memory db
+                    null,    // CursorFactory is null by default
+                    1);      // in-memory db version doesn't seem to matter
+            Log.d(TAG, "InMemoryTelephonyProviderV5DbHelper creating in-memory database");
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            // Set up the carriers table without any fields added in onUpgrade
+            // since these are the initial fields, there is no need to update this test fixture in
+            // the future
+            List<String> originalUniqueFields = new ArrayList<String>();
+            originalUniqueFields.add(Carriers.NUMERIC);
+            originalUniqueFields.add(Carriers.MCC);
+            originalUniqueFields.add(Carriers.MNC);
+            originalUniqueFields.add(Carriers.APN);
+            originalUniqueFields.add(Carriers.PROXY);
+            originalUniqueFields.add(Carriers.PORT);
+            originalUniqueFields.add(Carriers.MMSPROXY);
+            originalUniqueFields.add(Carriers.MMSPORT);
+            originalUniqueFields.add(Carriers.MMSC);
+            Log.d(TAG, "InMemoryTelephonyProviderV5DbHelper onCreate creating the carriers table");
+            db.execSQL(
+                    "CREATE TABLE carriers" +
+                    "(_id INTEGER PRIMARY KEY," +
+                    Carriers.NAME + " TEXT DEFAULT ''," +
+                    Carriers.NUMERIC + " TEXT DEFAULT ''," +
+                    Carriers.MCC + " TEXT DEFAULT ''," +
+                    Carriers.MNC + " TEXT DEFAULT ''," +
+                    Carriers.APN + " TEXT DEFAULT ''," +
+                    Carriers.USER + " TEXT DEFAULT ''," +
+                    Carriers.SERVER + " TEXT DEFAULT ''," +
+                    Carriers.PASSWORD + " TEXT DEFAULT ''," +
+                    Carriers.PROXY + " TEXT DEFAULT ''," +
+                    Carriers.PORT + " TEXT DEFAULT ''," +
+                    Carriers.MMSPROXY + " TEXT DEFAULT ''," +
+                    Carriers.MMSPORT + " TEXT DEFAULT ''," +
+                    Carriers.MMSC + " TEXT DEFAULT ''," +
+                    Carriers.TYPE + " TEXT DEFAULT ''," +
+                    Carriers.CURRENT + " INTEGER," +
+                    "UNIQUE (" + TextUtils.join(", ", originalUniqueFields) + "));");
+
+            // set up the siminfo table without any fields added in onUpgrade
+            // since these are the initial fields, there is no need to update this test fixture in
+            // the future
+            Log.d(TAG, "InMemoryTelephonyProviderV5DbHelper onCreate creating the siminfo table");
+            db.execSQL(
+                    "CREATE TABLE siminfo ("
+                    + SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID
+                    + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+                    + SubscriptionManager.ICC_ID + " TEXT NOT NULL,"
+                    + SubscriptionManager.SIM_SLOT_INDEX
+                        + " INTEGER DEFAULT " + SubscriptionManager.SIM_NOT_INSERTED + ","
+                    + SubscriptionManager.DISPLAY_NAME + " TEXT,"
+                    + SubscriptionManager.NAME_SOURCE
+                        + " INTEGER DEFAULT " + SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE + ","
+                    + SubscriptionManager.COLOR
+                        + " INTEGER DEFAULT " + SubscriptionManager.COLOR_DEFAULT + ","
+                    + SubscriptionManager.NUMBER + " TEXT,"
+                    + SubscriptionManager.DISPLAY_NUMBER_FORMAT + " INTEGER NOT NULL"
+                        + " DEFAULT " + SubscriptionManager.DISPLAY_NUMBER_DEFAULT + ","
+                    + SubscriptionManager.DATA_ROAMING
+                        + " INTEGER DEFAULT " + SubscriptionManager.DATA_ROAMING_DEFAULT + ","
+                    + SubscriptionManager.CARD_ID + " TEXT NOT NULL"
+                    + ");");
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            Log.d(TAG, "InMemoryTelephonyProviderV5DbHelper onUpgrade doing nothing");
+            return;
+        }
+    }
+}
diff --git a/tests/src/com/android/providers/telephony/TelephonyProviderTest.java b/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
index dde9e5c..b996dc3 100644
--- a/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
+++ b/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
@@ -16,60 +16,47 @@
 
 package com.android.providers.telephony;
 
-import android.annotation.TargetApi;
-import android.content.ContentProvider;
-import android.content.ContentResolver;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.Manifest;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.content.res.Resources;
-import android.content.SharedPreferences;
-import android.database.Cursor;
 import android.database.ContentObserver;
-import android.database.DatabaseErrorHandler;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteDatabase.CursorFactory;
+import android.database.Cursor;
 import android.net.Uri;
-import android.os.Build;
-import android.os.FileUtils;
 import android.os.Process;
+import android.provider.Telephony;
 import android.provider.Telephony.Carriers;
-import android.support.test.InstrumentationRegistry;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
-import android.test.AndroidTestCase;
-import android.test.mock.MockContentProvider;
 import android.test.mock.MockContentResolver;
 import android.test.mock.MockContext;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.providers.telephony.TelephonyProvider;
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.telephony.uicc.IccRecords;
+import com.android.internal.telephony.uicc.UiccController;
 
 import junit.framework.TestCase;
 
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
-import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.Map;
-import java.util.Set;
-
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.IntStream;
 
 /**
  * Tests for testing CRUD operations of TelephonyProvider.
@@ -90,13 +77,23 @@
     private MockContentResolver mContentResolver;
     private TelephonyProviderTestable mTelephonyProviderTestable;
 
+    @Mock
+    private UiccController mUiccController;
+
+    @Mock
+    private IccRecords mIcRecords;
+
     private int notifyChangeCount;
     private int notifyChangeRestoreCount;
+    private int notifyWfcCount;
+    private int notifyWfcCountWithTestSubId;
 
     private static final String TEST_SUBID = "1";
     private static final String TEST_OPERATOR = "123456";
     private static final String TEST_MCC = "123";
     private static final String TEST_MNC = "456";
+    private static final String TEST_SPN = TelephonyProviderTestable.TEST_SPN;
+
     // Used to test the path for URL_TELEPHONY_USING_SUBID with subid 1
     private static final Uri CONTENT_URI_WITH_SUBID = Uri.parse(
             "content://telephony/carriers/subId/" + TEST_SUBID);
@@ -107,6 +104,10 @@
     // Used to test the preferred apn
     private static final Uri URL_PREFERAPN_USING_SUBID = Uri.parse(
             "content://telephony/carriers/preferapn/subId/" + TEST_SUBID);
+    private static final Uri URL_WFC_ENABLED_USING_SUBID = Uri.parse(
+            "content://telephony/siminfo/" + TEST_SUBID);
+    private static final Uri URL_SIM_APN_LIST = Uri.parse(
+        "content://telephony/carriers/sim_apn_list");
 
     private static final String COLUMN_APN_ID = "apn_id";
 
@@ -126,6 +127,10 @@
         private final MockContentResolver mResolver;
         private TelephonyManager mTelephonyManager = mock(TelephonyManager.class);
 
+        private final List<String> GRANTED_PERMISSIONS = Arrays.asList(
+                Manifest.permission.MODIFY_PHONE_STATE, Manifest.permission.WRITE_APN_SETTINGS,
+                Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+
         public MockContextWithProvider(TelephonyProvider telephonyProvider) {
             mResolver = new MockContentResolver() {
                 @Override
@@ -134,6 +139,10 @@
                     notifyChangeCount++;
                     if (URL_RESTOREAPN_USING_SUBID.equals(uri)) {
                         notifyChangeRestoreCount++;
+                    } else if (SubscriptionManager.WFC_ENABLED_CONTENT_URI.equals(uri)) {
+                        notifyWfcCount++;
+                    } else if (URL_WFC_ENABLED_USING_SUBID.equals(uri)) {
+                        notifyWfcCountWithTestSubId++;
                     }
                 }
             };
@@ -141,6 +150,12 @@
             // return test subId 0 for all operators
             doReturn(TEST_OPERATOR).when(mTelephonyManager).getSimOperator(anyInt());
 
+            doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
+            doReturn(TEST_OPERATOR).when(mTelephonyManager).getSimOperator();
+            doReturn(mIcRecords).when(mUiccController).getIccRecords(anyInt(),
+                    ArgumentMatchers.eq(UiccController.APP_FAM_3GPP));
+            doReturn(TEST_SPN).when(mIcRecords).getServiceProviderName();
+
             // Add authority="telephony" to given telephonyProvider
             ProviderInfo providerInfo = new ProviderInfo();
             providerInfo.authority = "telephony";
@@ -186,7 +201,7 @@
         // Gives permission to write to the APN table within the MockContext
         @Override
         public int checkCallingOrSelfPermission(String permission) {
-            if (TextUtils.equals(permission, "android.permission.WRITE_APN_SETTINGS")) {
+            if (GRANTED_PERMISSIONS.contains(permission)) {
                 Log.d(TAG, "checkCallingOrSelfPermission: permission=" + permission
                         + ", returning PackageManager.PERMISSION_GRANTED");
                 return PackageManager.PERMISSION_GRANTED;
@@ -201,9 +216,11 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        MockitoAnnotations.initMocks(this);
         mTelephonyProviderTestable = new TelephonyProviderTestable();
         mContext = new MockContextWithProvider(mTelephonyProviderTestable);
         mContentResolver = (MockContentResolver) mContext.getContentResolver();
+        replaceInstance(UiccController.class, "mInstance", null, mUiccController);
         notifyChangeCount = 0;
         notifyChangeRestoreCount = 0;
     }
@@ -287,6 +304,64 @@
     }
 
     /**
+     * Test migrating int-based MCC/MNCs over to Strings in the sim info table
+     */
+    @Test
+    @SmallTest
+    public void testMccMncMigration() {
+        CarrierIdProviderTestable carrierIdProvider = new CarrierIdProviderTestable();
+        carrierIdProvider.initializeForTesting(mContext);
+        mContentResolver.addProvider(Telephony.CarrierId.All.CONTENT_URI.getAuthority(),
+                carrierIdProvider);
+        // Insert a few values into the carrier ID db
+        List<String> mccMncs = Arrays.asList("99910", "999110", "999060", "99905");
+        ContentValues[] carrierIdMccMncs = mccMncs.stream()
+                .map((mccMnc) -> {
+                    ContentValues cv = new ContentValues(1);
+                    cv.put(Telephony.CarrierId.All.MCCMNC, mccMnc);
+                    return cv;
+                }).toArray(ContentValues[]::new);
+        mContentResolver.bulkInsert(Telephony.CarrierId.All.CONTENT_URI, carrierIdMccMncs);
+
+        // Populate the sim info db with int-format entries
+        ContentValues[] existingSimInfoEntries = IntStream.range(0, mccMncs.size())
+                .mapToObj((idx) -> {
+                    int mcc = Integer.valueOf(mccMncs.get(idx).substring(0, 3));
+                    int mnc = Integer.valueOf(mccMncs.get(idx).substring(3));
+                    ContentValues cv = new ContentValues(4);
+                    cv.put(SubscriptionManager.MCC, mcc);
+                    cv.put(SubscriptionManager.MNC, mnc);
+                    cv.put(SubscriptionManager.ICC_ID, String.valueOf(idx));
+                    cv.put(SubscriptionManager.CARD_ID, String.valueOf(idx));
+                    return cv;
+        }).toArray(ContentValues[]::new);
+
+        mContentResolver.bulkInsert(SubscriptionManager.CONTENT_URI, existingSimInfoEntries);
+
+        // Run the upgrade helper on all the sim info entries.
+        String[] proj = {SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID,
+                SubscriptionManager.MCC, SubscriptionManager.MNC,
+                SubscriptionManager.MCC_STRING, SubscriptionManager.MNC_STRING};
+        try (Cursor c = mContentResolver.query(SubscriptionManager.CONTENT_URI, proj,
+                null, null, null)) {
+            while (c.moveToNext()) {
+                TelephonyProvider.fillInMccMncStringAtCursor(mContext,
+                        mTelephonyProviderTestable.getWritableDatabase(), c);
+            }
+        }
+
+        // Loop through and make sure that everything got filled in correctly.
+        try (Cursor c = mContentResolver.query(SubscriptionManager.CONTENT_URI, proj,
+                null, null, null)) {
+            while (c.moveToNext()) {
+                String mcc = c.getString(c.getColumnIndexOrThrow(SubscriptionManager.MCC_STRING));
+                String mnc = c.getString(c.getColumnIndexOrThrow(SubscriptionManager.MNC_STRING));
+                assertTrue(mccMncs.contains(mcc + mnc));
+            }
+        }
+    }
+
+    /**
      * Test updating values in carriers table. Verify that when update hits a conflict using URL_ID
      * we merge the rows.
      */
@@ -470,11 +545,13 @@
         final String insertCarrierName = "exampleCarrierName";
         final String insertIccId = "exampleIccId";
         final String insertCardId = "exampleCardId";
+        final int insertProfileClass = SubscriptionManager.PROFILE_CLASS_DEFAULT;
         contentValues.put(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID, insertSubId);
         contentValues.put(SubscriptionManager.DISPLAY_NAME, insertDisplayName);
         contentValues.put(SubscriptionManager.CARRIER_NAME, insertCarrierName);
         contentValues.put(SubscriptionManager.ICC_ID, insertIccId);
         contentValues.put(SubscriptionManager.CARD_ID, insertCardId);
+        contentValues.put(SubscriptionManager.PROFILE_CLASS, insertProfileClass);
 
         Log.d(TAG, "testSimTable Inserting contentValues: " + contentValues);
         mContentResolver.insert(SubscriptionManager.CONTENT_URI, contentValues);
@@ -485,6 +562,7 @@
             SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID,
             SubscriptionManager.CARRIER_NAME,
             SubscriptionManager.CARD_ID,
+            SubscriptionManager.PROFILE_CLASS,
         };
         final String selection = SubscriptionManager.DISPLAY_NAME + "=?";
         String[] selectionArgs = { insertDisplayName };
@@ -500,9 +578,11 @@
         final int resultSubId = cursor.getInt(0);
         final String resultCarrierName = cursor.getString(1);
         final String resultCardId = cursor.getString(2);
+        final int resultProfileClass = cursor.getInt(3);
         assertEquals(insertSubId, resultSubId);
         assertEquals(insertCarrierName, resultCarrierName);
         assertEquals(insertCardId, resultCardId);
+        assertEquals(insertProfileClass, resultProfileClass);
 
         // delete test content
         final String selectionToDelete = SubscriptionManager.DISPLAY_NAME + "=?";
@@ -546,7 +626,7 @@
         mTelephonyProviderTestable.fakeCallingUid(Process.SYSTEM_UID);
 
         final int current = 1;
-        final String numeric = "123456789";
+        final String numeric = TEST_OPERATOR;
 
         // Insert DPC record.
         final String dpcRecordApn = "exampleApnNameDPC";
@@ -668,7 +748,7 @@
         mTelephonyProviderTestable.fakeCallingUid(Process.SYSTEM_UID);
 
         final int current = 1;
-        final String numeric = "123456789";
+        final String numeric = TEST_OPERATOR;
         final String selection = Carriers.NUMERIC + "=?";
         final String[] selectionArgs = { numeric };
 
@@ -746,7 +826,7 @@
             mTelephonyProviderTestable.fakeCallingUid(Process.SYSTEM_UID);
 
             final int current = 1;
-            final String numeric = "123456789";
+            final String numeric = TEST_OPERATOR;
 
             // Insert DPC record.
             final String dpcRecordApn = "exampleApnNameDPC";
@@ -837,7 +917,7 @@
             mTelephonyProviderTestable.fakeCallingUid(Process.SYSTEM_UID);
 
             final int current = 1;
-            final String numeric = "123456789";
+            final String numeric = TEST_OPERATOR;
 
             // Insert DPC record 1.
             final String dpcRecordApn1 = "exampleApnNameDPC";
@@ -1017,7 +1097,7 @@
         editedValue.put(Carriers.NUMERIC, numeric1);
         editedValue.put(Carriers.MCC, mcc1);
         editedValue.put(Carriers.MNC, mnc1);
-        editedValue.put(Carriers.EDITED, value);
+        editedValue.put(Carriers.EDITED_STATUS, value);
         assertNotNull(mContentResolver.insert(URI_TELEPHONY, editedValue));
 
         Cursor cur = mContentResolver.query(URI_TELEPHONY, null, null, null, null);
@@ -1030,13 +1110,13 @@
         values.put(Carriers.NUMERIC, numeric1);
         values.put(Carriers.MCC, mcc1);
         values.put(Carriers.MNC, mnc1);
-        values.put(Carriers.EDITED, Carriers.UNEDITED);
+        values.put(Carriers.EDITED_STATUS, Carriers.UNEDITED);
         mContentResolver.insert(URI_TELEPHONY, values);
 
         String[] testProjection = {
             Carriers.NAME,
             Carriers.APN,
-            Carriers.EDITED,
+            Carriers.EDITED_STATUS,
             Carriers.TYPE,
             Carriers.PROTOCOL,
             Carriers.BEARER_BITMASK,
@@ -1064,7 +1144,7 @@
         editedValue.put(Carriers.NUMERIC, numeric1);
         editedValue.put(Carriers.MCC, mcc1);
         editedValue.put(Carriers.MNC, mnc1);
-        editedValue.put(Carriers.EDITED, value);
+        editedValue.put(Carriers.EDITED_STATUS, value);
         assertNotNull(mContentResolver.insert(URI_TELEPHONY, editedValue));
 
         // insert APN that conflicts with edited APN
@@ -1074,13 +1154,13 @@
         values.put(Carriers.NUMERIC, numeric1);
         values.put(Carriers.MCC, mcc1);
         values.put(Carriers.MNC, mnc1);
-        values.put(Carriers.EDITED, Carriers.UNEDITED);
+        values.put(Carriers.EDITED_STATUS, Carriers.UNEDITED);
         mContentResolver.insert(URI_TELEPHONY, values);
 
         String[] testProjection = {
             Carriers.NAME,
             Carriers.APN,
-            Carriers.EDITED,
+            Carriers.EDITED_STATUS,
             Carriers.TYPE,
             Carriers.PROTOCOL,
             Carriers.BEARER_BITMASK,
@@ -1339,4 +1419,129 @@
         assertEquals(0, cursor.getCount());
         assertEquals(3, notifyChangeRestoreCount);
     }
+
+    /**
+     * Test changes to siminfo/WFC_IMS_ENABLED and simInfo/ENHANCED_4G
+     */
+    @Test
+    @SmallTest
+    public void testUpdateWfcEnabled() {
+        // insert test contentValues
+        ContentValues contentValues = new ContentValues();
+        final int insertSubId = 1;
+        final String insertDisplayName = "exampleDisplayName";
+        final String insertCarrierName = "exampleCarrierName";
+        final String insertIccId = "exampleIccId";
+        final String insertCardId = "exampleCardId";
+        contentValues.put(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID, insertSubId);
+        contentValues.put(SubscriptionManager.DISPLAY_NAME, insertDisplayName);
+        contentValues.put(SubscriptionManager.CARRIER_NAME, insertCarrierName);
+        contentValues.put(SubscriptionManager.ICC_ID, insertIccId);
+        contentValues.put(SubscriptionManager.CARD_ID, insertCardId);
+
+        Log.d(TAG, "testSimTable Inserting wfc contentValues: " + contentValues);
+        mContentResolver.insert(SubscriptionManager.CONTENT_URI, contentValues);
+        assertEquals(0, notifyWfcCount);
+
+        // update wfc_enabled
+        ContentValues values = new ContentValues();
+        values.put(SubscriptionManager.WFC_IMS_ENABLED, true);
+        final String selection = SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=?";
+        final String[] selectionArgs = { "" + insertSubId };
+        mContentResolver.update(SubscriptionManager.CONTENT_URI, values, selection, selectionArgs);
+        assertEquals(1, notifyWfcCount);
+        assertEquals(0, notifyWfcCountWithTestSubId);
+
+        // update other fields
+        values = new ContentValues();
+        values.put(SubscriptionManager.DISPLAY_NAME, "exampleDisplayNameNew");
+        mContentResolver.update(SubscriptionManager.CONTENT_URI, values, selection, selectionArgs);
+        // expect no change on wfc count
+        assertEquals(1, notifyWfcCount);
+        assertEquals(0, notifyWfcCountWithTestSubId);
+
+        // update WFC using subId
+        values = new ContentValues();
+        values.put(SubscriptionManager.WFC_IMS_ENABLED, false);
+        mContentResolver.update(SubscriptionManager.getUriForSubscriptionId(insertSubId),
+                values, null, null);
+        assertEquals(1, notifyWfcCount);
+        assertEquals(0, notifyWfcCountWithTestSubId);
+    }
+
+    protected void replaceInstance(final Class c, final String instanceName,
+            final Object obj, final Object newValue)
+            throws Exception {
+        Field field = c.getDeclaredField(instanceName);
+        field.setAccessible(true);
+        field.set(obj, newValue);
+    }
+
+    @Test
+    @SmallTest
+    public void testSIMAPNLIST_APNMatchTheMCCMNCAndMVNO() {
+        // Test on getCurrentAPNList() step 1
+        final String apnName = "apnName";
+        final String carrierName = "name";
+        final String numeric = TEST_OPERATOR;
+        final String mvnoType = "spn";
+        final String mvnoData = TEST_SPN;
+
+        // Insert the APN and DB only have the MCC/MNC and MVNO APN
+        ContentValues contentValues = new ContentValues();
+        contentValues.put(Carriers.APN, apnName);
+        contentValues.put(Carriers.NAME, carrierName);
+        contentValues.put(Carriers.NUMERIC, numeric);
+        contentValues.put(Carriers.MVNO_TYPE, mvnoType);
+        contentValues.put(Carriers.MVNO_MATCH_DATA, mvnoData);
+        mContentResolver.insert(Carriers.CONTENT_URI, contentValues);
+
+        // Query DB
+        final String[] testProjection =
+                {
+                        Carriers.APN,
+                        Carriers.NAME,
+                        Carriers.NUMERIC,
+                        Carriers.MVNO_MATCH_DATA
+                };
+        Cursor cursor = mContentResolver.query(URL_SIM_APN_LIST,
+                testProjection, null, null, null);
+
+        cursor.moveToFirst();
+        assertEquals(apnName, cursor.getString(0));
+        assertEquals(carrierName, cursor.getString(1));
+        assertEquals(numeric, cursor.getString(2));
+        assertEquals(mvnoData, cursor.getString(3));
+    }
+
+    @Test
+    @SmallTest
+    public void testSIMAPNLIST_APNMatchTheParentMCCMNC() {
+        // Test on getCurrentAPNList() step 2
+        final String apnName = "apnName";
+        final String carrierName = "name";
+        final String numeric = TEST_OPERATOR;
+
+        // Insert the APN and DB only have the MNO APN
+        ContentValues contentValues = new ContentValues();
+        contentValues.put(Carriers.APN, apnName);
+        contentValues.put(Carriers.NAME, carrierName);
+        contentValues.put(Carriers.NUMERIC, numeric);
+        mContentResolver.insert(Carriers.CONTENT_URI, contentValues);
+
+        // Query DB
+        final String[] testProjection =
+                {
+                        Carriers.APN,
+                        Carriers.NAME,
+                        Carriers.NUMERIC,
+                };
+        Cursor cursor = mContentResolver.query(URL_SIM_APN_LIST,
+                testProjection, null, null, null);
+
+        cursor.moveToFirst();
+        assertEquals(apnName, cursor.getString(0));
+        assertEquals(carrierName, cursor.getString(1));
+        assertEquals(numeric, cursor.getString(2));
+    }
 }
diff --git a/tests/src/com/android/providers/telephony/TelephonyProviderTestable.java b/tests/src/com/android/providers/telephony/TelephonyProviderTestable.java
index 0a12831..6c2371d 100644
--- a/tests/src/com/android/providers/telephony/TelephonyProviderTestable.java
+++ b/tests/src/com/android/providers/telephony/TelephonyProviderTestable.java
@@ -15,23 +15,20 @@
  */
 package com.android.providers.telephony;
 
-import android.content.Context;
+import static android.provider.Telephony.Carriers.*;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
-import android.provider.Telephony;
-import android.support.test.InstrumentationRegistry;
-import android.telephony.SubscriptionManager;
-import android.text.TextUtils;
 import android.util.Log;
 
-import java.util.List;
-import java.util.ArrayList;
+import androidx.test.InstrumentationRegistry;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.providers.telephony.TelephonyProvider;
-import static android.provider.Telephony.Carriers.*;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
 
 /**
  * A subclass of TelephonyProvider used for testing on an in-memory database