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;
}