/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.telephony.subscription;

import android.annotation.CallbackExecutor;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelUuid;
import android.provider.Telephony;
import android.provider.Telephony.SimInfo;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.SubscriptionManager.DataRoamingMode;
import android.telephony.SubscriptionManager.DeviceToDeviceStatusSharingPreference;
import android.telephony.SubscriptionManager.ProfileClass;
import android.telephony.SubscriptionManager.SimDisplayNameSource;
import android.telephony.SubscriptionManager.SubscriptionType;
import android.telephony.SubscriptionManager.UsageSetting;
import android.telephony.TelephonyManager;
import android.telephony.UiccAccessRule;
import android.telephony.ims.ImsMmTelManager;
import android.text.TextUtils;
import android.util.Base64;
import android.util.IndentingPrintWriter;
import android.util.LocalLog;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.telephony.uicc.UiccController;
import com.android.internal.util.function.TriConsumer;
import com.android.telephony.Rlog;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * The subscription database manager is the wrapper of {@link SimInfo}
 * table. It's a full memory cache of the entire subscription database, and the update can be
 * asynchronously or synchronously. The database's cache allows multi threads to read
 * simultaneously, if no write is ongoing.
 *
 * Note that from Android 14, directly writing into the subscription database through content
 * resolver with {@link SimInfo#CONTENT_URI} will cause cache/db out of sync. All the read/write
 * to the database should go through {@link SubscriptionManagerService}.
 */
public class SubscriptionDatabaseManager extends Handler {
    private static final String LOG_TAG = "SDMGR";

    /** Whether enabling verbose debugging message or not. */
    private static final boolean VDBG = false;

    /** Invalid database row index. */
    private static final int INVALID_ROW_INDEX = -1;

    /** The mapping from {@link SimInfo} table to {@link SubscriptionInfoInternal} get methods. */
    private static final Map<String, Function<SubscriptionInfoInternal, ?>>
            SUBSCRIPTION_GET_METHOD_MAP = Map.ofEntries(
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID,
                    SubscriptionInfoInternal::getSubscriptionId),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_ICC_ID,
                    SubscriptionInfoInternal::getIccId),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_SIM_SLOT_INDEX,
                    SubscriptionInfoInternal::getSimSlotIndex),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_DISPLAY_NAME,
                    SubscriptionInfoInternal::getDisplayName),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CARRIER_NAME,
                    SubscriptionInfoInternal::getCarrierName),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_NAME_SOURCE,
                    SubscriptionInfoInternal::getDisplayNameSource),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_COLOR,
                    SubscriptionInfoInternal::getIconTint),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_NUMBER,
                    SubscriptionInfoInternal::getNumber),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_DATA_ROAMING,
                    SubscriptionInfoInternal::getDataRoaming),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_MCC_STRING,
                    SubscriptionInfoInternal::getMcc),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_MNC_STRING,
                    SubscriptionInfoInternal::getMnc),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_EHPLMNS,
                    SubscriptionInfoInternal::getEhplmns),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_HPLMNS,
                    SubscriptionInfoInternal::getHplmns),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_IS_EMBEDDED,
                    SubscriptionInfoInternal::getEmbedded),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CARD_ID,
                    SubscriptionInfoInternal::getCardString),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_ACCESS_RULES,
                    SubscriptionInfoInternal::getNativeAccessRules),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_ACCESS_RULES_FROM_CARRIER_CONFIGS,
                    SubscriptionInfoInternal::getCarrierConfigAccessRules),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_IS_REMOVABLE,
                    SubscriptionInfoInternal::getRemovableEmbedded),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_EXTREME_THREAT_ALERT,
                    SubscriptionInfoInternal::getCellBroadcastExtremeThreatAlertEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_SEVERE_THREAT_ALERT,
                    SubscriptionInfoInternal::getCellBroadcastSevereThreatAlertEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_AMBER_ALERT,
                    SubscriptionInfoInternal::getCellBroadcastAmberAlertEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_EMERGENCY_ALERT,
                    SubscriptionInfoInternal::getCellBroadcastEmergencyAlertEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_ALERT_SOUND_DURATION,
                    SubscriptionInfoInternal::getCellBroadcastAlertSoundDuration),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_ALERT_REMINDER_INTERVAL,
                    SubscriptionInfoInternal::getCellBroadcastAlertReminderInterval),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_ALERT_VIBRATE,
                    SubscriptionInfoInternal::getCellBroadcastAlertVibrationEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_ALERT_SPEECH,
                    SubscriptionInfoInternal::getCellBroadcastAlertSpeechEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_ETWS_TEST_ALERT,
                    SubscriptionInfoInternal::getCellBroadcastEtwsTestAlertEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_CHANNEL_50_ALERT,
                    SubscriptionInfoInternal::getCellBroadcastAreaInfoMessageEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_CMAS_TEST_ALERT,
                    SubscriptionInfoInternal::getCellBroadcastTestAlertEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_OPT_OUT_DIALOG,
                    SubscriptionInfoInternal::getCellBroadcastOptOutDialogEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_ENHANCED_4G_MODE_ENABLED,
                    SubscriptionInfoInternal::getEnhanced4GModeEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_VT_IMS_ENABLED,
                    SubscriptionInfoInternal::getVideoTelephonyEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_WFC_IMS_ENABLED,
                    SubscriptionInfoInternal::getWifiCallingEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_WFC_IMS_MODE,
                    SubscriptionInfoInternal::getWifiCallingMode),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_WFC_IMS_ROAMING_MODE,
                    SubscriptionInfoInternal::getWifiCallingModeForRoaming),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_WFC_IMS_ROAMING_ENABLED,
                    SubscriptionInfoInternal::getWifiCallingEnabledForRoaming),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_IS_OPPORTUNISTIC,
                    SubscriptionInfoInternal::getOpportunistic),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_GROUP_UUID,
                    SubscriptionInfoInternal::getGroupUuid),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_ISO_COUNTRY_CODE,
                    SubscriptionInfoInternal::getCountryIso),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CARRIER_ID,
                    SubscriptionInfoInternal::getCarrierId),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_PROFILE_CLASS,
                    SubscriptionInfoInternal::getProfileClass),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_SUBSCRIPTION_TYPE,
                    SubscriptionInfoInternal::getSubscriptionType),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_GROUP_OWNER,
                    SubscriptionInfoInternal::getGroupOwner),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_ENABLED_MOBILE_DATA_POLICIES,
                    SubscriptionInfoInternal::getEnabledMobileDataPolicies),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_IMSI,
                    SubscriptionInfoInternal::getImsi),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_UICC_APPLICATIONS_ENABLED,
                    SubscriptionInfoInternal::getUiccApplicationsEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_IMS_RCS_UCE_ENABLED,
                    SubscriptionInfoInternal::getRcsUceEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CROSS_SIM_CALLING_ENABLED,
                    SubscriptionInfoInternal::getCrossSimCallingEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_RCS_CONFIG,
                    SubscriptionInfoInternal::getRcsConfig),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_ALLOWED_NETWORK_TYPES_FOR_REASONS,
                    SubscriptionInfoInternal::getAllowedNetworkTypesForReasons),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_D2D_STATUS_SHARING,
                    SubscriptionInfoInternal::getDeviceToDeviceStatusSharingPreference),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_VOIMS_OPT_IN_STATUS,
                    SubscriptionInfoInternal::getVoImsOptInEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_D2D_STATUS_SHARING_SELECTED_CONTACTS,
                    SubscriptionInfoInternal::getDeviceToDeviceStatusSharingContacts),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_NR_ADVANCED_CALLING_ENABLED,
                    SubscriptionInfoInternal::getNrAdvancedCallingEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_PHONE_NUMBER_SOURCE_CARRIER,
                    SubscriptionInfoInternal::getNumberFromCarrier),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_PHONE_NUMBER_SOURCE_IMS,
                    SubscriptionInfoInternal::getNumberFromIms),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_PORT_INDEX,
                    SubscriptionInfoInternal::getPortIndex),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_USAGE_SETTING,
                    SubscriptionInfoInternal::getUsageSetting),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_TP_MESSAGE_REF,
                    SubscriptionInfoInternal::getLastUsedTPMessageReference),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_USER_HANDLE,
                    SubscriptionInfoInternal::getUserId),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_SATELLITE_ENABLED,
                    SubscriptionInfoInternal::getSatelliteEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
                    SubscriptionInfoInternal::getSatelliteAttachEnabledForCarrier),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_IS_NTN,
                    SubscriptionInfoInternal::getOnlyNonTerrestrialNetwork),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_SERVICE_CAPABILITIES,
                    SubscriptionInfoInternal::getServiceCapabilities),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_TRANSFER_STATUS,
                    SubscriptionInfoInternal::getTransferStatus),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS,
                    SubscriptionInfoInternal::getSatelliteEntitlementStatus),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS,
                    SubscriptionInfoInternal::getSatelliteEntitlementPlmns)
    );

    /**
     * The mapping from columns in {@link android.provider.Telephony.SimInfo} table to
     * {@link SubscriptionDatabaseManager} setting integer methods.
     */
    private static final Map<String, TriConsumer<SubscriptionDatabaseManager, Integer, Integer>>
            SUBSCRIPTION_SET_INTEGER_METHOD_MAP = Map.ofEntries(
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_SIM_SLOT_INDEX,
                    SubscriptionDatabaseManager::setSimSlotIndex),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_NAME_SOURCE,
                    SubscriptionDatabaseManager::setDisplayNameSource),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_COLOR,
                    SubscriptionDatabaseManager::setIconTint),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_DATA_ROAMING,
                    SubscriptionDatabaseManager::setDataRoaming),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_IS_EMBEDDED,
                    SubscriptionDatabaseManager::setEmbedded),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_IS_REMOVABLE,
                    SubscriptionDatabaseManager::setRemovableEmbedded),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_EXTREME_THREAT_ALERT,
                    SubscriptionDatabaseManager::setCellBroadcastExtremeThreatAlertEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_SEVERE_THREAT_ALERT,
                    SubscriptionDatabaseManager::setCellBroadcastSevereThreatAlertEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_AMBER_ALERT,
                    SubscriptionDatabaseManager::setCellBroadcastAmberAlertEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_EMERGENCY_ALERT,
                    SubscriptionDatabaseManager::setCellBroadcastEmergencyAlertEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_ALERT_SOUND_DURATION,
                    SubscriptionDatabaseManager::setCellBroadcastAlertSoundDuration),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_ALERT_REMINDER_INTERVAL,
                    SubscriptionDatabaseManager::setCellBroadcastAlertReminderInterval),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_ALERT_VIBRATE,
                    SubscriptionDatabaseManager::setCellBroadcastAlertVibrationEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_ALERT_SPEECH,
                    SubscriptionDatabaseManager::setCellBroadcastAlertSpeechEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_ETWS_TEST_ALERT,
                    SubscriptionDatabaseManager::setCellBroadcastEtwsTestAlertEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_CHANNEL_50_ALERT,
                    SubscriptionDatabaseManager::setCellBroadcastAreaInfoMessageEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_CMAS_TEST_ALERT,
                    SubscriptionDatabaseManager::setCellBroadcastTestAlertEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CB_OPT_OUT_DIALOG,
                    SubscriptionDatabaseManager::setCellBroadcastOptOutDialogEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_ENHANCED_4G_MODE_ENABLED,
                    SubscriptionDatabaseManager::setEnhanced4GModeEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_VT_IMS_ENABLED,
                    SubscriptionDatabaseManager::setVideoTelephonyEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_WFC_IMS_ENABLED,
                    SubscriptionDatabaseManager::setWifiCallingEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_WFC_IMS_MODE,
                    SubscriptionDatabaseManager::setWifiCallingMode),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_WFC_IMS_ROAMING_MODE,
                    SubscriptionDatabaseManager::setWifiCallingModeForRoaming),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_WFC_IMS_ROAMING_ENABLED,
                    SubscriptionDatabaseManager::setWifiCallingEnabledForRoaming),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_IS_OPPORTUNISTIC,
                    SubscriptionDatabaseManager::setOpportunistic),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CARRIER_ID,
                    SubscriptionDatabaseManager::setCarrierId),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_PROFILE_CLASS,
                    SubscriptionDatabaseManager::setProfileClass),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_SUBSCRIPTION_TYPE,
                    SubscriptionDatabaseManager::setSubscriptionType),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_UICC_APPLICATIONS_ENABLED,
                    SubscriptionDatabaseManager::setUiccApplicationsEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_IMS_RCS_UCE_ENABLED,
                    SubscriptionDatabaseManager::setRcsUceEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CROSS_SIM_CALLING_ENABLED,
                    SubscriptionDatabaseManager::setCrossSimCallingEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_D2D_STATUS_SHARING,
                    SubscriptionDatabaseManager::setDeviceToDeviceStatusSharingPreference),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_VOIMS_OPT_IN_STATUS,
                    SubscriptionDatabaseManager::setVoImsOptInEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_NR_ADVANCED_CALLING_ENABLED,
                    SubscriptionDatabaseManager::setNrAdvancedCallingEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_PORT_INDEX,
                    SubscriptionDatabaseManager::setPortIndex),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_USAGE_SETTING,
                    SubscriptionDatabaseManager::setUsageSetting),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_TP_MESSAGE_REF,
                    SubscriptionDatabaseManager::setLastUsedTPMessageReference),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_USER_HANDLE,
                    SubscriptionDatabaseManager::setUserId),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_SATELLITE_ENABLED,
                    SubscriptionDatabaseManager::setSatelliteEnabled),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
                    SubscriptionDatabaseManager::setSatelliteAttachEnabledForCarrier),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_IS_NTN,
                    SubscriptionDatabaseManager::setNtn),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_SERVICE_CAPABILITIES,
                    SubscriptionDatabaseManager::setServiceCapabilities),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_TRANSFER_STATUS,
                    SubscriptionDatabaseManager::setTransferStatus),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS,
                    SubscriptionDatabaseManager::setSatelliteEntitlementStatus)
    );

    /**
     * The mapping from columns in {@link android.provider.Telephony.SimInfo} table to
     * {@link SubscriptionDatabaseManager} setting string methods.
     */
    private static final Map<String, TriConsumer<SubscriptionDatabaseManager, Integer, String>>
            SUBSCRIPTION_SET_STRING_METHOD_MAP = Map.ofEntries(
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_ICC_ID,
                    SubscriptionDatabaseManager::setIccId),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_DISPLAY_NAME,
                    SubscriptionDatabaseManager::setDisplayName),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CARRIER_NAME,
                    SubscriptionDatabaseManager::setCarrierName),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_NUMBER,
                    SubscriptionDatabaseManager::setNumber),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_MCC_STRING,
                    SubscriptionDatabaseManager::setMcc),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_MNC_STRING,
                    SubscriptionDatabaseManager::setMnc),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_EHPLMNS,
                    SubscriptionDatabaseManager::setEhplmns),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_HPLMNS,
                    SubscriptionDatabaseManager::setHplmns),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_CARD_ID,
                    SubscriptionDatabaseManager::setCardString),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_GROUP_UUID,
                    SubscriptionDatabaseManager::setGroupUuid),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_ISO_COUNTRY_CODE,
                    SubscriptionDatabaseManager::setCountryIso),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_GROUP_OWNER,
                    SubscriptionDatabaseManager::setGroupOwner),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_ENABLED_MOBILE_DATA_POLICIES,
                    SubscriptionDatabaseManager::setEnabledMobileDataPolicies),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_IMSI,
                    SubscriptionDatabaseManager::setImsi),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_ALLOWED_NETWORK_TYPES_FOR_REASONS,
                    SubscriptionDatabaseManager::setAllowedNetworkTypesForReasons),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_D2D_STATUS_SHARING_SELECTED_CONTACTS,
                    SubscriptionDatabaseManager::setDeviceToDeviceStatusSharingContacts),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_PHONE_NUMBER_SOURCE_CARRIER,
                    SubscriptionDatabaseManager::setNumberFromCarrier),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_PHONE_NUMBER_SOURCE_IMS,
                    SubscriptionDatabaseManager::setNumberFromIms),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS,
                    SubscriptionDatabaseManager::setSatelliteEntitlementPlmns)
    );

    /**
     * The mapping from columns in {@link android.provider.Telephony.SimInfo} table to
     * {@link SubscriptionDatabaseManager} setting byte array methods.
     */
    private static final Map<String, TriConsumer<SubscriptionDatabaseManager, Integer, byte[]>>
            SUBSCRIPTION_SET_BYTE_ARRAY_METHOD_MAP = Map.ofEntries(
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_ACCESS_RULES,
                    SubscriptionDatabaseManager::setNativeAccessRules),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_ACCESS_RULES_FROM_CARRIER_CONFIGS,
                    SubscriptionDatabaseManager::setCarrierConfigAccessRules),
            new AbstractMap.SimpleImmutableEntry<>(
                    SimInfo.COLUMN_RCS_CONFIG,
                    SubscriptionDatabaseManager::setRcsConfig)
    );

    /**
     * The columns that should be in-sync between the subscriptions in the same group. Changing
     * the value in those fields will automatically apply to the rest of the subscriptions in the
     * group.
     *
     * @see SubscriptionManager#getSubscriptionsInGroup(ParcelUuid)
     */
    private static final Set<String> GROUP_SHARING_COLUMNS = Set.of(
            SimInfo.COLUMN_DISPLAY_NAME,
            SimInfo.COLUMN_NAME_SOURCE,
            SimInfo.COLUMN_COLOR,
            SimInfo.COLUMN_DATA_ROAMING,
            SimInfo.COLUMN_ENHANCED_4G_MODE_ENABLED,
            SimInfo.COLUMN_VT_IMS_ENABLED,
            SimInfo.COLUMN_WFC_IMS_ENABLED,
            SimInfo.COLUMN_WFC_IMS_MODE,
            SimInfo.COLUMN_WFC_IMS_ROAMING_MODE,
            SimInfo.COLUMN_WFC_IMS_ROAMING_ENABLED,
            SimInfo.COLUMN_ENABLED_MOBILE_DATA_POLICIES,
            SimInfo.COLUMN_UICC_APPLICATIONS_ENABLED,
            SimInfo.COLUMN_IMS_RCS_UCE_ENABLED,
            SimInfo.COLUMN_CROSS_SIM_CALLING_ENABLED,
            SimInfo.COLUMN_RCS_CONFIG,
            SimInfo.COLUMN_D2D_STATUS_SHARING,
            SimInfo.COLUMN_VOIMS_OPT_IN_STATUS,
            SimInfo.COLUMN_D2D_STATUS_SHARING_SELECTED_CONTACTS,
            SimInfo.COLUMN_NR_ADVANCED_CALLING_ENABLED,
            SimInfo.COLUMN_USER_HANDLE,
            SimInfo.COLUMN_SATELLITE_ENABLED,
            SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER
    );

    /**
     * The deprecated columns that do not have corresponding set methods in
     * {@link SubscriptionDatabaseManager}.
     */
    private static final Set<String> DEPRECATED_DATABASE_COLUMNS = Set.of(
            SimInfo.COLUMN_DISPLAY_NUMBER_FORMAT,
            SimInfo.COLUMN_MCC,
            SimInfo.COLUMN_MNC,
            SimInfo.COLUMN_SIM_PROVISIONING_STATUS,
            SimInfo.COLUMN_IS_METERED,
            SimInfo.COLUMN_DATA_ENABLED_OVERRIDE_RULES,
            SimInfo.COLUMN_ALLOWED_NETWORK_TYPES
    );

    /** The context */
    @NonNull
    private final Context mContext;

    /** The feature flags */
    @NonNull
    private final FeatureFlags mFeatureFlags;

    /** The callback used for passing events back to {@link SubscriptionManagerService}. */
    @NonNull
    private final SubscriptionDatabaseManagerCallback mCallback;

    /** UICC controller */
    private final UiccController mUiccController;

    /**
     * The read/write lock to protect the entire database access. Using a Re-entrant read lock as
     * much more read requests are expected than the write requests. All the access to
     * {@link #mAllSubscriptionInfoInternalCache} needs to be protected by this lock.
     */
    @NonNull
    private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock();

    /** Indicating whether access the database asynchronously or not. */
    private final boolean mAsyncMode;

    /** Local log for most important debug messages. */
    @NonNull
    private final LocalLog mLocalLog = new LocalLog(128);

    /**
     * The entire subscription database, including subscriptions from inserted, previously inserted
     * SIMs. This is the full memory cache of the subscription database. The key is the subscription
     * id. Note all the access to this map needs to be protected by the re-entrant lock
     * {@link #mReadWriteLock}.
     *
     * @see SimInfo
     */
    @GuardedBy("mReadWriteLock")
    @NonNull
    private final Map<Integer, SubscriptionInfoInternal> mAllSubscriptionInfoInternalCache =
            new HashMap<>(16);

    /** Whether database has been initialized after boot up. */
    @GuardedBy("this")
    private boolean mDatabaseInitialized = false;

    /**
     * This is the callback used for listening events from {@link SubscriptionDatabaseManager}.
     */
    public abstract static class SubscriptionDatabaseManagerCallback {
        /** The executor of the callback. */
        private final @NonNull Executor mExecutor;

        /**
         * Constructor
         *
         * @param executor The executor of the callback.
         */
        public SubscriptionDatabaseManagerCallback(@NonNull @CallbackExecutor Executor executor) {
            mExecutor = executor;
        }

        /**
         * @return The executor of the callback.
         */
        @VisibleForTesting
        public @NonNull Executor getExecutor() {
            return mExecutor;
        }

        /**
         * Invoke the callback from executor.
         *
         * @param runnable The callback method to invoke.
         */
        public void invokeFromExecutor(@NonNull Runnable runnable) {
            mExecutor.execute(runnable);
        }

        /**
         * Called when database has been initialized.
         */
        public abstract void onInitialized();

        /**
         * Called when subscription changed.
         *
         * @param subId The subscription id.
         */
        public abstract void onSubscriptionChanged(int subId);
    }

    /**
     * The constructor.
     *
     * @param context The context.
     * @param looper Looper for the handler.
     * @param featureFlags The feature flags.
     * @param callback Subscription database callback.
     */
    public SubscriptionDatabaseManager(@NonNull Context context, @NonNull Looper looper,
            @NonNull FeatureFlags featureFlags,
            @NonNull SubscriptionDatabaseManagerCallback callback) {
        super(looper);
        log("Created SubscriptionDatabaseManager.");
        mContext = context;
        mCallback = callback;
        mUiccController = UiccController.getInstance();
        mAsyncMode = mContext.getResources().getBoolean(
                com.android.internal.R.bool.config_subscription_database_async_update);
        mFeatureFlags = featureFlags;
        initializeDatabase();
    }

    /**
     * Helper method to get specific field from {@link SubscriptionInfoInternal} by the database
     * column name. {@link SubscriptionInfoInternal} represent one single record in the
     * {@link SimInfo} table. So every column has a corresponding get method in
     * {@link SubscriptionInfoInternal} (except for unused or deprecated columns).
     *
     * @param subInfo The subscription info.
     * @param columnName The database column name.
     *
     * @return The corresponding value from {@link SubscriptionInfoInternal}.
     *
     * @throws IllegalArgumentException if {@code columnName} is invalid.
     *
     * @see android.provider.Telephony.SimInfo for all the columns.
     */
    @NonNull
    private static Object getSubscriptionInfoFieldByColumnName(
            @NonNull SubscriptionInfoInternal subInfo, @NonNull String columnName) {
        if (SUBSCRIPTION_GET_METHOD_MAP.containsKey(columnName)) {
            return SUBSCRIPTION_GET_METHOD_MAP.get(columnName).apply(subInfo);
        }
        throw new IllegalArgumentException("Invalid column name " + columnName);
    }

    /**
     * Get a specific field from the subscription database by {@code subId} and {@code columnName}.
     *
     * @param subId The subscription id.
     * @param columnName The database column name.
     *
     * @return The value from subscription database.
     *
     * @throws IllegalArgumentException if {@code subId} or {@code columnName} is invalid.
     *
     * @see android.provider.Telephony.SimInfo for all the columns.
     */
    @NonNull
    public Object getSubscriptionProperty(int subId, @NonNull String columnName) {
        SubscriptionInfoInternal subInfo = getSubscriptionInfoInternal(subId);
        if (subInfo == null) {
            throw new IllegalArgumentException("getSubscriptionProperty: Invalid subId " + subId
                    + ", columnName=" + columnName);
        }

        return getSubscriptionInfoFieldByColumnName(subInfo, columnName);
    }

    /**
     * Set a field in the subscription database. Note not all fields are supported.
     *
     * @param subId Subscription Id of Subscription.
     * @param columnName Column name in the database. Note not all fields are supported.
     * @param value Value to store in the database.
     *
     * @throws IllegalArgumentException if {@code subId} or {@code columnName} is invalid, or
     * {@code value} cannot be converted to the corresponding database column format.
     * @throws NumberFormatException if a string value cannot be converted to integer.
     * @throws ClassCastException if {@code value} cannot be casted to the required type.
     *
     * @see android.provider.Telephony.SimInfo for all the columns.
     */
    public void setSubscriptionProperty(int subId, @NonNull String columnName,
            @NonNull Object value) {
        if (SUBSCRIPTION_SET_INTEGER_METHOD_MAP.containsKey(columnName)) {
            // For integer type columns, accepting both integer and string that can be converted to
            // integer.
            int intValue;
            if (value instanceof String) {
                intValue = Integer.parseInt((String) value);
            } else if (value instanceof Integer) {
                intValue = (int) value;
            } else {
                throw new ClassCastException("columnName=" + columnName + ", cannot cast "
                        + value.getClass() + " to integer.");
            }
            SUBSCRIPTION_SET_INTEGER_METHOD_MAP.get(columnName).accept(this, subId, intValue);
        } else if (SUBSCRIPTION_SET_STRING_METHOD_MAP.containsKey(columnName)) {
            // For string type columns. Will throw exception if value is not string type.
            SUBSCRIPTION_SET_STRING_METHOD_MAP.get(columnName).accept(this, subId, (String) value);
        } else if (SUBSCRIPTION_SET_BYTE_ARRAY_METHOD_MAP.containsKey(columnName)) {
            // For byte array type columns, accepting both byte[] and string that can be converted
            // to byte[] using base 64 encoding/decoding.
            byte[] byteArrayValue;
            if (value instanceof String) {
                byteArrayValue = Base64.decode((String) value, Base64.DEFAULT);
            } else if (value instanceof byte[]) {
                byteArrayValue = (byte[]) value;
            } else {
                throw new ClassCastException("columnName=" + columnName + ", cannot cast "
                        + value.getClass() + " to byte[].");
            }
            SUBSCRIPTION_SET_BYTE_ARRAY_METHOD_MAP.get(columnName).accept(
                    this, subId, byteArrayValue);
        } else {
            throw new IllegalArgumentException("Does not support set " + columnName + ".");
        }
    }

    /**
     * Comparing the old/new {@link SubscriptionInfoInternal} and create content values for database
     * update. If any field in the new subscription info is different from the old one, then each
     * delta will be added into the {@link ContentValues}.
     *
     * @param oldSubInfo The old {@link SubscriptionInfoInternal}.
     * @param newSubInfo The new {@link SubscriptionInfoInternal}.
     *
     * @return The delta content values for database update.
     */
    @NonNull
    private ContentValues createDeltaContentValues(@Nullable SubscriptionInfoInternal oldSubInfo,
            @NonNull SubscriptionInfoInternal newSubInfo) {
        ContentValues deltaContentValues = new ContentValues();

        for (String columnName : Telephony.SimInfo.getAllColumns()) {
            if (DEPRECATED_DATABASE_COLUMNS.contains(columnName)) continue;
            // subId is generated by the database. Cannot be updated.
            if (columnName.equals(SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID)) continue;
            Object newValue = getSubscriptionInfoFieldByColumnName(newSubInfo, columnName);
            if (newValue != null) {
                Object oldValue = null;
                if (oldSubInfo != null) {
                    oldValue = getSubscriptionInfoFieldByColumnName(oldSubInfo, columnName);
                }
                // Some columns need special handling. We need to convert them into a format that
                // is accepted by the database.
                if (!Objects.equals(oldValue, newValue)) {
                    deltaContentValues.putObject(columnName, newValue);
                }
            }
        }
        return deltaContentValues;
    }

    /**
     * Synchronously insert a new record into the database. This operation is synchronous because
     * we need to convert the inserted row index into the subscription id.
     *
     * @param contentValues The fields of the subscription to be inserted into the database.
     *
     * @return The row index of the new record. {@link #INVALID_ROW_INDEX} if insertion failed.
     */
    private int insertNewRecordIntoDatabaseSync(@NonNull ContentValues contentValues) {
        Objects.requireNonNull(contentValues);
        Uri uri = mContext.getContentResolver().insert(SimInfo.CONTENT_URI, contentValues);
        if (uri != null && uri.getLastPathSegment() != null) {
            int subId = Integer.parseInt(uri.getLastPathSegment());
            if (SubscriptionManager.isValidSubscriptionId(subId)) {
                logv("insertNewRecordIntoDatabaseSync: contentValues=" + contentValues);
                logl("insertNewRecordIntoDatabaseSync: Successfully added subscription. subId="
                        + uri.getLastPathSegment());
                return subId;
            }
        }

        logel("insertNewRecordIntoDatabaseSync: Failed to insert subscription into database. "
                + "contentValues=" + contentValues);
        return INVALID_ROW_INDEX;
    }

    /**
     * Insert a new subscription info. The subscription info must have subscription id
     * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}. Note this is a slow method, so be
     * cautious to call this method.
     *
     * @param subInfo The subscription info to update.
     *
     * @return The new subscription id. {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} (-1) if
     * insertion fails.
     */
    public int insertSubscriptionInfo(@NonNull SubscriptionInfoInternal subInfo) {
        Objects.requireNonNull(subInfo);
        // A new subscription to be inserted must have invalid subscription id.
        if (SubscriptionManager.isValidSubscriptionId(subInfo.getSubscriptionId())) {
            throw new RuntimeException("insertSubscriptionInfo: Not a new subscription to "
                    + "insert. subInfo=" + subInfo);
        }

        synchronized (this) {
            if (!mDatabaseInitialized) {
                throw new IllegalStateException(
                        "Database has not been initialized. Can't insert new "
                                + "record at this point.");
            }
        }

        int subId;
        // Grab the write lock so no other threads can read or write the cache.
        mReadWriteLock.writeLock().lock();
        try {
            // Synchronously insert into the database. Note this should be the only synchronous
            // write operation performed by the subscription database manager. The reason is that
            // we need to get the sub id for cache update.
            subId = insertNewRecordIntoDatabaseSync(createDeltaContentValues(null, subInfo));
            if (subId > 0) {
                mAllSubscriptionInfoInternalCache.put(subId, new SubscriptionInfoInternal
                        .Builder(subInfo)
                        .setId(subId).build());
            } else {
                logel("insertSubscriptionInfo: Failed to insert a new subscription. subInfo="
                        + subInfo);
            }
        } finally {
            mReadWriteLock.writeLock().unlock();
        }

        mCallback.invokeFromExecutor(() -> mCallback.onSubscriptionChanged(subId));
        return subId;
    }

    /**
     * Remove a subscription record from the database.
     *
     * @param subId The subscription id of the subscription to be deleted.
     *
     * @throws IllegalArgumentException If {@code subId} is invalid.
     */
    public void removeSubscriptionInfo(int subId) {
        if (!mAllSubscriptionInfoInternalCache.containsKey(subId)) {
            throw new IllegalArgumentException("subId " + subId + " is invalid.");
        }

        mReadWriteLock.writeLock().lock();
        try {
            if (mContext.getContentResolver().delete(SimInfo.CONTENT_URI,
                    SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID + "=?",
                    new String[]{Integer.toString(subId)}) > 0) {
                mAllSubscriptionInfoInternalCache.remove(subId);
            } else {
                logel("Failed to remove subscription with subId=" + subId);
            }
        } finally {
            mReadWriteLock.writeLock().unlock();
        }

        mCallback.invokeFromExecutor(() -> mCallback.onSubscriptionChanged(subId));
    }

    /**
     * Update a subscription in the database (synchronously or asynchronously).
     *
     * @param subId The subscription id of the subscription to be updated.
     * @param contentValues The fields to be update.
     *
     * @return The number of rows updated. Note if the database is configured as asynchronously
     * update, then this will be always 1.
     */
    private int updateDatabase(int subId, @NonNull ContentValues contentValues) {
        logv("updateDatabase: prepare to update sub " + subId);

        synchronized (this) {
            if (!mDatabaseInitialized) {
                logel("updateDatabase: Database has not been initialized. Can't update database at "
                        + "this point. contentValues=" + contentValues);
                return 0;
            }
        }

        if (mAsyncMode) {
            // Perform the update in the handler thread asynchronously.
            post(() -> {
                mContext.getContentResolver().update(Uri.withAppendedPath(
                        SimInfo.CONTENT_URI, String.valueOf(subId)), contentValues, null, null);
                logv("updateDatabase: async updated subscription in the database."
                            + " subId=" + subId + ", contentValues= " + contentValues.getValues());
            });
            return 1;
        } else {
            logv("updateDatabase: sync updated subscription in the database."
                    + " subId=" + subId + ", contentValues= " + contentValues.getValues());

            return mContext.getContentResolver().update(Uri.withAppendedPath(
                    SimInfo.CONTENT_URI, String.valueOf(subId)), contentValues, null, null);
        }
    }

    /**
     * Update a certain field of subscription in the database. Also update the subscription cache
     * {@link #mAllSubscriptionInfoInternalCache}.
     *
     * @param subId The subscription id.
     * @param columnName The database column name from the database table {@link SimInfo}.
     * @param newValue The new value to update the subscription info cache
     * {@link #mAllSubscriptionInfoInternalCache}.
     * @param builderSetMethod The {@link SubscriptionInfo.Builder} method to set a specific field
     * when constructing the new {@link SubscriptionInfo}. This should be one of the
     * SubscriptionInfoInternal.Builder.setXxxx method.
     * @param <T> The type of newValue for subscription cache update.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    private <T> void writeDatabaseAndCacheHelper(int subId, @NonNull String columnName,
            @Nullable T newValue,
            BiFunction<SubscriptionInfoInternal.Builder, T, SubscriptionInfoInternal.Builder>
                    builderSetMethod) {
        ContentValues contentValues = new ContentValues();

        // Grab the write lock so no other threads can read or write the cache.
        mReadWriteLock.writeLock().lock();
        try {
            final SubscriptionInfoInternal oldSubInfo =
                    mAllSubscriptionInfoInternalCache.get(subId);
            if (oldSubInfo == null) {
                logel("Subscription doesn't exist. subId=" + subId + ", columnName=" + columnName);
                throw new IllegalArgumentException("Subscription doesn't exist. subId=" + subId
                        + ", columnName=" + columnName);
            }

            // Check if writing this field should automatically write to the rest of subscriptions
            // in the same group.
            final boolean syncToGroup = GROUP_SHARING_COLUMNS.contains(columnName);

            mAllSubscriptionInfoInternalCache.forEach((id, subInfo) -> {
                if (id == subId || (syncToGroup && !oldSubInfo.getGroupUuid().isEmpty()
                        && oldSubInfo.getGroupUuid().equals(subInfo.getGroupUuid()))) {
                    // Check if the new value is different from the old value in the cache.
                    if (!Objects.equals(getSubscriptionInfoFieldByColumnName(subInfo, columnName),
                            newValue)) {
                        logv("writeDatabaseAndCacheHelper: subId=" + subId + ",columnName="
                                + columnName + ", newValue=" + newValue);
                        // If the value is different, then we need to update the cache. Since all
                        // fields in SubscriptionInfo are final, we need to create a new
                        // SubscriptionInfo.
                        SubscriptionInfoInternal.Builder builder = new SubscriptionInfoInternal
                                .Builder(subInfo);

                        // Apply the new value to the builder. This line is equivalent to
                        // builder.setXxxxxx(newValue);
                        builder = builderSetMethod.apply(builder, newValue);

                        // Prepare the content value for update.
                        contentValues.putObject(columnName, newValue);
                        if (updateDatabase(id, contentValues) > 0) {
                            // Update the subscription database cache.
                            mAllSubscriptionInfoInternalCache.put(id, builder.build());
                            mCallback.invokeFromExecutor(()
                                    -> mCallback.onSubscriptionChanged(subId));
                        }
                    }
                }
            });
        } finally {
            mReadWriteLock.writeLock().unlock();
        }
    }

    /**
     * Update the database with the {@link SubscriptionInfoInternal}, and also update the cache.
     *
     * @param newSubInfo The new {@link SubscriptionInfoInternal}.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void updateSubscription(@NonNull SubscriptionInfoInternal newSubInfo) {
        Objects.requireNonNull(newSubInfo);

        // Grab the write lock so no other threads can read or write the cache.
        mReadWriteLock.writeLock().lock();
        try {
            int subId = newSubInfo.getSubscriptionId();
            SubscriptionInfoInternal oldSubInfo = mAllSubscriptionInfoInternalCache.get(
                    newSubInfo.getSubscriptionId());
            if (oldSubInfo == null) {
                throw new IllegalArgumentException("updateSubscription: subscription does not "
                        + "exist. subId=" + subId);
            }
            if (oldSubInfo.equals(newSubInfo)) return;

            if (updateDatabase(subId, createDeltaContentValues(oldSubInfo, newSubInfo)) > 0) {
                mAllSubscriptionInfoInternalCache.put(subId, newSubInfo);
                mCallback.invokeFromExecutor(() -> mCallback.onSubscriptionChanged(subId));
            }
        } finally {
            mReadWriteLock.writeLock().unlock();
        }
    }

    /**
     * Set the ICCID of the SIM that is associated with the subscription.
     *
     * @param subId Subscription id.
     * @param iccId The ICCID of the SIM that is associated with this subscription.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setIccId(int subId, @NonNull String iccId) {
        Objects.requireNonNull(iccId);
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_ICC_ID, iccId,
                SubscriptionInfoInternal.Builder::setIccId);
    }

    /**
     * Set the SIM index of the slot that currently contains the subscription. Set to
     * {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if the subscription is inactive.
     *
     * @param subId Subscription id.
     * @param simSlotIndex The SIM slot index.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setSimSlotIndex(int subId, int simSlotIndex) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_SIM_SLOT_INDEX, simSlotIndex,
                SubscriptionInfoInternal.Builder::setSimSlotIndex);
    }

    /**
     * Set the name displayed to the user that identifies this subscription. This name is used
     * in Settings page and can be renamed by the user.
     *
     * @param subId Subscription id.
     * @param displayName The display name.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setDisplayName(int subId, @NonNull String displayName) {
        Objects.requireNonNull(displayName);
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_DISPLAY_NAME, displayName,
                SubscriptionInfoInternal.Builder::setDisplayName);
    }

    /**
     * Set the name displayed to the user that identifies subscription provider name. This name
     * is the SPN displayed in status bar and many other places. Can't be renamed by the user.
     *
     * @param subId Subscription id.
     * @param carrierName The carrier name.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setCarrierName(int subId, @NonNull String carrierName) {
        Objects.requireNonNull(carrierName);
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_CARRIER_NAME, carrierName,
                SubscriptionInfoInternal.Builder::setCarrierName);
    }

    /**
     * Set the source of the display name.
     *
     * @param subId Subscription id.
     * @param displayNameSource The source of the display name.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     *
     * @see SubscriptionInfo#getDisplayName()
     */
    public void setDisplayNameSource(int subId, @SimDisplayNameSource int displayNameSource) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_NAME_SOURCE, displayNameSource,
                SubscriptionInfoInternal.Builder::setDisplayNameSource);
    }

    /**
     * Set the color to be used for tinting the icon when displaying to the user.
     *
     * @param subId Subscription id.
     * @param iconTint The color to be used for tinting the icon when displaying to the user.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setIconTint(int subId, @ColorInt int iconTint) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_COLOR, iconTint,
                SubscriptionInfoInternal.Builder::setIconTint);
    }

    /**
     * Set the number presented to the user identify this subscription.
     *
     * @param subId Subscription id.
     * @param number the number presented to the user identify this subscription.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setNumber(int subId, @NonNull String number) {
        Objects.requireNonNull(number);
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_NUMBER, number,
                SubscriptionInfoInternal.Builder::setNumber);
    }

    /**
     * Set whether user enables data roaming for this subscription or not.
     *
     * @param subId Subscription id.
     * @param dataRoaming Data roaming mode. Either
     * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or
     * {@link SubscriptionManager#DATA_ROAMING_DISABLE}
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setDataRoaming(int subId, @DataRoamingMode int dataRoaming) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_DATA_ROAMING, dataRoaming,
                SubscriptionInfoInternal.Builder::setDataRoaming);
    }

    /**
     * Set the mobile country code.
     *
     * @param subId Subscription id.
     * @param mcc The mobile country code.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setMcc(int subId, @NonNull String mcc) {
        Objects.requireNonNull(mcc);
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_MCC_STRING, mcc,
                SubscriptionInfoInternal.Builder::setMcc);
    }

    /**
     * Set the mobile network code.
     *
     * @param subId Subscription id.
     * @param mnc Mobile network code.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setMnc(int subId, @NonNull String mnc) {
        Objects.requireNonNull(mnc);
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_MNC_STRING, mnc,
                SubscriptionInfoInternal.Builder::setMnc);
    }

    /**
     * Set EHPLMNs associated with the subscription.
     *
     * @param subId Subscription id.
     * @param ehplmns EHPLMNs associated with the subscription.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setEhplmns(int subId, @NonNull String[] ehplmns) {
        Objects.requireNonNull(ehplmns);
        setEhplmns(subId, Arrays.stream(ehplmns)
                .filter(Predicate.not(TextUtils::isEmpty))
                .collect(Collectors.joining(",")));
    }

    /**
     * Set EHPLMNs associated with the subscription.
     *
     * @param subId Subscription id.
     * @param ehplmns EHPLMNs associated with the subscription.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setEhplmns(int subId, @NonNull String ehplmns) {
        Objects.requireNonNull(ehplmns);
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_EHPLMNS, ehplmns,
                SubscriptionInfoInternal.Builder::setEhplmns);
    }

    /**
     * Set HPLMNs associated with the subscription.
     *
     * @param subId Subscription id.
     * @param hplmns HPLMNs associated with the subscription.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setHplmns(int subId, @NonNull String[] hplmns) {
        Objects.requireNonNull(hplmns);
        setHplmns(subId, Arrays.stream(hplmns)
                .filter(Predicate.not(TextUtils::isEmpty))
                .collect(Collectors.joining(",")));
    }

    /**
     * Set HPLMNs associated with the subscription.
     *
     * @param subId Subscription id.
     * @param hplmns HPLMNs associated with the subscription.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setHplmns(int subId, @NonNull String hplmns) {
        Objects.requireNonNull(hplmns);
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_HPLMNS, hplmns,
                SubscriptionInfoInternal.Builder::setHplmns);
    }

    /**
     * Set whether the subscription is from eSIM or not.
     *
     * @param subId Subscription id.
     * @param isEmbedded if the subscription is from eSIM.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setEmbedded(int subId, int isEmbedded) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_IS_EMBEDDED, isEmbedded,
                SubscriptionInfoInternal.Builder::setEmbedded);
    }

    /**
     * Set whether the subscription is from eSIM or not.
     *
     * @param subId Subscription id.
     * @param isEmbedded {@code true} if the subscription is from eSIM.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setEmbedded(int subId, boolean isEmbedded) {
        setEmbedded(subId, isEmbedded ? 1 : 0);
    }

    /**
     * Set the card string of the SIM card. This is usually the ICCID or EID.
     *
     * @param subId Subscription id.
     * @param cardString The card string of the SIM card.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     *
     * @see SubscriptionInfo#getCardString()
     */
    public void setCardString(int subId, @NonNull String cardString) {
        Objects.requireNonNull(cardString);
        // Also update the public card id.
        setCardId(subId, mUiccController.convertToPublicCardId(cardString));
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_CARD_ID, cardString,
                SubscriptionInfoInternal.Builder::setCardString);
    }

    /**
     * Set the card id. This is the non-PII card id converted from
     * {@link SubscriptionInfoInternal#getCardString()}. This field only exists in
     * {@link SubscriptionInfo}, but not the database.
     *
     * @param subId Subscription id.
     * @param cardId The card id.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setCardId(int subId, int cardId) {
        // card id does not have a corresponding SimInfo column. So we only update the cache.

        // Grab the write lock so no other threads can read or write the cache.
        mReadWriteLock.writeLock().lock();
        try {
            SubscriptionInfoInternal subInfoCache = mAllSubscriptionInfoInternalCache.get(subId);
            if (subInfoCache == null) {
                throw new IllegalArgumentException("setCardId: Subscription doesn't exist. subId="
                        + subId);
            }
            mAllSubscriptionInfoInternalCache.put(subId,
                    new SubscriptionInfoInternal.Builder(subInfoCache)
                            .setCardId(cardId).build());
        } finally {
            mReadWriteLock.writeLock().unlock();
        }
    }

    /**
     * Set the native access rules for this subscription, if it is embedded and defines any.
     * This does not include access rules for non-embedded subscriptions.
     *
     * @param subId Subscription id.
     * @param nativeAccessRules The native access rules for this subscription.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setNativeAccessRules(int subId, @NonNull byte[] nativeAccessRules) {
        Objects.requireNonNull(nativeAccessRules);
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_ACCESS_RULES, nativeAccessRules,
                SubscriptionInfoInternal.Builder::setNativeAccessRules);
    }

    /**
     * Set the carrier certificates for this subscription that are saved in carrier configs.
     * This does not include access rules from the Uicc, whether embedded or non-embedded.
     *
     * @param subId Subscription id.
     * @param carrierConfigAccessRules The carrier certificates for this subscription.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setCarrierConfigAccessRules(int subId, @NonNull byte[] carrierConfigAccessRules) {
        Objects.requireNonNull(carrierConfigAccessRules);
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_ACCESS_RULES_FROM_CARRIER_CONFIGS,
                carrierConfigAccessRules,
                SubscriptionInfoInternal.Builder::setCarrierConfigAccessRules);
    }

    /**
     * Set the carrier certificates for this subscription that are saved in carrier configs.
     * This does not include access rules from the Uicc, whether embedded or non-embedded.
     *
     * @param subId Subscription id.
     * @param carrierConfigAccessRules The carrier certificates for this subscription.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setCarrierConfigAccessRules(int subId,
            @NonNull UiccAccessRule[] carrierConfigAccessRules) {
        Objects.requireNonNull(carrierConfigAccessRules);
        byte[] carrierConfigAccessRulesBytes = UiccAccessRule.encodeRules(carrierConfigAccessRules);
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_ACCESS_RULES_FROM_CARRIER_CONFIGS,
                carrierConfigAccessRulesBytes,
                SubscriptionInfoInternal.Builder::setCarrierConfigAccessRules);
    }

    /**
     * Set whether an embedded subscription is on a removable card. Such subscriptions are
     * marked inaccessible as soon as the current card is removed. Otherwise, they will remain
     * accessible unless explicitly deleted. Only meaningful for embedded subscription.
     *
     * @param subId Subscription id.
     * @param isRemovableEmbedded if the subscription is from the removable embedded SIM.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setRemovableEmbedded(int subId, int isRemovableEmbedded) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_IS_REMOVABLE, isRemovableEmbedded,
                SubscriptionInfoInternal.Builder::setRemovableEmbedded);
    }

    /**
     * Set whether cell broadcast extreme threat alert is enabled by the user or not.
     *
     * @param subId Subscription id.
     * @param isExtremeThreatAlertEnabled whether cell broadcast extreme threat alert is enabled by
     * the user or not.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setCellBroadcastExtremeThreatAlertEnabled(int subId,
            int isExtremeThreatAlertEnabled) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_CB_EXTREME_THREAT_ALERT,
                isExtremeThreatAlertEnabled,
                SubscriptionInfoInternal.Builder::setCellBroadcastExtremeThreatAlertEnabled);
    }

    /**
     * Set whether cell broadcast severe threat alert is enabled by the user or not.
     *
     * @param subId Subscription id.
     * @param isSevereThreatAlertEnabled whether cell broadcast severe threat alert is enabled by
     * the user or not.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setCellBroadcastSevereThreatAlertEnabled(int subId,
            int isSevereThreatAlertEnabled) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_CB_SEVERE_THREAT_ALERT,
                isSevereThreatAlertEnabled,
                SubscriptionInfoInternal.Builder::setCellBroadcastSevereThreatAlertEnabled);
    }

    /**
     * Set whether cell broadcast amber alert is enabled by the user or not.
     *
     * @param subId Subscription id.
     * @param isAmberAlertEnabled whether cell broadcast amber alert is enabled by
     * the user or not.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setCellBroadcastAmberAlertEnabled(int subId, int isAmberAlertEnabled) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_CB_AMBER_ALERT, isAmberAlertEnabled,
                SubscriptionInfoInternal.Builder::setCellBroadcastAmberAlertEnabled);
    }

    /**
     * Set whether cell broadcast emergency alert is enabled by the user or not.
     *
     * @param subId Subscription id.
     * @param isEmergencyAlertEnabled whether cell broadcast emergency alert is enabled by
     * the user or not.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setCellBroadcastEmergencyAlertEnabled(int subId,
            int isEmergencyAlertEnabled) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_CB_EMERGENCY_ALERT,
                isEmergencyAlertEnabled,
                SubscriptionInfoInternal.Builder::setCellBroadcastEmergencyAlertEnabled);
    }

    /**
     * Set cell broadcast alert sound duration.
     *
     * @param subId Subscription id.
     * @param alertSoundDuration Alert sound duration in seconds.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setCellBroadcastAlertSoundDuration(int subId, int alertSoundDuration) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_CB_ALERT_SOUND_DURATION,
                alertSoundDuration,
                SubscriptionInfoInternal.Builder::setCellBroadcastAlertSoundDuration);
    }

    /**
     * Set cell broadcast alert reminder interval.
     *
     * @param subId Subscription id.
     * @param reminderInterval Alert reminder interval in milliseconds.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setCellBroadcastAlertReminderInterval(int subId, int reminderInterval) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_CB_ALERT_REMINDER_INTERVAL,
                reminderInterval,
                SubscriptionInfoInternal.Builder::setCellBroadcastAlertReminderInterval);
    }

    /**
     * Set whether cell broadcast alert vibration is enabled by the user or not.
     *
     * @param subId Subscription id.
     * @param isAlertVibrationEnabled whether cell broadcast alert vibration is enabled by the user
     * or not.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setCellBroadcastAlertVibrationEnabled(int subId, int isAlertVibrationEnabled) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_CB_ALERT_VIBRATE, isAlertVibrationEnabled,
                SubscriptionInfoInternal.Builder::setCellBroadcastAlertVibrationEnabled);
    }

    /**
     * Set whether cell broadcast alert speech is enabled by the user or not.
     *
     * @param subId Subscription id.
     * @param isAlertSpeechEnabled whether cell broadcast alert speech is enabled by the user or
     * not.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setCellBroadcastAlertSpeechEnabled(int subId, int isAlertSpeechEnabled) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_CB_ALERT_SPEECH, isAlertSpeechEnabled,
                SubscriptionInfoInternal.Builder::setCellBroadcastAlertSpeechEnabled);
    }

    /**
     * Set whether ETWS test alert is enabled by the user or not.
     *
     * @param subId Subscription id.
     * @param isEtwsTestAlertEnabled whether cell broadcast ETWS test alert is enabled by the user
     * or not.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setCellBroadcastEtwsTestAlertEnabled(int subId, int isEtwsTestAlertEnabled) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_CB_ETWS_TEST_ALERT,
                isEtwsTestAlertEnabled,
                SubscriptionInfoInternal.Builder::setCellBroadcastEtwsTestAlertEnabled);
    }

    /**
     * Set whether area info message is enabled by the user or not.
     *
     * @param subId Subscription id.
     * @param isAreaInfoMessageEnabled whether cell broadcast area info message is enabled by the
     * user or not.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setCellBroadcastAreaInfoMessageEnabled(int subId, int isAreaInfoMessageEnabled) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_CB_CHANNEL_50_ALERT,
                isAreaInfoMessageEnabled,
                SubscriptionInfoInternal.Builder::setCellBroadcastAreaInfoMessageEnabled);
    }

    /**
     * Set whether cell broadcast test alert is enabled by the user or not.
     *
     * @param subId Subscription id.
     * @param isTestAlertEnabled whether cell broadcast test alert is enabled by the user or not.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setCellBroadcastTestAlertEnabled(int subId, int isTestAlertEnabled) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_CB_CMAS_TEST_ALERT, isTestAlertEnabled,
                SubscriptionInfoInternal.Builder::setCellBroadcastTestAlertEnabled);
    }

    /**
     * Set whether cell broadcast opt-out dialog should be shown or not.
     *
     * @param subId Subscription id.
     * @param isOptOutDialogEnabled whether cell broadcast opt-out dialog should be shown or not.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setCellBroadcastOptOutDialogEnabled(int subId, int isOptOutDialogEnabled) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_CB_OPT_OUT_DIALOG, isOptOutDialogEnabled,
                SubscriptionInfoInternal.Builder::setCellBroadcastOptOutDialogEnabled);
    }

    /**
     * Set whether enhanced 4G mode is enabled by the user or not.
     *
     * @param subId Subscription id.
     * @param isEnhanced4GModeEnabled whether enhanced 4G mode is enabled by the user or not.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setEnhanced4GModeEnabled(int subId, int isEnhanced4GModeEnabled) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_ENHANCED_4G_MODE_ENABLED,
                isEnhanced4GModeEnabled,
                SubscriptionInfoInternal.Builder::setEnhanced4GModeEnabled);
    }

    /**
     * Set whether video telephony is enabled by the user or not.
     *
     * @param subId Subscription id.
     * @param isVideoTelephonyEnabled whether video telephony is enabled by the user or not.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setVideoTelephonyEnabled(int subId, int isVideoTelephonyEnabled) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_VT_IMS_ENABLED, isVideoTelephonyEnabled,
                SubscriptionInfoInternal.Builder::setVideoTelephonyEnabled);
    }

    /**
     * Set whether Wi-Fi calling is enabled by the user or not when the device is not roaming.
     *
     * @param subId Subscription id.
     * @param isWifiCallingEnabled whether Wi-Fi calling is enabled by the user or not when the
     * device is not roaming.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setWifiCallingEnabled(int subId, int isWifiCallingEnabled) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_WFC_IMS_ENABLED, isWifiCallingEnabled,
                SubscriptionInfoInternal.Builder::setWifiCallingEnabled);
    }

    /**
     * Set Wi-Fi calling mode when the device is not roaming.
     *
     * @param subId Subscription id.
     * @param wifiCallingMode Wi-Fi calling mode when the device is not roaming.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setWifiCallingMode(int subId,
            @ImsMmTelManager.WiFiCallingMode int wifiCallingMode) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_WFC_IMS_MODE, wifiCallingMode,
                SubscriptionInfoInternal.Builder::setWifiCallingMode);
    }

    /**
     * Set Wi-Fi calling mode when the device is roaming.
     *
     * @param subId Subscription id.
     * @param wifiCallingModeForRoaming Wi-Fi calling mode when the device is roaming.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setWifiCallingModeForRoaming(int subId,
            @ImsMmTelManager.WiFiCallingMode int wifiCallingModeForRoaming) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_WFC_IMS_ROAMING_MODE,
                wifiCallingModeForRoaming,
                SubscriptionInfoInternal.Builder::setWifiCallingModeForRoaming);
    }

    /**
     * Set whether Wi-Fi calling is enabled by the user or not when the device is roaming.
     *
     * @param subId Subscription id.
     * @param isWifiCallingEnabledForRoaming whether Wi-Fi calling is enabled by the user or not
     * when the device is roaming.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setWifiCallingEnabledForRoaming(int subId, int isWifiCallingEnabledForRoaming) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_WFC_IMS_ROAMING_ENABLED,
                isWifiCallingEnabledForRoaming,
                SubscriptionInfoInternal.Builder::setWifiCallingEnabledForRoaming);
    }

    /**
     * Set whether the subscription is opportunistic or not.
     *
     * @param subId Subscription id.
     * @param isOpportunistic if the subscription is opportunistic.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setOpportunistic(int subId, boolean isOpportunistic) {
        setOpportunistic(subId, isOpportunistic ? 1 : 0);
    }

    /**
     * Set whether the subscription is opportunistic or not.
     *
     * @param subId Subscription id.
     * @param isOpportunistic if the subscription is opportunistic.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setOpportunistic(int subId, int isOpportunistic) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_IS_OPPORTUNISTIC, isOpportunistic,
                SubscriptionInfoInternal.Builder::setOpportunistic);
    }

    /**
     * Set the group UUID of the subscription group.
     *
     * @param subId Subscription id.
     * @param groupUuid The group UUID.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     *
     * @see SubscriptionInfo#getGroupUuid()
     */
    public void setGroupUuid(int subId, @NonNull String groupUuid) {
        Objects.requireNonNull(groupUuid);
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_GROUP_UUID, groupUuid,
                SubscriptionInfoInternal.Builder::setGroupUuid);
    }

    /**
     * Set the ISO Country code for the subscription's provider.
     *
     * @param subId Subscription id.
     * @param countryIso The ISO country code for the subscription's provider.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setCountryIso(int subId, @NonNull String countryIso) {
        Objects.requireNonNull(countryIso);
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_ISO_COUNTRY_CODE, countryIso,
                SubscriptionInfoInternal.Builder::setCountryIso);
    }

    /**
     * Set the subscription carrier id.
     *
     * @param subId Subscription id.
     * @param carrierId The carrier id.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     *
     * @see TelephonyManager#getSimCarrierId()
     */
    public void setCarrierId(int subId, int carrierId) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_CARRIER_ID, carrierId,
                SubscriptionInfoInternal.Builder::setCarrierId);
    }

    /**
     * Set the profile class populated from the profile metadata if present.
     *
     * @param subId Subscription id.
     * @param profileClass the profile class populated from the profile metadata if present.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     *
     * @see SubscriptionInfo#getProfileClass()
     */
    public void setProfileClass(int subId, @ProfileClass int profileClass) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_PROFILE_CLASS, profileClass,
                SubscriptionInfoInternal.Builder::setProfileClass);
    }

    /**
     * Set the subscription type.
     *
     * @param subId Subscription id.
     * @param type Subscription type.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setSubscriptionType(int subId, @SubscriptionType int type) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_SUBSCRIPTION_TYPE, type,
                SubscriptionInfoInternal.Builder::setType);
    }

    /**
     * Set the owner package of group the subscription belongs to.
     *
     * @param subId Subscription id.
     * @param groupOwner Owner package of group the subscription belongs to.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setGroupOwner(int subId, @NonNull String groupOwner) {
        Objects.requireNonNull(groupOwner);
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_GROUP_OWNER, groupOwner,
                SubscriptionInfoInternal.Builder::setGroupOwner);
    }

    /**
     * Set the enabled mobile data policies.
     *
     * @param subId Subscription id.
     * @param enabledMobileDataPolicies The enabled mobile data policies.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setEnabledMobileDataPolicies(int subId, @NonNull String enabledMobileDataPolicies) {
        Objects.requireNonNull(enabledMobileDataPolicies);
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_ENABLED_MOBILE_DATA_POLICIES,
                enabledMobileDataPolicies,
                SubscriptionInfoInternal.Builder::setEnabledMobileDataPolicies);
    }

    /**
     * Set the IMSI (International Mobile Subscriber Identity) of the subscription.
     *
     * @param subId Subscription id.
     * @param imsi The IMSI.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setImsi(int subId, @NonNull String imsi) {
        Objects.requireNonNull(imsi);
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_IMSI, imsi,
                SubscriptionInfoInternal.Builder::setImsi);
    }

    /**
     * Set whether Uicc applications are configured to enable or not.
     *
     * @param subId Subscription id.
     * @param areUiccApplicationsEnabled if Uicc applications are configured to enable.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setUiccApplicationsEnabled(int subId, boolean areUiccApplicationsEnabled) {
        setUiccApplicationsEnabled(subId, areUiccApplicationsEnabled ? 1 : 0);
    }

    /**
     * Set whether Uicc applications are configured to enable or not.
     *
     * @param subId Subscription id.
     * @param areUiccApplicationsEnabled if Uicc applications are configured to enable.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setUiccApplicationsEnabled(int subId, int areUiccApplicationsEnabled) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_UICC_APPLICATIONS_ENABLED,
                areUiccApplicationsEnabled,
                SubscriptionInfoInternal.Builder::setUiccApplicationsEnabled);
    }

    /**
     * Set whether the user has enabled IMS RCS User Capability Exchange (UCE) for this
     * subscription.
     *
     * @param subId Subscription id.
     * @param isRcsUceEnabled If the user enabled RCS UCE for this subscription.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setRcsUceEnabled(int subId, int isRcsUceEnabled) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_IMS_RCS_UCE_ENABLED, isRcsUceEnabled,
                SubscriptionInfoInternal.Builder::setRcsUceEnabled);
    }

    /**
     * Set whether the user has enabled cross SIM calling for this subscription.
     *
     * @param subId Subscription id.
     * @param isCrossSimCallingEnabled If the user enabled cross SIM calling for this
     * subscription.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setCrossSimCallingEnabled(int subId, int isCrossSimCallingEnabled) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_CROSS_SIM_CALLING_ENABLED,
                isCrossSimCallingEnabled,
                SubscriptionInfoInternal.Builder::setCrossSimCallingEnabled);
    }

    /**
     * Set the RCS config for this subscription.
     *
     * @param subId Subscription id.
     * @param rcsConfig The RCS config for this subscription.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setRcsConfig(int subId, @NonNull byte[] rcsConfig) {
        Objects.requireNonNull(rcsConfig);
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_RCS_CONFIG, rcsConfig,
                SubscriptionInfoInternal.Builder::setRcsConfig);
    }

    /**
     * Set the allowed network types for reasons.
     *
     * @param subId Subscription id.
     * @param allowedNetworkTypesForReasons The allowed network types for reasons in string
     * format. The format is "[reason]=[network types bitmask], [reason]=[network types bitmask],
     * ...". For example, "user=1239287394, thermal=298791239, carrier=3456812312".
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setAllowedNetworkTypesForReasons(int subId,
            @NonNull String allowedNetworkTypesForReasons) {
        Objects.requireNonNull(allowedNetworkTypesForReasons);
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_ALLOWED_NETWORK_TYPES_FOR_REASONS,
                allowedNetworkTypesForReasons,
                SubscriptionInfoInternal.Builder::setAllowedNetworkTypesForReasons);
    }

    /**
     * Set device to device sharing status.
     *
     * @param subId Subscription id.
     * @param deviceToDeviceStatusSharingPreference Device to device sharing status.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setDeviceToDeviceStatusSharingPreference(int subId,
            @DeviceToDeviceStatusSharingPreference int deviceToDeviceStatusSharingPreference) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_D2D_STATUS_SHARING,
                deviceToDeviceStatusSharingPreference,
                SubscriptionInfoInternal.Builder::setDeviceToDeviceStatusSharingPreference);
    }

    /**
     * Set whether the user has opted-in voice over IMS.
     *
     * @param subId Subscription id.
     * @param isVoImsOptInEnabled Whether the user has opted-in voice over IMS.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setVoImsOptInEnabled(int subId, int isVoImsOptInEnabled) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_VOIMS_OPT_IN_STATUS, isVoImsOptInEnabled,
                SubscriptionInfoInternal.Builder::setVoImsOptInEnabled);
    }

    /**
     * Set contacts information that allow device to device sharing.
     *
     * @param subId Subscription id.
     * @param deviceToDeviceStatusSharingContacts contacts information that allow device to
     * device sharing.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setDeviceToDeviceStatusSharingContacts(int subId,
            @NonNull String deviceToDeviceStatusSharingContacts) {
        Objects.requireNonNull(deviceToDeviceStatusSharingContacts);
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_D2D_STATUS_SHARING_SELECTED_CONTACTS,
                deviceToDeviceStatusSharingContacts,
                SubscriptionInfoInternal.Builder::setDeviceToDeviceStatusSharingContacts);
    }

    /**
     * Set whether the user has enabled NR advanced calling.
     *
     * @param subId Subscription id.
     * @param isNrAdvancedCallingEnabled Whether the user has enabled NR advanced calling.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setNrAdvancedCallingEnabled(int subId, int isNrAdvancedCallingEnabled) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_NR_ADVANCED_CALLING_ENABLED,
                isNrAdvancedCallingEnabled,
                SubscriptionInfoInternal.Builder::setNrAdvancedCallingEnabled);
    }

    /**
     * Set the phone number retrieved from carrier.
     *
     * @param subId Subscription id.
     * @param numberFromCarrier The phone number retrieved from carrier.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setNumberFromCarrier(int subId, @NonNull String numberFromCarrier) {
        Objects.requireNonNull(numberFromCarrier);
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_PHONE_NUMBER_SOURCE_CARRIER,
                numberFromCarrier, SubscriptionInfoInternal.Builder::setNumberFromCarrier);
    }

    /**
     * Set the phone number retrieved from IMS.
     *
     * @param subId Subscription id.
     * @param numberFromIms The phone number retrieved from IMS.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setNumberFromIms(int subId, @NonNull String numberFromIms) {
        Objects.requireNonNull(numberFromIms);
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_PHONE_NUMBER_SOURCE_IMS,
                numberFromIms, SubscriptionInfoInternal.Builder::setNumberFromIms);
    }

    /**
     * Set the port index of the Uicc card.
     *
     * @param subId Subscription id.
     * @param portIndex The port index of the Uicc card.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setPortIndex(int subId, int portIndex) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_PORT_INDEX, portIndex,
                SubscriptionInfoInternal.Builder::setPortIndex);
    }

    /**
     * Set subscription's preferred usage setting.
     *
     * @param subId Subscription id.
     * @param usageSetting Subscription's preferred usage setting.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setUsageSetting(int subId, @UsageSetting int usageSetting) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_USAGE_SETTING, usageSetting,
                SubscriptionInfoInternal.Builder::setUsageSetting);
    }

    /**
     * Set last used TP message reference.
     *
     * @param subId Subscription id.
     * @param lastUsedTPMessageReference Last used TP message reference.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setLastUsedTPMessageReference(int subId, int lastUsedTPMessageReference) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_TP_MESSAGE_REF,
                lastUsedTPMessageReference,
                SubscriptionInfoInternal.Builder::setLastUsedTPMessageReference);
    }

    /**
     * Set the user id associated with this subscription.
     *
     * @param subId Subscription id.
     * @param userId The user id associated with this subscription.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setUserId(int subId, @UserIdInt int userId) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_USER_HANDLE, userId,
                SubscriptionInfoInternal.Builder::setUserId);
    }

    /**
     * Set whether satellite is enabled or not.
     *
     * @param subId Subscription id.
     * @param isSatelliteEnabled if satellite is enabled or not.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setSatelliteEnabled(int subId, int isSatelliteEnabled) {
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_SATELLITE_ENABLED,
                isSatelliteEnabled,
                SubscriptionInfoInternal.Builder::setSatelliteEnabled);
    }

    /**
     * Set whether satellite attach for carrier is enabled or disabled by user.
     *
     * @param subId Subscription id.
     * @param isSatelliteAttachEnabledForCarrier Whether satellite attach for carrier is enabled or
     * disabled.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setSatelliteAttachEnabledForCarrier(int subId,
            int isSatelliteAttachEnabledForCarrier) {
        writeDatabaseAndCacheHelper(subId,
                SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
                isSatelliteAttachEnabledForCarrier,
                SubscriptionInfoInternal.Builder::setSatelliteAttachEnabledForCarrier);
    }

    /**
     * Set whether the subscription is exclusively used for non-terrestrial networks or not.
     *
     * @param subId Subscription ID.
     * @param isNtn {@code 1} if it is a non-terrestrial network subscription.
     * {@code 0} otherwise.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setNtn(int subId, int isNtn) {
        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
            return;
        }
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_IS_NTN, isNtn,
                SubscriptionInfoInternal.Builder::setOnlyNonTerrestrialNetwork);
    }

    /**
     * Set whether group of the subscription is disabled. This is only useful if it's a grouped
     * opportunistic subscription. In this case, if all primary (non-opportunistic)
     * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile),
     * we should disable this opportunistic subscription.
     *
     * @param subId Subscription id.
     * @param isGroupDisabled if group of the subscription is disabled.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setGroupDisabled(int subId, boolean isGroupDisabled) {
        // group disabled does not have a corresponding SimInfo column. So we only update the cache.
        boolean isChanged = false;
        // Grab the write lock so no other threads can read or write the cache.
        mReadWriteLock.writeLock().lock();
        try {
            SubscriptionInfoInternal subInfoCache = mAllSubscriptionInfoInternalCache.get(subId);
            if (subInfoCache == null) {
                throw new IllegalArgumentException("setGroupDisabled: Subscription doesn't exist. "
                        + "subId=" + subId);
            }
            isChanged = subInfoCache.isGroupDisabled() != isGroupDisabled;
            mAllSubscriptionInfoInternalCache.put(subId,
                    new SubscriptionInfoInternal.Builder(subInfoCache)
                            .setGroupDisabled(isGroupDisabled).build());
        } finally {
            mReadWriteLock.writeLock().unlock();
        }

        if (isChanged) {
            log("setGroupDisabled value changed, firing the callback");
            mCallback.invokeFromExecutor(() -> mCallback.onSubscriptionChanged(subId));
        }
    }

    /**
     * Set service capabilities the subscription support.
     * @param subId Subscription id.
     * @param capabilities Service capabilities bitmasks
     */
    public void setServiceCapabilities(int subId, int capabilities) {
        if (!mFeatureFlags.dataOnlyCellularService()) {
            return;
        }
        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_SERVICE_CAPABILITIES,
                capabilities, SubscriptionInfoInternal.Builder::setServiceCapabilities);
    }

    /**
     * Set whether satellite entitlement status is enabled by entitlement query result.
     *
     * @param subId Subscription id.
     * @param isSatelliteEntitlementStatus Whether satellite entitlement status is enabled or
     * disabled.
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setSatelliteEntitlementStatus(int subId,
            int isSatelliteEntitlementStatus) {
        writeDatabaseAndCacheHelper(subId,
                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS,
                isSatelliteEntitlementStatus,
                SubscriptionInfoInternal.Builder::setSatelliteEntitlementStatus);
    }

    /**
     * Set satellite entitlement plmns by entitlement query result.
     *
     * @param subId Subscription id.
     * @param satelliteEntitlementPlmns Satellite entitlement plmns
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setSatelliteEntitlementPlmns(int subId,
            @NonNull String satelliteEntitlementPlmns) {
        writeDatabaseAndCacheHelper(subId,
                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS,
                satelliteEntitlementPlmns,
                SubscriptionInfoInternal.Builder::setSatelliteEntitlementPlmns);
    }

    /**
     * Set satellite entitlement plmn list by entitlement query result.
     *
     * @param subId Subscription id.
     * @param satelliteEntitlementPlmnList Satellite entitlement plmn list
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setSatelliteEntitlementPlmnList(int subId,
            @NonNull List<String> satelliteEntitlementPlmnList) {
        String satelliteEntitlementPlmns = satelliteEntitlementPlmnList.stream().collect(
                Collectors.joining(","));
        setSatelliteEntitlementPlmns(subId, satelliteEntitlementPlmns);
    }

    /**
     * Reload the database from content provider to the cache. This must be a synchronous operation
     * to prevent cache/database out-of-sync. Callers should be cautious to call this method because
     * it might take longer time to complete.
     */
    public void reloadDatabaseSync() {
        logl("reloadDatabaseSync");
        // Synchronously load the database into the cache.
        loadDatabaseInternal();
    }

    /**
     * Load the database from content provider to the cache.
     */
    private void loadDatabaseInternal() {
        logl("loadDatabaseInternal");
        try (Cursor cursor = mContext.getContentResolver().query(
                SimInfo.CONTENT_URI, null, null, null, null)) {
            mReadWriteLock.writeLock().lock();
            try {
                Map<Integer, SubscriptionInfoInternal> newAllSubscriptionInfoInternalCache =
                        new HashMap<>();
                boolean changed = false;
                while (cursor != null && cursor.moveToNext()) {
                    SubscriptionInfoInternal subInfo = createSubscriptionInfoFromCursor(cursor);
                    newAllSubscriptionInfoInternalCache.put(subInfo.getSubscriptionId(), subInfo);
                    if (!Objects.equals(mAllSubscriptionInfoInternalCache
                            .get(subInfo.getSubscriptionId()), subInfo)) {
                        mCallback.invokeFromExecutor(() -> mCallback.onSubscriptionChanged(
                                subInfo.getSubscriptionId()));
                        changed = true;
                    }
                }

                if (changed) {
                    mAllSubscriptionInfoInternalCache.clear();
                    mAllSubscriptionInfoInternalCache.putAll(newAllSubscriptionInfoInternalCache);

                    logl("Loaded " + mAllSubscriptionInfoInternalCache.size()
                            + " records from the subscription database.");
                    mAllSubscriptionInfoInternalCache.forEach(
                            (subId, subInfo) -> log("  " + subInfo.toString()));
                }
            } finally {
                mReadWriteLock.writeLock().unlock();
            }
        }
    }

    /**
     * Initialize the database cache. Load the entire database into the cache.
     */
    private void initializeDatabase() {
        if (mAsyncMode) {
            // Load the database asynchronously.
            post(() -> {
                synchronized (this) {
                    loadDatabaseInternal();
                    mDatabaseInitialized = true;
                    mCallback.invokeFromExecutor(mCallback::onInitialized);
                }
            });
        } else {
            // Load the database synchronously.
            synchronized (this) {
                loadDatabaseInternal();
                mDatabaseInitialized = true;
                mCallback.invokeFromExecutor(mCallback::onInitialized);
            }
        }
    }

    /**
     * Build the {@link SubscriptionInfoInternal} from database.
     *
     * @param cursor  The cursor of the database.
     *
     * @return The subscription info from a single database record.
     */
    @NonNull
    private SubscriptionInfoInternal createSubscriptionInfoFromCursor(@NonNull Cursor cursor) {
        SubscriptionInfoInternal.Builder builder = new SubscriptionInfoInternal.Builder();
        int id = cursor.getInt(cursor.getColumnIndexOrThrow(
                SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID));
        builder.setId(id)
                .setIccId(TextUtils.emptyIfNull(cursor.getString(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_ICC_ID))))
                .setSimSlotIndex(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_SIM_SLOT_INDEX)))
                .setDisplayName(TextUtils.emptyIfNull(cursor.getString(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_DISPLAY_NAME))))
                .setCarrierName(TextUtils.emptyIfNull(cursor.getString(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_CARRIER_NAME))))
                .setDisplayNameSource(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_NAME_SOURCE)))
                .setIconTint(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_COLOR)))
                .setNumber(TextUtils.emptyIfNull(cursor.getString(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_NUMBER))))
                .setDataRoaming(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_DATA_ROAMING)))
                .setMcc(TextUtils.emptyIfNull(cursor.getString(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_MCC_STRING))))
                .setMnc(TextUtils.emptyIfNull(cursor.getString(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_MNC_STRING))))
                .setEhplmns(TextUtils.emptyIfNull(cursor.getString(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_EHPLMNS))))
                .setHplmns(TextUtils.emptyIfNull(cursor.getString(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_HPLMNS))))
                .setEmbedded(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_IS_EMBEDDED)));

        String cardString = TextUtils.emptyIfNull(cursor.getString(cursor.getColumnIndexOrThrow(
                SimInfo.COLUMN_CARD_ID)));
        builder.setCardString(cardString);
        // publicCardId is the publicly exposed int card ID
        int publicCardId = mUiccController.convertToPublicCardId(cardString);

        byte[] rules = cursor.getBlob(cursor.getColumnIndexOrThrow(SimInfo.COLUMN_ACCESS_RULES));
        if (rules != null) {
            builder.setNativeAccessRules(rules);
        }

        rules = cursor.getBlob(cursor.getColumnIndexOrThrow(
                SimInfo.COLUMN_ACCESS_RULES_FROM_CARRIER_CONFIGS));
        if (rules != null) {
            builder.setCarrierConfigAccessRules(rules);
        }

        byte[] config = cursor.getBlob(cursor.getColumnIndexOrThrow(SimInfo.COLUMN_RCS_CONFIG));
        if (config != null) {
            builder.setRcsConfig(config);
        }

        builder.setCardId(publicCardId)
                .setRemovableEmbedded(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_IS_REMOVABLE)))
                .setCellBroadcastExtremeThreatAlertEnabled(cursor.getInt(cursor
                        .getColumnIndexOrThrow(SimInfo.COLUMN_CB_EXTREME_THREAT_ALERT)))
                .setCellBroadcastSevereThreatAlertEnabled(cursor.getInt(cursor
                        .getColumnIndexOrThrow(SimInfo.COLUMN_CB_SEVERE_THREAT_ALERT)))
                .setCellBroadcastAmberAlertEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_CB_AMBER_ALERT)))
                .setCellBroadcastEmergencyAlertEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_CB_EMERGENCY_ALERT)))
                .setCellBroadcastAlertSoundDuration(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_CB_ALERT_SOUND_DURATION)))
                .setCellBroadcastAlertReminderInterval(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_CB_ALERT_REMINDER_INTERVAL)))
                .setCellBroadcastAlertVibrationEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_CB_ALERT_VIBRATE)))
                .setCellBroadcastAlertSpeechEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_CB_ALERT_SPEECH)))
                .setCellBroadcastEtwsTestAlertEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_CB_ETWS_TEST_ALERT)))
                .setCellBroadcastAreaInfoMessageEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_CB_CHANNEL_50_ALERT)))
                .setCellBroadcastTestAlertEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_CB_CMAS_TEST_ALERT)))
                .setCellBroadcastOptOutDialogEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_CB_OPT_OUT_DIALOG)))
                .setEnhanced4GModeEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_ENHANCED_4G_MODE_ENABLED)))
                .setVideoTelephonyEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_VT_IMS_ENABLED)))
                .setWifiCallingEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_WFC_IMS_ENABLED)))
                .setWifiCallingMode(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_WFC_IMS_MODE)))
                .setWifiCallingModeForRoaming(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_WFC_IMS_ROAMING_MODE)))
                .setWifiCallingEnabledForRoaming(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_WFC_IMS_ROAMING_ENABLED)))
                .setOpportunistic(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_IS_OPPORTUNISTIC)))
                .setGroupUuid(TextUtils.emptyIfNull(cursor.getString(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_GROUP_UUID))))
                .setCountryIso(TextUtils.emptyIfNull(cursor.getString(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_ISO_COUNTRY_CODE))))
                .setCarrierId(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_CARRIER_ID)))
                .setProfileClass(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_PROFILE_CLASS)))
                .setType(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_SUBSCRIPTION_TYPE)))
                .setGroupOwner(TextUtils.emptyIfNull(cursor.getString(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_GROUP_OWNER))))
                .setEnabledMobileDataPolicies(TextUtils.emptyIfNull(
                        cursor.getString(cursor.getColumnIndexOrThrow(
                                SimInfo.COLUMN_ENABLED_MOBILE_DATA_POLICIES))))
                .setImsi(TextUtils.emptyIfNull(cursor.getString(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_IMSI))))
                .setUiccApplicationsEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_UICC_APPLICATIONS_ENABLED)))
                .setRcsUceEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_IMS_RCS_UCE_ENABLED)))
                .setCrossSimCallingEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_CROSS_SIM_CALLING_ENABLED)))
                .setAllowedNetworkTypesForReasons(TextUtils.emptyIfNull(
                        cursor.getString(cursor.getColumnIndexOrThrow(
                                SimInfo.COLUMN_ALLOWED_NETWORK_TYPES_FOR_REASONS))))
                .setDeviceToDeviceStatusSharingPreference(cursor.getInt(
                        cursor.getColumnIndexOrThrow(
                                SimInfo.COLUMN_D2D_STATUS_SHARING)))
                .setVoImsOptInEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_VOIMS_OPT_IN_STATUS)))
                .setDeviceToDeviceStatusSharingContacts(TextUtils.emptyIfNull(cursor.getString(
                        cursor.getColumnIndexOrThrow(
                                SimInfo.COLUMN_D2D_STATUS_SHARING_SELECTED_CONTACTS))))
                .setNrAdvancedCallingEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_NR_ADVANCED_CALLING_ENABLED)))
                .setNumberFromCarrier(TextUtils.emptyIfNull(cursor.getString(
                        cursor.getColumnIndexOrThrow(
                                SimInfo.COLUMN_PHONE_NUMBER_SOURCE_CARRIER))))
                .setNumberFromIms(TextUtils.emptyIfNull(cursor.getString(
                        cursor.getColumnIndexOrThrow(
                                SimInfo.COLUMN_PHONE_NUMBER_SOURCE_IMS))))
                .setPortIndex(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_PORT_INDEX)))
                .setUsageSetting(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_USAGE_SETTING)))
                .setLastUsedTPMessageReference(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_TP_MESSAGE_REF)))
                .setUserId(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_USER_HANDLE)))
                .setSatelliteEnabled(cursor.getInt(cursor.getColumnIndexOrThrow(
                        SimInfo.COLUMN_SATELLITE_ENABLED)))
                .setSatelliteAttachEnabledForCarrier(cursor.getInt(
                        cursor.getColumnIndexOrThrow(
                                SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER)))
                .setServiceCapabilities(cursor.getInt(
                        cursor.getColumnIndexOrThrow(
                                SimInfo.COLUMN_SERVICE_CAPABILITIES)))
                .setSatelliteEntitlementStatus(cursor.getInt(
                        cursor.getColumnIndexOrThrow(
                                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS)))
                .setSatelliteEntitlementPlmns(cursor.getString(
                        cursor.getColumnIndexOrThrow(
                                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS)));
        if (mFeatureFlags.oemEnabledSatelliteFlag()) {
            builder.setOnlyNonTerrestrialNetwork(cursor.getInt(cursor.getColumnIndexOrThrow(
                    SimInfo.COLUMN_IS_NTN)));
        }
        if (mFeatureFlags.supportPsimToEsimConversion()) {
            builder.setTransferStatus(cursor.getInt(cursor.getColumnIndexOrThrow(
                    SimInfo.COLUMN_TRANSFER_STATUS)));
        }
        return builder.build();
    }

    /**
     * Sync the group sharing fields from reference subscription to the rest of the subscriptions in
     * the same group. For example, if user enables wifi calling, the same setting should be applied
     * to all subscriptions in the same group.
     *
     * @param subId The subscription id of reference subscription.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void syncToGroup(int subId) {
        if (!mAllSubscriptionInfoInternalCache.containsKey(subId)) {
            throw new IllegalArgumentException("Invalid subId " + subId);
        }

        for (String column : GROUP_SHARING_COLUMNS) {
            // Get the value from the reference subscription, and set to itself again.
            // writeDatabaseAndCacheHelper() will automatically sync to the rest of the group.
            setSubscriptionProperty(subId, column, getSubscriptionProperty(subId, column));
        }
    }

    /**
     * Get the subscription info by subscription id.
     *
     * @param subId The subscription id.
     *
     * @return The subscription info. {@code null} if not found.
     */
    @Nullable
    public SubscriptionInfoInternal getSubscriptionInfoInternal(int subId) {
        mReadWriteLock.readLock().lock();
        try {
            return mAllSubscriptionInfoInternalCache.get(subId);
        } finally {
            mReadWriteLock.readLock().unlock();
        }
    }

    /**
     * @return All subscription infos in the database.
     */
    @NonNull
    public List<SubscriptionInfoInternal> getAllSubscriptions() {
        mReadWriteLock.readLock().lock();
        try {
            return new ArrayList<>(mAllSubscriptionInfoInternalCache.values());
        } finally {
            mReadWriteLock.readLock().unlock();
        }
    }

    /**
     * Get subscription info by ICCID.
     *
     * @param iccId The ICCID of the SIM card.
     * @return The subscription info if found. {@code null} if not found.
     */
    @Nullable
    public SubscriptionInfoInternal getSubscriptionInfoInternalByIccId(@NonNull String iccId) {
        mReadWriteLock.readLock().lock();
        try {
            return mAllSubscriptionInfoInternalCache.values().stream()
                    .filter(subInfo -> subInfo.getIccId().equals(iccId))
                    .findFirst()
                    .orElse(null);
        } finally {
            mReadWriteLock.readLock().unlock();
        }
    }

    /**
     * Set the transfer status of the subscriptionInfo that corresponds to subId.
     *
     * @param subId Subscription ID.
     * @param status The transfer status to change.
     *
     * @throws IllegalArgumentException if the subscription does not exist.
     */
    public void setTransferStatus(int subId, int status) {
        if (!mFeatureFlags.supportPsimToEsimConversion()) {
            log("SubscriptionDatabaseManager:supportPsimToEsimConversion is false");
            return;
        }

        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_TRANSFER_STATUS,
                status,
                SubscriptionInfoInternal.Builder::setTransferStatus);
    }

    /**
     * Log debug messages.
     *
     * @param s debug messages
     */
    private void log(@NonNull String s) {
        Rlog.d(LOG_TAG, s);
    }

    /**
     * Log error messages.
     *
     * @param s error messages
     */
    private void loge(@NonNull String s) {
        Rlog.e(LOG_TAG, s);
    }

    /**
     * Log verbose messages.
     *
     * @param s debug messages.
     */
    private void logv(@NonNull String s) {
        if (VDBG) Rlog.v(LOG_TAG, s);
    }

    /**
     * Log error messages and also log into the local log.
     *
     * @param s debug messages
     */
    private void logel(@NonNull String s) {
        loge(s);
        mLocalLog.log(s);
    }

    /**
     * Log debug messages and also log into the local log.
     *
     * @param s debug messages
     */
    private void logl(@NonNull String s) {
        log(s);
        mLocalLog.log(s);
    }

    /**
     * Dump the state of {@link SubscriptionDatabaseManager}.
     *
     * @param fd File descriptor
     * @param printWriter Print writer
     * @param args Arguments
     */
    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter printWriter,
            @NonNull String[] args) {
        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
        pw.println(SubscriptionDatabaseManager.class.getSimpleName() + ":");
        pw.increaseIndent();
        pw.println("All subscriptions:");
        pw.increaseIndent();
        mReadWriteLock.readLock().lock();
        try {
            mAllSubscriptionInfoInternalCache.forEach((subId, subInfo) -> pw.println(subInfo));
        } finally {
            mReadWriteLock.readLock().unlock();
        }
        pw.decreaseIndent();
        pw.println();
        pw.println("mAsyncMode=" + mAsyncMode);
        synchronized (this) {
            pw.println("mDatabaseInitialized=" + mDatabaseInitialized);
        }
        pw.println("mReadWriteLock=" + mReadWriteLock);
        pw.println();
        pw.println("Local log:");
        pw.increaseIndent();
        mLocalLog.dump(fd, printWriter, args);
        pw.decreaseIndent();
        pw.decreaseIndent();
    }
}
