Merge "Revert "Revert "Update SIM info table to have strings for mcc mnc"""
diff --git a/src/com/android/providers/telephony/TelephonyProvider.java b/src/com/android/providers/telephony/TelephonyProvider.java
index d0043e2..df80276 100644
--- a/src/com/android/providers/telephony/TelephonyProvider.java
+++ b/src/com/android/providers/telephony/TelephonyProvider.java
@@ -95,6 +95,7 @@
 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;
@@ -128,6 +129,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.zip.CRC32;
 import java.util.Set;
@@ -139,7 +141,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 = 27 << 16;
     private static final int URL_UNKNOWN = 0;
     private static final int URL_TELEPHONY = 1;
     private static final int URL_CURRENT = 2;
@@ -345,6 +347,8 @@
                 + " 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.SIM_PROVISIONING_STATUS
                 + " INTEGER DEFAULT " + SubscriptionManager.SIM_PROVISIONED + ","
                 + SubscriptionManager.IS_EMBEDDED + " INTEGER DEFAULT 0,"
@@ -1071,6 +1075,32 @@
                 }
                 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 (DBG) {
                 log("dbh.onUpgrade:- db=" + db + " oldV=" + oldVersion + " newV=" + newVersion);
             }
@@ -3358,12 +3388,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/src/com/android/providers/telephony/CarrierIdProviderTest.java b/tests/src/com/android/providers/telephony/CarrierIdProviderTest.java
index 4adcf43..44d9ec9 100644
--- a/tests/src/com/android/providers/telephony/CarrierIdProviderTest.java
+++ b/tests/src/com/android/providers/telephony/CarrierIdProviderTest.java
@@ -104,14 +104,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());
 
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/TelephonyProviderTest.java b/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
index dde9e5c..465b5b1 100644
--- a/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
+++ b/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
@@ -16,59 +16,40 @@
 
 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 junit.framework.TestCase;
 
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Test;
-import org.mockito.ArgumentCaptor;
 
-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.util.Arrays;
+import java.util.List;
+import java.util.stream.IntStream;
 
 
 /**
@@ -126,6 +107,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
@@ -186,7 +171,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;
@@ -287,6 +272,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.
      */