Create APIs to properly backup and restore per-sim configs.

Bug: 172244913
Test: make
Change-Id: I39d31f4b753b924bc6773978546388d546be54b2
diff --git a/src/com/android/providers/telephony/TelephonyProvider.java b/src/com/android/providers/telephony/TelephonyProvider.java
index 1d10cf9..681c307 100644
--- a/src/com/android/providers/telephony/TelephonyProvider.java
+++ b/src/com/android/providers/telephony/TelephonyProvider.java
@@ -69,16 +69,21 @@
 import static android.provider.Telephony.Carriers.USER_VISIBLE;
 import static android.provider.Telephony.Carriers.WAIT_TIME_RETRY;
 import static android.provider.Telephony.Carriers._ID;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.app.compat.CompatChanges;
 import android.content.ComponentName;
 import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
+import android.content.OperationApplicationException;
 import android.content.ServiceConnection;
 import android.content.SharedPreferences;
 import android.content.UriMatcher;
@@ -94,8 +99,10 @@
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.IBinder;
+import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemProperties;
@@ -103,9 +110,11 @@
 import android.provider.Telephony;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.TelephonyProtoEnums;
 import android.telephony.data.ApnSetting;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Xml;
@@ -113,29 +122,35 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyStatsLog;
 import com.android.internal.util.XmlUtils;
 import android.service.carrier.IApnSourceService;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.Integer;
+import java.lang.NoSuchFieldException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 import java.util.zip.CheckedInputStream;
 import java.util.zip.CRC32;
 
@@ -178,6 +193,8 @@
     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 int URL_SIMINFO_SUW_RESTORE = 28;
+    private static final int URL_SIMINFO_SIM_INSERTED_RESTORE = 29;
 
     /**
      * Default value for mtu if it's not set. Moved from PhoneConstants.
@@ -243,6 +260,18 @@
     private static final String ORDER_BY_SUB_ID =
             Telephony.SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID + " ASC";
 
+    @VisibleForTesting
+    static final String BACKED_UP_SIM_SPECIFIC_SETTINGS_FILE = "sim_specific_settings_file";
+    // Holds names and value types of SimInfoDb columns to backup.
+    private static final Map<String, Integer> SIM_INFO_COLUMNS_TO_BACKUP = new HashMap();
+    private static final String KEY_SIMINFO_DB_ROW_PREFIX = "KEY_SIMINFO_DB_ROW_";
+    private static final int DEFAULT_INT_COLUMN_VALUE = -111;
+    private static final String SIM_INSERTED_RESTORE_URI_SUFFIX = "sim_inserted_restore";
+    @VisibleForTesting
+    static final String KEY_BACKUP_DATA_FORMAT_VERSION = "KEY_BACKUP_DATA_FORMAT_VERSION";
+    @VisibleForTesting
+    static final String KEY_PREVIOUSLY_RESTORED_SUB_IDS = "KEY_PREVIOUSLY_RESTORED_SUB_IDS";
+
     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>();
@@ -351,6 +380,25 @@
         MVNO_TYPE_STRING_MAP.put("imsi", ApnSetting.MVNO_TYPE_IMSI);
         MVNO_TYPE_STRING_MAP.put("gid", ApnSetting.MVNO_TYPE_GID);
         MVNO_TYPE_STRING_MAP.put("iccid", ApnSetting.MVNO_TYPE_ICCID);
+
+        // To B&R a new config, simply add the column name and its appropriate value type to
+        // SIM_INFO_COLUMNS_TO_BACKUP. To no longer B&R a column, simply remove it from
+        // SIM_INFO_COLUMNS_TO_BACKUP. For both cases, add appropriate versioning logic in
+        // convertBackedUpDataToContentValues(ContentValues contenValues)
+        SIM_INFO_COLUMNS_TO_BACKUP.put(
+                Telephony.SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID, Cursor.FIELD_TYPE_INTEGER);
+        SIM_INFO_COLUMNS_TO_BACKUP.put(
+                Telephony.SimInfo.COLUMN_ICC_ID, Cursor.FIELD_TYPE_STRING);
+        SIM_INFO_COLUMNS_TO_BACKUP.put(
+                Telephony.SimInfo.COLUMN_NUMBER, Cursor.FIELD_TYPE_STRING);
+        SIM_INFO_COLUMNS_TO_BACKUP.put(
+            Telephony.SimInfo.COLUMN_CARRIER_ID, Cursor.FIELD_TYPE_INTEGER);
+        SIM_INFO_COLUMNS_TO_BACKUP.put(
+            Telephony.SimInfo.COLUMN_IMS_RCS_UCE_ENABLED, Cursor.FIELD_TYPE_INTEGER);
+        SIM_INFO_COLUMNS_TO_BACKUP.put(
+                Telephony.SimInfo.COLUMN_ENHANCED_4G_MODE_ENABLED, Cursor.FIELD_TYPE_INTEGER);
+        SIM_INFO_COLUMNS_TO_BACKUP.put(
+                Telephony.SimInfo.COLUMN_VT_IMS_ENABLED, Cursor.FIELD_TYPE_INTEGER);
     }
 
     @VisibleForTesting
@@ -487,6 +535,11 @@
 
         s_urlMatcher.addURI("telephony", "siminfo", URL_SIMINFO);
         s_urlMatcher.addURI("telephony", "siminfo/#", URL_SIMINFO_USING_SUBID);
+        s_urlMatcher.addURI("telephony", "siminfo/backup_and_restore/suw_restore",
+                URL_SIMINFO_SUW_RESTORE);
+        s_urlMatcher.addURI("telephony", "siminfo/backup_and_restore/" +
+                SIM_INSERTED_RESTORE_URI_SUFFIX,
+                URL_SIMINFO_SIM_INSERTED_RESTORE);
 
         s_urlMatcher.addURI("telephony", "carriers/subId/*", URL_TELEPHONY_USING_SUBID);
         s_urlMatcher.addURI("telephony", "carriers/current/subId/*", URL_CURRENT_USING_SUBID);
@@ -2986,6 +3039,459 @@
     }
 
     @Override
+    public synchronized Bundle call(String method, @Nullable String args, @Nullable Bundle bundle) {
+        if (SubscriptionManager.GET_SIM_SPECIFIC_SETTINGS_METHOD_NAME.equals(method)) {
+            getContext().enforceCallingOrSelfPermission(
+                    android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, TAG);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return retrieveSimSpecificSettings();
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        } else if (SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME.equals(method)) {
+            getContext().enforceCallingOrSelfPermission(
+                    android.Manifest.permission.MODIFY_PHONE_STATE, TAG);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                restoreSimSpecificSettings(bundle, args);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        } else {
+            loge("method is not recognized");
+        }
+
+        return null;
+    }
+
+    /**
+     * See {@link SubscriptionController#GET_SIM_SPECIFIC_SETTINGS_METHOD_NAME} for details
+     */
+    private Bundle retrieveSimSpecificSettings() {
+        Bundle resultBundle = new Bundle();
+        resultBundle.putByteArray(SubscriptionManager.KEY_SIM_SPECIFIC_SETTINGS_DATA,
+                getSimSpecificDataToBackUp());
+
+        return resultBundle;
+    }
+
+    /**
+     * Attempts to restore the backed up sim-specific configs to device. End result is SimInfoDB is
+     * modified to match any backed up configs for the appropriate inserted sims.
+     *
+     * @param bundle containing the data to be restored. If {@code null}, then backed up
+     * data should already be in internal storage and will be retrieved from there.
+     * @param iccId of the SIM that a restore is being attempted for. If {@code null}, then try to
+     * restore for all simInfo entries in SimInfoDB
+     */
+    private void restoreSimSpecificSettings(@Nullable Bundle bundle, @Nullable String iccId) {
+        int restoreCase = TelephonyProtoEnums.SIM_RESTORE_CASE_UNDEFINED_USE_CASE;
+        if (bundle != null) {
+            restoreCase = TelephonyProtoEnums.SIM_RESTORE_CASE_SUW;
+            if (!writeSimSettingsToInternalStorage(
+                    bundle.getByteArray(SubscriptionManager.KEY_SIM_SPECIFIC_SETTINGS_DATA))) {
+                return;
+            }
+        } else if (iccId != null){
+            restoreCase = TelephonyProtoEnums.SIM_RESTORE_CASE_SIM_INSERTED;
+        }
+        mergeBackedUpDataToSimInfoDb(restoreCase, iccId);
+    }
+
+    @VisibleForTesting
+    boolean writeSimSettingsToInternalStorage(byte[] data) {
+        File file = new File(getContext().getFilesDir(), BACKED_UP_SIM_SPECIFIC_SETTINGS_FILE);
+        try (FileOutputStream fos = new FileOutputStream(file)) {
+            fos.write(data);
+        } catch (IOException e) {
+            loge("Not able to create internal file with per-sim configs. Failed with error "
+                    + e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Attempt to match any SimInfoDB entries to what is in the internal backup data file and
+     * update DB entry with the adequate backed up data.
+     *
+     * @param restoreCase one of the SimSpecificSettingsRestoreMatchingCriteria values defined in
+     * frameworks/proto_logging/stats/enums/telephony/enums.proto
+     * @param iccId of the SIM that a restore is being attempted for. If {@code null}, then try to
+     * restore for all simInfo entries in SimInfoDB
+     */
+    private void mergeBackedUpDataToSimInfoDb(int restoreCase, @Nullable String iccId) {
+        // Get data stored in internal file
+        File file = new File(getContext().getFilesDir(), BACKED_UP_SIM_SPECIFIC_SETTINGS_FILE);
+        if (!file.exists()) {
+            loge("internal sim-specific settings backup data file does not exist. "
+                + "Aborting restore");
+            return;
+        }
+
+        PersistableBundle bundle = null;
+        try (FileInputStream fis = new FileInputStream(file)) {
+            bundle = PersistableBundle.readFromStream(fis);
+        } catch (IOException e) {
+            loge("Failed to convert backed up per-sim configs to bundle. Stopping restore. "
+                + "Failed with error " + e);
+            return;
+        }
+
+        String selection = null;
+        String[] selectionArgs = null;
+        if (iccId != null) {
+            selection = Telephony.SimInfo.COLUMN_ICC_ID + "=?";
+            selectionArgs = new String[]{iccId};
+        }
+        try (Cursor cursor = query(
+                SubscriptionManager.CONTENT_URI,
+                new String[]{
+                        Telephony.SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID,
+                        Telephony.SimInfo.COLUMN_ICC_ID,
+                        Telephony.SimInfo.COLUMN_NUMBER,
+                        Telephony.SimInfo.COLUMN_CARRIER_ID},
+                selection,
+                selectionArgs,
+                ORDER_BY_SUB_ID)) {
+            findAndRestoreAllMatches(bundle, cursor, restoreCase);
+        }
+    }
+
+    private void findAndRestoreAllMatches(PersistableBundle backedUpDataBundle, Cursor cursor,
+            int restoreCase) {
+        int[] previouslyRestoredSubIdsArray =
+                backedUpDataBundle.getIntArray(KEY_PREVIOUSLY_RESTORED_SUB_IDS);
+        List<Integer> previouslyRestoredSubIdsList = previouslyRestoredSubIdsArray != null
+                ? Arrays.stream(previouslyRestoredSubIdsArray).boxed().collect(Collectors.toList())
+                : new ArrayList<>();
+        List<Integer> newlyRestoredSubIds = new ArrayList<>();
+        int backupDataFormatVersion = backedUpDataBundle
+                .getInt(KEY_BACKUP_DATA_FORMAT_VERSION, -1);
+
+        while (cursor != null && cursor.moveToNext()) {
+            // Get all the possible matching criteria.
+            int subIdColumnIndex = cursor.getColumnIndex(
+                    Telephony.SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID);
+            int currSubIdFromDb = cursor.getInt(subIdColumnIndex);
+
+            if (previouslyRestoredSubIdsList.contains(currSubIdFromDb)) {
+                // Abort restore for any sims that were previously restored.
+                continue;
+            }
+
+            int iccIdColumnIndex = cursor.getColumnIndex(Telephony.SimInfo.COLUMN_ICC_ID);
+            String currIccIdFromDb = cursor.getString(iccIdColumnIndex);
+
+            int phoneNumberColumnIndex = cursor.getColumnIndex(Telephony.SimInfo.COLUMN_NUMBER);
+            String currPhoneNumberFromDb = cursor.getString(phoneNumberColumnIndex);
+
+            int carrierIdColumnIndex = cursor.getColumnIndex(Telephony.SimInfo.COLUMN_CARRIER_ID);
+            int currCarrierIdFromDb = cursor.getInt(carrierIdColumnIndex);
+
+            // Find the best match from backed up data.
+            SimRestoreMatch bestRestoreMatch = null;
+            for (int rowNum = 0; true; rowNum++) {
+                PersistableBundle currRow = backedUpDataBundle.getPersistableBundle(
+                        KEY_SIMINFO_DB_ROW_PREFIX + rowNum);
+                if (currRow == null) {
+                    break;
+                }
+
+                SimRestoreMatch currSimRestoreMatch = new SimRestoreMatch(
+                        currIccIdFromDb, currCarrierIdFromDb, currPhoneNumberFromDb, currRow,
+                        backupDataFormatVersion);
+
+                if (currSimRestoreMatch == null) {
+                    continue;
+                }
+
+                /*
+                 * The three following match cases are ordered by descending priority:
+                 *   - Match by iccId: If iccId of backup data matches iccId of any inserted sims,
+                 *       we confidently restore all configs.
+                 *   - Match phone number and carrierId: If both of these values match, we
+                 *       confidently restore all configs.
+                 *   - Match only carrierId: If only carrierId of backup data matches an inserted
+                 *       sim, we only restore non-sensitive configs.
+                 *
+                 * Having a matchScore value for each match allows us to control these priorities.
+                 */
+                if (bestRestoreMatch == null || (currSimRestoreMatch.getMatchScore()
+                        >= bestRestoreMatch.getMatchScore()
+                        && currSimRestoreMatch.getContentValues() != null)) {
+                    bestRestoreMatch = currSimRestoreMatch;
+                }
+            }
+
+            if (bestRestoreMatch != null) {
+                if (bestRestoreMatch.getMatchScore() != 0) {
+                    if (restoreCase == TelephonyProtoEnums.SIM_RESTORE_CASE_SUW) {
+                        update(SubscriptionManager.SIM_INFO_SUW_RESTORE_CONTENT_URI,
+                                bestRestoreMatch.getContentValues(),
+                                Telephony.SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID + "=?",
+                                new String[]{Integer.toString(currSubIdFromDb)});
+                    } else if (restoreCase == TelephonyProtoEnums.SIM_RESTORE_CASE_SIM_INSERTED) {
+                        Uri simInsertedRestoreUri = Uri.withAppendedPath(
+                                SubscriptionManager.SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI,
+                                SIM_INSERTED_RESTORE_URI_SUFFIX);
+                        update(simInsertedRestoreUri,
+                                bestRestoreMatch.getContentValues(),
+                                Telephony.SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID + "=?",
+                                new String[]{Integer.toString(currSubIdFromDb)});
+                    }
+                    log("Restore of inserterd SIM's sim-specific settings has been successfully "
+                            + "completed.");
+                    TelephonyStatsLog.write(TelephonyStatsLog.SIM_SPECIFIC_SETTINGS_RESTORED,
+                            TelephonyProtoEnums.SIM_RESTORE_RESULT_SUCCESS,
+                            restoreCase, bestRestoreMatch.getMatchingCriteriaForLogging());
+                    newlyRestoredSubIds.add(currSubIdFromDb);
+                } else {
+                    TelephonyStatsLog.write(TelephonyStatsLog.SIM_SPECIFIC_SETTINGS_RESTORED,
+                            TelephonyProtoEnums.SIM_RESTORE_RESULT_NONE_MATCH,
+                            restoreCase, bestRestoreMatch.getMatchingCriteriaForLogging());
+                }
+            } else {
+                log("No matching SIM in backup data. SIM-specific settings not restored.");
+                TelephonyStatsLog.write(TelephonyStatsLog.SIM_SPECIFIC_SETTINGS_RESTORED,
+                        TelephonyProtoEnums.SIM_RESTORE_RESULT_ZERO_SIM_IN_BACKUP,
+                        restoreCase, TelephonyProtoEnums.SIM_RESTORE_MATCHING_CRITERIA_NONE);
+            }
+        }
+
+        // Update the internal file with subIds that we just restored.
+        previouslyRestoredSubIdsList.addAll(newlyRestoredSubIds);
+        backedUpDataBundle.putIntArray(
+                KEY_PREVIOUSLY_RESTORED_SUB_IDS,
+                previouslyRestoredSubIdsList.stream().mapToInt(i -> i).toArray());
+        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
+            backedUpDataBundle.writeToStream(outputStream);
+            writeSimSettingsToInternalStorage(outputStream.toByteArray());
+        } catch (IOException e) {
+            loge("Not able to convert SimInfoDB to byte array. Not storing which subIds were "
+                    + "restored");
+        }
+    }
+
+    private static class SimRestoreMatch {
+
+        private Set<Integer> matches = new ArraySet<>();
+        private int subId;
+        private ContentValues contentValues;
+        private int matchingCriteria;
+        private int matchScore;
+
+        private static final int ICCID_MATCH = 1;
+        private static final int PHONE_NUMBER_MATCH = 2;
+        private static final int CARRIER_ID_MATCH = 3;
+
+        public SimRestoreMatch(String iccIdFromDb, int carrierIdFromDb,
+                String phoneNumberFromDb, PersistableBundle backedUpSimInfoEntry,
+                int backupDataFormatVersion) {
+            subId = backedUpSimInfoEntry.getInt(
+                Telephony.SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID,
+                DEFAULT_INT_COLUMN_VALUE);
+            String iccIdFromBackup = backedUpSimInfoEntry.getString(Telephony.SimInfo.COLUMN_ICC_ID,
+                  "");
+            String phoneNumberFromBackup = backedUpSimInfoEntry.getString(
+                  Telephony.SimInfo.COLUMN_NUMBER, "");
+            int carrierIdFromBackup = backedUpSimInfoEntry.getInt(
+                  Telephony.SimInfo.COLUMN_CARRIER_ID,
+                  TelephonyManager.UNKNOWN_CARRIER_ID);
+
+
+            // find all matching fields
+            if (iccIdFromDb != null && iccIdFromDb.equals(iccIdFromBackup)
+                    && !iccIdFromBackup.isEmpty()) {
+                matches.add(ICCID_MATCH);
+            }
+            if (carrierIdFromDb == carrierIdFromBackup
+                    && carrierIdFromBackup != TelephonyManager.UNKNOWN_CARRIER_ID) {
+                matches.add(CARRIER_ID_MATCH);
+            }
+            if (phoneNumberFromDb != null && phoneNumberFromDb.equals(phoneNumberFromBackup)
+                    && !phoneNumberFromBackup.isEmpty()) {
+                matches.add(PHONE_NUMBER_MATCH);
+            }
+
+            contentValues = convertBackedUpDataToContentValues(
+                    backedUpSimInfoEntry, backupDataFormatVersion);
+            matchScore = calculateMatchScore();
+            matchingCriteria = calculateMatchingCriteria();
+        }
+
+        public int getSubId() {
+            return subId;
+        }
+
+        public ContentValues getContentValues() {
+            return contentValues;
+        }
+
+        public int getMatchScore() {
+            return matchScore;
+        }
+
+        private int calculateMatchScore() {
+            int score = 0;
+
+            if (matches.contains(ICCID_MATCH)) {
+                score += 100;
+            }
+            if (matches.contains(CARRIER_ID_MATCH)) {
+                if (matches.contains(PHONE_NUMBER_MATCH)) {
+                    score += 10;
+                } else {
+                    score += 1;
+                }
+            }
+
+            return score;
+        }
+
+        public int getMatchingCriteriaForLogging() {
+            return matchingCriteria;
+        }
+
+        private int calculateMatchingCriteria() {
+            if (matches.contains(ICCID_MATCH)) {
+                return TelephonyProtoEnums.SIM_RESTORE_MATCHING_CRITERIA_ICCID;
+            }
+            if (matches.contains(CARRIER_ID_MATCH)) {
+                if (matches.contains(PHONE_NUMBER_MATCH)) {
+                    return TelephonyProtoEnums
+                        .SIM_RESTORE_MATCHING_CRITERIA_CARRIER_ID_AND_PHONE_NUMBER;
+                } else {
+                    return TelephonyProtoEnums.SIM_RESTORE_MATCHING_CRITERIA_CARRIER_ID_ONLY;
+                }
+            }
+            return TelephonyProtoEnums.SIM_RESTORE_MATCHING_CRITERIA_NONE;
+        }
+
+        private ContentValues convertBackedUpDataToContentValues(
+                PersistableBundle backedUpSimInfoEntry, int backupDataFormatVersion) {
+            if (DATABASE_VERSION != 48 << 16) {
+                throw new AssertionError("The database schema has been updated which might make "
+                    + "the format of #BACKED_UP_SIM_SPECIFIC_SETTINGS_FILE outdated. Make sure to "
+                    + "1) review whether any of the columns in #SIM_INFO_COLUMNS_TO_BACKUP have "
+                    + "been migrated or deleted, 2) add the new column name into one of those "
+                    + "maps, 3) add migration code in this method as necessary, and 4) update the "
+                    + "version check in this if statement.");
+            }
+            ContentValues contentValues = new ContentValues();
+            // Don't restore anything if restoring from a newer version of the current database.
+            if (backupDataFormatVersion > DATABASE_VERSION) {
+                return null;
+            }
+
+            /* Any migration logic should be placed under this comment block.
+             * ex:
+             *   if (backupDataFormatVersion >= 48 << 19) {
+             *     contentValues.put(NEW_COLUMN_NAME_2,
+             *         backedUpSimInfoEntry.getInt( OLD_COLUMN_NAME, DEFAULT_INT_COLUMN_VALUE));
+             *     ...
+             *   } else if (backupDataFormatVersion >= 48 << 17) {
+             *     contentValues.put(NEW_COLUMN_NAME_1,
+             *         backedUpSimInfoEntry.getInt(OLD_COLUMN_NAME, DEFAULT_INT_COLUMN_VALUE));
+             *     ...
+             *   } else {
+             *     // The values from the first format of backup ever available.
+             *     contentValues.put(Telephony.SimInfo.COLUMN_IMS_RCS_UCE_ENABLED,
+             *         backedUpSimInfoEntry.getInt(
+             *             Telephony.SimInfo.COLUMN_IMS_RCS_UCE_ENABLED,
+             *             DEFAULT_INT_COLUMN_VALUE));
+             *     contentValues.put(Telephony.SimInfo.COLUMN_ENHANCED_4G_MODE_ENABLED,
+             *         backedUpSimInfoEntry.getString(
+             *              Telephony.SimInfo.COLUMN_ENHANCED_4G_MODE_ENABLED, ""));
+             *     contentValues.put(Telephony.SimInfo.COLUMN_VT_IMS_ENABLED,
+             *               backedUpSimInfoEntry.getString(Telephony.SimInfo.COLUMN_VT_IMS_ENABLED,
+             *               ""));
+             *   }
+             *
+             * Also make sure to add necessary removal of sensitive settings in
+             * polishContentValues(ContentValues contentValues).
+             */
+            contentValues.put(Telephony.SimInfo.COLUMN_IMS_RCS_UCE_ENABLED,
+                    backedUpSimInfoEntry.getInt(
+                            Telephony.SimInfo.COLUMN_IMS_RCS_UCE_ENABLED,
+                            DEFAULT_INT_COLUMN_VALUE));
+            contentValues.put(Telephony.SimInfo.COLUMN_ENHANCED_4G_MODE_ENABLED,
+                    backedUpSimInfoEntry.getInt(
+                            Telephony.SimInfo.COLUMN_ENHANCED_4G_MODE_ENABLED,
+                            DEFAULT_INT_COLUMN_VALUE));
+            contentValues.put(Telephony.SimInfo.COLUMN_VT_IMS_ENABLED,
+                    backedUpSimInfoEntry.getInt(
+                            Telephony.SimInfo.COLUMN_VT_IMS_ENABLED,
+                            DEFAULT_INT_COLUMN_VALUE));
+
+            return polishContentValues(contentValues);
+        }
+
+        private ContentValues polishContentValues(ContentValues contentValues) {
+            if (matches.contains(ICCID_MATCH)) {
+                return contentValues;
+            } else if (matches.contains(CARRIER_ID_MATCH)) {
+                if (!matches.contains(PHONE_NUMBER_MATCH)) {
+                    // Low confidence match should not restore sensitive configs.
+                    if (contentValues.containsKey(Telephony.SimInfo.COLUMN_IMS_RCS_UCE_ENABLED)) {
+                        contentValues.remove(Telephony.SimInfo.COLUMN_IMS_RCS_UCE_ENABLED);
+                    }
+                }
+                return contentValues;
+            }
+            return null;
+        }
+
+    }
+
+    /**
+     * Retrieves data from all columns in SimInfoDB of backup/restore interest.
+     *
+     * @return data of interest from SimInfoDB as a byte array.
+     */
+    private byte[] getSimSpecificDataToBackUp() {
+        String[] projection = SIM_INFO_COLUMNS_TO_BACKUP.keySet()
+                .toArray(new String[SIM_INFO_COLUMNS_TO_BACKUP.size()]);
+
+        try (Cursor cursor = query(SubscriptionManager.CONTENT_URI, projection, null, null, null);
+                ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
+            PersistableBundle topLevelBundle = new PersistableBundle();
+            topLevelBundle.putInt(KEY_BACKUP_DATA_FORMAT_VERSION, DATABASE_VERSION);
+            for (int rowNum = 0; cursor != null && cursor.moveToNext(); rowNum++) {
+                PersistableBundle rowBundle = convertSimInfoDbEntryToPersistableBundle(cursor);
+                topLevelBundle.putPersistableBundle(KEY_SIMINFO_DB_ROW_PREFIX + rowNum, rowBundle);
+            }
+            topLevelBundle.writeToStream(outputStream);
+            return outputStream.toByteArray();
+        } catch (IOException e) {
+            loge("Not able to convert SimInfoDB to byte array. Returning empty byte array");
+            return new byte[0];
+        }
+    }
+
+    private static PersistableBundle convertSimInfoDbEntryToPersistableBundle(Cursor cursor) {
+        PersistableBundle bundle = new PersistableBundle();
+        for (Map.Entry<String, Integer> column : SIM_INFO_COLUMNS_TO_BACKUP.entrySet()) {
+            String columnName = column.getKey();
+            int columnType = column.getValue();
+            int columnIndex = cursor.getColumnIndex(columnName);
+            if (columnType == Cursor.FIELD_TYPE_INTEGER) {
+                bundle.putInt(columnName, cursor.getInt(columnIndex));
+            } else if (columnType == Cursor.FIELD_TYPE_STRING) {
+                bundle.putString(columnName, cursor.getString(columnIndex));
+            } else {
+                throw new AssertionError("SimInfoDB column to be backed up is not recognized. Make "
+                    + "sure to properly add the desired colum name and value type to "
+                    + "SIM_INFO_COLUMNS_TO_BACKUP.");
+            }
+        }
+
+        return bundle;
+    }
+
+    @Override
     public synchronized Cursor query(Uri url, String[] projectionIn, String selection,
             String[] selectionArgs, String sort) {
         if (VDBG) log("query: url=" + url + ", projectionIn=" + projectionIn + ", selection="
@@ -3948,6 +4454,15 @@
                 break;
             }
 
+            case URL_SIMINFO_SUW_RESTORE:
+                count = db.update(SIMINFO_TABLE, values, where, whereArgs);
+                uriType = URL_SIMINFO_SUW_RESTORE;
+                break;
+
+            case URL_SIMINFO_SIM_INSERTED_RESTORE:
+                count = db.update(SIMINFO_TABLE, values, where, whereArgs);
+                break;
+
             default: {
                 throw new UnsupportedOperationException("Cannot update that URL: " + url);
             }
@@ -3956,6 +4471,12 @@
         if (count > 0) {
             boolean usingSubId = false;
             switch (uriType) {
+                case URL_SIMINFO_SIM_INSERTED_RESTORE:
+                    break;
+                case URL_SIMINFO_SUW_RESTORE:
+                    getContext().getContentResolver().notifyChange(
+                            SubscriptionManager.SIM_INFO_SUW_RESTORE_CONTENT_URI, null);
+                    // intentional fall through from above case
                 case URL_SIMINFO_USING_SUBID:
                     usingSubId = true;
                     // intentional fall through from above case
diff --git a/tests/src/com/android/providers/telephony/TelephonyProviderTest.java b/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
index 203848a..46e9fd1 100644
--- a/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
+++ b/tests/src/com/android/providers/telephony/TelephonyProviderTest.java
@@ -34,6 +34,9 @@
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Environment;
+import android.os.Bundle;
+import android.os.PersistableBundle;
 import android.os.Process;
 import android.provider.Telephony;
 import android.provider.Telephony.Carriers;
@@ -53,6 +56,9 @@
 import org.junit.Test;
 import org.mockito.MockitoAnnotations;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
 import java.util.Arrays;
 import java.util.List;
 import java.util.stream.IntStream;
@@ -112,6 +118,112 @@
     private static final Uri URI_ENFORCE_MANAGED= Uri.parse("content://telephony/carriers/enforce_managed");
     private static final String ENFORCED_KEY = "enforced";
 
+
+    private static final int MATCHING_SUB_ID = 987654321;
+    private static final String MATCHING_ICCID = "MATCHING_ICCID";
+    private static final String MATCHING_PHONE_NUMBER = "MATCHING_PHONE_NUMBER";
+    private static final int MATCHING_CARRIER_ID = 123456789;
+
+    // Represents an entry in the SimInfoDb
+    private static final ContentValues TEST_SIM_INFO_VALUES = new ContentValues();
+    private static final int ARBITRARY_SIMINFO_DB_TEST_INT_VALUE = 999999;
+    private static final String ARBITRARY_SIMINFO_DB_TEST_STRING_VALUE
+            = "ARBITRARY_TEST_STRING_VALUE";
+
+    private static final ContentValues BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_ICCID
+            = new ContentValues();
+    private static final int ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_1 = 111111;
+    private static final String ARBITRARY_SIMINFO_DB_TEST_STRING_VALUE_1
+            = "ARBITRARY_TEST_STRING_VALUE_1";
+
+    private static final ContentValues BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_NUMBER_AND_CID
+            = new ContentValues();
+    private static final int ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_2 = 222222;
+    private static final String ARBITRARY_SIMINFO_DB_TEST_STRING_VALUE_2
+            = "ARBITRARY_TEST_STRING_VALUE_2";
+
+    private static final ContentValues BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_CID
+            = new ContentValues();
+    private static final int ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_3 = 333333;
+    private static final String ARBITRARY_SIMINFO_DB_TEST_STRING_VALUE_3
+            = "ARBITRARY_TEST_STRING_VALUE_3";
+
+    static {
+        TEST_SIM_INFO_VALUES.put(Telephony.SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID,
+                MATCHING_SUB_ID);
+        TEST_SIM_INFO_VALUES.put(Telephony.SimInfo.COLUMN_ICC_ID, MATCHING_ICCID);
+        TEST_SIM_INFO_VALUES.put(Telephony.SimInfo.COLUMN_NUMBER, MATCHING_PHONE_NUMBER);
+        TEST_SIM_INFO_VALUES.put(Telephony.SimInfo.COLUMN_CARD_ID,
+                ARBITRARY_SIMINFO_DB_TEST_STRING_VALUE);
+        TEST_SIM_INFO_VALUES.put(Telephony.SimInfo.COLUMN_CARRIER_ID, MATCHING_CARRIER_ID);
+        TEST_SIM_INFO_VALUES.put(Telephony.SimInfo.COLUMN_ENHANCED_4G_MODE_ENABLED,
+            ARBITRARY_SIMINFO_DB_TEST_INT_VALUE);
+        TEST_SIM_INFO_VALUES.put(Telephony.SimInfo.COLUMN_VT_IMS_ENABLED,
+                ARBITRARY_SIMINFO_DB_TEST_INT_VALUE);
+        TEST_SIM_INFO_VALUES.put(Telephony.SimInfo.COLUMN_IMS_RCS_UCE_ENABLED,
+                ARBITRARY_SIMINFO_DB_TEST_INT_VALUE);
+
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_ICCID.put(
+                Telephony.SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID,
+                ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_1);
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_ICCID.put(
+                Telephony.SimInfo.COLUMN_ICC_ID, MATCHING_ICCID);
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_ICCID.put(
+                Telephony.SimInfo.COLUMN_NUMBER, ARBITRARY_SIMINFO_DB_TEST_STRING_VALUE_1);
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_ICCID.put(
+                Telephony.SimInfo.COLUMN_CARD_ID, ARBITRARY_SIMINFO_DB_TEST_STRING_VALUE_1);
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_ICCID.put(
+                Telephony.SimInfo.COLUMN_CARRIER_ID, ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_1);
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_ICCID.put(
+                Telephony.SimInfo.COLUMN_ENHANCED_4G_MODE_ENABLED,
+                ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_1);
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_ICCID.put(
+                Telephony.SimInfo.COLUMN_VT_IMS_ENABLED, ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_1);
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_ICCID.put(
+                Telephony.SimInfo.COLUMN_IMS_RCS_UCE_ENABLED,
+                ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_1);
+
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_NUMBER_AND_CID.put(
+                Telephony.SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID,
+                ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_2);
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_NUMBER_AND_CID.put(
+                Telephony.SimInfo.COLUMN_ICC_ID, ARBITRARY_SIMINFO_DB_TEST_STRING_VALUE_2);
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_NUMBER_AND_CID.put(
+                Telephony.SimInfo.COLUMN_NUMBER, MATCHING_PHONE_NUMBER);
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_NUMBER_AND_CID.put(
+                Telephony.SimInfo.COLUMN_CARD_ID, ARBITRARY_SIMINFO_DB_TEST_STRING_VALUE_2);
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_NUMBER_AND_CID.put(
+                Telephony.SimInfo.COLUMN_CARRIER_ID, MATCHING_CARRIER_ID);
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_NUMBER_AND_CID.put(
+                Telephony.SimInfo.COLUMN_ENHANCED_4G_MODE_ENABLED,
+                ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_2);
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_NUMBER_AND_CID.put(
+                Telephony.SimInfo.COLUMN_VT_IMS_ENABLED, ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_2);
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_NUMBER_AND_CID.put(
+                Telephony.SimInfo.COLUMN_IMS_RCS_UCE_ENABLED,
+                ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_2);
+
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_CID.put(
+                Telephony.SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID,
+                ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_3);
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_CID.put(Telephony.SimInfo.COLUMN_ICC_ID,
+                ARBITRARY_SIMINFO_DB_TEST_STRING_VALUE_3);
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_CID.put(Telephony.SimInfo.COLUMN_NUMBER,
+                ARBITRARY_SIMINFO_DB_TEST_STRING_VALUE_3);
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_CID.put(Telephony.SimInfo.COLUMN_CARD_ID,
+                ARBITRARY_SIMINFO_DB_TEST_STRING_VALUE);
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_CID.put(Telephony.SimInfo.COLUMN_CARRIER_ID,
+                MATCHING_CARRIER_ID);
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_CID.put(
+                Telephony.SimInfo.COLUMN_ENHANCED_4G_MODE_ENABLED,
+                ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_3);
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_CID.put(Telephony.SimInfo.COLUMN_VT_IMS_ENABLED,
+                ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_3);
+        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_CID.put(
+                Telephony.SimInfo.COLUMN_IMS_RCS_UCE_ENABLED,
+                ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_3);
+    }
+
     /**
      * This is used to give the TelephonyProviderTest a mocked context which takes a
      * TelephonyProvider and attaches it to the ContentResolver with telephony authority.
@@ -221,6 +333,20 @@
                 return PackageManager.PERMISSION_DENIED;
             }
         }
+
+        @Override
+        public void enforceCallingOrSelfPermission(String permission, String message) {
+            if (permission == android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE
+                    || permission == android.Manifest.permission.MODIFY_PHONE_STATE) {
+                return;
+            }
+            throw new SecurityException("Unavailable permission requested");
+        }
+
+        @Override
+        public File getFilesDir() {
+            return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
+        }
     }
 
     @Override
@@ -241,6 +367,13 @@
     protected void tearDown() throws Exception {
         super.tearDown();
         mTelephonyProviderTestable.closeDatabase();
+
+        // Remove the internal file created by SIM-specific settings restore
+        File file = new File(mContext.getFilesDir(),
+                mTelephonyProviderTestable.BACKED_UP_SIM_SPECIFIC_SETTINGS_FILE);
+        if (file.exists()) {
+            file.delete();
+        }
     }
 
     /**
@@ -623,6 +756,162 @@
         assertEquals(0, cursor.getCount());
     }
 
+    @Test
+    public void testFullRestoreOnMatchingIccId() {
+        byte[] simSpecificSettingsData = getBackupData(
+                new ContentValues[]{
+                        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_ICCID,
+                        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_NUMBER_AND_CID,
+                        BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_CID});
+        createInternalBackupFile(simSpecificSettingsData);
+        mContentResolver.insert(SubscriptionManager.CONTENT_URI, TEST_SIM_INFO_VALUES);
+
+        mContext.getContentResolver().call(
+            SubscriptionManager.SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI,
+            SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME,
+            MATCHING_ICCID, null);
+
+        Cursor cursor = mContentResolver.query(SubscriptionManager.CONTENT_URI,
+            null, null, null, null);
+        assertEquals(1, cursor.getCount());
+        cursor.moveToFirst();
+
+        // Make sure SubId didn't get overridden.
+        assertEquals(
+            (int)TEST_SIM_INFO_VALUES.getAsInteger(
+                    Telephony.SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID),
+            getIntValueFromCursor(cursor, Telephony.SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID));
+        // Ensure all other values got updated.
+        assertEquals(ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_1,
+                getIntValueFromCursor(cursor, Telephony.SimInfo.COLUMN_ENHANCED_4G_MODE_ENABLED));
+        assertEquals(ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_1,
+                getIntValueFromCursor(cursor, Telephony.SimInfo.COLUMN_VT_IMS_ENABLED));
+        assertEquals(ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_1,
+                getIntValueFromCursor(cursor, Telephony.SimInfo.COLUMN_IMS_RCS_UCE_ENABLED));
+
+        assertRestoredSubIdIsRemembered();
+    }
+
+    @Test
+    public void testFullRestoreOnMatchingNumberAndCid() {
+        byte[] simSpecificSettingsData = getBackupData(
+                new ContentValues[]{
+                    BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_NUMBER_AND_CID,
+                    BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_CID});
+        createInternalBackupFile(simSpecificSettingsData);
+        mContentResolver.insert(SubscriptionManager.CONTENT_URI, TEST_SIM_INFO_VALUES);
+
+        mContext.getContentResolver().call(
+            SubscriptionManager.SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI,
+            SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME,
+            MATCHING_ICCID, null);
+
+        Cursor cursor = mContentResolver.query(SubscriptionManager.CONTENT_URI,
+            null, null, null, null);
+        assertEquals(1, cursor.getCount());
+        cursor.moveToFirst();
+
+        // Make sure SubId didn't get overridden.
+        assertEquals(
+            (int) TEST_SIM_INFO_VALUES.getAsInteger(
+                    Telephony.SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID),
+            getIntValueFromCursor(cursor, Telephony.SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID));
+        // Ensure all other values got updated.
+        assertEquals(ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_2,
+                getIntValueFromCursor(cursor, Telephony.SimInfo.COLUMN_ENHANCED_4G_MODE_ENABLED));
+        assertEquals(ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_2,
+                getIntValueFromCursor(cursor, Telephony.SimInfo.COLUMN_VT_IMS_ENABLED));
+        assertEquals(ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_2,
+                getIntValueFromCursor(cursor, Telephony.SimInfo.COLUMN_IMS_RCS_UCE_ENABLED));
+
+        assertRestoredSubIdIsRemembered();
+    }
+
+    @Test
+    public void testFullRestoreOnMatchingCidOnly() {
+        byte[] simSpecificSettingsData = getBackupData(
+            new ContentValues[]{
+                BACKED_UP_SIM_INFO_VALUES_WITH_MATCHING_CID});
+        createInternalBackupFile(simSpecificSettingsData);
+        mContentResolver.insert(SubscriptionManager.CONTENT_URI, TEST_SIM_INFO_VALUES);
+
+        mContext.getContentResolver().call(
+            SubscriptionManager.SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI,
+            SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME,
+            MATCHING_ICCID, null);
+
+        Cursor cursor = mContentResolver.query(SubscriptionManager.CONTENT_URI,
+            null, null, null, null);
+        assertEquals(1, cursor.getCount());
+        cursor.moveToFirst();
+
+        // Make sure SubId didn't get overridden.
+        assertEquals(
+            (int) TEST_SIM_INFO_VALUES.getAsInteger(
+                    Telephony.SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID),
+            getIntValueFromCursor(cursor, Telephony.SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID));
+        // Ensure sensitive settings did not get updated.
+        assertEquals(ARBITRARY_SIMINFO_DB_TEST_INT_VALUE,
+                getIntValueFromCursor(cursor, Telephony.SimInfo.COLUMN_IMS_RCS_UCE_ENABLED));
+        // Ensure all other values got updated.
+        assertEquals(ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_3,
+                getIntValueFromCursor(cursor, Telephony.SimInfo.COLUMN_ENHANCED_4G_MODE_ENABLED));
+        assertEquals(ARBITRARY_SIMINFO_DB_TEST_INT_VALUE_3,
+                getIntValueFromCursor(cursor, Telephony.SimInfo.COLUMN_VT_IMS_ENABLED));
+
+        assertRestoredSubIdIsRemembered();
+    }
+
+    private void assertRestoredSubIdIsRemembered() {
+        PersistableBundle bundle = getPersistableBundleFromInternalStorageFile();
+        int[] previouslyRestoredSubIds =
+                bundle.getIntArray(TelephonyProvider.KEY_PREVIOUSLY_RESTORED_SUB_IDS);
+        assertNotNull(previouslyRestoredSubIds);
+        assertEquals(MATCHING_SUB_ID, previouslyRestoredSubIds[0]);
+    }
+
+    private PersistableBundle getPersistableBundleFromInternalStorageFile() {
+        File file = new File(Environment.getExternalStoragePublicDirectory(
+                Environment.DIRECTORY_DOWNLOADS),
+                TelephonyProvider.BACKED_UP_SIM_SPECIFIC_SETTINGS_FILE);
+        try (FileInputStream fis = new FileInputStream(file)) {
+            return PersistableBundle.readFromStream(fis);
+        } catch (IOException e) {
+        }
+
+        return null;
+    }
+
+    private byte[] getBackupData(ContentValues[] contentValues) {
+        setUpMockContext(true);
+
+        int rowsAdded = mContentResolver.bulkInsert(SubscriptionManager.CONTENT_URI, contentValues);
+        assertEquals(rowsAdded, contentValues.length);
+
+        Cursor cursor = mContentResolver.query(SubscriptionManager.CONTENT_URI,
+            null, null, null, null);
+        assertEquals(cursor.getCount(), contentValues.length);
+
+        Bundle bundle =  mContext.getContentResolver().call(
+            SubscriptionManager.SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI,
+            SubscriptionManager.GET_SIM_SPECIFIC_SETTINGS_METHOD_NAME, null, null);
+        byte[] data = bundle.getByteArray(SubscriptionManager.KEY_SIM_SPECIFIC_SETTINGS_DATA);
+
+        int rowsDeleted = mContentResolver.delete(SubscriptionManager.CONTENT_URI, null, null);
+        assertEquals(rowsDeleted, contentValues.length);
+
+        return data;
+    }
+
+    private void createInternalBackupFile(byte[] data) {
+        mTelephonyProviderTestable.writeSimSettingsToInternalStorage(data);
+    }
+
+    private int getIntValueFromCursor(Cursor cursor, String columnName) {
+        int columnIndex = cursor.getColumnIndex(columnName);
+        return cursor.getInt(columnIndex);
+    }
+
     private int parseIdFromInsertedUri(Uri uri) throws NumberFormatException {
         return (uri != null) ? Integer.parseInt(uri.getLastPathSegment()) : -1;
     }