Merge "Provision to enable VDBG logs through mobile for UICC component." into main
diff --git a/proto/src/persist_atoms.proto b/proto/src/persist_atoms.proto
index 3f964b3..9dbdcb0 100644
--- a/proto/src/persist_atoms.proto
+++ b/proto/src/persist_atoms.proto
@@ -720,6 +720,9 @@
optional int32 count_of_satellite_access_check_fail = 28;
optional bool is_provisioned = 29;
optional int32 carrier_id = 30;
+ optional int32 count_of_satellite_allowed_state_changed_events = 31;
+ optional int32 count_of_successful_location_queries = 32;
+ optional int32 count_of_failed_location_queries = 33;
}
message SatelliteSession {
@@ -818,6 +821,7 @@
optional int32 satellite_session_gap_avg_sec = 6;
optional int32 satellite_session_gap_max_sec = 7;
optional int32 carrier_id = 8;
+ optional bool is_device_entitled = 9;
}
message SatelliteEntitlement {
@@ -846,4 +850,5 @@
repeated string country_codes = 8;
optional int32 config_data_source = 9;
optional int32 carrier_id = 10;
+ optional int32 triggering_event = 11;
}
diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java
index 5d31283..9e432e1 100644
--- a/src/java/com/android/internal/telephony/data/DataNetworkController.java
+++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java
@@ -3195,7 +3195,7 @@
telephonyNetworkRequest, DataEvaluationReason.DATA_RETRY);
if (!evaluation.containsDisallowedReasons()) {
DataProfile dataProfile = dataSetupRetryEntry.dataProfile;
- if (dataProfile == null) {
+ if (dataProfile == null || !mDataProfileManager.isDataProfileCompatible(dataProfile)) {
dataProfile = evaluation.getCandidateDataProfile();
}
if (dataProfile != null) {
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
index 4e773f3..b758733 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
@@ -831,6 +831,7 @@
}
case CMD_DOWNLOAD_SUBSCRIPTION: {
DownloadRequest request = (DownloadRequest) message.obj;
+ EuiccSession.get().startSession(EuiccSession.DOWNLOAD);
mEuiccService.downloadSubscription(slotId,
request.mPortIndex,
request.mSubscription,
@@ -845,6 +846,7 @@
.onDownloadComplete(result);
onCommandEnd(callback);
});
+ EuiccSession.get().endSession(EuiccSession.DOWNLOAD);
}
});
break;
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccSession.java b/src/java/com/android/internal/telephony/euicc/EuiccSession.java
new file mode 100644
index 0000000..99b8720
--- /dev/null
+++ b/src/java/com/android/internal/telephony/euicc/EuiccSession.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2024 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.euicc;
+
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.Flags;
+import com.android.internal.telephony.uicc.euicc.apdu.ApduSender;
+import com.android.telephony.Rlog;
+
+import java.util.Set;
+
+/**
+ * A eUICC transaction session aims to optimize multiple back-to-back EuiccPort API calls by only
+ * open and close a logical channel once.
+ *
+ * <p>This class is thread-safe.
+ */
+public class EuiccSession {
+ private static final String TAG = "EuiccSession";
+
+ // **** Well known session IDs, see #startSession() ****
+ public static final String DOWNLOAD = "DOWNLOAD";
+
+ @GuardedBy("EuiccSession.class")
+ private static EuiccSession sInstance;
+
+ public static synchronized EuiccSession get() {
+ if (sInstance == null) {
+ sInstance = new EuiccSession();
+ }
+ return sInstance;
+ }
+
+ @GuardedBy("this")
+ private final Set<String> mSessions = new ArraySet<>();
+
+ @GuardedBy("this")
+ private final Set<ApduSender> mApduSenders = new ArraySet<>();
+
+ /**
+ * Marks the start of a eUICC transaction session.
+ *
+ * <p>A session means a long-open logical channel (see {@link ApduSender}) used to
+ * send multiple APDUs for one action e.g. {@link EuiccController#downloadSubscription()}.
+ * Those APDUs can be send by one or multiple {@link EuiccCardController} methods.
+ *
+ * <p>Ideally a session should correespond to one phoneId and hence just one logical channel.
+ * But many {@link EuiccCardController} methods uses first available port and is not specific
+ * to a phoneId. So EuiccController cannot choose one phoneId to use. Hence a session has to
+ * be not specific to phoneId, i.e. for DSDS device both phoneId's will be in a session.
+ *
+ * <p>If called multiple times with different {@code sessionId}'s, the session is truly closed
+ * when the all sessions are ended. See {@link #endSession()}.
+ *
+ * @param sessionId The session ID.
+ */
+ public void startSession(String sessionId) {
+ if (!Flags.optimizationApduSender()) {
+ // Other methods in this class is no-op if no session started.
+ // Do not add flag to other methods, so if the flag gets turned off,
+ // the session can be ended properly.
+ return;
+ }
+ Rlog.i(TAG, "startSession: " + sessionId);
+ synchronized(this) {
+ mSessions.add(sessionId);
+ }
+ }
+
+ /** Returns {@code true} if there is at least one session ongoing. */
+ public boolean hasSession() {
+ boolean hasSession;
+ synchronized(this) {
+ hasSession = !mSessions.isEmpty();
+ }
+ Rlog.i(TAG, "hasSession: " + hasSession);
+ return hasSession;
+ }
+
+ /**
+ * Notes that a logical channel may be opened by the {@code apduSender}, which will
+ * be used to close the channel when session ends (see {@link #endSession()}).
+ *
+ * <p>No-op if no session ongoing (see {@link #hasSession()}).
+ *
+ * @param apduSender The ApduSender that will open the channel.
+ */
+ public void noteChannelOpen(ApduSender apduSender) {
+ Rlog.i(TAG, "noteChannelOpen: " + apduSender);
+ synchronized(this) {
+ if (hasSession()) {
+ mApduSenders.add(apduSender);
+ }
+ }
+ }
+
+ /**
+ * Marks the end of a eUICC transaction session. If this ends the last ongoing session,
+ * try to close the logical channel using the noted {@code apduSender}
+ * (see {@link #noteChannelOpen()}).
+ *
+ * @param sessionId The session ID.
+ */
+ public void endSession(String sessionId) {
+ Rlog.i(TAG, "endSession: " + sessionId);
+ ApduSender[] apduSenders = new ApduSender[0];
+ synchronized(this) {
+ boolean sessionRemoved = mSessions.remove(sessionId);
+ // sessionRemoved is false if the `sessionId` was never started or there was
+ // no session at all i.e. `sessions` is empty. Don't bother invoke `apduSender`.
+ if (sessionRemoved && mSessions.isEmpty()) {
+ // copy mApduSenders to a local variable so we don't call closeAnyOpenChannel()
+ // which can take time in synchronized block.
+ apduSenders = mApduSenders.toArray(apduSenders);
+ mApduSenders.clear();
+ }
+ }
+ for (ApduSender apduSender : apduSenders) {
+ apduSender.closeAnyOpenChannel();
+ }
+ }
+
+ @VisibleForTesting
+ public EuiccSession() {}
+}
diff --git a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
index 767c39a..e43bf3c 100644
--- a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
+++ b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
@@ -1477,7 +1477,10 @@
satelliteController.countOfDisallowedSatelliteAccess,
satelliteController.countOfSatelliteAccessCheckFail,
satelliteController.isProvisioned,
- satelliteController.carrierId);
+ satelliteController.carrierId,
+ satelliteController.countOfSatelliteAllowedStateChangedEvents,
+ satelliteController.countOfSuccessfulLocationQueries,
+ satelliteController.countOfFailedLocationQueries);
}
private static StatsEvent buildStatsEvent(SatelliteSession satelliteSession) {
@@ -1591,7 +1594,8 @@
stats.satelliteSessionGapMinSec,
stats.satelliteSessionGapAvgSec,
stats.satelliteSessionGapMaxSec,
- stats.carrierId);
+ stats.carrierId,
+ stats.isDeviceEntitled);
}
private static StatsEvent buildStatsEvent(SatelliteEntitlement stats) {
@@ -1624,7 +1628,8 @@
stats.resultCode,
stats.countryCodes,
stats.configDataSource,
- stats.carrierId);
+ stats.carrierId,
+ stats.triggeringEvent);
}
/** Returns all phones in {@link PhoneFactory}, or an empty array if phones not made yet. */
diff --git a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
index 9fc0e6f..cf44aaf 100644
--- a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
+++ b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
@@ -770,9 +770,15 @@
atom.countOfAllowedSatelliteAccess += stats.countOfAllowedSatelliteAccess;
atom.countOfDisallowedSatelliteAccess += stats.countOfDisallowedSatelliteAccess;
atom.countOfSatelliteAccessCheckFail += stats.countOfSatelliteAccessCheckFail;
+
atom.isProvisioned = stats.isProvisioned;
atom.carrierId = stats.carrierId;
+ atom.countOfSatelliteAllowedStateChangedEvents
+ += stats.countOfSatelliteAllowedStateChangedEvents;
+ atom.countOfSuccessfulLocationQueries += stats.countOfSuccessfulLocationQueries;
+ atom.countOfFailedLocationQueries += stats.countOfFailedLocationQueries;
+
mAtoms.satelliteController = atomArray;
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
}
@@ -872,6 +878,7 @@
atom.satelliteSessionGapAvgSec = stats.satelliteSessionGapAvgSec;
atom.satelliteSessionGapMaxSec = stats.satelliteSessionGapMaxSec;
atom.carrierId = stats.carrierId;
+ atom.isDeviceEntitled = stats.isDeviceEntitled;
mAtoms.carrierRoamingSatelliteControllerStats = atomArray;
saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
diff --git a/src/java/com/android/internal/telephony/metrics/SatelliteStats.java b/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
index ddf0a4e..71c1bf3 100644
--- a/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
+++ b/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
@@ -19,6 +19,8 @@
import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_NONE;
import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
+import static com.android.internal.telephony.satellite.SatelliteConstants.TRIGGERING_EVENT_UNKNOWN;
+
import android.telephony.satellite.NtnSignalStrength;
import android.telephony.satellite.SatelliteManager;
@@ -95,6 +97,9 @@
private final int mCountOfSatelliteAccessCheckFail;
private static boolean sIsProvisioned;
private static int sCarrierId = UNKNOWN_CARRIER_ID;
+ private final int mCountOfSatelliteAllowedStateChangedEvents;
+ private final int mCountOfSuccessfulLocationQueries;
+ private final int mCountOfFailedLocationQueries;
private SatelliteControllerParams(Builder builder) {
this.mCountOfSatelliteServiceEnablementsSuccess =
@@ -150,6 +155,13 @@
if (builder.mCarrierId.isPresent()) {
this.sCarrierId = builder.mCarrierId.get();
}
+
+ this.mCountOfSatelliteAllowedStateChangedEvents =
+ builder.mCountOfSatelliteAllowedStateChangedEvents;
+ this.mCountOfSuccessfulLocationQueries =
+ builder.mCountOfSuccessfulLocationQueries;
+ this.mCountOfFailedLocationQueries =
+ builder.mCountOfFailedLocationQueries;
}
public int getCountOfSatelliteServiceEnablementsSuccess() {
@@ -272,6 +284,18 @@
return sCarrierId;
}
+ public int getCountOfSatelliteAllowedStateChangedEvents() {
+ return mCountOfSatelliteAllowedStateChangedEvents;
+ }
+
+ public int getCountOfSuccessfulLocationQueries() {
+ return mCountOfSuccessfulLocationQueries;
+ }
+
+ public int getCountOfFailedLocationQueries() {
+ return mCountOfFailedLocationQueries;
+ }
+
/**
* A builder class to create {@link SatelliteControllerParams} data structure class
*/
@@ -306,6 +330,9 @@
private int mCountOfSatelliteAccessCheckFail = 0;
private Optional<Boolean> mIsProvisioned = Optional.empty();
private Optional<Integer> mCarrierId = Optional.empty();
+ private int mCountOfSatelliteAllowedStateChangedEvents = 0;
+ private int mCountOfSuccessfulLocationQueries = 0;
+ private int mCountOfFailedLocationQueries = 0;
/**
* Sets countOfSatelliteServiceEnablementsSuccess value of {@link SatelliteController}
@@ -603,6 +630,37 @@
}
/**
+ * Sets countOfSatelliteAllowedStateChangedEvents value of {@link SatelliteController}
+ * atom
+ * then returns Builder class
+ */
+ public Builder setCountOfSatelliteAllowedStateChangedEvents(
+ int countOfSatelliteAllowedStateChangedEvents) {
+ this.mCountOfSatelliteAllowedStateChangedEvents =
+ countOfSatelliteAllowedStateChangedEvents;
+ return this;
+ }
+
+ /**
+ * Sets countOfSuccessfulLocationQueries value of {@link SatelliteController} atom
+ * then returns Builder class
+ */
+ public Builder setCountOfSuccessfulLocationQueries(
+ int countOfSuccessfulLocationQueries) {
+ this.mCountOfSuccessfulLocationQueries = countOfSuccessfulLocationQueries;
+ return this;
+ }
+
+ /**
+ * Sets countOfFailedLocationQueries value of {@link SatelliteController} atom
+ * then returns Builder class
+ */
+ public Builder setCountOfFailedLocationQueries(int countOfFailedLocationQueries) {
+ this.mCountOfFailedLocationQueries = countOfFailedLocationQueries;
+ return this;
+ }
+
+ /**
* Returns ControllerParams, which contains whole component of
* {@link SatelliteController} atom
*/
@@ -653,6 +711,10 @@
+ ", countOfSatelliteAccessCheckFail=" + mCountOfSatelliteAccessCheckFail
+ ", isProvisioned=" + sIsProvisioned
+ ", carrierId=" + sCarrierId
+ + ", countOfSatelliteAllowedStateChangedEvents="
+ + mCountOfSatelliteAllowedStateChangedEvents
+ + ", countOfSuccessfulLocationQueries=" + mCountOfSuccessfulLocationQueries
+ + ", countOfFailedLocationQueries=" + mCountOfFailedLocationQueries
+ ")";
}
}
@@ -1808,6 +1870,7 @@
private final int mSatelliteSessionGapAvgSec;
private final int mSatelliteSessionGapMaxSec;
private static int sCarrierId;
+ private static boolean sIsDeviceEntitled;
private CarrierRoamingSatelliteControllerStatsParams(Builder builder) {
this.mConfigDataSource = builder.mConfigDataSource;
@@ -1825,6 +1888,11 @@
if (builder.mCarrierId.isPresent()) {
this.sCarrierId = builder.mCarrierId.get();
}
+
+ // isDeviceEntitled value should be updated only when it is meaningful.
+ if (builder.mIsDeviceEntitled.isPresent()) {
+ this.sIsDeviceEntitled = builder.mIsDeviceEntitled.get();
+ }
}
public int getConfigDataSource() {
@@ -1860,6 +1928,10 @@
return sCarrierId;
}
+ public boolean isDeviceEntitled() {
+ return sIsDeviceEntitled;
+ }
+
/**
* A builder class to create {@link CarrierRoamingSatelliteControllerStatsParams}
* data structure class
@@ -1873,6 +1945,7 @@
private int mSatelliteSessionGapAvgSec = 0;
private int mSatelliteSessionGapMaxSec = 0;
private Optional<Integer> mCarrierId = Optional.empty();
+ private Optional<Boolean> mIsDeviceEntitled = Optional.empty();
/**
* Sets configDataSource value of {@link CarrierRoamingSatelliteControllerStats} atom
@@ -1946,6 +2019,12 @@
return this;
}
+ /** Sets whether the device is currently entitled or not. */
+ public Builder setIsDeviceEntitled(boolean isDeviceEntitled) {
+ this.mIsDeviceEntitled = Optional.of(isDeviceEntitled);
+ return this;
+ }
+
/**
* Returns CarrierRoamingSatelliteControllerStatsParams, which contains whole component
* of {@link CarrierRoamingSatelliteControllerStats} atom
@@ -1969,7 +2048,8 @@
+ ", satelliteSessionGapMinSec=" + mSatelliteSessionGapMinSec
+ ", satelliteSessionGapAvgSec=" + mSatelliteSessionGapAvgSec
+ ", satelliteSessionGapMaxSec=" + mSatelliteSessionGapMaxSec
- + ", CarrierId=" + sCarrierId
+ + ", carrierId=" + sCarrierId
+ + ", isDeviceEntitled=" + sIsDeviceEntitled
+ ")";
}
}
@@ -2202,6 +2282,7 @@
private final String[] mCountryCodes;
private final @SatelliteConstants.ConfigDataSource int mConfigDataSource;
private final int mCarrierId;
+ private final int mTriggeringEvent;
private SatelliteAccessControllerParams(Builder builder) {
this.mAccessControlType = builder.mAccessControlType;
@@ -2214,6 +2295,7 @@
this.mCountryCodes = builder.mCountryCodes;
this.mConfigDataSource = builder.mConfigDataSource;
this.mCarrierId = builder.mCarrierId;
+ this.mTriggeringEvent = builder.mTriggeringEvent;
}
public @SatelliteConstants.AccessControlType int getAccessControlType() {
@@ -2256,6 +2338,10 @@
return mCarrierId;
}
+ @SatelliteConstants.TriggeringEvent public int getTriggeringEvent() {
+ return mTriggeringEvent;
+ }
+
/**
* A builder class to create {@link SatelliteAccessControllerParams} data structure class
*/
@@ -2270,6 +2356,8 @@
private String[] mCountryCodes;
private @SatelliteConstants.ConfigDataSource int mConfigDataSource;
private int mCarrierId = UNKNOWN_CARRIER_ID;
+ private @SatelliteConstants.TriggeringEvent int mTriggeringEvent =
+ TRIGGERING_EVENT_UNKNOWN;
/**
* Sets AccessControlType value of {@link #SatelliteAccessController}
@@ -2337,6 +2425,13 @@
return this;
}
+ /** Sets the triggering evenr for current satellite access controller metric. */
+ public Builder setTriggeringEvent(
+ @SatelliteConstants.TriggeringEvent int triggeringEvent) {
+ this.mTriggeringEvent = triggeringEvent;
+ return this;
+ }
+
/**
* Returns AccessControllerParams, which contains whole component of
* {@link #SatelliteAccessController} atom
@@ -2360,6 +2455,7 @@
+ ", CountryCodes=" + Arrays.toString(mCountryCodes)
+ ", ConfigDataSource=" + mConfigDataSource
+ ", CarrierId=" + mCarrierId
+ + ", TriggeringEvent=" + mTriggeringEvent
+ ")";
}
}
@@ -2405,6 +2501,11 @@
proto.countOfSatelliteAccessCheckFail = param.getCountOfSatelliteAccessCheckFail();
proto.isProvisioned = param.isProvisioned();
proto.carrierId = param.getCarrierId();
+ proto.countOfSatelliteAllowedStateChangedEvents =
+ param.getCountOfSatelliteAllowedStateChangedEvents();
+ proto.countOfSuccessfulLocationQueries = param.getCountOfSuccessfulLocationQueries();
+ proto.countOfFailedLocationQueries = param.getCountOfFailedLocationQueries();
+
mAtomsStorage.addSatelliteControllerStats(proto);
}
@@ -2521,6 +2622,7 @@
proto.satelliteSessionGapAvgSec = param.mSatelliteSessionGapAvgSec;
proto.satelliteSessionGapMaxSec = param.mSatelliteSessionGapMaxSec;
proto.carrierId = param.getCarrierId();
+ proto.isDeviceEntitled = param.isDeviceEntitled();
mAtomsStorage.addCarrierRoamingSatelliteControllerStats(proto);
}
@@ -2559,6 +2661,7 @@
proto.countryCodes = param.getCountryCodes();
proto.configDataSource = param.getConfigDataSource();
proto.carrierId = param.getCarrierId();
+ proto.triggeringEvent = param.getTriggeringEvent();
mAtomsStorage.addSatelliteAccessControllerStats(proto);
}
}
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramController.java b/src/java/com/android/internal/telephony/satellite/DatagramController.java
index 9b483b8..a816906 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramController.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramController.java
@@ -252,6 +252,7 @@
@NonNull Consumer<Integer> callback) {
mDatagramDispatcher.sendSatelliteDatagram(subId, datagramType, datagram,
needFullScreenPointingUI, callback);
+ mPointingAppController.onSendDatagramRequested(subId, datagramType);
}
/**
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java b/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
index 9cff658..5a89c40 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
@@ -40,6 +40,7 @@
import android.telephony.DropBoxManagerLoggerBackend;
import android.telephony.PersistentLogger;
import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
import android.telephony.satellite.ISatelliteDatagramCallback;
import android.telephony.satellite.SatelliteDatagram;
import android.telephony.satellite.SatelliteManager;
@@ -454,8 +455,7 @@
if (mIsDemoMode && error == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
SatelliteDatagram datagram = mDatagramController.popDemoModeDatagram();
- final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(
- request.subId, mContext);
+ final int validSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
SatelliteDatagramListenerHandler listenerHandler =
mSatelliteDatagramListenerHandlers.get(validSubId);
if (listenerHandler != null) {
@@ -517,7 +517,7 @@
return SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
}
- final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
+ final int validSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
SatelliteDatagramListenerHandler satelliteDatagramListenerHandler =
mSatelliteDatagramListenerHandlers.get(validSubId);
if (satelliteDatagramListenerHandler == null) {
@@ -543,7 +543,7 @@
*/
public void unregisterForSatelliteDatagram(int subId,
@NonNull ISatelliteDatagramCallback callback) {
- final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
+ final int validSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
SatelliteDatagramListenerHandler handler =
mSatelliteDatagramListenerHandlers.get(validSubId);
if (handler != null) {
@@ -575,7 +575,8 @@
callback.accept(SatelliteManager.SATELLITE_RESULT_MODEM_BUSY);
return;
}
- pollPendingSatelliteDatagramsInternal(subId, callback);
+ pollPendingSatelliteDatagramsInternal(
+ SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, callback);
}
private void handleSatelliteConnectedEvent() {
diff --git a/src/java/com/android/internal/telephony/satellite/PointingAppController.java b/src/java/com/android/internal/telephony/satellite/PointingAppController.java
index 06281c7..ae2781d 100644
--- a/src/java/com/android/internal/telephony/satellite/PointingAppController.java
+++ b/src/java/com/android/internal/telephony/satellite/PointingAppController.java
@@ -37,6 +37,7 @@
import android.telephony.DropBoxManagerLoggerBackend;
import android.telephony.PersistentLogger;
import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
import android.telephony.satellite.PointingInfo;
import android.telephony.satellite.SatelliteManager;
@@ -189,6 +190,7 @@
public static final int EVENT_SEND_DATAGRAM_STATE_CHANGED = 2;
public static final int EVENT_RECEIVE_DATAGRAM_STATE_CHANGED = 3;
public static final int EVENT_DATAGRAM_TRANSFER_STATE_CHANGED = 4;
+ public static final int EVENT_SEND_DATAGRAM_REQUESTED = 5;
private final ConcurrentHashMap<IBinder, ISatelliteTransmissionUpdateCallback> mListeners;
@@ -277,6 +279,24 @@
break;
}
+ case EVENT_SEND_DATAGRAM_REQUESTED: {
+ logd("Received EVENT_SEND_DATAGRAM_REQUESTED");
+ int datagramType = (int) msg.obj;
+ List<IBinder> toBeRemoved = new ArrayList<>();
+ mListeners.values().forEach(listener -> {
+ try {
+ listener.onSendDatagramRequested(datagramType);
+ } catch (RemoteException e) {
+ logd("EVENT_SEND_DATAGRAM_REQUESTED RemoteException: " + e);
+ toBeRemoved.add(listener.asBinder());
+ }
+ });
+ toBeRemoved.forEach(listener -> {
+ mListeners.remove(listener);
+ });
+ break;
+ }
+
default:
loge("SatelliteTransmissionUpdateHandler unknown event: " + msg.what);
}
@@ -290,6 +310,7 @@
*/
public void registerForSatelliteTransmissionUpdates(int subId,
ISatelliteTransmissionUpdateCallback callback) {
+ subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
SatelliteTransmissionUpdateHandler handler =
mSatelliteTransmissionUpdateHandlers.get(subId);
if (handler != null) {
@@ -318,6 +339,7 @@
*/
public void unregisterForSatelliteTransmissionUpdates(int subId, Consumer<Integer> result,
ISatelliteTransmissionUpdateCallback callback) {
+ subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
SatelliteTransmissionUpdateHandler handler =
mSatelliteTransmissionUpdateHandlers.get(subId);
if (handler != null) {
@@ -426,6 +448,7 @@
int sendPendingCount, int errorCode) {
DatagramTransferStateHandlerRequest request = new DatagramTransferStateHandlerRequest(
datagramType, datagramTransferState, sendPendingCount, errorCode);
+ subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
SatelliteTransmissionUpdateHandler handler =
mSatelliteTransmissionUpdateHandlers.get(subId);
@@ -439,12 +462,32 @@
}
}
+ /**
+ * This API is used to notify PointingAppController that a send datagram has just been
+ * requested.
+ */
+ public void onSendDatagramRequested(
+ int subId, @SatelliteManager.DatagramType int datagramType) {
+ subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+ SatelliteTransmissionUpdateHandler handler =
+ mSatelliteTransmissionUpdateHandlers.get(subId);
+ if (handler != null) {
+ Message msg = handler.obtainMessage(
+ SatelliteTransmissionUpdateHandler.EVENT_SEND_DATAGRAM_REQUESTED,
+ datagramType);
+ msg.sendToTarget();
+ } else {
+ ploge("SatelliteTransmissionUpdateHandler not found for subId: " + subId);
+ }
+ }
+
public void updateReceiveDatagramTransferState(int subId,
@SatelliteManager.SatelliteDatagramTransferState int datagramTransferState,
int receivePendingCount, int errorCode) {
DatagramTransferStateHandlerRequest request = new DatagramTransferStateHandlerRequest(
SatelliteManager.DATAGRAM_TYPE_UNKNOWN, datagramTransferState, receivePendingCount,
errorCode);
+ subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
SatelliteTransmissionUpdateHandler handler =
mSatelliteTransmissionUpdateHandlers.get(subId);
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteConstants.java b/src/java/com/android/internal/telephony/satellite/SatelliteConstants.java
index 384dfa5..a5afe4a 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteConstants.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteConstants.java
@@ -104,4 +104,22 @@
})
@Retention(RetentionPolicy.SOURCE)
public @interface AccessControlType {}
+
+ //// Unknown reason.
+ public static final int TRIGGERING_EVENT_UNKNOWN = 0;
+ // Satellite Access Controller has been triggered by an external event.
+ public static final int TRIGGERING_EVENT_EXTERNAL_REQUEST = 1;
+ // Satellite Access Controller has been triggered by an MCC change event.
+ public static final int TRIGGERING_EVENT_MCC_CHANGED = 2;
+ //Satellite Access Controller has been triggered due to the location setting being enabled.
+ public static final int TRIGGERING_EVENT_LOCATION_SETTINGS_ENABLED = 3;
+
+ @IntDef(prefix = {"TRIGGERING_EVENT_"}, value = {
+ TRIGGERING_EVENT_UNKNOWN,
+ TRIGGERING_EVENT_EXTERNAL_REQUEST,
+ TRIGGERING_EVENT_MCC_CHANGED,
+ TRIGGERING_EVENT_LOCATION_SETTINGS_ENABLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TriggeringEvent {}
}
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
index 672e683..52ec0ba 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -206,6 +206,10 @@
public static final long DEFAULT_CARRIER_EMERGENCY_CALL_WAIT_FOR_CONNECTION_TIMEOUT_MILLIS =
TimeUnit.SECONDS.toMillis(30);
+ /** Sets report entitled metrics cool down to 23 hours to help enforcing privacy requirement.*/
+ private static final long WAIT_FOR_REPORT_ENTITLED_MERTICS_TIMEOUT_MILLIS =
+ TimeUnit.HOURS.toMillis(23);
+
/** Message codes used in handleMessage() */
//TODO: Move the Commands and events related to position updates to PointingAppController
private static final int CMD_START_SATELLITE_TRANSMISSION_UPDATES = 1;
@@ -256,6 +260,7 @@
private static final int EVENT_UPDATE_SATELLITE_ENABLE_ATTRIBUTES_DONE = 51;
protected static final int
EVENT_WAIT_FOR_UPDATE_SATELLITE_ENABLE_ATTRIBUTES_RESPONSE_TIMED_OUT = 52;
+ private static final int EVENT_WAIT_FOR_REPORT_ENTITLED_TO_MERTICS_HYSTERESIS_TIMED_OUT = 53;
@NonNull private static SatelliteController sInstance;
@NonNull private final Context mContext;
@@ -709,6 +714,7 @@
new HandlerExecutor(new Handler(looper)), mSubscriptionsChangedListener);
}
registerDefaultSmsSubscriptionChangedBroadcastReceiver();
+ updateSatelliteProvisionedStatePerSubscriberId();
}
class SatelliteSubscriptionsChangedListener
@@ -1767,6 +1773,21 @@
synchronized (mIsRadioOnLock) {
mRadioOffRequested = false;
}
+ break;
+ }
+
+ case EVENT_WAIT_FOR_REPORT_ENTITLED_TO_MERTICS_HYSTERESIS_TIMED_OUT: {
+ // TODO: b/366329504 report carrier roaming metrics for multiple subscription IDs.
+ synchronized (mSupportedSatelliteServicesLock) {
+ int defaultSubId = mSubscriptionManagerService.getDefaultSubId();
+ boolean isEntitled = mSatelliteEntitlementStatusPerCarrier.get(defaultSubId,
+ false);
+ mCarrierRoamingSatelliteControllerStats.reportIsDeviceEntitled(isEntitled);
+ }
+ sendMessageDelayed(obtainMessage(
+ EVENT_WAIT_FOR_REPORT_ENTITLED_TO_MERTICS_HYSTERESIS_TIMED_OUT),
+ WAIT_FOR_REPORT_ENTITLED_MERTICS_TIMEOUT_MILLIS);
+ break;
}
default:
@@ -3601,6 +3622,13 @@
if (mSatelliteEntitlementStatusPerCarrier.get(subId, false) != entitlementEnabled) {
logd("update the carrier satellite enabled to " + entitlementEnabled);
mSatelliteEntitlementStatusPerCarrier.put(subId, entitlementEnabled);
+ mCarrierRoamingSatelliteControllerStats.reportIsDeviceEntitled(entitlementEnabled);
+ if (hasMessages(EVENT_WAIT_FOR_REPORT_ENTITLED_TO_MERTICS_HYSTERESIS_TIMED_OUT)) {
+ removeMessages(EVENT_WAIT_FOR_REPORT_ENTITLED_TO_MERTICS_HYSTERESIS_TIMED_OUT);
+ sendMessageDelayed(obtainMessage(
+ EVENT_WAIT_FOR_REPORT_ENTITLED_TO_MERTICS_HYSTERESIS_TIMED_OUT),
+ WAIT_FOR_REPORT_ENTITLED_MERTICS_TIMEOUT_MILLIS);
+ }
try {
mSubscriptionManagerService.setSubscriptionProperty(subId,
SATELLITE_ENTITLEMENT_STATUS, entitlementEnabled ? "1" : "0");
@@ -4003,6 +4031,17 @@
}
provisionChanged = true;
mProvisionedSubscriberId.put(subscriberInfo.getSubscriberId(), provisioned);
+ int subId = mSubscriberIdPerSub.getOrDefault(subscriberInfo.getSubscriberId(),
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ try {
+ mSubscriptionManagerService.setIsSatelliteProvisionedForNonIpDatagram(subId,
+ provisioned);
+ plogd("handleEventSatelliteSubscriptionProvisionStateChanged: set Provision "
+ + "state to db subId=" + subId);
+ } catch (IllegalArgumentException | SecurityException ex) {
+ ploge("setIsSatelliteProvisionedForNonIpDatagram: subId=" + subId + ", ex="
+ + ex);
+ }
}
}
if (!provisionChanged) {
@@ -4020,6 +4059,7 @@
&& mProvisionedSubscriberId.containsValue(Boolean.TRUE);
mControllerMetricsStats.setIsProvisioned(isProvisioned);
}
+ handleStateChangedForCarrierRoamingNtnEligibility();
}
private void notifySatelliteSubscriptionProvisionStateChanged(
@@ -4595,6 +4635,7 @@
updateCarrierConfig(subId);
updateSatelliteESOSSupported(subId);
+ updateSatelliteProvisionedStatePerSubscriberId();
updateEntitlementPlmnListPerCarrier(subId);
updateSupportedSatelliteServicesForActiveSubscriptions();
processNewCarrierConfigData(subId);
@@ -4687,6 +4728,34 @@
}
}
+ /** If the provision state per subscriberId for the cached is not exist, check the database for
+ * the corresponding value and use it. */
+ private void updateSatelliteProvisionedStatePerSubscriberId() {
+ if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+ return;
+ }
+
+ List<SubscriptionInfo> allSubInfos = mSubscriptionManagerService.getAllSubInfoList(
+ mContext.getOpPackageName(), mContext.getAttributionTag());
+ for (SubscriptionInfo info : allSubInfos) {
+ int subId = info.getSubscriptionId();
+ Pair<String, Integer> subscriberIdPair = getSubscriberIdAndType(
+ mSubscriptionManagerService.getSubscriptionInfo(subId));
+ String subscriberId = subscriberIdPair.first;
+ synchronized (mSatelliteTokenProvisionedLock) {
+ if (mProvisionedSubscriberId.get(subscriberId) == null) {
+ boolean Provisioned = mSubscriptionManagerService
+ .isSatelliteProvisionedForNonIpDatagram(subId);
+ if (Provisioned) {
+ mProvisionedSubscriberId.put(subscriberId, true);
+ logd("updateSatelliteProvisionStatePerSubscriberId: " + subscriberId
+ + " set true");
+ }
+ }
+ }
+ }
+ }
+
@NonNull
private String[] readStringArrayFromOverlayConfig(@ArrayRes int id) {
String[] strArray = null;
@@ -4842,6 +4911,13 @@
}
boolean result = entitlementStatus.equals("1");
mSatelliteEntitlementStatusPerCarrier.put(subId, result);
+ mCarrierRoamingSatelliteControllerStats.reportIsDeviceEntitled(result);
+ if (hasMessages(EVENT_WAIT_FOR_REPORT_ENTITLED_TO_MERTICS_HYSTERESIS_TIMED_OUT)) {
+ removeMessages(EVENT_WAIT_FOR_REPORT_ENTITLED_TO_MERTICS_HYSTERESIS_TIMED_OUT);
+ sendMessageDelayed(obtainMessage(
+ EVENT_WAIT_FOR_REPORT_ENTITLED_TO_MERTICS_HYSTERESIS_TIMED_OUT),
+ WAIT_FOR_REPORT_ENTITLED_MERTICS_TIMEOUT_MILLIS);
+ }
}
if (!mSatelliteEntitlementStatusPerCarrier.get(subId, false)) {
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/AccessControllerMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/AccessControllerMetricsStats.java
index 25f6976..4333253 100644
--- a/src/java/com/android/internal/telephony/satellite/metrics/AccessControllerMetricsStats.java
+++ b/src/java/com/android/internal/telephony/satellite/metrics/AccessControllerMetricsStats.java
@@ -18,7 +18,9 @@
import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
+import static com.android.internal.telephony.satellite.SatelliteConstants.ACCESS_CONTROL_TYPE_UNKNOWN;
import static com.android.internal.telephony.satellite.SatelliteConstants.CONFIG_DATA_SOURCE_UNKNOWN;
+import static com.android.internal.telephony.satellite.SatelliteConstants.TRIGGERING_EVENT_UNKNOWN;
import android.annotation.NonNull;
import android.telephony.satellite.SatelliteManager;
@@ -44,6 +46,7 @@
private String[] mCountryCodes;
private @SatelliteConstants.ConfigDataSource int mConfigDataSource;
private int mCarrierId;
+ private @SatelliteConstants.TriggeringEvent int mTriggeringEvent;
private AccessControllerMetricsStats() {
initializeAccessControllerMetricsParam();
}
@@ -63,7 +66,7 @@
return sInstance;
}
private void initializeAccessControllerMetricsParam() {
- mAccessControlType = SatelliteConstants.ACCESS_CONTROL_TYPE_UNKNOWN;
+ mAccessControlType = ACCESS_CONTROL_TYPE_UNKNOWN;
mLocationQueryTimeMillis = 0;
mOnDeviceLookupTimeMillis = 0;
mTotalCheckingTimeMillis = 0;
@@ -73,6 +76,7 @@
mCountryCodes = new String[0];
mConfigDataSource = CONFIG_DATA_SOURCE_UNKNOWN;
mCarrierId = UNKNOWN_CARRIER_ID;
+ mTriggeringEvent = TRIGGERING_EVENT_UNKNOWN;
}
/**
* Sets the Access Control Type for current satellite enablement.
@@ -164,7 +168,6 @@
logd("setConfigDataSource: config data source = " + mConfigDataSource);
return this;
}
-
/**
* Sets the carrier id for NTN satellite service.
* @param carrierId Carrier ID of currently available NTN Satellite Network.
@@ -174,6 +177,16 @@
logd("setCarrierId: Carrier ID = " + mCarrierId);
return this;
}
+ /**
+ * Sets the triggering event for satellite access controller operation.
+ * @param triggeringEvent triggering event.
+ */
+ public AccessControllerMetricsStats setTriggeringEvent(
+ @SatelliteConstants.TriggeringEvent int triggeringEvent) {
+ mTriggeringEvent = triggeringEvent;
+ logd("setTriggeringEvent: triggering event = " + mTriggeringEvent);
+ return this;
+ }
/** Report the access controller metrics atoms to PersistAtomsStorage in telephony. */
public void reportAccessControllerMetrics() {
@@ -189,6 +202,7 @@
.setCountryCodes(mCountryCodes)
.setConfigDatasource(mConfigDataSource)
.setCarrierId(mCarrierId)
+ .setTriggeringEvent(mTriggeringEvent)
.build();
logd("reportAccessControllerMetrics: " + accessControllerParams.toString());
SatelliteStats.getInstance().onSatelliteAccessControllerMetrics(accessControllerParams);
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteControllerStats.java b/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteControllerStats.java
index b0cea29..e97d234 100644
--- a/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteControllerStats.java
+++ b/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteControllerStats.java
@@ -88,6 +88,14 @@
.build());
}
+ /** Capture whether the device is satellite entitled or not */
+ public void reportIsDeviceEntitled(boolean isDeviceEntitled) {
+ mSatelliteStats.onCarrierRoamingSatelliteControllerStatsMetrics(
+ new SatelliteStats.CarrierRoamingSatelliteControllerStatsParams.Builder()
+ .setIsDeviceEntitled(isDeviceEntitled)
+ .build());
+ }
+
private static void logd(@NonNull String log) {
Log.d(TAG, log);
}
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java
index 9f42d3d..ec135da 100644
--- a/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java
+++ b/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java
@@ -374,6 +374,7 @@
/** Capture the latest provisioned state for satellite service */
@VisibleForTesting
public void setIsProvisioned(boolean isProvisioned) {
+ logd("setIsProvisioned:" + isProvisioned);
mSatelliteStats.onSatelliteControllerMetrics(
new SatelliteStats.SatelliteControllerParams.Builder()
.setIsProvisioned(isProvisioned)
@@ -383,12 +384,41 @@
/** Capture the NB-IoT NTN carrier ID */
@VisibleForTesting
public void setCarrierId(int carrierId) {
+ logd("setCarrierId:" + carrierId);
mSatelliteStats.onSatelliteControllerMetrics(
new SatelliteStats.SatelliteControllerParams.Builder()
.setCarrierId(carrierId)
.build());
}
+ /**
+ * Report a counter when allowed state has changed.
+ */
+ public void reportAllowedStateChanged() {
+ logd("reportAllowedStateChanged:");
+ mSatelliteStats.onSatelliteControllerMetrics(
+ new SatelliteStats.SatelliteControllerParams.Builder()
+ .setCountOfSatelliteAllowedStateChangedEvents(ADD_COUNT)
+ .build());
+ }
+
+ /**
+ * Report a counter when location query was successful or failed.
+ */
+ public void reportLocationQuerySuccessful(boolean result) {
+ SatelliteStats.SatelliteControllerParams.Builder builder;
+ if (result) {
+ builder = new SatelliteStats.SatelliteControllerParams.Builder()
+ .setCountOfSuccessfulLocationQueries(ADD_COUNT);
+ } else {
+ builder = new SatelliteStats.SatelliteControllerParams.Builder()
+ .setCountOfFailedLocationQueries(ADD_COUNT);
+ }
+ SatelliteStats.SatelliteControllerParams controllerParam = builder.build();
+ logd("reportLocationQuerySuccessful:" + controllerParam);
+ mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
+ }
+
/** Receives the battery status whether it is in charging or not, update interval is 60 sec. */
private final BroadcastReceiver mBatteryStatusReceiver = new BroadcastReceiver() {
private long mLastUpdatedTime = 0;
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java b/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java
index 97fb9ca..b17a0fd 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java
@@ -25,7 +25,9 @@
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 @@
* 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 @@
// 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 @@
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 @@
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,7 +118,10 @@
mTransmitApdu = new TransmitApduLogicalChannelInvocation(ci);
mChannelKey = CHANNEL_ID_PRE + "_" + phoneId;
mChannelResponseKey = CHANNEL_RESPONSE_ID_PRE + "_" + phoneId;
- closeExistingChannelIfExists();
+ mHandler = new Handler();
+
+ mChannelInUse = false;
+ closeAnyOpenChannel();
}
/**
@@ -117,83 +140,125 @@
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) {
- AsyncResultHelper.throwException(
- new ApduException("The logical channel is still in use."),
- resultCallback, handler);
- return;
- }
- } else {
- AsyncResultHelper.throwException(
- new ApduException("The logical channel is in use."),
- resultCallback, handler);
- return;
- }
- }
- mChannelOpened = true;
+ if (!acquireChannelLock()) {
+ AsyncResultHelper.throwException(
+ new ApduException("The logical channel is still in use."),
+ resultCallback,
+ handler);
+ return;
}
- mOpenChannel.invoke(mAid, new AsyncResultCallback<IccOpenLogicalChannelResponse>() {
- @Override
- public void onResult(IccOpenLogicalChannelResponse openChannelResponse) {
- int channel = openChannelResponse.getChannel();
- int status = openChannelResponse.getStatus();
- byte[] selectResponse = openChannelResponse.getSelectResponse();
- if (status == IccOpenLogicalChannelResponse.STATUS_NO_SUCH_ELEMENT) {
- channel = PreferenceManager.getDefaultSharedPreferences(mContext)
- .getInt(mChannelKey, IccOpenLogicalChannelResponse.INVALID_CHANNEL);
- if (channel != IccOpenLogicalChannelResponse.INVALID_CHANNEL) {
- logv("Try to use already opened channel: " + channel);
- status = IccOpenLogicalChannelResponse.STATUS_NO_ERROR;
- String storedResponse = PreferenceManager
- .getDefaultSharedPreferences(mContext)
- .getString(mChannelResponseKey, "");
- 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: "
- + 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,
- 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);
- return;
- }
- sendCommand(builder.getCommands(), 0 /* index */, 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);
}
- }, handler);
+ 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);
+ }
+ 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) {
+ int channel = openChannelResponse.getChannel();
+ int status = openChannelResponse.getStatus();
+ byte[] selectResponse = openChannelResponse.getSelectResponse();
+ if (status == IccOpenLogicalChannelResponse.STATUS_NO_SUCH_ELEMENT) {
+ channel = PreferenceManager.getDefaultSharedPreferences(mContext)
+ .getInt(mChannelKey,
+ IccOpenLogicalChannelResponse.INVALID_CHANNEL);
+ if (channel != IccOpenLogicalChannelResponse.INVALID_CHANNEL) {
+ logv("Try to use already opened channel: " + channel);
+ status = IccOpenLogicalChannelResponse.STATUS_NO_ERROR;
+ String storedResponse = PreferenceManager
+ .getDefaultSharedPreferences(mContext)
+ .getString(mChannelResponseKey, "");
+ selectResponse = Base64.decode(storedResponse, Base64.DEFAULT);
+ }
+ }
+
+ if (channel == IccOpenLogicalChannelResponse.INVALID_CHANNEL
+ || status != IccOpenLogicalChannelResponse.STATUS_NO_ERROR) {
+ mChannelOpened = false;
+ resultCallback.onException(
+ new ApduException("Failed to open logical channel for AID: "
+ + mAid + ", with status: " + status));
+ return;
+ }
+ PreferenceManager.getDefaultSharedPreferences(mContext)
+ .edit()
+ .putInt(mChannelKey, channel)
+ .putString(mChannelResponseKey,
+ Base64.encodeToString(selectResponse, Base64.DEFAULT)).apply();
+ mChannelOpened = true;
+
+ RequestBuilder builder =
+ getRequestBuilderWithOpenedChannel(requestProvider,
+ closeChannelImmediately, resultCallback, handler);
+ if (builder == null) {
+ return;
+ }
+
+ sendCommand(builder.getCommands(), 0 /* index */,
+ closeChannelImmediately, resultCallback, handler);
+ }
+ },
+ handler);
}
/**
@@ -206,6 +271,7 @@
private void sendCommand(
List<ApduCommand> commands,
int index,
+ boolean closeChannelImmediately,
ApduSenderResultCallback resultCallback,
Handler handler) {
ApduCommand command = commands.get(index);
@@ -220,9 +286,21 @@
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;
}
@@ -232,11 +310,17 @@
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);
@@ -285,6 +369,41 @@
}, 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.
*
@@ -302,14 +421,9 @@
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();
- mChannelOpened = false;
- mChannelLock.notify();
- }
+ tearDownPreferences();
+ mChannelOpened = false;
+ releaseChannelLock();
if (exception == null) {
resultCallback.onResult(response);
@@ -321,37 +435,97 @@
}
/**
- * 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) {
- int channelId = PreferenceManager.getDefaultSharedPreferences(mContext)
+ 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("[closeAnyOpenChannel] Channel closed successfully: " + channelId);
+ tearDownPreferences();
}
- 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();
- }
-
- synchronized (mChannelLock) {
- mChannelOpened = false;
- mChannelLock.notify();
- }
- }
- }, new Handler());
+ // 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;
+ 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;
+ }
+ }
+ mChannelInUse = true;
+ logd("Channel lock acquired.");
+ return true;
}
}
}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
index 0b66459..ee713c6 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
@@ -3219,6 +3219,118 @@
}
@Test
+ public void testSetupDataNetworkWithCandidateProfileWithIncompatibleRetryDataProfile() throws Exception {
+ mDataNetworkControllerUT
+ .getDataRetryManager()
+ .registerCallback(mMockedDataRetryManagerCallback);
+ setFailedSetupDataResponse(mMockedWwanDataServiceManager,
+ DataFailCause.ONLY_IPV4_ALLOWED, 2500 /* mSec */, false);
+ mDataNetworkControllerUT.addNetworkRequest(
+ createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+ processAllMessages();
+ verify(mMockedWwanDataServiceManager, times(1)).setupDataCall(anyInt(),
+ any(DataProfile.class), anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(),
+ any(), any(), anyBoolean(), any(Message.class));
+
+ moveTimeForward(2500);
+ processAllMessages();
+ ArgumentCaptor<DataRetryManager.DataSetupRetryEntry> retryEntry =
+ ArgumentCaptor.forClass(DataRetryManager.DataSetupRetryEntry.class);
+ verify(mMockedDataRetryManagerCallback, times(1))
+ .onDataNetworkSetupRetry(retryEntry.capture());
+
+ ArgumentCaptor<List<ThrottleStatus>> throttleStatusCaptor =
+ ArgumentCaptor.forClass(List.class);
+ verify(mMockedDataRetryManagerCallback)
+ .onThrottleStatusChanged(throttleStatusCaptor.capture());
+
+ assertThat(retryEntry.getValue().dataProfile).isNotNull();
+
+ assertThat(retryEntry.getValue().dataProfile).isEqualTo(mGeneralPurposeDataProfile);
+ doReturn(false)
+ .when(mDataProfileManager)
+ .isDataProfileCompatible(retryEntry.getValue().dataProfile);
+
+ doReturn(mDuplicatedGeneralPurposeDataProfile).when(mDataProfileManager)
+ .getDataProfileForNetworkRequest(any(TelephonyNetworkRequest.class),
+ anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
+
+ setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 2);
+
+ mDataNetworkControllerUT
+ .getDataRetryManager()
+ .obtainMessage(
+ 6 /* EVENT_DATA_PROFILE_UNTHROTTLED*/,
+ new AsyncResult(
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+ mGeneralPurposeDataProfile,
+ null))
+ .sendToTarget();
+ processAllFutureMessages();
+
+ verify(mMockedWwanDataServiceManager, times(2)).setupDataCall(anyInt(),
+ any(DataProfile.class), anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(),
+ any(), any(), anyBoolean(), any(Message.class));
+
+ verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ verifyConnectedNetworkHasDataProfile(mDuplicatedGeneralPurposeDataProfile);
+ }
+
+ @Test
+ public void testSetupDataNetworkRetryWithCompatibleRetryDataProfile() throws Exception {
+ mDataNetworkControllerUT
+ .getDataRetryManager()
+ .registerCallback(mMockedDataRetryManagerCallback);
+ setFailedSetupDataResponse(mMockedWwanDataServiceManager,
+ DataFailCause.ONLY_IPV4_ALLOWED, 2500 /* mSec */, false);
+ mDataNetworkControllerUT.addNetworkRequest(
+ createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+ processAllMessages();
+ verify(mMockedWwanDataServiceManager, times(1)).setupDataCall(anyInt(),
+ any(DataProfile.class), anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(),
+ any(), any(), anyBoolean(), any(Message.class));
+
+ moveTimeForward(2500);
+ processAllMessages();
+ ArgumentCaptor<DataRetryManager.DataSetupRetryEntry> retryEntry =
+ ArgumentCaptor.forClass(DataRetryManager.DataSetupRetryEntry.class);
+ verify(mMockedDataRetryManagerCallback, times(1))
+ .onDataNetworkSetupRetry(retryEntry.capture());
+
+ ArgumentCaptor<List<ThrottleStatus>> throttleStatusCaptor =
+ ArgumentCaptor.forClass(List.class);
+ verify(mMockedDataRetryManagerCallback)
+ .onThrottleStatusChanged(throttleStatusCaptor.capture());
+
+ assertThat(retryEntry.getValue().dataProfile).isNotNull();
+
+ assertThat(retryEntry.getValue().dataProfile).isEqualTo(mGeneralPurposeDataProfile);
+
+ assertThat(mDataProfileManager.isDataProfileCompatible(retryEntry.getValue().dataProfile))
+ .isTrue();
+
+ setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 2);
+
+ mDataNetworkControllerUT
+ .getDataRetryManager()
+ .obtainMessage(
+ 6 /* EVENT_DATA_PROFILE_UNTHROTTLED*/,
+ new AsyncResult(
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+ mGeneralPurposeDataProfile,
+ null))
+ .sendToTarget();
+ processAllFutureMessages();
+
+ verify(mMockedWwanDataServiceManager, times(2)).setupDataCall(anyInt(),
+ any(DataProfile.class), anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(),
+ any(), any(), anyBoolean(), any(Message.class));
+
+ verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ verifyConnectedNetworkHasDataProfile(mGeneralPurposeDataProfile);
+ }
+
+ @Test
public void testSetupDataNetworkRetryFailed() {
mDataNetworkControllerUT.getDataRetryManager()
.registerCallback(mMockedDataRetryManagerCallback);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccSessionTest.java b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccSessionTest.java
new file mode 100644
index 0000000..f91088c
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccSessionTest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2024 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.euicc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.never;
+
+import android.app.PendingIntent;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+import android.service.euicc.DownloadSubscriptionResult;
+import android.service.euicc.EuiccService;
+import android.service.euicc.GetDefaultDownloadableSubscriptionListResult;
+import android.service.euicc.GetDownloadableSubscriptionMetadataResult;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccAccessRule;
+import android.telephony.UiccCardInfo;
+import android.telephony.UiccPortInfo;
+import android.telephony.euicc.DownloadableSubscription;
+import android.telephony.euicc.EuiccInfo;
+import android.telephony.euicc.EuiccManager;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.euicc.EuiccConnector.GetOtaStatusCommandCallback;
+import com.android.internal.telephony.euicc.EuiccConnector.OtaStatusChangedCallback;
+import com.android.internal.telephony.uicc.euicc.apdu.ApduSender;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.Flags;
+import com.android.internal.telephony.uicc.UiccSlot;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.mockito.stubbing.Stubber;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+public class EuiccSessionTest extends TelephonyTest {
+ @Rule
+ public final TestRule compatChangeRule = new PlatformCompatChangeRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
+ public final MockitoRule rule = MockitoJUnit.rule();
+
+ private static final String SESSION_ID_1 = "SESSION_ID_1";
+ private static final String SESSION_ID_2 = "SESSION_ID_2";
+
+ private EuiccSession mEuiccSession;
+ @Mock private ApduSender mApduSender;
+
+ @Before
+ public void setUp() throws Exception {
+ mEuiccSession = new EuiccSession();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_OPTIMIZATION_APDU_SENDER)
+ public void startOneSession_featureDisabled_noop() throws Exception {
+ mEuiccSession.startSession(SESSION_ID_1);
+ mEuiccSession.noteChannelOpen(mApduSender);
+
+ assertThat(mEuiccSession.hasSession()).isFalse();
+
+ mEuiccSession.endSession(SESSION_ID_1);
+
+ assertThat(mEuiccSession.hasSession()).isFalse();
+ verify(mApduSender, never()).closeAnyOpenChannel();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_OPTIMIZATION_APDU_SENDER)
+ public void startOneSession_endSession_hasSession() throws Exception {
+ mEuiccSession.startSession(SESSION_ID_1);
+ mEuiccSession.noteChannelOpen(mApduSender);
+
+ assertThat(mEuiccSession.hasSession()).isTrue();
+
+ mEuiccSession.endSession(SESSION_ID_2);
+
+ assertThat(mEuiccSession.hasSession()).isTrue();
+ verify(mApduSender, never()).closeAnyOpenChannel();
+
+ mEuiccSession.endSession(SESSION_ID_1);
+
+ assertThat(mEuiccSession.hasSession()).isFalse();
+ verify(mApduSender).closeAnyOpenChannel();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_OPTIMIZATION_APDU_SENDER)
+ public void startTwoSession_endSession_hasSession() throws Exception {
+ mEuiccSession.startSession(SESSION_ID_1);
+ mEuiccSession.noteChannelOpen(mApduSender);
+ mEuiccSession.startSession(SESSION_ID_2);
+
+ assertThat(mEuiccSession.hasSession()).isTrue();
+
+ mEuiccSession.endSession(SESSION_ID_1);
+ verify(mApduSender, never()).closeAnyOpenChannel();
+
+ assertThat(mEuiccSession.hasSession()).isTrue();
+
+ mEuiccSession.endSession(SESSION_ID_2);
+
+ assertThat(mEuiccSession.hasSession()).isFalse();
+ verify(mApduSender).closeAnyOpenChannel();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_OPTIMIZATION_APDU_SENDER)
+ public void noteChannelOpen_noSession_noop() throws Exception {
+ // noteChannelOpen called without a session started
+ mEuiccSession.noteChannelOpen(mApduSender);
+
+ assertThat(mEuiccSession.hasSession()).isFalse();
+
+ mEuiccSession.endSession(SESSION_ID_1);
+
+ assertThat(mEuiccSession.hasSession()).isFalse();
+ verify(mApduSender, never()).closeAnyOpenChannel();
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
index c986be4..e371c59 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
@@ -1376,6 +1376,7 @@
mCarrierRoamingSatelliteControllerStats1.satelliteSessionGapAvgSec = 3;
mCarrierRoamingSatelliteControllerStats1.satelliteSessionGapMaxSec = 4;
mCarrierRoamingSatelliteControllerStats1.carrierId = 1;
+ mCarrierRoamingSatelliteControllerStats1.isDeviceEntitled = true;
mCarrierRoamingSatelliteControllerStats2 = new CarrierRoamingSatelliteControllerStats();
mCarrierRoamingSatelliteControllerStats2.configDataSource =
@@ -1387,6 +1388,7 @@
mCarrierRoamingSatelliteControllerStats2.satelliteSessionGapAvgSec = 10;
mCarrierRoamingSatelliteControllerStats2.satelliteSessionGapMaxSec = 15;
mCarrierRoamingSatelliteControllerStats2.carrierId = 10;
+ mCarrierRoamingSatelliteControllerStats2.isDeviceEntitled = false;
// CarrierRoamingSatelliteController has one data point
mCarrierRoamingSatelliteControllerStats = new CarrierRoamingSatelliteControllerStats[] {
@@ -5003,6 +5005,7 @@
expected.satelliteSessionGapMaxSec =
mCarrierRoamingSatelliteControllerStats2.satelliteSessionGapMaxSec;
expected.carrierId = mCarrierRoamingSatelliteControllerStats2.carrierId;
+ expected.isDeviceEntitled = mCarrierRoamingSatelliteControllerStats2.isDeviceEntitled;
verifyCurrentStateSavedToFileOnce();
CarrierRoamingSatelliteControllerStats[] output =
@@ -6261,7 +6264,7 @@
assertEquals(tested[0].satelliteSessionGapAvgSec, expectedStats.satelliteSessionGapAvgSec);
assertEquals(tested[0].satelliteSessionGapMaxSec, expectedStats.satelliteSessionGapMaxSec);
assertEquals(tested[0].carrierId, expectedStats.carrierId);
-
+ assertEquals(tested[0].isDeviceEntitled, expectedStats.isDeviceEntitled);
}
private static void assertHasStatsAndCount(
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java
index da0dfe9..5740336 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java
@@ -434,6 +434,8 @@
.setSatelliteSessionGapMinSec(15)
.setSatelliteSessionGapAvgSec(30)
.setSatelliteSessionGapMaxSec(45)
+ .setCarrierId(10)
+ .setIsDeviceEntitled(true)
.build();
mSatelliteStats.onCarrierRoamingSatelliteControllerStatsMetrics(param);
@@ -452,6 +454,70 @@
assertEquals(param.getSatelliteSessionGapMinSec(), stats.satelliteSessionGapMinSec);
assertEquals(param.getSatelliteSessionGapAvgSec(), stats.satelliteSessionGapAvgSec);
assertEquals(param.getSatelliteSessionGapMaxSec(), stats.satelliteSessionGapMaxSec);
+ assertEquals(param.getCarrierId(), stats.carrierId);
+ assertEquals(param.isDeviceEntitled(), stats.isDeviceEntitled);
+
+ verifyNoMoreInteractions(mPersistAtomsStorage);
+ }
+
+ @Test
+ public void onCarrierRoamingSatelliteControllerStatsMetrics_testStaticFields()
+ throws Exception {
+ SatelliteStats.CarrierRoamingSatelliteControllerStatsParams param =
+ new SatelliteStats.CarrierRoamingSatelliteControllerStatsParams.Builder()
+ .setConfigDataSource(4)
+ .setCountOfEntitlementStatusQueryRequest(6)
+ .setCountOfSatelliteConfigUpdateRequest(2)
+ .setCountOfSatelliteNotificationDisplayed(1)
+ .setSatelliteSessionGapMinSec(15)
+ .setSatelliteSessionGapAvgSec(30)
+ .setSatelliteSessionGapMaxSec(45)
+ .setCarrierId(10)
+ .setIsDeviceEntitled(true)
+ .build();
+ mSatelliteStats.onCarrierRoamingSatelliteControllerStatsMetrics(param);
+
+ ArgumentCaptor<CarrierRoamingSatelliteControllerStats> captor =
+ ArgumentCaptor.forClass(CarrierRoamingSatelliteControllerStats.class);
+ verify(mPersistAtomsStorage, times(1)).addCarrierRoamingSatelliteControllerStats(
+ captor.capture());
+ CarrierRoamingSatelliteControllerStats stats = captor.getValue();
+ assertEquals(param.getCountOfEntitlementStatusQueryRequest(),
+ stats.countOfEntitlementStatusQueryRequest);
+ assertEquals(param.getCarrierId(), stats.carrierId);
+ assertEquals(param.isDeviceEntitled(), stats.isDeviceEntitled);
+
+ param = new SatelliteStats.CarrierRoamingSatelliteControllerStatsParams.Builder()
+ .setCountOfSatelliteConfigUpdateRequest(2)
+ .build();
+ mSatelliteStats.onCarrierRoamingSatelliteControllerStatsMetrics(param);
+
+ captor = ArgumentCaptor.forClass(CarrierRoamingSatelliteControllerStats.class);
+ verify(mPersistAtomsStorage, times(2)).addCarrierRoamingSatelliteControllerStats(
+ captor.capture());
+ stats = captor.getValue();
+ // count should be added
+ assertEquals(2, stats.countOfSatelliteConfigUpdateRequest);
+ // static values should not be updated
+ assertEquals(10, stats.carrierId);
+ assertEquals(true, stats.isDeviceEntitled);
+
+ param = new SatelliteStats.CarrierRoamingSatelliteControllerStatsParams.Builder()
+ .setCountOfSatelliteConfigUpdateRequest(2)
+ .setCarrierId(20)
+ .setIsDeviceEntitled(false)
+ .build();
+ mSatelliteStats.onCarrierRoamingSatelliteControllerStatsMetrics(param);
+
+ captor = ArgumentCaptor.forClass(CarrierRoamingSatelliteControllerStats.class);
+ verify(mPersistAtomsStorage, times(3)).addCarrierRoamingSatelliteControllerStats(
+ captor.capture());
+ stats = captor.getValue();
+ // count should be added
+ assertEquals(2, stats.countOfSatelliteConfigUpdateRequest);
+ // static values should be updated
+ assertEquals(20, stats.carrierId);
+ assertEquals(false, stats.isDeviceEntitled);
verifyNoMoreInteractions(mPersistAtomsStorage);
}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
index bf1a8bd..e964ced 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
@@ -50,6 +50,7 @@
import android.os.Message;
import android.os.RemoteException;
import android.provider.Telephony;
+import android.telephony.SubscriptionManager;
import android.telephony.satellite.ISatelliteDatagramCallback;
import android.telephony.satellite.SatelliteDatagram;
import android.telephony.satellite.SatelliteManager;
@@ -80,7 +81,7 @@
@TestableLooper.RunWithLooper
public class DatagramReceiverTest extends TelephonyTest {
private static final String TAG = "DatagramReceiverTest";
- private static final int SUB_ID = 0;
+ private static final int SUB_ID = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
private static final String TEST_MESSAGE = "This is a test datagram message";
private static final long TEST_EXPIRE_TIMER_SATELLITE_ALIGN = TimeUnit.SECONDS.toMillis(1);
private static final long TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS =
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/PointingAppControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/PointingAppControllerTest.java
index 36d32fe..aa24fb9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/PointingAppControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/PointingAppControllerTest.java
@@ -196,6 +196,11 @@
}
}
+ @Override
+ public void onSendDatagramRequested(int datagramType) {
+ logd("onSendDatagramRequested: datagramType=" + datagramType);
+ }
+
public int getDatagramType() {
return mDatagramType;
}
@@ -413,7 +418,6 @@
TestSatelliteTransmissionUpdateCallback callback2 = new
TestSatelliteTransmissionUpdateCallback();
int subId1 = 3;
- int subId2 = 4;
mPointingAppController.registerForSatelliteTransmissionUpdates(subId1, callback1);
mInOrder.verify(mMockSatelliteModemInterface).registerForSatellitePositionInfoChanged(any(),
eq(1), eq(null));
@@ -424,16 +428,6 @@
.registerForSatellitePositionInfoChanged(any(), eq(1), eq(null));
mInOrder.verify(mMockSatelliteModemInterface, never())
.registerForDatagramTransferStateChanged(any(), eq(4), eq(null));
- mPointingAppController.registerForSatelliteTransmissionUpdates(subId2, callback1);
- mInOrder.verify(mMockSatelliteModemInterface).registerForSatellitePositionInfoChanged(any(),
- eq(1), eq(null));
- mInOrder.verify(mMockSatelliteModemInterface).registerForDatagramTransferStateChanged(any(),
- eq(4), eq(null));
- mPointingAppController.registerForSatelliteTransmissionUpdates(subId2, callback2);
- mInOrder.verify(mMockSatelliteModemInterface, never())
- .registerForSatellitePositionInfoChanged(any(), eq(1), eq(null));
- mInOrder.verify(mMockSatelliteModemInterface, never())
- .registerForDatagramTransferStateChanged(any(), eq(4), eq(null));
mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId1,
mResultListener::offer, callback1);
processAllMessages();
@@ -446,22 +440,6 @@
any(Handler.class));
mInOrder.verify(mMockSatelliteModemInterface).unregisterForDatagramTransferStateChanged(
any(Handler.class));
- mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId2,
- mResultListener::offer, callback1);
- processAllMessages();
- assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_RESULT_SUCCESS);
- mResultListener.remove();
- mInOrder.verify(mMockSatelliteModemInterface, never())
- .unregisterForSatellitePositionInfoChanged(any(Handler.class));
- mInOrder.verify(mMockSatelliteModemInterface, never())
- .unregisterForDatagramTransferStateChanged(any(Handler.class));
- mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId2,
- mResultListener::offer, callback2);
- processAllMessages();
- mInOrder.verify(mMockSatelliteModemInterface).unregisterForSatellitePositionInfoChanged(
- any(Handler.class));
- mInOrder.verify(mMockSatelliteModemInterface).unregisterForDatagramTransferStateChanged(
- any(Handler.class));
mInOrder = null;
}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
index 58a77a7..1d2889a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
@@ -4541,6 +4541,55 @@
verify(mContext, times(1)).sendBroadcast(any(Intent.class));
}
+ @Test
+ public void testProvisionStatusPerSubscriberIdGetFromDb() throws Exception {
+ when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+
+ setSatelliteSubscriberTesting();
+ // Check if the cache is not updated when the value read from the database is false.
+ verifyProvisionStatusPerSubscriberIdGetFromDb(false);
+
+ // Check if the cache is updated when the value read from the database is true.
+ verifyProvisionStatusPerSubscriberIdGetFromDb(true);
+ }
+
+ @Test
+ public void testProvisionStatusPerSubscriberIdStoreToDb() throws Exception {
+ when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+
+ setSatelliteSubscriberTesting();
+ // Check if the cache is not updated when the value read from the database is false.
+ verifyProvisionStatusPerSubscriberIdGetFromDb(false);
+
+ List<SatelliteSubscriberInfo> inputList = getExpectedSatelliteSubscriberInfoList();
+ verifyProvisionSatellite(inputList);
+ verify(mMockSubscriptionManagerService).setIsSatelliteProvisionedForNonIpDatagram(
+ eq(SUB_ID), eq(true));
+ }
+
+ private void verifyProvisionStatusPerSubscriberIdGetFromDb(boolean provision) {
+ doReturn(provision).when(
+ mMockSubscriptionManagerService).isSatelliteProvisionedForNonIpDatagram(anyInt());
+ mCarrierConfigBundle.putString(KEY_SATELLITE_NIDD_APN_NAME_STRING, mNiddApn);
+ mCarrierConfigBundle.putBoolean(KEY_SATELLITE_ESOS_SUPPORTED_BOOL, true);
+ for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+ : mCarrierConfigChangedListenerList) {
+ pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+ /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+ );
+ }
+ moveTimeForward(TimeUnit.MINUTES.toMillis(1));
+ processAllMessages();
+ mSatelliteControllerUT.requestSatelliteSubscriberProvisionStatus(
+ mRequestSatelliteSubscriberProvisionStatusReceiver);
+ moveTimeForward(TimeUnit.MINUTES.toMillis(1));
+ processAllMessages();
+ assertEquals(SATELLITE_RESULT_SUCCESS,
+ mRequestSatelliteSubscriberProvisionStatusResultCode);
+ assertEquals(provision,
+ mRequestSatelliteSubscriberProvisionStatusResultList.get(0).getProvisionStatus());
+ }
+
private void setComponentName() {
when(mSatelliteControllerUT.getStringFromOverlayConfigTest(
R.string.config_satellite_gateway_service_package))
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java
index 5a07714..172211c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java
@@ -16,6 +16,7 @@
package com.android.internal.telephony.uicc.euicc.apdu;
+import static com.android.internal.telephony.CommandException.Error.RADIO_NOT_AVAILABLE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
@@ -24,13 +25,19 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.os.Handler;
import android.os.Looper;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.preference.PreferenceManager;
+import android.telephony.IccOpenLogicalChannelResponse;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -38,17 +45,23 @@
import com.android.internal.telephony.CommandException;
import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.euicc.EuiccSession;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.uicc.IccIoResult;
import com.android.internal.telephony.uicc.IccUtils;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class ApduSenderTest {
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static class ResponseCaptor extends ApduSenderResultCallback {
public byte[] response;
@@ -78,6 +91,13 @@
}
}
+ private static final int PHONE_ID = 0;
+ private static final String SESSION_ID = "TEST";
+ // keep in sync with ApduSender.mChannelKey
+ private static final String SHARED_PREFS_KEY_CHANNEL_ID = "esim-channel_0";
+ // keep in sync with ApduSender.mChannelResponseKey
+ private static final String SHARED_PREFS_KEY_CHANNEL_RESPONSE = "esim-res-id_0";
+
// Mocked classes
private CommandsInterface mMockCi;
@@ -89,15 +109,16 @@
@Before
public void setUp() {
- mMockCi = mock(CommandsInterface.class);
- mHandler = new Handler(Looper.myLooper());
+ mSetFlagsRule.enableFlags(Flags.FLAG_OPTIMIZATION_APDU_SENDER);
+ mMockCi = mock(CommandsInterface.class);
+ mLooper = TestableLooper.get(this);
+ mHandler = new Handler(mLooper.getLooper());
mResponseCaptor = new ResponseCaptor();
mSelectResponse = null;
- mSender = new ApduSender(InstrumentationRegistry.getContext(), 0 /* phoneId= */,
+ mSender = new ApduSender(InstrumentationRegistry.getContext(), PHONE_ID,
mMockCi, ApduSender.ISD_R_AID, false /* supportExtendedApdu */);
- mLooper = TestableLooper.get(this);
}
@After
@@ -108,6 +129,9 @@
mResponseCaptor = null;
mSelectResponse = null;
mSender = null;
+
+ EuiccSession.get().endSession(SESSION_ID);
+ clearSharedPreferences();
}
@Test
@@ -162,8 +186,11 @@
mLooper.processAllMessages();
assertEquals("A1A1A1", IccUtils.bytesToHexString(mResponseCaptor.response));
- verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
- eq(3), eq(0), eq("a"), anyBoolean(), any());
+ InOrder inOrder = inOrder(mMockCi);
+ inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
+ inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10),
+ eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any());
+ inOrder.verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any());
}
@Test
@@ -182,14 +209,17 @@
mLooper.processAllMessages();
assertEquals("A4", IccUtils.bytesToHexString(mResponseCaptor.response));
- verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
- eq(3), eq(0), eq("a"), anyBoolean(), any());
- verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
- eq(3), eq(1), eq("ab"), anyBoolean(), any());
- verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
- eq(3), eq(0), eq(""), anyBoolean(), any());
- verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x91),
- eq(0), eq(2), eq("abcd"), anyBoolean(), any());
+ InOrder inOrder = inOrder(mMockCi);
+ inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
+ inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10),
+ eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any());
+ inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10),
+ eq(1), eq(2), eq(3), eq(1), eq("ab"), anyBoolean(), any());
+ inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10),
+ eq(1), eq(2), eq(3), eq(0), eq(""), anyBoolean(), any());
+ inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81),
+ eq(0xE2), eq(0x91), eq(0), eq(2), eq("abcd"), anyBoolean(), any());
+ inOrder.verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any());
}
@Test
@@ -354,4 +384,155 @@
assertTrue(mResponseCaptor.exception instanceof ApduException);
verify(mMockCi, times(1)).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
}
+
+ @Test
+ public void testConstructor_closeOpenChannelInSharedPreference() throws InterruptedException {
+ // Open a channel and not close it, by making CI.iccTransmitApduLogicalChannel throw.
+ int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
+ doThrow(new RuntimeException()).when(mMockCi).iccTransmitApduLogicalChannel(
+ eq(channel), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(),
+ anyBoolean(), any());
+ mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
+ 10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
+ mLooper.processAllMessages();
+ // Stub close channel
+ reset(mMockCi);
+ LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
+
+ // Call constructor
+ mSender = new ApduSender(InstrumentationRegistry.getContext(), PHONE_ID,
+ mMockCi, ApduSender.ISD_R_AID, false /* supportExtendedApdu */);
+ mLooper.processAllMessages();
+
+ // The constructor should have closed channel
+ verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any());
+ assertEquals(-1, getChannelIdFromSharedPreferences());
+ }
+
+ @Test
+ public void testSend_OpenChannelFailedNoSuchElement_useChannelInSharedPreference() {
+ // Open a channel but not close, by making CI.iccTransmitApduLogicalChannel throw.
+ int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
+ doThrow(new RuntimeException()).when(mMockCi).iccTransmitApduLogicalChannel(
+ eq(channel), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(),
+ anyBoolean(), any());
+ mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
+ 10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
+ mLooper.processAllMessages();
+ reset(mMockCi);
+ // Constructor fails to close channel
+ LogicalChannelMocker.mockCloseLogicalChannel(
+ mMockCi, channel, new CommandException(RADIO_NOT_AVAILABLE));
+ mSender = new ApduSender(InstrumentationRegistry.getContext(), PHONE_ID,
+ mMockCi, ApduSender.ISD_R_AID, false /* supportExtendedApdu */);
+ mLooper.processAllMessages();
+ reset(mMockCi);
+ // Stub open channel failure NO_SUCH_ELEMENT
+ LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi,
+ new CommandException(CommandException.Error.NO_SUCH_ELEMENT));
+ LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A1A1A19000");
+ LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
+
+ mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
+ 10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
+ mLooper.processAllMessages();
+
+ // open channel would fail, and send/close would succeed because of
+ // previous open response saved in sharedPref
+ InOrder inOrder = inOrder(mMockCi);
+ inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
+ inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel),
+ eq(channel | 10), eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any());
+ inOrder.verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any());
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void testSend_euiccSession_shouldNotCloseChannel()
+ throws InterruptedException {
+ int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
+ LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A1A1A19000");
+ LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
+ EuiccSession.get().startSession(SESSION_ID);
+
+ mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
+ 10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
+ mLooper.processAllMessages();
+
+ assertEquals("A1A1A1", IccUtils.bytesToHexString(mResponseCaptor.response));
+ InOrder inOrder = inOrder(mMockCi);
+ inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
+ inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10),
+ eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any());
+ // No iccCloseLogicalChannel
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void testSendTwice_euiccSession_shouldOpenChannelOnceNotCloseChannel()
+ throws InterruptedException {
+ int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
+ LogicalChannelMocker.mockSendToLogicalChannel(
+ mMockCi, channel, "A1A1A19000", "A1A1A19000");
+ LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
+ EuiccSession.get().startSession(SESSION_ID);
+
+ mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
+ 10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
+ mLooper.processAllMessages();
+ mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
+ 10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
+ mLooper.processAllMessages();
+
+ assertEquals("A1A1A1", IccUtils.bytesToHexString(mResponseCaptor.response));
+ InOrder inOrder = inOrder(mMockCi);
+ // iccOpenLogicalChannel once
+ inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
+ // iccTransmitApduLogicalChannel twice
+ inOrder.verify(mMockCi, times(2)).iccTransmitApduLogicalChannel(eq(channel),
+ eq(channel | 10), eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any());
+ // No iccCloseLogicalChannel
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void testSendTwice_thenEndSession() throws InterruptedException {
+ int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
+ LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel,
+ "A1A1A19000", "A1A1A19000");
+ LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
+ EuiccSession.get().startSession(SESSION_ID);
+
+ mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
+ 10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
+ mLooper.processAllMessages();
+ mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
+ 10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
+ mLooper.processAllMessages();
+ EuiccSession.get().endSession(SESSION_ID);
+ mLooper.processAllMessages();
+
+ assertEquals("A1A1A1", IccUtils.bytesToHexString(mResponseCaptor.response));
+ InOrder inOrder = inOrder(mMockCi);
+ // iccOpenLogicalChannel once
+ inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
+ // iccTransmitApduLogicalChannel twice
+ inOrder.verify(mMockCi, times(2)).iccTransmitApduLogicalChannel(eq(channel),
+ eq(channel | 10), eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any());
+ // iccCloseLogicalChannel once
+ inOrder.verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any());
+ }
+
+ private int getChannelIdFromSharedPreferences() {
+ return PreferenceManager.getDefaultSharedPreferences(InstrumentationRegistry.getContext())
+ .getInt(SHARED_PREFS_KEY_CHANNEL_ID, -1);
+ }
+
+ private void clearSharedPreferences() {
+ PreferenceManager.getDefaultSharedPreferences(InstrumentationRegistry.getContext())
+ .edit()
+ .remove(SHARED_PREFS_KEY_CHANNEL_ID)
+ .remove(SHARED_PREFS_KEY_CHANNEL_RESPONSE)
+ .apply();
+ }
}