• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.internal.telephony.euicc;
17 
18 import android.annotation.Nullable;
19 import android.content.Context;
20 import android.util.ArraySet;
21 
22 import com.android.internal.annotations.GuardedBy;
23 import com.android.internal.annotations.VisibleForTesting;
24 import com.android.internal.telephony.flags.Flags;
25 import com.android.internal.telephony.uicc.euicc.apdu.ApduSender;
26 import com.android.telephony.Rlog;
27 
28 import java.util.Set;
29 
30 /**
31  * A eUICC transaction session aims to optimize multiple back-to-back EuiccPort API calls by only
32  * open and close a logical channel once.
33  *
34  * <p>This class is thread-safe.
35  */
36 public class EuiccSession {
37     private static final String TAG = "EuiccSession";
38 
39     // **** Well known session IDs, see #startSession() ****
40     public static final String DOWNLOAD = "DOWNLOAD";
41 
42     @GuardedBy("EuiccSession.class")
43     private static EuiccSession sInstance;
44 
get(Context context)45     public static synchronized EuiccSession get(Context context) {
46         if (sInstance == null) {
47             sInstance = new EuiccSession(context);
48         }
49         return sInstance;
50     }
51 
52     @GuardedBy("this")
53     private final Set<String> mSessions = new ArraySet<>();
54 
55     @GuardedBy("this")
56     private final Set<ApduSender> mApduSenders = new ArraySet<>();
57     private final Context mContext;
58 
59     /**
60      * Returns true if the ApduSender optimization is enabled i.e. a logical channel is opened
61      * and kept open for multiple APDU commands within one session.
62      *
63      * This is gated by both an aconfig flag and a device-specific flag.
64      */
optimizeApduSender()65     private boolean optimizeApduSender() {
66         return Flags.optimizationApduSender() && mContext.getResources().getBoolean(
67                 com.android.internal.R.bool.euicc_optimize_apdu_sender);
68     }
69 
70     /**
71      * Marks the start of a eUICC transaction session.
72      *
73      * <p>A session means a long-open logical channel (see {@link ApduSender}) used to
74      * send multiple APDUs for one action e.g. {@link EuiccController#downloadSubscription()}.
75      * Those APDUs can be send by one or multiple {@link EuiccCardController} methods.
76      *
77      * <p>Ideally a session should correespond to one phoneId and hence just one logical channel.
78      * But many {@link EuiccCardController} methods uses first available port and is not specific
79      * to a phoneId. So EuiccController cannot choose one phoneId to use. Hence a session has to
80      * be not specific to phoneId, i.e. for DSDS device both phoneId's will be in a session.
81      *
82      * <p>If called multiple times with different {@code sessionId}'s, the session is truly closed
83      * when the all sessions are ended. See {@link #endSession()}.
84      *
85      * @param sessionId The session ID.
86      */
startSession(String sessionId)87     public void startSession(String sessionId) {
88         if (!optimizeApduSender()) {
89             // Other methods in this class is no-op if no session started.
90             // Do not add flag to other methods, so if the flag gets turned off,
91             // the session can be ended properly.
92             return;
93         }
94         Rlog.i(TAG, "startSession: " + sessionId);
95         synchronized(this) {
96             mSessions.add(sessionId);
97         }
98     }
99 
100     /** Returns {@code true} if there is at least one session ongoing. */
hasSession()101     public boolean hasSession() {
102         boolean hasSession = hasSessionInternal();
103         Rlog.i(TAG, "hasSession: " + hasSession);
104         return hasSession;
105     }
106 
107     // The bare metal implementation of hasSession() without logging.
hasSessionInternal()108     private boolean hasSessionInternal() {
109         synchronized(this) {
110             return !mSessions.isEmpty();
111         }
112     }
113 
114     /**
115      * Notes that a logical channel may be opened by the {@code apduSender}, which will
116      * be used to close the channel when session ends (see {@link #endSession()}).
117      *
118      * <p>No-op if no session ongoing (see {@link #hasSession()}).
119      *
120      * @param apduSender The ApduSender that will open the channel.
121      */
noteChannelOpen(ApduSender apduSender)122     public void noteChannelOpen(ApduSender apduSender) {
123         Rlog.i(TAG, "noteChannelOpen: " + apduSender);
124         synchronized(this) {
125             if (hasSessionInternal()) {
126                 mApduSenders.add(apduSender);
127             }
128         }
129     }
130 
131     /**
132      * Marks the end of a eUICC transaction session. If this ends the last ongoing session,
133      * try to close the logical channel using the noted {@code apduSender}s
134      * (see {@link #noteChannelOpen()}).
135      *
136      * @param sessionId The session ID.
137      */
endSession(String sessionId)138     public void endSession(String sessionId) {
139         Rlog.i(TAG, "endSession: " + sessionId);
140         endSessionInternal(sessionId);
141     }
142 
143     /**
144      * Marks the end of all eUICC transaction sessions and close the logical
145      * channels using the noted {@code apduSender}s
146      * (see {@link #noteChannelOpen()}).
147      *
148      * <p>This is useful in global cleanup e.g. when EuiccService
149      * implementation app crashes and indivial {@link #endSession()} calls
150      * won't happen in {@link EuiccConnector}.
151      */
endAllSessions()152     public void endAllSessions() {
153         Rlog.i(TAG, "endAllSessions");
154         endSessionInternal(null);
155     }
156 
157     // The implementation of endSession(sessionId) or endAllSessions() when the sessionId is null,
158     // without logging.
endSessionInternal(@ullable String sessionId)159     private void endSessionInternal(@Nullable String sessionId) {
160         ApduSender[] apduSenders = new ApduSender[0];
161         synchronized(this) {
162             boolean sessionRemoved = removeOrClear(mSessions, sessionId);
163             // 1. sessionRemoved is false if the `sessionId` was never started or there was
164             // no session. Don't bother invoke `apduSender`.
165             // 2. If some session is removed, and as a result there is no more session, we
166             // can clsoe channels.
167             if (sessionRemoved && !hasSessionInternal()) {
168                 // copy mApduSenders to a local variable so we don't call closeAnyOpenChannel()
169                 // which can take time in synchronized block.
170                 apduSenders = mApduSenders.toArray(apduSenders);
171                 mApduSenders.clear();
172             }
173         }
174         for (ApduSender apduSender : apduSenders) {
175             apduSender.closeAnyOpenChannel();
176         }
177     }
178 
179     /**
180      * Removes the given element from the set. If the element is null, clears the set.
181      *
182      * @return true if the set changed as a result of the call
183      */
removeOrClear(Set<String> collection, @Nullable String element)184     private static boolean removeOrClear(Set<String> collection, @Nullable String element) {
185         if (element == null) {
186             boolean collectionChanged = !collection.isEmpty();
187             collection.clear();
188             return collectionChanged;
189         } else {
190             return collection.remove(element);
191         }
192     }
193 
194     @VisibleForTesting
EuiccSession(Context context)195     public EuiccSession(Context context) {
196         mContext = context;
197     }
198 }
199