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

Commit 5d94e6cf authored by Aman Gupta's avatar Aman Gupta
Browse files

Revert^2 "Optimize euicc invocations by keeping channel open sessi..."

This reverts commit 6bd93036.

Reason for revert: the main culprit cl has been rolled back from the release branch so now rolling back this cls.

Change-Id: I399e51ec60bc010372b3e9e774df69ab01807701
parent 0dc776da
Loading
Loading
Loading
Loading
+287 −128
Original line number Diff line number Diff line
@@ -25,7 +25,9 @@ import android.preference.PreferenceManager;
import android.telephony.IccOpenLogicalChannelResponse;
import android.util.Base64;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.euicc.EuiccSession;
import com.android.internal.telephony.uicc.IccIoResult;
import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback;
import com.android.internal.telephony.uicc.euicc.async.AsyncResultHelper;
@@ -40,7 +42,14 @@ import java.util.List;
 * before sending and closed after all APDU commands are sent. The complete response of the last
 * APDU command will be returned. If any APDU command returns an error status (other than
 * {@link #STATUS_NO_ERROR}) or causing an exception, an {@link ApduException} will be returned
 * immediately without sending the rest of commands. This class is thread-safe.
 * immediately without sending the rest of commands.
 *
 * <p>If {@link EuiccSession} indicates ongoing session(s), the behavior changes: 1) before
 * sending, check if a channel is opened already. If yes, reuse the channel and send APDU commands
 * directly. If no, open a channel before sending. 2) The channel is closed when EuiccSession
 * class ends all sessions, independent of APDU sending.
 *
 * <p>This class is thread-safe.
 *
 * @hide
 */
@@ -54,6 +63,7 @@ public class ApduSender {
    // Status code of APDU response
    private static final int STATUS_NO_ERROR = 0x9000;
    private static final int SW1_NO_ERROR = 0x91;
    private static final int STATUS_CHANNEL_CLOSED = 0x6881; // b/359336875

    private static final int WAIT_TIME_MS = 2000;
    private static final String CHANNEL_ID_PRE = "esim-channel";
@@ -68,6 +78,10 @@ public class ApduSender {
        Rlog.d(LOG_TAG, msg);
    }

    private static void loge(String msg) {
        Rlog.e(LOG_TAG, msg);
    }

    private final String mAid;
    private final boolean mSupportExtendedApdu;
    private final OpenLogicalChannelInvocation mOpenChannel;
@@ -76,10 +90,16 @@ public class ApduSender {
    private final Context mContext;
    private final String mChannelKey;
    private final String mChannelResponseKey;
    // closeAnyOpenChannel() needs a handler for its async callbacks.
    private final Handler mHandler;

    // Lock for accessing mChannelOpened. We only allow to open a single logical channel at any
    // time for an AID.
    private final Object mChannelLock = new Object();
    // Lock for accessing mChannelInUse. We only allow to open a single logical
    // channel at any time for an AID and to invoke one command at any time.
    // Only the thread (and its async callbacks) that sets mChannelInUse
    // can open/close/send, and update mChannelOpened.
    private final Object mChannelInUseLock = new Object();
    @GuardedBy("mChannelInUseLock")
    private boolean mChannelInUse;
    private boolean mChannelOpened;

    /**
@@ -98,6 +118,8 @@ public class ApduSender {
        mTransmitApdu = new TransmitApduLogicalChannelInvocation(ci);
        mChannelKey = CHANNEL_ID_PRE + "_" + phoneId;
        mChannelResponseKey = CHANNEL_RESPONSE_ID_PRE + "_" + phoneId;
        mHandler = new Handler();
        mChannelInUse = false;
    }

    /**
@@ -116,31 +138,78 @@ public class ApduSender {
            RequestProvider requestProvider,
            ApduSenderResultCallback resultCallback,
            Handler handler) {
        synchronized (mChannelLock) {
            if (mChannelOpened) {
                if (!Looper.getMainLooper().equals(Looper.myLooper())) {
                    logd("Logical channel has already been opened. Wait.");
                    try {
                        mChannelLock.wait(WAIT_TIME_MS);
                    } catch (InterruptedException e) {
                        // nothing to do
                    }
                    if (mChannelOpened) {
        if (!acquireChannelLock()) {
            AsyncResultHelper.throwException(
                    new ApduException("The logical channel is still in use."),
                                resultCallback, handler);
                    resultCallback,
                    handler);
            return;
        }
                } else {
                    AsyncResultHelper.throwException(
                            new ApduException("The logical channel is in use."),
                            resultCallback, handler);

        boolean euiccSession = EuiccSession.get().hasSession();
        // Case 1, channel was already opened AND EuiccSession is ongoing.
        // sendCommand directly. Do not immediately close channel after sendCommand.
        // Case 2, channel was already opened AND EuiccSession is not ongoing. This means
        // EuiccSession#endSession is already called but closeAnyOpenChannel() is not
        // yet executed because of waiting to acquire lock hold by this thread.
        // sendCommand directly. Close channel immediately anyways after sendCommand.
        // Case 3, channel is not open AND EuiccSession is ongoing. Open channel
        // before sendCommand. Do not immediately close channel after sendCommand.
        // Case 4, channel is not open AND EuiccSession is not ongoing. Open channel
        // before sendCommand. Close channel immediately after sendCommand.
        if (mChannelOpened) {  // Case 1 or 2
            if (euiccSession) {
                EuiccSession.get().noteChannelOpen(this);
            }
            RequestBuilder builder = getRequestBuilderWithOpenedChannel(requestProvider,
                    !euiccSession /* closeChannelImmediately */, resultCallback, handler);
            if (builder == null) {
                return;
            }
            sendCommand(builder.getCommands(), 0 /* index */,
                    !euiccSession /* closeChannelImmediately */, resultCallback, handler);
        } else {  // Case 3 or 4
            if (euiccSession) {
                EuiccSession.get().noteChannelOpen(this);
            }
            mChannelOpened = true;
            openChannel(requestProvider,
                    !euiccSession /* closeChannelImmediately */, resultCallback, handler);
        }
    }

    private RequestBuilder getRequestBuilderWithOpenedChannel(
            RequestProvider requestProvider,
            boolean closeChannelImmediately,
            ApduSenderResultCallback resultCallback,
            Handler handler) {
        Throwable requestException = null;
        int channel =
                PreferenceManager.getDefaultSharedPreferences(mContext)
                        .getInt(mChannelKey, IccOpenLogicalChannelResponse.INVALID_CHANNEL);
        String storedResponse =
                PreferenceManager.getDefaultSharedPreferences(mContext)
                        .getString(mChannelResponseKey, "");
        byte[] selectResponse = Base64.decode(storedResponse, Base64.DEFAULT);
        RequestBuilder builder = new RequestBuilder(channel, mSupportExtendedApdu);
        try {
            requestProvider.buildRequest(selectResponse, builder);
        } catch (Throwable e) {
            requestException = e;
        }
        if (builder.getCommands().isEmpty() || requestException != null) {
            logd("Release as commands are empty or exception occurred");
            returnRespnseOrException(channel, closeChannelImmediately,
                    null /* response */, requestException, resultCallback, handler);
            return null;
        }
        return builder;
    }

    private void openChannel(
            RequestProvider requestProvider,
            boolean closeChannelImmediately,
            ApduSenderResultCallback resultCallback,
            Handler handler) {
        mOpenChannel.invoke(mAid, new AsyncResultCallback<IccOpenLogicalChannelResponse>() {
                    @Override
                    public void onResult(IccOpenLogicalChannelResponse openChannelResponse) {
@@ -149,7 +218,8 @@ public class ApduSender {
                        byte[] selectResponse = openChannelResponse.getSelectResponse();
                        if (status == IccOpenLogicalChannelResponse.STATUS_NO_SUCH_ELEMENT) {
                            channel = PreferenceManager.getDefaultSharedPreferences(mContext)
                                .getInt(mChannelKey, IccOpenLogicalChannelResponse.INVALID_CHANNEL);
                                            .getInt(mChannelKey,
                                                    IccOpenLogicalChannelResponse.INVALID_CHANNEL);
                            if (channel != IccOpenLogicalChannelResponse.INVALID_CHANNEL) {
                                logv("Try to use already opened channel: " + channel);
                                status = IccOpenLogicalChannelResponse.STATUS_NO_ERROR;
@@ -159,54 +229,34 @@ public class ApduSender {
                                selectResponse = Base64.decode(storedResponse, Base64.DEFAULT);
                            }
                        }

                        if (channel == IccOpenLogicalChannelResponse.INVALID_CHANNEL
                                || status != IccOpenLogicalChannelResponse.STATUS_NO_ERROR) {
                    synchronized (mChannelLock) {
                            mChannelOpened = false;
                        mChannelLock.notify();
                    }
                            resultCallback.onException(
                            new ApduException("Failed to open logical channel opened for AID: "
                                    new ApduException("Failed to open logical channel for AID: "
                                            + mAid + ", with status: " + status));
                            return;
                        }

                RequestBuilder builder = new RequestBuilder(channel, mSupportExtendedApdu);
                Throwable requestException = null;
                        PreferenceManager.getDefaultSharedPreferences(mContext)
                        .edit().putInt(mChannelKey, channel).apply();
                PreferenceManager.getDefaultSharedPreferences(mContext)
                        .edit().putString(mChannelResponseKey,
                                .edit()
                                .putInt(mChannelKey, channel)
                                .putString(mChannelResponseKey,
                                    Base64.encodeToString(selectResponse, Base64.DEFAULT)).apply();
                try {
                    requestProvider.buildRequest(selectResponse, builder);
                } catch (Throwable e) {
                    requestException = e;
                }
                if (builder.getCommands().isEmpty() || requestException != null) {
                    // Just close the channel if we don't have commands to send or an error
                    // was encountered.
                    closeAndReturn(channel, null /* response */, requestException, resultCallback,
                            handler);
                        mChannelOpened = true;

                        RequestBuilder builder =
                                getRequestBuilderWithOpenedChannel(requestProvider,
                                        closeChannelImmediately, resultCallback, handler);
                        if (builder == null) {
                            return;
                        }
                sendCommand(builder.getCommands(), 0 /* index */, resultCallback, handler);
            }
        }, handler);
    }

    /**
     * Closes any open channel.
     *
     * <p>Used by EuiccSession#endSession.
     */
    public void closeAnyOpenChannel() {
        // TODO: implement this. Different from existing closeExistingChannelIfExists()
        // which is only used in constructor and don't worry about multi-thread racing.
        // 1. Acquire channel lock
        // 2. Check sharedpref for existing open channel
        // 3. Close any open channel
        // 4. Release channel lock
                        sendCommand(builder.getCommands(), 0 /* index */,
                                closeChannelImmediately, resultCallback, handler);
                    }
                },
                handler);
    }

    /**
@@ -219,6 +269,7 @@ public class ApduSender {
    private void sendCommand(
            List<ApduCommand> commands,
            int index,
            boolean closeChannelImmediately,
            ApduSenderResultCallback resultCallback,
            Handler handler) {
        ApduCommand command = commands.get(index);
@@ -233,9 +284,21 @@ public class ApduSender {
                            public void onResult(IccIoResult fullResponse) {
                                logv("Full APDU response: " + fullResponse);
                                int status = (fullResponse.sw1 << 8) | fullResponse.sw2;
                                if (status != STATUS_NO_ERROR && fullResponse.sw1 != SW1_NO_ERROR) {
                                    closeAndReturn(command.channel, null /* response */,
                                            new ApduException(status), resultCallback, handler);
                                if (status != STATUS_NO_ERROR
                                        && fullResponse.sw1 != SW1_NO_ERROR) {
                                    if (status == STATUS_CHANNEL_CLOSED) {
                                        // Channel is closed by EUICC e.g. REFRESH.
                                        tearDownPreferences();
                                        mChannelOpened = false;
                                        // TODO: add retry
                                    }
                                    returnRespnseOrException(
                                            command.channel,
                                            closeChannelImmediately,
                                            null /* response */,
                                            new ApduException(status),
                                            resultCallback,
                                            handler);
                                    return;
                                }

@@ -245,11 +308,17 @@ public class ApduSender {
                                                fullResponse);
                                if (continueSendCommand) {
                                    // Sends the next command
                                    sendCommand(commands, index + 1, resultCallback, handler);
                                    sendCommand(commands, index + 1,
                                            closeChannelImmediately, resultCallback, handler);
                                } else {
                                    // Returns the result of the last command
                                    closeAndReturn(command.channel, fullResponse.payload,
                                            null /* exception */, resultCallback, handler);
                                    returnRespnseOrException(
                                            command.channel,
                                            closeChannelImmediately,
                                            fullResponse.payload,
                                            null /* exception */,
                                            resultCallback,
                                            handler);
                                }
                            }
                        }, handler);
@@ -298,6 +367,41 @@ public class ApduSender {
                }, handler);
    }

    private void tearDownPreferences() {
        PreferenceManager.getDefaultSharedPreferences(mContext)
                .edit()
                .remove(mChannelKey)
                .remove(mChannelResponseKey)
                .apply();
    }

    /**
     * Fires the {@code resultCallback} to return a response or exception. Also
     * closes the open logical channel if {@code closeChannelImmediately} is {@code true}.
     */
    private void returnRespnseOrException(
            int channel,
            boolean closeChannelImmediately,
            @Nullable byte[] response,
            @Nullable Throwable exception,
            ApduSenderResultCallback resultCallback,
            Handler handler) {
        if (closeChannelImmediately) {
            closeAndReturn(
                    channel,
                    response,
                    exception,
                    resultCallback,
                    handler);
        } else {
            releaseChannelLockAndReturn(
                    response,
                    exception,
                    resultCallback,
                    handler);
        }
    }

    /**
     * Closes the opened logical channel.
     *
@@ -315,14 +419,9 @@ public class ApduSender {
        mCloseChannel.invoke(channel, new AsyncResultCallback<Boolean>() {
            @Override
            public void onResult(Boolean aBoolean) {
                synchronized (mChannelLock) {
                    PreferenceManager.getDefaultSharedPreferences(mContext)
                            .edit().remove(mChannelKey).apply();
                    PreferenceManager.getDefaultSharedPreferences(mContext)
                            .edit().remove(mChannelResponseKey).apply();
                tearDownPreferences();
                mChannelOpened = false;
                    mChannelLock.notify();
                }
                releaseChannelLock();

                if (exception == null) {
                    resultCallback.onResult(response);
@@ -334,37 +433,97 @@ public class ApduSender {
    }

    /**
     * Cleanup the existing opened channel which was remainined opened earlier due
     * to failure or crash.
     * Cleanup the existing opened channel which remained opened earlier due
     * to:
     *
     * <p> 1) onging EuiccSession. This will be called by {@link EuiccSession#endSession()}
     * from non-main-thread. Or,
     *
     * <p> 2) telephony crash. This will be called by constructor from main-thread.
     */
    private void closeExistingChannelIfExists() {
        if (mCloseChannel != null) {
    public void closeAnyOpenChannel() {
        if (!acquireChannelLock()) {
            // This cannot happen for case 2) when called by constructor
            loge("[closeAnyOpenChannel] failed to acquire channel lock");
            return;
        }
        int channelId = PreferenceManager.getDefaultSharedPreferences(mContext)
                .getInt(mChannelKey, IccOpenLogicalChannelResponse.INVALID_CHANNEL);
            if (channelId != IccOpenLogicalChannelResponse.INVALID_CHANNEL) {
                logv("Trying to clean up the opened channel : " +  channelId);
                synchronized (mChannelLock) {
                    mChannelOpened = true;
                    mChannelLock.notify();
        if (channelId == IccOpenLogicalChannelResponse.INVALID_CHANNEL) {
            releaseChannelLock();
            return;
        }
        logv("[closeAnyOpenChannel] closing the open channel : " +  channelId);
        mCloseChannel.invoke(channelId, new AsyncResultCallback<Boolean>() {
            @Override
            public void onResult(Boolean isSuccess) {
                if (isSuccess) {
                          logv("Channel closed successfully: " +  channelId);
                          PreferenceManager.getDefaultSharedPreferences(mContext)
                                 .edit().remove(mChannelResponseKey).apply();
                          PreferenceManager.getDefaultSharedPreferences(mContext)
                                 .edit().remove(mChannelKey).apply();
                    logv("[closeAnyOpenChannel] Channel closed successfully: " + channelId);
                    tearDownPreferences();
                }

                       synchronized (mChannelLock) {
                // Even if CloseChannel failed, pretend that the channel is closed.
                // So next send() will try open the channel again. If the channel is
                // indeed still open, we use the channelId saved in sharedPref.
                mChannelOpened = false;
                           mChannelLock.notify();
                releaseChannelLock();
            }
        }, mHandler);
    }

    // releases channel and callback
    private void releaseChannelLockAndReturn(
            @Nullable byte[] response,
            @Nullable Throwable exception,
            ApduSenderResultCallback resultCallback,
            Handler handler) {
        handler.post(
                () -> {
                    releaseChannelLock();
                    if (exception == null) {
                        resultCallback.onResult(response);
                    } else {
                        resultCallback.onException(exception);
                    }
                });
    }

    private void releaseChannelLock() {
        synchronized (mChannelInUseLock) {
            logd("Channel lock released.");
            mChannelInUse = false;
            mChannelInUseLock.notify();
        }
    }

    /**
     * Acquires channel lock and returns {@code true} if successful.
     *
     * <p>It fails and returns {@code false} when:
     * <ul>
     *   <li>Called from main thread, and mChannelInUse=true, fails immediately.
     *   <li>Called from non main thread, and mChannelInUse=true after 2 seconds waiting, fails.
     * </ul>
     */
    private boolean acquireChannelLock() {
        synchronized (mChannelInUseLock) {
            if (mChannelInUse) {
                if (!Looper.getMainLooper().equals(Looper.myLooper())) {
                    logd("Logical channel is in use. Wait.");
                    try {
                        mChannelInUseLock.wait(WAIT_TIME_MS);
                    } catch (InterruptedException e) {
                        // nothing to do
                    }
                    if (mChannelInUse) {
                        return false;
                    }
                } else {
                    return false;
                }
                }, new Handler());
            }
            mChannelInUse = true;
            logd("Channel lock acquired.");
            return true;
        }
    }
}
+196 −14

File changed.

Preview size limit exceeded, changes collapsed.