Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dce529f0 authored by Jeff Davidson's avatar Jeff Davidson
Browse files

Implement embedded subscription list APIs.

Listing embedded subscriptions is fairly simple - we just return all
subscriptions for which IS_EMBEDDED = 1. We expose these through two
new APIs which return subscriptions which are either active or
embedded; one API (for the LPA and other callers with permission)
returns all such subscriptions, and the other (for carrier apps)
returns only subscriptions accessible to the calling app.

The cache of embedded subscriptions is updated whenever the SIM state
becomes ABSENT, ERROR, or LOADED; whenever an API call happens which
impacts the list of subscriptions (like a download or a delete); or
whenever the LPA requests a refresh due to some data change.

To support this change, the update process now runs on a background
thread instead of the main thread, as IPCs can't be made from the main
thread without deadlocking, in general. (Note though that this
probably should have been done before as the update logic was still
reading from/writing to a SQLite-backed ContentProvider, and since the
phone process currently contains UI for settings).

The update process pulls all cached embedded subscriptions (omitting
non-removable ones if the update is for a removable eUICC), as well as
non-embedded subscriptions with matching IMSIs. Subscriptions are
matched with those returned by the LPA and updated; any other cached
subscription in the above list is "removed" by marking it as
IS_EMBEDDED = 0. (This is equivalent to what happens when a physical
SIM is removed).

For the nickname, we repurpose the existing DISPLAY_NAME field.
However, there are settings UIs which allow updating this field, which
means those updates will be clobbered every time the list is updated.
A follow-up CL will propagate updates to this field from Settings to
the stored nickname on the eUICC's profile so that they persist.

Bug: 35851809
Test: Unit tests; e2e tests switching back and forth between embedded
and removable eUICC.
Change-Id: Ie714c0f7fc1a9d147008a2598dfdeac865ba120c
parent 25f518f0
Loading
Loading
Loading
Loading
+8 −2
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.LocalLog;

import com.android.internal.os.BackgroundThread;
import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
import com.android.internal.telephony.dataconnection.TelephonyNetworkFactory;
import com.android.internal.telephony.euicc.EuiccController;
@@ -214,8 +215,8 @@ public class PhoneFactory {
                sMadeDefaults = true;

                Rlog.i(LOG_TAG, "Creating SubInfoRecordUpdater ");
                sSubInfoRecordUpdater = new SubscriptionInfoUpdater(context,
                        sPhones, sCommandsInterfaces);
                sSubInfoRecordUpdater = new SubscriptionInfoUpdater(
                        BackgroundThread.get().getLooper(), context, sPhones, sCommandsInterfaces);
                SubscriptionController.getInstance().updatePhonesAvailability(sPhones);

                // Start monitoring after defaults have been made.
@@ -352,6 +353,11 @@ public class PhoneFactory {
        return ImsPhoneFactory.makePhone(sContext, phoneNotifier, defaultPhone);
    }

    /** Request a refresh of the embedded subscription list. */
    public static void refreshEmbeddedSubscriptionList() {
        sSubInfoRecordUpdater.refreshEmbeddedSubscriptionList();
    }

    /**
     * Adds a local log category.
     *
+194 −22
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.internal.telephony;

import android.Manifest;
import android.app.AppOpsManager;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -35,15 +36,19 @@ import android.telephony.Rlog;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.UiccAccessRule;
import android.telephony.euicc.EuiccManager;
import android.text.TextUtils;
import android.text.format.Time;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IccCardConstants.State;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
@@ -54,6 +59,7 @@ import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * SubscriptionController to provide an inter-process communication to
@@ -117,6 +123,17 @@ public class SubscriptionController extends ISub.Stub {
        }
    }

    private static final Comparator<SubscriptionInfo> SUBSCRIPTION_INFO_COMPARATOR =
            (arg0, arg1) -> {
                // Primary sort key on SimSlotIndex
                int flag = arg0.getSimSlotIndex() - arg1.getSimSlotIndex();
                if (flag == 0) {
                    // Secondary sort on SubscriptionId
                    return arg0.getSubscriptionId() - arg1.getSubscriptionId();
                }
                return flag;
            };

    protected final Object mLock = new Object();

    /** The singleton instance. */
@@ -290,13 +307,23 @@ public class SubscriptionController extends ISub.Stub {
                SubscriptionManager.MNC));
        // FIXME: consider stick this into database too
        String countryIso = getSubscriptionCountryIso(id);
        boolean isEmbedded = cursor.getInt(cursor.getColumnIndexOrThrow(
                SubscriptionManager.IS_EMBEDDED)) == 1;
        UiccAccessRule[] accessRules;
        if (isEmbedded) {
            accessRules = UiccAccessRule.decodeRules(cursor.getBlob(
                    cursor.getColumnIndexOrThrow(SubscriptionManager.ACCESS_RULES)));
        } else {
            accessRules = null;
        }

        if (VDBG) {
            String iccIdToPrint = SubscriptionInfo.givePrintableIccid(iccId);
            logd("[getSubInfoRecord] id:" + id + " iccid:" + iccIdToPrint + " simSlotIndex:"
                    + simSlotIndex + " displayName:" + displayName + " nameSource:" + nameSource
                    + " iconTint:" + iconTint + " dataRoaming:" + dataRoaming
                    + " mcc:" + mcc + " mnc:" + mnc + " countIso:" + countryIso);
                    + " mcc:" + mcc + " mnc:" + mnc + " countIso:" + countryIso + " isEmbedded:"
                    + isEmbedded + " accessRules:" + Arrays.toString(accessRules));
        }

        // If line1number has been set to a different number, use it instead.
@@ -305,7 +332,8 @@ public class SubscriptionController extends ISub.Stub {
            number = line1Number;
        }
        return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
                nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso);
                nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
                isEmbedded, accessRules);
    }

    /**
@@ -565,18 +593,7 @@ public class SubscriptionController extends ISub.Stub {

            if (subList != null) {
                // FIXME: Unnecessary when an insertion sort is used!
                Collections.sort(subList, new Comparator<SubscriptionInfo>() {
                    @Override
                    public int compare(SubscriptionInfo arg0, SubscriptionInfo arg1) {
                        // Primary sort key on SimSlotIndex
                        int flag = arg0.getSimSlotIndex() - arg1.getSimSlotIndex();
                        if (flag == 0) {
                            // Secondary sort on SubscriptionId
                            return arg0.getSubscriptionId() - arg1.getSubscriptionId();
                        }
                        return flag;
                    }
                });
                subList.sort(SUBSCRIPTION_INFO_COMPARATOR);

                if (VDBG) logdl("[getActiveSubInfoList]- " + subList.size() + " infos return");
            } else {
@@ -664,6 +681,144 @@ public class SubscriptionController extends ISub.Stub {
        return mTelephonyManager.getSimCount();
    }

    @Override
    public List<SubscriptionInfo> getAvailableSubscriptionInfoList(String callingPackage) {
        if (!canReadPhoneState(callingPackage, "getAvailableSubscriptionInfoList")) {
            throw new SecurityException("Need READ_PHONE_STATE to call "
                    + " getAvailableSubscriptionInfoList");
        }

        // Now that all security checks pass, perform the operation as ourselves.
        final long identity = Binder.clearCallingIdentity();
        try {
            EuiccManager euiccManager =
                    (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE);
            if (!euiccManager.isEnabled()) {
                if (DBG) logdl("[getAvailableSubInfoList] Embedded subscriptions are disabled");
                return null;
            }

            List<SubscriptionInfo> subList = getSubInfo(
                    SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR "
                            + SubscriptionManager.IS_EMBEDDED + "=1", null);

            if (subList != null) {
                subList.sort(SUBSCRIPTION_INFO_COMPARATOR);

                if (VDBG) logdl("[getAvailableSubInfoList]- " + subList.size() + " infos return");
            } else {
                if (DBG) logdl("[getAvailableSubInfoList]- no info return");
            }

            return subList;
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    @Override
    public List<SubscriptionInfo> getAccessibleSubscriptionInfoList(String callingPackage) {
        EuiccManager euiccManager = (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE);
        if (!euiccManager.isEnabled()) {
            if (DBG) {
                logdl("[getAccessibleSubInfoList] Embedded subscriptions are disabled");
            }
            return null;
        }

        // Verify that the given package belongs to the calling UID.
        mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);

        // Perform the operation as ourselves. If the caller cannot read phone state, they may still
        // have carrier privileges per the subscription metadata, so we always need to make the
        // query and then filter the results.
        final long identity = Binder.clearCallingIdentity();
        List<SubscriptionInfo> subList;
        try {
            subList = getSubInfo(SubscriptionManager.IS_EMBEDDED + "=1", null);
        } finally {
            Binder.restoreCallingIdentity(identity);
        }

        if (subList == null) {
            if (DBG) logdl("[getAccessibleSubInfoList] No info returned");
            return null;
        }

        // Filter the list to only include subscriptions which the (restored) caller can manage.
        List<SubscriptionInfo> filteredList = subList.stream()
                .filter(subscriptionInfo ->
                        subscriptionInfo.canManageSubscription(mContext, callingPackage))
                .sorted(SUBSCRIPTION_INFO_COMPARATOR)
                .collect(Collectors.toList());
        if (VDBG) {
            logdl("[getAccessibleSubInfoList] " + filteredList.size() + " infos returned");
        }
        return filteredList;
    }

    /**
     * Return the list of subscriptions in the database which are either:
     * <ul>
     * <li>Embedded (but see note about {@code includeNonRemovableSubscriptions}, or
     * <li>In the given list of current embedded ICCIDs (which may not yet be in the database, or
     *     which may not currently be marked as embedded).
     * </ul>
     *
     * <p>NOTE: This is not accessible to external processes, so it does not need a permission
     * check. It is only intended for use by {@link SubscriptionInfoUpdater}.
     *
     * @param embeddedIccids all ICCIDs of available embedded subscriptions. This is used to surface
     *     entries for profiles which had been previously deleted.
     * @param isEuiccRemovable whether the current ICCID is removable. Non-removable subscriptions
     *     will only be returned if the current ICCID is not removable; otherwise, they are left
     *     alone (not returned here unless in the embeddedIccids list) under the assumption that
     *     they will still be accessible when the eUICC containing them is activated.
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public List<SubscriptionInfo> getSubscriptionInfoListForEmbeddedSubscriptionUpdate(
            String[] embeddedIccids, boolean isEuiccRemovable) {
        StringBuilder whereClause = new StringBuilder();
        whereClause.append("(").append(SubscriptionManager.IS_EMBEDDED).append("=1");
        if (isEuiccRemovable) {
            // Current eUICC is removable, so don't return non-removable subscriptions (which would
            // be deleted), as these are expected to still be present on a different, non-removable
            // eUICC.
            whereClause.append(" AND ").append(SubscriptionManager.IS_REMOVABLE).append("=1");
        }
        // Else, return both removable and non-removable subscriptions. This is expected to delete
        // all removable subscriptions, which is desired as they may not be accessible.

        whereClause.append(") OR ").append(SubscriptionManager.ICC_ID).append(" IN (");
        // ICCIDs are validated to contain only numbers when passed in, and come from a trusted
        // app, so no need to escape.
        for (int i = 0; i < embeddedIccids.length; i++) {
            if (i > 0) {
                whereClause.append(",");
            }
            whereClause.append("\"").append(embeddedIccids[i]).append("\"");
        }
        whereClause.append(")");

        List<SubscriptionInfo> list = getSubInfo(whereClause.toString(), null);
        if (list == null) {
            return Collections.emptyList();
        }
        return list;
    }

    @Override
    public void requestEmbeddedSubscriptionInfoListRefresh() {
        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS,
                "requestEmbeddedSubscriptionInfoListRefresh");
        long token = Binder.clearCallingIdentity();
        try {
            PhoneFactory.refreshEmbeddedSubscriptionList();
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    /**
     * Add a new SubInfoRecord to subinfo database if needed
     * @param iccId the IccId of the SIM card
@@ -691,18 +846,11 @@ public class SubscriptionController extends ISub.Stub {
                            SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.NAME_SOURCE},
                    SubscriptionManager.ICC_ID + "=?", new String[]{iccId}, null);

            int color = getUnusedColor(mContext.getOpPackageName());
            boolean setDisplayName = false;
            try {
                if (cursor == null || !cursor.moveToFirst()) {
                    setDisplayName = true;
                    ContentValues value = new ContentValues();
                    value.put(SubscriptionManager.ICC_ID, iccId);
                    // default SIM color differs between slots
                    value.put(SubscriptionManager.COLOR, color);
                    value.put(SubscriptionManager.SIM_SLOT_INDEX, slotIndex);
                    value.put(SubscriptionManager.CARRIER_NAME, "");
                    Uri uri = resolver.insert(SubscriptionManager.CONTENT_URI, value);
                    Uri uri = insertEmptySubInfoRecord(iccId, slotIndex);
                    if (DBG) logdl("[addSubInfoRecord] New record created: " + uri);
                } else {
                    int subId = cursor.getInt(0);
@@ -828,6 +976,27 @@ public class SubscriptionController extends ISub.Stub {
        return 0;
    }

    /**
     * Insert an empty SubInfo record into the database.
     *
     * <p>NOTE: This is not accessible to external processes, so it does not need a permission
     * check. It is only intended for use by {@link SubscriptionInfoUpdater}.
     *
     * <p>Precondition: No record exists with this iccId.
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public Uri insertEmptySubInfoRecord(String iccId, int slotIndex) {
        ContentResolver resolver = mContext.getContentResolver();
        ContentValues value = new ContentValues();
        value.put(SubscriptionManager.ICC_ID, iccId);
        int color = getUnusedColor(mContext.getOpPackageName());
        // default SIM color differs between slots
        value.put(SubscriptionManager.COLOR, color);
        value.put(SubscriptionManager.SIM_SLOT_INDEX, slotIndex);
        value.put(SubscriptionManager.CARRIER_NAME, "");
        return resolver.insert(SubscriptionManager.CONTENT_URI, value);
    }

    /**
     * Generate and set carrier text based on input parameters
     * @param showPlmn flag to indicate if plmn should be included in carrier text
@@ -975,6 +1144,9 @@ public class SubscriptionController extends ISub.Stub {
                value.put(SubscriptionManager.NAME_SOURCE, nameSource);
            }
            if (DBG) logd("[setDisplayName]- mDisplayName:" + nameToSet + " set");
            // TODO(b/33075886): If this is an embedded subscription, we must also save the new name
            // to the eSIM itself. Currently it will be blown away the next time the subscription
            // list is updated.

            int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
                    value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" +
+121 −2
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import android.content.pm.IPackageManager;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -36,13 +37,20 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.service.euicc.EuiccProfileInfo;
import android.service.euicc.GetEuiccProfileInfoListResult;
import android.telephony.CarrierConfigManager;
import android.telephony.Rlog;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.UiccAccessRule;
import android.telephony.euicc.EuiccManager;
import android.text.TextUtils;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.euicc.EuiccController;
import com.android.internal.telephony.uicc.IccCardProxy;
import com.android.internal.telephony.uicc.IccConstants;
import com.android.internal.telephony.uicc.IccFileHandler;
@@ -51,6 +59,7 @@ import com.android.internal.telephony.uicc.IccUtils;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -71,6 +80,7 @@ public class SubscriptionInfoUpdater extends Handler {
    private static final int EVENT_SIM_IO_ERROR = 6;
    private static final int EVENT_SIM_UNKNOWN = 7;
    private static final int EVENT_SIM_RESTRICTED = 8;
    private static final int EVENT_REFRESH_EMBEDDED_SUBSCRIPTIONS = 9;

    private static final String ICCID_STRING_FOR_NO_SIM = "";
    /**
@@ -104,6 +114,7 @@ public class SubscriptionInfoUpdater extends Handler {
    private static String mIccId[] = new String[PROJECT_SIM_NUM];
    private static int[] mInsertSimState = new int[PROJECT_SIM_NUM];
    private SubscriptionManager mSubscriptionManager = null;
    private EuiccManager mEuiccManager;
    private IPackageManager mPackageManager;
    private UserManager mUserManager;
    private Map<Integer, Intent> rebroadcastIntentsOnUnlock = new HashMap<>();
@@ -112,12 +123,15 @@ public class SubscriptionInfoUpdater extends Handler {
    private int mCurrentlyActiveUserId;
    private CarrierServiceBindHelper mCarrierServiceBindHelper;

    public SubscriptionInfoUpdater(Context context, Phone[] phone, CommandsInterface[] ci) {
    public SubscriptionInfoUpdater(
            Looper looper, Context context, Phone[] phone, CommandsInterface[] ci) {
        super(looper);
        logd("Constructor invoked");

        mContext = context;
        mPhone = phone;
        mSubscriptionManager = SubscriptionManager.from(mContext);
        mEuiccManager = (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE);
        mPackageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);

@@ -333,11 +347,22 @@ public class SubscriptionInfoUpdater extends Handler {
                updateCarrierServices(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED);
                break;

            case EVENT_REFRESH_EMBEDDED_SUBSCRIPTIONS:
                if (isAllIccIdQueryDone()) {
                    updateEmbeddedSubscriptions();
                    SubscriptionController.getInstance().notifySubscriptionInfoChanged();
                }
                break;

            default:
                logd("Unknown msg:" + msg.what);
        }
    }

    void refreshEmbeddedSubscriptionList() {
        sendEmptyMessage(EVENT_REFRESH_EMBEDDED_SUBSCRIPTIONS);
    }

    private static class QueryIccIdUserObj {
        public String reason;
        public int slotId;
@@ -648,8 +673,102 @@ public class SubscriptionInfoUpdater extends Handler {
        mSubscriptionManager.setDefaultDataSubId(
                mSubscriptionManager.getDefaultDataSubscriptionId());

        updateEmbeddedSubscriptions();

        SubscriptionController.getInstance().notifySubscriptionInfoChanged();
        logd("updateSubscriptionInfoByIccId:- SsubscriptionInfo update complete");
        logd("updateSubscriptionInfoByIccId:- SubscriptionInfo update complete");
    }

    /** Update the cached list of embedded subscriptions. */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    public void updateEmbeddedSubscriptions() {
        // Do nothing if eUICCs are disabled. (Previous entries may remain in the cache, but they
        // are filtered out of list calls as long as EuiccManager.isEnabled returns false).
        if (!mEuiccManager.isEnabled()) {
            return;
        }

        GetEuiccProfileInfoListResult result =
                EuiccController.get().blockingGetEuiccProfileInfoList();
        if (result == null) {
            // IPC to the eUICC controller failed.
            return;
        }

        final EuiccProfileInfo[] embeddedProfiles;
        if (result.result == GetEuiccProfileInfoListResult.RESULT_OK) {
            embeddedProfiles = result.profiles;
        } else if (result.result == GetEuiccProfileInfoListResult.RESULT_GENERIC_ERROR) {
            // If there's an error listing profiles, treat it equivalently to a successful
            // listing which returned no profiles under the assumption that none are currently
            // accessible.
            embeddedProfiles = new EuiccProfileInfo[0];
        } else {
            Log.wtf(LOG_TAG, "Unknown result from getEuiccProfileInfoList: " + result.result);
            return;
        }
        final boolean isRemovable = result.isRemovable;

        final String[] embeddedIccids = new String[embeddedProfiles.length];
        for (int i = 0; i < embeddedProfiles.length; i++) {
            embeddedIccids[i] = embeddedProfiles[i].iccid;
        }

        // Update or insert records for all embedded subscriptions (except non-removable ones if the
        // current eUICC is non-removable, since we assume these are still accessible though not
        // returned by the eUICC controller).
        List<SubscriptionInfo> existingSubscriptions = SubscriptionController.getInstance()
                .getSubscriptionInfoListForEmbeddedSubscriptionUpdate(embeddedIccids, isRemovable);
        ContentResolver contentResolver = mContext.getContentResolver();
        for (EuiccProfileInfo embeddedProfile : embeddedProfiles) {
            int index =
                    findSubscriptionInfoForIccid(existingSubscriptions, embeddedProfile.iccid);
            if (index < 0) {
                // No existing entry for this ICCID; create an empty one.
                SubscriptionController.getInstance().insertEmptySubInfoRecord(
                        embeddedProfile.iccid, SubscriptionManager.SIM_NOT_INSERTED);
            } else {
                existingSubscriptions.remove(index);
            }
            ContentValues values = new ContentValues();
            values.put(SubscriptionManager.IS_EMBEDDED, 1);
            values.put(SubscriptionManager.ACCESS_RULES,
                    embeddedProfile.accessRules == null ? null :
                            UiccAccessRule.encodeRules(embeddedProfile.accessRules));
            values.put(SubscriptionManager.IS_REMOVABLE, isRemovable);
            values.put(SubscriptionManager.DISPLAY_NAME, embeddedProfile.nickname);
            values.put(SubscriptionManager.NAME_SOURCE, SubscriptionManager.NAME_SOURCE_USER_INPUT);
            contentResolver.update(SubscriptionManager.CONTENT_URI, values,
                    SubscriptionManager.ICC_ID + "=\"" + embeddedProfile.iccid + "\"", null);
        }

        // Remove all remaining subscriptions which have embedded = true. We set embedded to false
        // to ensure they are not returned in the list of embedded subscriptions (but keep them
        // around in case the subscription is added back later, which is equivalent to a removable
        // SIM being removed and reinserted).
        if (!existingSubscriptions.isEmpty()) {
            List<String> iccidsToRemove = new ArrayList<>();
            for (int i = 0; i < existingSubscriptions.size(); i++) {
                SubscriptionInfo info = existingSubscriptions.get(i);
                if (info.isEmbedded()) {
                    iccidsToRemove.add("\"" + info.getIccId() + "\"");
                }
            }
            String whereClause = SubscriptionManager.ICC_ID + " IN ("
                    + TextUtils.join(",", iccidsToRemove) + ")";
            ContentValues values = new ContentValues();
            values.put(SubscriptionManager.IS_EMBEDDED, 0);
            contentResolver.update(SubscriptionManager.CONTENT_URI, values, whereClause, null);
        }
    }

    private static int findSubscriptionInfoForIccid(List<SubscriptionInfo> list, String iccid) {
        for (int i = 0; i < list.size(); i++) {
            if (TextUtils.equals(iccid, list.get(i).getIccId())) {
                return i;
            }
        }
        return -1;
    }

    private boolean isNewSim(String iccId, String[] oldIccId) {
+26 −0

File changed.

Preview size limit exceeded, changes collapsed.

+70 −17

File changed.

Preview size limit exceeded, changes collapsed.

Loading