Merge "Add entitlement capability for satellite" into main
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index de14c83..5fb2d57 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -248,6 +248,7 @@
import com.android.phone.callcomposer.CallComposerPictureTransfer;
import com.android.phone.callcomposer.ImageData;
import com.android.phone.satellite.accesscontrol.SatelliteAccessController;
+import com.android.phone.satellite.entitlement.SatelliteEntitlementController;
import com.android.phone.settings.PickSmsSubscriptionActivity;
import com.android.phone.slice.SlicePurchaseController;
import com.android.phone.utils.CarrierAllowListInfo;
@@ -2477,6 +2478,10 @@
PropertyInvalidatedCache.invalidateCache(TelephonyManager.CACHE_KEY_PHONE_ACCOUNT_TO_SUBID);
publish();
CarrierAllowListInfo.loadInstance(mApp);
+
+ // Create the SatelliteEntitlementController singleton, for using the get the
+ // entitlementStatus for satellite service.
+ SatelliteEntitlementController.make(mApp, mFeatureFlags);
}
@VisibleForTesting
diff --git a/src/com/android/phone/satellite/entitlement/SatelliteEntitlementApi.java b/src/com/android/phone/satellite/entitlement/SatelliteEntitlementApi.java
new file mode 100644
index 0000000..c856eb5
--- /dev/null
+++ b/src/com/android/phone/satellite/entitlement/SatelliteEntitlementApi.java
@@ -0,0 +1,71 @@
+/*
+ * 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.phone.satellite.entitlement;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+
+import com.android.libraries.entitlement.CarrierConfig;
+import com.android.libraries.entitlement.ServiceEntitlement;
+import com.android.libraries.entitlement.ServiceEntitlementException;
+import com.android.libraries.entitlement.ServiceEntitlementRequest;
+
+/**
+ * Class that sends an HTTP request to the entitlement server and processes the response to check
+ * whether satellite service can be activated.
+ * @hide
+ */
+public class SatelliteEntitlementApi {
+ @NonNull
+ private final ServiceEntitlement mServiceEntitlement;
+ private final Context mContext;
+
+ public SatelliteEntitlementApi(@NonNull Context context,
+ @NonNull PersistableBundle carrierConfig, @NonNull int subId) {
+ mContext = context;
+ mServiceEntitlement = new ServiceEntitlement(mContext,
+ getCarrierConfigFromEntitlementServerUrl(carrierConfig), subId);
+ }
+
+ /**
+ * Returns satellite entitlement result from the entitlement server.
+ * @return The SatelliteEntitlementResult
+ */
+ public SatelliteEntitlementResult checkEntitlementStatus() throws ServiceEntitlementException {
+ ServiceEntitlementRequest.Builder requestBuilder = ServiceEntitlementRequest.builder();
+ requestBuilder.setAcceptContentType(ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON);
+ ServiceEntitlementRequest request = requestBuilder.build();
+
+ String response = mServiceEntitlement.queryEntitlementStatus(
+ ServiceEntitlement.APP_SATELLITE_ENTITLEMENT, request);
+ SatelliteEntitlementResponse satelliteEntitlementResponse =
+ new SatelliteEntitlementResponse(response);
+ return new SatelliteEntitlementResult(satelliteEntitlementResponse.getEntitlementStatus(),
+ satelliteEntitlementResponse.getPlmnAllowed());
+ }
+
+ @NonNull
+ private CarrierConfig getCarrierConfigFromEntitlementServerUrl(
+ @NonNull PersistableBundle carrierConfig) {
+ String entitlementServiceUrl = carrierConfig.getString(
+ CarrierConfigManager.ImsServiceEntitlement.KEY_ENTITLEMENT_SERVER_URL_STRING,
+ "");
+ return CarrierConfig.builder().setServerUrl(entitlementServiceUrl).build();
+ }
+}
diff --git a/src/com/android/phone/satellite/entitlement/SatelliteEntitlementController.java b/src/com/android/phone/satellite/entitlement/SatelliteEntitlementController.java
new file mode 100644
index 0000000..8227c84
--- /dev/null
+++ b/src/com/android/phone/satellite/entitlement/SatelliteEntitlementController.java
@@ -0,0 +1,542 @@
+/*
+ * 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.phone.satellite.entitlement;
+
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_ENABLED;
+
+import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME;
+import static java.time.temporal.ChronoUnit.SECONDS;
+
+import android.annotation.NonNull;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.ExponentialBackoff;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.satellite.SatelliteController;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
+import com.android.libraries.entitlement.ServiceEntitlementException;
+
+import java.time.Instant;
+import java.time.format.DateTimeParseException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class query the entitlement server to receive values for satellite services and passes the
+ * response to the {@link com.android.internal.telephony.satellite.SatelliteController}.
+ * @hide
+ */
+public class SatelliteEntitlementController extends Handler {
+ private static final String TAG = "SatelliteEntitlementController";
+ @NonNull private static SatelliteEntitlementController sInstance;
+ /** Message code used in handleMessage() */
+ private static final int CMD_START_QUERY_ENTITLEMENT = 1;
+ private static final int CMD_RETRY_QUERY_ENTITLEMENT = 2;
+ private static final int CMD_STOP_RETRY_QUERY_ENTITLEMENT = 3;
+
+ /** Retry on next trigger event. */
+ private static final int HTTP_RESPONSE_500 = 500;
+ /** Retry after the time specified in the “Retry-After” header. After retry count doesn't exceed
+ * MAX_RETRY_COUNT. */
+ private static final int HTTP_RESPONSE_503 = 503;
+ /** Default query refresh time is 1 month. */
+
+ private static final int DEFAULT_QUERY_REFRESH_DAYS = 30;
+ private static final long INITIAL_DELAY_MILLIS = TimeUnit.MINUTES.toMillis(10); // 10 min
+ private static final long MAX_DELAY_MILLIS = TimeUnit.DAYS.toMillis(5); // 5 days
+ private static final int MULTIPLIER = 2;
+ private static final int MAX_RETRY_COUNT = 5;
+ @NonNull private final SubscriptionManagerService mSubscriptionManagerService;
+ @NonNull private final CarrierConfigManager mCarrierConfigManager;
+ @NonNull private final CarrierConfigManager.CarrierConfigChangeListener
+ mCarrierConfigChangeListener;
+ @NonNull private final ConnectivityManager mConnectivityManager;
+ @NonNull private final ConnectivityManager.NetworkCallback mNetworkCallback;
+ @NonNull private final BroadcastReceiver mReceiver;
+ @NonNull private final Context mContext;
+ private final Object mLock = new Object();
+ /** Map key : subId, value : ExponentialBackoff. */
+ private Map<Integer, ExponentialBackoff> mExponentialBackoffPerSub = new HashMap<>();
+ /** Map key : subId, value : SatelliteEntitlementResult. */
+ private Map<Integer, SatelliteEntitlementResult> mSatelliteEntitlementResultPerSub =
+ new HashMap<>();
+ /** Map key : subId, value : the last query time to millis. */
+ private Map<Integer, Long> mLastQueryTimePerSub = new HashMap<>();
+ /** Map key : subId, value : Count the number of retries caused by the 'ExponentialBackoff' and
+ * '503 error case with the Retry-After header'. */
+ private Map<Integer, Integer> mRetryCountPerSub = new HashMap<>();
+
+ /**
+ * Create the SatelliteEntitlementController singleton instance.
+ * @param context The Context to use to create the SatelliteEntitlementController.
+ * @param featureFlags The feature flag.
+ */
+ public static void make(@NonNull Context context, @NonNull FeatureFlags featureFlags) {
+ if (!featureFlags.carrierEnabledSatelliteFlag()) {
+ logd("carrierEnabledSatelliteFlag is disabled. don't created this.");
+ return;
+ }
+ if (sInstance == null) {
+ HandlerThread handlerThread = new HandlerThread(TAG);
+ handlerThread.start();
+ sInstance =
+ new SatelliteEntitlementController(context, handlerThread.getLooper());
+ }
+ }
+
+ /**
+ * Create a SatelliteEntitlementController to request query to the entitlement server for
+ * satellite services and receive responses.
+ *
+ * @param context The Context for the SatelliteEntitlementController.
+ * @param looper The looper for the handler. It does not run on main thread.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public SatelliteEntitlementController(@NonNull Context context, @NonNull Looper looper) {
+ super(looper);
+ mContext = context;
+ mSubscriptionManagerService = SubscriptionManagerService.getInstance();
+ mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
+ mCarrierConfigChangeListener = (slotIndex, subId, carrierId, specificCarrierId) ->
+ handleCarrierConfigChanged(slotIndex, subId, carrierId, specificCarrierId);
+ mCarrierConfigManager.registerCarrierConfigChangeListener(this::post,
+ mCarrierConfigChangeListener);
+ mConnectivityManager = context.getSystemService(ConnectivityManager.class);
+ mNetworkCallback = new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onAvailable(Network network) {
+ handleInternetConnected();
+ }
+
+ @Override
+ public void onLost(Network network) {
+ handleInternetDisconnected();
+ }
+ };
+ NetworkRequest networkrequest = new NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build();
+ mConnectivityManager.registerNetworkCallback(networkrequest, mNetworkCallback, this);
+ mReceiver = new SatelliteEntitlementControllerReceiver();
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ context.registerReceiver(mReceiver, intentFilter);
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ switch (msg.what) {
+ case CMD_START_QUERY_ENTITLEMENT:
+ handleCmdStartQueryEntitlement();
+ break;
+ case CMD_RETRY_QUERY_ENTITLEMENT:
+ handleCmdRetryQueryEntitlement(msg.arg1);
+ break;
+ case CMD_STOP_RETRY_QUERY_ENTITLEMENT:
+ stopExponentialBackoff(msg.arg1);
+ break;
+ default:
+ logd("do not used this message");
+ }
+ }
+
+ private void handleCarrierConfigChanged(int slotIndex, int subId, int carrierId,
+ int specificCarrierId) {
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ return;
+ }
+ logd("handleCarrierConfigChanged(): slotIndex(" + slotIndex + "), subId("
+ + subId + "), carrierId(" + carrierId + "), specificCarrierId("
+ + specificCarrierId + ")");
+
+ sendEmptyMessage(CMD_START_QUERY_ENTITLEMENT);
+ }
+
+ private class SatelliteEntitlementControllerReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
+ boolean airplaneMode = intent.getBooleanExtra("state", false);
+ handleAirplaneModeChange(airplaneMode);
+ }
+ }
+ }
+
+ private void handleAirplaneModeChange(boolean airplaneMode) {
+ if (!airplaneMode) {
+ resetEntitlementQueryCounts(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ }
+ }
+
+ private boolean isInternetConnected() {
+ Network activeNetwork = mConnectivityManager.getActiveNetwork();
+ NetworkCapabilities networkCapabilities =
+ mConnectivityManager.getNetworkCapabilities(activeNetwork);
+ // TODO b/319780796 Add checking if it is not a satellite.
+ return networkCapabilities != null
+ && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ }
+
+ private void handleInternetConnected() {
+ sendEmptyMessage(CMD_START_QUERY_ENTITLEMENT);
+ }
+
+ private void handleInternetDisconnected() {
+ mExponentialBackoffPerSub.forEach((key, value) -> {
+ Message message = obtainMessage();
+ message.what = CMD_STOP_RETRY_QUERY_ENTITLEMENT;
+ message.arg1 = key;
+ sendMessage(message);
+ });
+ }
+
+ /**
+ * Check if the device can request to entitlement server (if there is an internet connection and
+ * if the throttle time has passed since the last request), and then pass the response to
+ * SatelliteController if the response is received.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public void handleCmdStartQueryEntitlement() {
+ if (!isInternetConnected()) {
+ logd("Internet disconnected");
+ return;
+ }
+
+ for (int subId : mSubscriptionManagerService.getActiveSubIdList(true)) {
+ if (!shouldQueryEntitlement(subId)) {
+ return;
+ }
+
+ // Check the satellite service query result from the entitlement server for the
+ // satellite service.
+ try {
+ mSatelliteEntitlementResultPerSub.remove(subId);
+ mSatelliteEntitlementResultPerSub.put(subId, getSatelliteEntitlementApi(
+ subId).checkEntitlementStatus());
+ } catch (ServiceEntitlementException e) {
+ loge(e.toString());
+ if (!isInternetConnected()) {
+ logd("handleCmdStartQueryEntitlement: disconnected. " + e);
+ return;
+ }
+ if (shouldHandleErrorResponse(e, subId)) {
+ logd("handleCmdStartQueryEntitlement: handle response.");
+ return;
+ }
+ startExponentialBackoff(subId);
+ return;
+ }
+ queryCompleted(subId);
+ }
+ }
+
+ /** When airplane mode changes from on to off, reset the values required to start the first
+ * query. */
+ private void resetEntitlementQueryCounts(String event) {
+ logd("resetEntitlementQueryCounts: " + event);
+ mLastQueryTimePerSub = new HashMap<>();
+ mExponentialBackoffPerSub = new HashMap<>();
+ mRetryCountPerSub = new HashMap<>();
+ }
+
+ /**
+ * If the HTTP response does not receive a body containing the 200 ok with sat mode
+ * configuration,
+ *
+ * 1. If the 500 response received, then no more retry until next event occurred.
+ * 2. If the 503 response with Retry-After header received, then the query is retried until
+ * MAX_RETRY_COUNT.
+ * 3. If other response or exception is occurred, then the query is retried until
+ * MAX_RETRY_COUNT is reached using the ExponentialBackoff.
+ */
+ private void handleCmdRetryQueryEntitlement(int subId) {
+ logd("handleCmdRetryQueryEntitlement: " + subId);
+ try {
+ synchronized (mLock) {
+ mSatelliteEntitlementResultPerSub.put(subId, getSatelliteEntitlementApi(
+ subId).checkEntitlementStatus());
+ }
+ } catch (ServiceEntitlementException e) {
+ if (!isInternetConnected()) {
+ logd("retryQuery: Internet disconnected. reset the retry and after the "
+ + "internet is connected then the first query is triggered." + e);
+ stopExponentialBackoff(subId);
+ return;
+ }
+ if (shouldHandleErrorResponse(e, subId)) {
+ logd("retryQuery: handle response.");
+ stopExponentialBackoff(subId);
+ return;
+ }
+ mExponentialBackoffPerSub.get(subId).notifyFailed();
+ mRetryCountPerSub.put(subId,
+ mRetryCountPerSub.getOrDefault(subId, 0) + 1);
+ logd("handleCmdRetryQueryEntitlement:" + e + "[" + subId + "] cnt="
+ + mRetryCountPerSub.getOrDefault(subId, 0) + "] Retrying in "
+ + mExponentialBackoffPerSub.get(subId).getCurrentDelay() + " ms.");
+ }
+ }
+
+ /** Only handle '500' and '503 with retry-after header' error responses received.
+ * If the 500 response is received, no retry until the next trigger event occurs.
+ * If the 503 response with Retry-After header, retry is attempted according to the value in the
+ * Retry-After header up to MAX_RETRY_COUNT.
+ * In other cases, it performs an exponential backoff process. */
+ private boolean shouldHandleErrorResponse(ServiceEntitlementException e, int subId) {
+ int responseCode = e.getHttpStatus();
+ logd("shouldHandleErrorResponse: received the " + responseCode);
+ if (responseCode == HTTP_RESPONSE_503 && e.getRetryAfter() != null
+ && !e.getRetryAfter().isEmpty()) {
+ if (mRetryCountPerSub.getOrDefault(subId, 0) >= MAX_RETRY_COUNT) {
+ logd("The 503 retry after reaching the " + MAX_RETRY_COUNT
+ + "The retry will not be attempted until the next trigger event.");
+ queryCompleted(subId);
+ return true;
+ }
+ long retryAfterSeconds = parseSecondsFromRetryAfter(e.getRetryAfter());
+ if (retryAfterSeconds == -1) {
+ logd("Unable parsing the retry-after. try to exponential backoff.");
+ return false;
+ }
+ mRetryCountPerSub.put(subId, mRetryCountPerSub.getOrDefault(subId, 0) + 1);
+ logd("[" + subId + "] cnt=" + mRetryCountPerSub.getOrDefault(subId, 0)
+ + " Retrying in " + TimeUnit.SECONDS.toMillis(retryAfterSeconds) + " sec");
+ Message message = obtainMessage();
+ message.what = CMD_RETRY_QUERY_ENTITLEMENT;
+ message.arg1 = subId;
+ sendMessageDelayed(message, TimeUnit.SECONDS.toMillis(retryAfterSeconds));
+ return true;
+ } else if (responseCode == HTTP_RESPONSE_500) {
+ logd("The retry on the next trigger event.");
+ queryCompleted(subId);
+ return true;
+ }
+ return false;
+ }
+
+ /** Parse the HTTP-date or a number of seconds in the retry-after value. */
+ private long parseSecondsFromRetryAfter(String retryAfter) {
+ try {
+ return Long.parseLong(retryAfter);
+ } catch (NumberFormatException numberFormatException) {
+ }
+
+ try {
+ return SECONDS.between(
+ Instant.now(), RFC_1123_DATE_TIME.parse(retryAfter, Instant::from));
+ } catch (DateTimeParseException dateTimeParseException) {
+ }
+
+ return -1;
+ }
+
+ private void startExponentialBackoff(int subId) {
+ stopExponentialBackoff(subId);
+ mExponentialBackoffPerSub.put(subId,
+ new ExponentialBackoff(INITIAL_DELAY_MILLIS, MAX_DELAY_MILLIS,
+ MULTIPLIER, this.getLooper(), () -> {
+ synchronized (mLock) {
+ if (mSatelliteEntitlementResultPerSub.containsKey(subId)) {
+ logd("handleCmdStartQueryEntitlement: get the response "
+ + "successfully.");
+ mExponentialBackoffPerSub.get(subId).stop();
+ queryCompleted(subId);
+ return;
+ }
+
+ if (mRetryCountPerSub.getOrDefault(subId, 0) >= MAX_RETRY_COUNT) {
+ logd("The ExponentialBackoff is stopped after reaching the "
+ + MAX_RETRY_COUNT + ". The retry don't attempted until the"
+ + " refresh time expires.");
+ mExponentialBackoffPerSub.get(subId).stop();
+ queryCompleted(subId);
+ return;
+ }
+ if (!mSatelliteEntitlementResultPerSub.containsKey(subId)) {
+ handleCmdRetryQueryEntitlement(subId);
+ }
+ }
+ }));
+ mExponentialBackoffPerSub.get(subId).start();
+ mRetryCountPerSub.put(subId, mRetryCountPerSub.getOrDefault(subId, 0) + 1);
+ logd("start ExponentialBackoff [" + mRetryCountPerSub.getOrDefault(subId, 0)
+ + "] Retrying in " + mExponentialBackoffPerSub.get(subId).getCurrentDelay()
+ + " ms.");
+ }
+
+ /** If the Internet connection is lost during the ExponentialBackoff, stop the
+ * ExponentialBackoff and reset it. */
+ private void stopExponentialBackoff(int subId) {
+ if (isExponentialBackoffInProgress(subId)) {
+ logd("stopExponentialBackoff: reset ExponentialBackoff");
+ mExponentialBackoffPerSub.get(subId).stop();
+ mExponentialBackoffPerSub.remove(subId);
+ }
+ }
+
+ /**
+ * No more query retry, update the result. If there is no response from the server, then used
+ * the default value - 'satellite disabled' and empty 'PLMN allowed list'.
+ * And then it send a delayed message to trigger the query again after A refresh day has passed.
+ */
+ private void queryCompleted(int subId) {
+ if (!mSatelliteEntitlementResultPerSub.containsKey(subId)) {
+ logd("queryCompleted: create default SatelliteEntitlementResult");
+ mSatelliteEntitlementResultPerSub.put(subId,
+ SatelliteEntitlementResult.getDefaultResult());
+ }
+
+ saveLastQueryTime(subId);
+ Message message = obtainMessage();
+ message.what = CMD_START_QUERY_ENTITLEMENT;
+ message.arg1 = subId;
+ sendMessageDelayed(message, TimeUnit.DAYS.toMillis(
+ getSatelliteEntitlementStatusRefreshDays(subId)));
+ logd("queryCompleted: updateSatelliteEntitlementStatus");
+ updateSatelliteEntitlementStatus(subId,
+ mSatelliteEntitlementResultPerSub.get(subId).getEntitlementStatus()
+ == SATELLITE_ENTITLEMENT_STATUS_ENABLED,
+ mSatelliteEntitlementResultPerSub.get(subId).getAllowedPLMNList());
+ stopExponentialBackoff(subId);
+ mRetryCountPerSub.remove(subId);
+ }
+
+ /** Check whether there is a saved subId. Returns true if there is a saved subId,
+ * otherwise return false.*/
+ private boolean isExponentialBackoffInProgress(int subId) {
+ return mExponentialBackoffPerSub.containsKey(subId);
+ }
+
+ /**
+ * Check if the subId can query the entitlement server to get the satellite configuration.
+ */
+ private boolean shouldQueryEntitlement(int subId) {
+ if (!isSatelliteEntitlementSupported(subId)) {
+ logd("Doesn't support entitlement query for satellite.");
+ return false;
+ }
+
+ if (isExponentialBackoffInProgress(subId)) {
+ logd("In progress ExponentialBackoff.");
+ return false;
+ }
+
+ return shouldRefreshEntitlementStatus(subId);
+ }
+
+ /**
+ * Compare the last query time to the refresh time from the CarrierConfig to see if the device
+ * can query the entitlement server.
+ */
+ private boolean shouldRefreshEntitlementStatus(int subId) {
+ long lastQueryTimeMillis = getLastQueryTime(subId);
+ long refreshTimeMillis = TimeUnit.DAYS.toMillis(
+ getSatelliteEntitlementStatusRefreshDays(subId));
+ boolean isAvailable =
+ (System.currentTimeMillis() - lastQueryTimeMillis) > refreshTimeMillis;
+ if (!isAvailable) {
+ logd("query is already done. can query after " + Instant.ofEpochMilli(
+ refreshTimeMillis + lastQueryTimeMillis));
+ }
+ return isAvailable;
+ }
+
+ /**
+ * Get the SatelliteEntitlementApi.
+ *
+ * @param subId The subId of the subscription for creating SatelliteEntitlementApi
+ * @return A new SatelliteEntitlementApi object.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public SatelliteEntitlementApi getSatelliteEntitlementApi(int subId) {
+ return new SatelliteEntitlementApi(mContext, getConfigForSubId(subId), subId);
+ }
+
+ /** If there is a value stored in the cache, it is used. If there is no value stored in the
+ * cache, it is considered the first query. */
+ private long getLastQueryTime(int subId) {
+ return mLastQueryTimePerSub.getOrDefault(subId, 0L);
+ }
+
+ /** Return the satellite entitlement status refresh days from carrier config. */
+ private int getSatelliteEntitlementStatusRefreshDays(int subId) {
+ return getConfigForSubId(subId).getInt(
+ CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT,
+ DEFAULT_QUERY_REFRESH_DAYS);
+ }
+
+ /** Return the satellite entitlement supported bool from carrier config. */
+ private boolean isSatelliteEntitlementSupported(int subId) {
+ return getConfigForSubId(subId).getBoolean(
+ CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL);
+ }
+
+ @NonNull
+ private PersistableBundle getConfigForSubId(int subId) {
+ PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId,
+ CarrierConfigManager.ImsServiceEntitlement.KEY_ENTITLEMENT_SERVER_URL_STRING,
+ CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT,
+ CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL);
+ if (config == null || config.isEmpty()) {
+ config = CarrierConfigManager.getDefaultConfig();
+ }
+ return config;
+ }
+
+ private void saveLastQueryTime(int subId) {
+ long lastQueryTimeMillis = System.currentTimeMillis();
+ mLastQueryTimePerSub.put(subId, lastQueryTimeMillis);
+ }
+
+ /**
+ * Send to satelliteController for update the satellite service enabled or not and plmn Allowed
+ * list.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public void updateSatelliteEntitlementStatus(int subId, boolean enabled,
+ List<String> plmnAllowedList) {
+ SatelliteController.getInstance().updateSatelliteEntitlementStatus(subId, enabled,
+ plmnAllowedList, null);
+ }
+
+ private static void logd(String log) {
+ Rlog.d(TAG, log);
+ }
+
+ private static void loge(String log) {
+ Rlog.e(TAG, log);
+ }
+}
diff --git a/src/com/android/phone/satellite/entitlement/SatelliteEntitlementResponse.java b/src/com/android/phone/satellite/entitlement/SatelliteEntitlementResponse.java
new file mode 100644
index 0000000..1fe0ecf
--- /dev/null
+++ b/src/com/android/phone/satellite/entitlement/SatelliteEntitlementResponse.java
@@ -0,0 +1,151 @@
+/*
+ * 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.phone.satellite.entitlement;
+
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_DISABLED;
+
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.satellite.SatelliteNetworkInfo;
+import com.android.libraries.entitlement.ServiceEntitlement;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * This class parses whether the satellite service configuration.
+ * @hide
+ */
+public class SatelliteEntitlementResponse {
+ private static final String TAG = "SatelliteEntitlementResponse";
+
+ /** Overall status of the SatMode entitlement, stating if the satellite service can be offered
+ * on the device, and if it can be activated or not by the user. */
+ private static final String ENTITLEMENT_STATUS_KEY = "EntitlementStatus";
+ /** List of allowed PLMNs where the service can be used. */
+ private static final String PLMN_ALLOWED_KEY = "PLMNAllowed";
+ /** List of barred PLMNs where the service can’t be used. */
+ private static final String PLMN_BARRED_KEY = "PLMNBarred";
+ /** allowed PLMN-ID where the service can be used or is barred. */
+ private static final String PLMN_KEY = "PLMN";
+ /** The data plan is of the metered or un-metered type. This value is optional. */
+ private static final String DATA_PLAN_TYPE_KEY = "DataPlanType";
+
+ @SatelliteEntitlementResult.SatelliteEntitlementStatus private int mEntitlementStatus;
+
+ /**
+ * <p> Available options are :
+ * "PLMNAllowed":[{ "PLMN": "XXXXXX", “DataPlanType”: "unmetered"},
+ * {"PLMN": "XXXXXX", “DataPlanType”: "metered"},
+ * {"PLMN": "XXXXXX"}]
+ */
+ private List<SatelliteNetworkInfo> mPlmnAllowedList;
+ /**
+ * <p> Available option is :
+ * "PLMNBarred":[{"PLMN": "XXXXXX"}, {"PLMN”:"XXXXXX"}]
+ */
+ private List<String> mPlmnBarredList;
+
+ public SatelliteEntitlementResponse(String response) {
+ mEntitlementStatus = SATELLITE_ENTITLEMENT_STATUS_DISABLED;
+ mPlmnAllowedList = new ArrayList<>();
+ mPlmnBarredList = new ArrayList<>();
+ parsingResponse(response);
+ }
+
+ /**
+ * Get the entitlement status for the satellite service
+ * @return The satellite entitlement status
+ */
+ public int getEntitlementStatus() {
+ return mEntitlementStatus;
+ }
+
+ /**
+ * Get the PLMNAllowed from the response
+ * @return The PLMNs Allowed list. PLMN and Data Plan Type(optional).
+ */
+ public List<SatelliteNetworkInfo> getPlmnAllowed() {
+ return mPlmnAllowedList.stream().map((info) -> new SatelliteNetworkInfo(info.mPlmn,
+ info.mDataPlanType)).collect(Collectors.toList());
+ }
+
+ /**
+ * Get the PLMNBarredList from the response
+ * @return The PLMNs Barred List
+ */
+ @VisibleForTesting
+ public List<String> getPlmnBarredList() {
+ return mPlmnBarredList.stream().map(String::new).collect(Collectors.toList());
+ }
+
+ private void parsingResponse(String response) {
+ JSONObject jsonAuthResponse = null;
+ try {
+ jsonAuthResponse = new JSONObject(response);
+ if (!jsonAuthResponse.has(ServiceEntitlement.APP_SATELLITE_ENTITLEMENT)) {
+ loge("parsingResponse failed with no app");
+ return;
+ }
+ JSONObject jsonToken = jsonAuthResponse.getJSONObject(
+ ServiceEntitlement.APP_SATELLITE_ENTITLEMENT);
+ if (jsonToken.has(ENTITLEMENT_STATUS_KEY)) {
+ String entitlementStatus = jsonToken.getString(ENTITLEMENT_STATUS_KEY);
+ if (entitlementStatus == null) {
+ loge("parsingResponse EntitlementStatus is null");
+ return;
+ }
+ mEntitlementStatus = Integer.valueOf(entitlementStatus);
+ }
+ if (jsonToken.has(PLMN_ALLOWED_KEY)) {
+ JSONArray jsonArray = jsonToken.getJSONArray(PLMN_ALLOWED_KEY);
+ mPlmnAllowedList = new ArrayList<>();
+ for (int i = 0; i < jsonArray.length(); i++) {
+ String dataPlanType = jsonArray.getJSONObject(i).has(DATA_PLAN_TYPE_KEY)
+ ? jsonArray.getJSONObject(i).getString(DATA_PLAN_TYPE_KEY) : "";
+ mPlmnAllowedList.add(new SatelliteNetworkInfo(
+ jsonArray.getJSONObject(i).getString(PLMN_KEY), dataPlanType));
+ }
+ }
+ if (jsonToken.has(PLMN_BARRED_KEY)) {
+ mPlmnBarredList = new ArrayList<>();
+ JSONArray jsonArray = jsonToken.getJSONArray(PLMN_BARRED_KEY);
+ for (int i = 0; i < jsonArray.length(); i++) {
+ mPlmnBarredList.add(jsonArray.getJSONObject(i).getString(PLMN_KEY));
+ }
+ }
+ } catch (JSONException e) {
+ loge("parsingResponse: failed JSONException", e);
+ } catch (NumberFormatException e) {
+ loge("parsingResponse: failed NumberFormatException", e);
+ }
+ }
+
+ private static void loge(String log) {
+ Log.e(TAG, log);
+ }
+
+ private static void loge(String log, Exception e) {
+ Log.e(TAG, log, e);
+ }
+}
diff --git a/src/com/android/phone/satellite/entitlement/SatelliteEntitlementResult.java b/src/com/android/phone/satellite/entitlement/SatelliteEntitlementResult.java
new file mode 100644
index 0000000..3289232
--- /dev/null
+++ b/src/com/android/phone/satellite/entitlement/SatelliteEntitlementResult.java
@@ -0,0 +1,98 @@
+/*
+ * 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.phone.satellite.entitlement;
+
+import android.annotation.IntDef;
+
+import com.android.internal.telephony.satellite.SatelliteNetworkInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * This class stores the result of the satellite entitlement query and passes them to
+ * SatelliteEntitlementController.
+ */
+public class SatelliteEntitlementResult {
+ /** SatMode allowed, but not yet provisioned and activated on the network. */
+ public static final int SATELLITE_ENTITLEMENT_STATUS_DISABLED = 0;
+ /** SatMode service allowed, provisioned and activated on the network. User can access the
+ * satellite service. */
+ public static final int SATELLITE_ENTITLEMENT_STATUS_ENABLED = 1;
+ /** SatMode cannot be offered for network or device. */
+ public static final int SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE = 2;
+ /** SatMode is being provisioned on the network. Not yet activated. */
+ public static final int SATELLITE_ENTITLEMENT_STATUS_PROVISIONING = 3;
+
+ @IntDef(prefix = {"SATELLITE_ENTITLEMENT_STATUS_"}, value = {
+ SATELLITE_ENTITLEMENT_STATUS_DISABLED,
+ SATELLITE_ENTITLEMENT_STATUS_ENABLED,
+ SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE,
+ SATELLITE_ENTITLEMENT_STATUS_PROVISIONING
+ })
+ public @interface SatelliteEntitlementStatus {}
+
+ private @SatelliteEntitlementStatus int mEntitlementStatus;
+ /**
+ * An SatelliteNetworkInfo list consisting of the PLMN and the DataPlanType in the PLMNAlowed
+ * item of the satellite configuration received from the entitlement server.
+ */
+ private List<SatelliteNetworkInfo> mAllowedSatelliteNetworkInfoList;
+
+ /**
+ * Store the result of the satellite entitlement response.
+ *
+ * @param entitlementStatus The entitlement status.
+ * @param allowedSatelliteNetworkInfoList The allowedSatelliteNetworkInfoList
+ */
+ public SatelliteEntitlementResult(@SatelliteEntitlementStatus int entitlementStatus,
+ List<SatelliteNetworkInfo> allowedSatelliteNetworkInfoList) {
+ mEntitlementStatus = entitlementStatus;
+ mAllowedSatelliteNetworkInfoList = allowedSatelliteNetworkInfoList;
+ }
+
+ /**
+ * Get the entitlement status.
+ *
+ * @return The entitlement status.
+ */
+ public @SatelliteEntitlementStatus int getEntitlementStatus() {
+ return mEntitlementStatus;
+ }
+
+ /**
+ * Get the plmn allowed list
+ *
+ * @return The plmn allowed list.
+ */
+ public List<String> getAllowedPLMNList() {
+ return mAllowedSatelliteNetworkInfoList.stream().map(info -> info.mPlmn).collect(
+ Collectors.toList());
+ }
+
+ /**
+ * Get the default SatelliteEntitlementResult. EntitlementStatus set to
+ * `SATELLITE_ENTITLEMENT_STATUS_DISABLED` and SatelliteNetworkInfo list set to empty.
+ *
+ * @return If there is no response, return default SatelliteEntitlementResult
+ */
+ public static SatelliteEntitlementResult getDefaultResult() {
+ return new SatelliteEntitlementResult(SATELLITE_ENTITLEMENT_STATUS_DISABLED,
+ new ArrayList<>());
+ }
+}
diff --git a/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementApiTest.java b/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementApiTest.java
new file mode 100644
index 0000000..f096e0d
--- /dev/null
+++ b/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementApiTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.phone.satellite.entitlement;
+
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_DISABLED;
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_ENABLED;
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE;
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_PROVISIONING;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyVararg;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+
+import android.content.Context;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.libraries.entitlement.ServiceEntitlement;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class SatelliteEntitlementApiTest {
+ private static final String TEST_URL = "https://test.url";
+ private static final List<String> TEST_PLMN_ALLOWED = Arrays.asList("31026", "302820");
+ @Mock
+ Context mContext;
+ @Mock
+ ServiceEntitlement mServiceEntitlement;
+ @Mock
+ CarrierConfigManager mCarrierConfigManager;
+ @Mock
+ TelephonyManager mTelephonyManager;
+ private PersistableBundle mCarrierConfigBundle;
+ private SatelliteEntitlementApi mSatelliteEntitlementAPI;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(Context.CARRIER_CONFIG_SERVICE).when(mContext).getSystemServiceName(
+ CarrierConfigManager.class);
+ doReturn(mCarrierConfigManager).when(mContext).getSystemService(
+ Context.CARRIER_CONFIG_SERVICE);
+ mCarrierConfigBundle = new PersistableBundle();
+ doReturn(mCarrierConfigBundle)
+ .when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyVararg());
+ doReturn(Context.TELEPHONY_SERVICE).when(mContext).getSystemServiceName(
+ TelephonyManager.class);
+ doReturn(mTelephonyManager).when(mContext).getSystemService(Context.TELEPHONY_SERVICE);
+ doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
+
+ mSatelliteEntitlementAPI = new SatelliteEntitlementApi(mContext, mCarrierConfigBundle,
+ SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
+ }
+
+ @Test
+ public void testCheckEntitlementStatus() throws Exception {
+ mCarrierConfigBundle.putString(
+ CarrierConfigManager.ImsServiceEntitlement.KEY_ENTITLEMENT_SERVER_URL_STRING,
+ TEST_URL);
+ Field fieldServiceEntitlement = SatelliteEntitlementApi.class.getDeclaredField(
+ "mServiceEntitlement");
+ fieldServiceEntitlement.setAccessible(true);
+ fieldServiceEntitlement.set(mSatelliteEntitlementAPI, mServiceEntitlement);
+
+ // Get the EntitlementStatus to DISABLED
+ int expectedEntitlementStatus = SATELLITE_ENTITLEMENT_STATUS_DISABLED;
+ doReturn(getResponse(SATELLITE_ENTITLEMENT_STATUS_DISABLED))
+ .when(mServiceEntitlement)
+ .queryEntitlementStatus(eq(ServiceEntitlement.APP_SATELLITE_ENTITLEMENT), any());
+ SatelliteEntitlementResult result =
+ mSatelliteEntitlementAPI.checkEntitlementStatus();
+ assertNotNull(result);
+ assertEquals(expectedEntitlementStatus, result.getEntitlementStatus());
+ assertTrue(result.getAllowedPLMNList().size() == 0);
+
+ // Get the EntitlementStatus to ENABLED
+ expectedEntitlementStatus = SATELLITE_ENTITLEMENT_STATUS_ENABLED;
+ doReturn(getResponse(SATELLITE_ENTITLEMENT_STATUS_ENABLED))
+ .when(mServiceEntitlement)
+ .queryEntitlementStatus(eq(ServiceEntitlement.APP_SATELLITE_ENTITLEMENT), any());
+ result = mSatelliteEntitlementAPI.checkEntitlementStatus();
+ assertNotNull(result);
+ assertEquals(expectedEntitlementStatus, result.getEntitlementStatus());
+ assertEquals(TEST_PLMN_ALLOWED, result.getAllowedPLMNList());
+
+ // Get the EntitlementStatus to INCOMPATIBLE
+ expectedEntitlementStatus = SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE;
+ doReturn(getResponse(SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE))
+ .when(mServiceEntitlement)
+ .queryEntitlementStatus(eq(ServiceEntitlement.APP_SATELLITE_ENTITLEMENT), any());
+ result = mSatelliteEntitlementAPI.checkEntitlementStatus();
+ assertNotNull(result);
+ assertEquals(expectedEntitlementStatus, result.getEntitlementStatus());
+ assertTrue(result.getAllowedPLMNList().size() == 0);
+
+ // Get the EntitlementStatus to PROVISIONING
+ expectedEntitlementStatus = SATELLITE_ENTITLEMENT_STATUS_PROVISIONING;
+ doReturn(getResponse(SATELLITE_ENTITLEMENT_STATUS_PROVISIONING))
+ .when(mServiceEntitlement)
+ .queryEntitlementStatus(eq(ServiceEntitlement.APP_SATELLITE_ENTITLEMENT), any());
+ result = mSatelliteEntitlementAPI.checkEntitlementStatus();
+ assertNotNull(result);
+ assertEquals(expectedEntitlementStatus, result.getEntitlementStatus());
+ assertTrue(result.getAllowedPLMNList().size() == 0);
+ }
+
+ private String getResponse(int entitlementStatus) {
+ return "{\"VERS\":{\"version\":\"1\",\"validity\":\"172800\"},"
+ + "\"TOKEN\":{\"token\":\"ASH127AHHA88SF\"},\""
+ + ServiceEntitlement.APP_SATELLITE_ENTITLEMENT + "\":{"
+ + "\"EntitlementStatus\":\"" + entitlementStatus + "\""
+ + getPLMNListOrEmpty(entitlementStatus)
+ + "}}";
+ }
+
+ private String getPLMNListOrEmpty(int entitlementStatus) {
+ return entitlementStatus == SATELLITE_ENTITLEMENT_STATUS_ENABLED ? ","
+ + "\"PLMNAllowed\":[{\"PLMN\":\"31026\",\"DataPlanType\":\"unmetered\"},"
+ + "{\"PLMN\":\"302820\",\"DataPlanType\":\"metered\"}]" : "";
+ }
+}
diff --git a/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementControllerTest.java b/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementControllerTest.java
new file mode 100644
index 0000000..e208e6c
--- /dev/null
+++ b/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementControllerTest.java
@@ -0,0 +1,378 @@
+/*
+ * 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.phone.satellite.entitlement;
+
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_ENABLED;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anyVararg;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.wifi.WifiInfo;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.TelephonyManager;
+import android.testing.TestableLooper;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+import com.android.internal.telephony.ExponentialBackoff;
+import com.android.internal.telephony.satellite.SatelliteController;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidJUnit4.class)
+public class SatelliteEntitlementControllerTest extends TelephonyTestBase {
+ private static final String TAG = "SatelliteEntitlementControllerTest";
+ private static final int SUB_ID = 0;
+ private static final int SUB_ID_2 = 1;
+ private static final int[] ACTIVE_SUB_ID = {SUB_ID};
+ private static final int DEFAULT_QUERY_REFRESH_DAY = 30;
+ private static final List<String> PLMN_ALLOWED_LIST = Arrays.asList("31026", "302820");
+ @Mock
+ CarrierConfigManager mCarrierConfigManager;
+ @Mock
+ ConnectivityManager mConnectivityManager;
+ @Mock Network mNetwork;
+ @Mock TelephonyManager mTelephonyManager;
+ @Mock SubscriptionManagerService mMockSubscriptionManagerService;
+ @Mock SatelliteEntitlementApi mSatelliteEntitlementApi;
+ @Mock SatelliteEntitlementResult mSatelliteEntitlementResult;
+ @Mock SatelliteController mSatelliteController;
+ @Mock ExponentialBackoff mExponentialBackoff;
+ private PersistableBundle mCarrierConfigBundle;
+ private TestSatelliteEntitlementController mSatelliteEntitlementController;
+ private Handler mHandler;
+ private TestableLooper mTestableLooper;
+ private List<Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener>>
+ mCarrierConfigChangedListenerList = new ArrayList<>();
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+
+ replaceInstance(SubscriptionManagerService.class, "sInstance", null,
+ mMockSubscriptionManagerService);
+ replaceInstance(SatelliteController.class, "sInstance", null, mSatelliteController);
+
+ HandlerThread handlerThread = new HandlerThread("SatelliteEntitlementController");
+ handlerThread.start();
+ mHandler = new Handler(handlerThread.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ }
+ };
+ mTestableLooper = new TestableLooper(mHandler.getLooper());
+ doReturn(Context.TELEPHONY_SERVICE).when(mContext).getSystemServiceName(
+ TelephonyManager.class);
+ doReturn(mTelephonyManager).when(mContext).getSystemService(Context.TELEPHONY_SERVICE);
+ doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
+ doReturn(Context.CARRIER_CONFIG_SERVICE).when(mContext).getSystemServiceName(
+ CarrierConfigManager.class);
+ doReturn(mCarrierConfigManager).when(mContext).getSystemService(
+ Context.CARRIER_CONFIG_SERVICE);
+ doAnswer(invocation -> {
+ Executor executor = invocation.getArgument(0);
+ CarrierConfigManager.CarrierConfigChangeListener listener = invocation.getArgument(1);
+ mCarrierConfigChangedListenerList.add(new Pair<>(executor, listener));
+ return null;
+ }).when(mCarrierConfigManager).registerCarrierConfigChangeListener(
+ any(Executor.class),
+ any(CarrierConfigManager.CarrierConfigChangeListener.class));
+ mCarrierConfigBundle = new PersistableBundle();
+ mCarrierConfigBundle.putInt(
+ CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT,
+ DEFAULT_QUERY_REFRESH_DAY);
+ mCarrierConfigBundle.putBoolean(
+ CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
+ doReturn(mCarrierConfigBundle)
+ .when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyVararg());
+ doReturn(Context.CONNECTIVITY_SERVICE).when(mContext).getSystemServiceName(
+ ConnectivityManager.class);
+ doReturn(mConnectivityManager).when(mContext).getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ doReturn(mNetwork).when(mConnectivityManager).getActiveNetwork();
+ doReturn(ACTIVE_SUB_ID).when(mMockSubscriptionManagerService).getActiveSubIdList(true);
+ mSatelliteEntitlementController = new TestSatelliteEntitlementController(mContext,
+ mHandler.getLooper(), mSatelliteEntitlementApi);
+ mSatelliteEntitlementController = spy(mSatelliteEntitlementController);
+ doReturn(mSatelliteEntitlementResult).when(
+ mSatelliteEntitlementApi).checkEntitlementStatus();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ public void testIsQueryAvailable() throws Exception {
+ doReturn(ACTIVE_SUB_ID).when(mMockSubscriptionManagerService).getActiveSubIdList(true);
+
+ // Verify don't start the query when KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL is false.
+ mCarrierConfigBundle.putBoolean(
+ CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false);
+ mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
+
+ verify(mSatelliteEntitlementApi, never()).checkEntitlementStatus();
+ verify(mSatelliteController, never()).updateSatelliteEntitlementStatus(anyInt(),
+ anyBoolean(), anyList(), any());
+
+ mCarrierConfigBundle.putBoolean(
+ CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
+ // Verify don't start the query when ExponentialBackoff is in progressed.
+ replaceInstance(SatelliteEntitlementController.class, "mExponentialBackoffPerSub",
+ mSatelliteEntitlementController, Map.of(SUB_ID, mExponentialBackoff));
+ mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
+
+ verify(mSatelliteEntitlementApi, never()).checkEntitlementStatus();
+ verify(mSatelliteController, never()).updateSatelliteEntitlementStatus(anyInt(),
+ anyBoolean(), anyList(), any());
+
+ replaceInstance(SatelliteEntitlementController.class, "mExponentialBackoffPerSub",
+ mSatelliteEntitlementController, new HashMap<>());
+ // Verify don't start the query when Internet is disconnected.
+ doReturn(ACTIVE_SUB_ID).when(mMockSubscriptionManagerService).getActiveSubIdList(true);
+ setInternetConnected(false);
+ mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
+
+ verify(mSatelliteEntitlementApi, never()).checkEntitlementStatus();
+ verify(mSatelliteController, never()).updateSatelliteEntitlementStatus(anyInt(),
+ anyBoolean(), anyList(), any());
+
+ setInternetConnected(true);
+ // Verify don't start the query when last query refresh time is not expired.
+ setLastQueryTime(System.currentTimeMillis());
+ mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
+
+ verify(mSatelliteEntitlementApi, never()).checkEntitlementStatus();
+ verify(mSatelliteController, never()).updateSatelliteEntitlementStatus(anyInt(),
+ anyBoolean(), anyList(), any());
+
+ // Verify start the query when isQueryAvailable return true
+ setLastQueryTime(0L);
+ doReturn(mSatelliteEntitlementResult).when(
+ mSatelliteEntitlementApi).checkEntitlementStatus();
+ setSatelliteEntitlementResult(SATELLITE_ENTITLEMENT_STATUS_ENABLED, PLMN_ALLOWED_LIST);
+ mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
+
+ verify(mSatelliteEntitlementApi).checkEntitlementStatus();
+ verify(mSatelliteController).updateSatelliteEntitlementStatus(anyInt(),
+ anyBoolean(), anyList(), any());
+ }
+
+ @Test
+ public void testCheckSatelliteEntitlementStatus() throws Exception {
+ setIsQueryAvailableTrue();
+ // Verify don't call the checkSatelliteEntitlementStatus when getActiveSubIdList is empty.
+ doReturn(new int[]{}).when(mMockSubscriptionManagerService).getActiveSubIdList(true);
+ mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
+
+ verify(mSatelliteEntitlementApi, never()).checkEntitlementStatus();
+ // Verify don't call the updateSatelliteEntitlementStatus.
+ verify(mSatelliteController, never()).updateSatelliteEntitlementStatus(anyInt(),
+ anyBoolean(), anyList(), any());
+
+ // Verify call the checkSatelliteEntitlementStatus with invalid response.
+ setIsQueryAvailableTrue();
+ doReturn(mSatelliteEntitlementResult).when(
+ mSatelliteEntitlementApi).checkEntitlementStatus();
+ replaceInstance(SatelliteEntitlementController.class,
+ "mSatelliteEntitlementResultPerSub", mSatelliteEntitlementController,
+ new HashMap<>());
+ mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
+
+ verify(mSatelliteEntitlementApi).checkEntitlementStatus();
+ // Verify call the updateSatelliteEntitlementStatus with satellite service is disabled
+ // and empty PLMNAllowed
+ verify(mSatelliteController).updateSatelliteEntitlementStatus(eq(SUB_ID),
+ eq(false), eq(new ArrayList<>()), any());
+
+ // Verify call the checkSatelliteEntitlementStatus with the subscribed result.
+ clearInvocationsForMock();
+ setIsQueryAvailableTrue();
+ doReturn(mSatelliteEntitlementResult).when(
+ mSatelliteEntitlementApi).checkEntitlementStatus();
+ setSatelliteEntitlementResult(SATELLITE_ENTITLEMENT_STATUS_ENABLED, PLMN_ALLOWED_LIST);
+ mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
+
+ verify(mSatelliteEntitlementApi).checkEntitlementStatus();
+ // Verify call the updateSatelliteEntitlementStatus with satellite service is enable and
+ // availablePLMNAllowedList
+ verify(mSatelliteController).updateSatelliteEntitlementStatus(eq(SUB_ID), eq(true),
+ eq(PLMN_ALLOWED_LIST), any());
+
+ // Change subId and verify call the updateSatelliteEntitlementStatus with satellite
+ // service is enable and availablePLMNAllowedList
+ clearInvocationsForMock();
+ doReturn(new int[]{SUB_ID_2}).when(mMockSubscriptionManagerService).getActiveSubIdList(
+ true);
+ mSatelliteEntitlementController.handleCmdStartQueryEntitlement();
+
+ verify(mSatelliteEntitlementApi).checkEntitlementStatus();
+ verify(mSatelliteController).updateSatelliteEntitlementStatus(eq(SUB_ID_2), eq(true),
+ eq(PLMN_ALLOWED_LIST), any());
+ }
+
+ @Test
+ public void testCheckSatelliteEntitlementStatusWhenInternetConnected() throws Exception {
+ Field fieldNetworkCallback = SatelliteEntitlementController.class.getDeclaredField(
+ "mNetworkCallback");
+ fieldNetworkCallback.setAccessible(true);
+ ConnectivityManager.NetworkCallback networkCallback =
+ (ConnectivityManager.NetworkCallback) fieldNetworkCallback.get(
+ mSatelliteEntitlementController);
+ Network mockNetwork = mock(Network.class);
+
+ // Verify the called the checkSatelliteEntitlementStatus when Internet is connected.
+ setInternetConnected(true);
+ setLastQueryTime(0L);
+ setSatelliteEntitlementResult(SATELLITE_ENTITLEMENT_STATUS_ENABLED, PLMN_ALLOWED_LIST);
+
+ networkCallback.onAvailable(mockNetwork);
+ mTestableLooper.processAllMessages();
+
+ verify(mSatelliteEntitlementApi).checkEntitlementStatus();
+ // Verify call the updateSatelliteEntitlementStatus with satellite service is available.
+ verify(mSatelliteController).updateSatelliteEntitlementStatus(eq(SUB_ID), eq(true),
+ eq(PLMN_ALLOWED_LIST), any());
+ }
+
+ @Test
+ public void testCheckSatelliteEntitlementStatusWhenCarrierConfigChanged() throws Exception {
+ // Verify the called the checkSatelliteEntitlementStatus when CarrierConfigChanged
+ // occurred and Internet is connected.
+ setInternetConnected(true);
+ setLastQueryTime(0L);
+ setSatelliteEntitlementResult(SATELLITE_ENTITLEMENT_STATUS_ENABLED, PLMN_ALLOWED_LIST);
+ triggerCarrierConfigChanged();
+
+ verify(mSatelliteEntitlementApi).checkEntitlementStatus();
+ // Verify call the updateSatelliteEntitlementStatus with satellite service is available.
+ verify(mSatelliteController).updateSatelliteEntitlementStatus(eq(SUB_ID), eq(true),
+ eq(PLMN_ALLOWED_LIST), any());
+ }
+
+ private void triggerCarrierConfigChanged() {
+ for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+ : mCarrierConfigChangedListenerList) {
+ pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+ /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+ );
+ }
+ mTestableLooper.processAllMessages();
+ }
+
+ private void clearInvocationsForMock() {
+ clearInvocations(mSatelliteEntitlementApi);
+ clearInvocations(mSatelliteController);
+ }
+
+ private void setIsQueryAvailableTrue() throws Exception {
+ doReturn(ACTIVE_SUB_ID).when(mMockSubscriptionManagerService).getActiveSubIdList(true);
+ mCarrierConfigBundle.putBoolean(
+ CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
+ replaceInstance(SatelliteEntitlementController.class, "mRetryCountPerSub",
+ mSatelliteEntitlementController, new HashMap<>());
+ setInternetConnected(true);
+ setLastQueryTime(0L);
+ replaceInstance(SatelliteEntitlementController.class,
+ "mSatelliteEntitlementResultPerSub", mSatelliteEntitlementController,
+ new HashMap<>());
+ }
+
+ private void setInternetConnected(boolean connected) {
+ NetworkCapabilities networkCapabilities = new NetworkCapabilities.Builder().build();
+
+ if (connected) {
+ networkCapabilities = new NetworkCapabilities.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .setTransportInfo(mock(WifiInfo.class))
+ .build();
+ }
+ doReturn(networkCapabilities).when(mConnectivityManager).getNetworkCapabilities(mNetwork);
+ }
+
+ private void setSatelliteEntitlementResult(int entitlementStatus,
+ List<String> plmnAllowedList) {
+ doReturn(entitlementStatus).when(mSatelliteEntitlementResult).getEntitlementStatus();
+ doReturn(plmnAllowedList).when(mSatelliteEntitlementResult).getAllowedPLMNList();
+ }
+
+ private void setLastQueryTime(Long lastQueryTime) throws Exception {
+ Map<Integer, Long> lastQueryTimePerSub = new HashMap<>();
+ replaceInstance(SatelliteEntitlementController.class, "mLastQueryTimePerSub",
+ mSatelliteEntitlementController, lastQueryTimePerSub);
+ lastQueryTimePerSub.put(SUB_ID, lastQueryTime);
+ }
+
+ public static class TestSatelliteEntitlementController extends SatelliteEntitlementController {
+ private SatelliteEntitlementApi mInjectSatelliteEntitlementApi;
+
+ TestSatelliteEntitlementController(@NonNull Context context, @NonNull Looper looper,
+ SatelliteEntitlementApi api) {
+ super(context, looper);
+ mInjectSatelliteEntitlementApi = api;
+ }
+
+ @Override
+ public SatelliteEntitlementApi getSatelliteEntitlementApi(int subId) {
+ logd("getSatelliteEntitlementApi");
+ return mInjectSatelliteEntitlementApi;
+ }
+ }
+
+ private static void logd(String log) {
+ Log.d(TAG, log);
+ }
+}
diff --git a/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementResponseTest.java b/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementResponseTest.java
new file mode 100644
index 0000000..45e2a71
--- /dev/null
+++ b/tests/src/com/android/phone/satellite/entitlement/SatelliteEntitlementResponseTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.phone.satellite.entitlement;
+
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_DISABLED;
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_ENABLED;
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE;
+import static com.android.phone.satellite.entitlement.SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_PROVISIONING;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.telephony.satellite.SatelliteNetworkInfo;
+import com.android.libraries.entitlement.ServiceEntitlement;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class SatelliteEntitlementResponseTest {
+ private static final String TEST_OTHER_APP_ID = "ap201x";
+ private static final List<SatelliteNetworkInfo> TEST_PLMN_DATA_PLAN_TYPE_LIST = Arrays.asList(
+ new SatelliteNetworkInfo("31026", "unmetered"),
+ new SatelliteNetworkInfo("302820", "metered"));
+ private static final List<String> TEST_PLMN_BARRED_LIST = Arrays.asList("31017", "302020");
+ private static final String RESPONSE_WITHOUT_SATELLITE_APP_ID =
+ "{\"VERS\":{\"version\":\"1\",\"validity\":\"172800\"},"
+ + "\"TOKEN\":{\"token\":\"ASH127AHHA88SF\"},\""
+ + TEST_OTHER_APP_ID + "\":{"
+ + "\"EntitlementStatus\":\"" + SATELLITE_ENTITLEMENT_STATUS_ENABLED + "\"}}";
+ private static final String RESPONSE_WITHOUT_ENTITLEMENT_STATUS =
+ "{\"VERS\":{\"version\":\"1\",\"validity\":\"172800\"},"
+ + "\"TOKEN\":{\"token\":\"ASH127AHHA88SF\"},\""
+ + ServiceEntitlement.APP_SATELLITE_ENTITLEMENT + "\":{}}";
+
+ @Test
+ public void testGetSatelliteEntitlementResponse() throws Exception {
+ // Received the body with satellite service enabled.
+ SatelliteEntitlementResponse response = new SatelliteEntitlementResponse(
+ getResponse(SATELLITE_ENTITLEMENT_STATUS_ENABLED));
+ assertEquals(SATELLITE_ENTITLEMENT_STATUS_ENABLED, response.getEntitlementStatus());
+ assertEquals(TEST_PLMN_DATA_PLAN_TYPE_LIST.get(0).mPlmn,
+ response.getPlmnAllowed().get(0).mPlmn);
+ assertEquals(TEST_PLMN_DATA_PLAN_TYPE_LIST.get(0).mDataPlanType,
+ response.getPlmnAllowed().get(0).mDataPlanType);
+ assertEquals(TEST_PLMN_DATA_PLAN_TYPE_LIST.get(1).mPlmn,
+ response.getPlmnAllowed().get(1).mPlmn);
+ assertEquals(TEST_PLMN_DATA_PLAN_TYPE_LIST.get(1).mDataPlanType,
+ response.getPlmnAllowed().get(1).mDataPlanType);
+ assertEquals(TEST_PLMN_BARRED_LIST, response.getPlmnBarredList());
+
+ // Received the empty body.
+ response = new SatelliteEntitlementResponse("");
+ assertEquals(SATELLITE_ENTITLEMENT_STATUS_DISABLED, response.getEntitlementStatus());
+ assertTrue(response.getPlmnAllowed().size() == 0);
+ assertTrue(response.getPlmnBarredList().size() == 0);
+
+ // Received the body without satellite app id.
+ response = new SatelliteEntitlementResponse(RESPONSE_WITHOUT_SATELLITE_APP_ID);
+ assertEquals(SATELLITE_ENTITLEMENT_STATUS_DISABLED, response.getEntitlementStatus());
+ assertTrue(response.getPlmnAllowed().size() == 0);
+ assertTrue(response.getPlmnBarredList().size() == 0);
+
+ // Received the body without EntitlementStatus.
+ response = new SatelliteEntitlementResponse(RESPONSE_WITHOUT_ENTITLEMENT_STATUS);
+ assertEquals(SATELLITE_ENTITLEMENT_STATUS_DISABLED, response.getEntitlementStatus());
+ assertTrue(response.getPlmnAllowed().size() == 0);
+ assertTrue(response.getPlmnBarredList().size() == 0);
+
+ // Received the body with an entitlementStatus value of DISABLED.
+ response = new SatelliteEntitlementResponse(
+ getResponse(SATELLITE_ENTITLEMENT_STATUS_DISABLED));
+ assertEquals(SATELLITE_ENTITLEMENT_STATUS_DISABLED, response.getEntitlementStatus());
+ assertTrue(response.getPlmnAllowed().size() == 0);
+ assertTrue(response.getPlmnBarredList().size() == 0);
+
+ // Received the body with an entitlementStatus value of INCOMPATIBLE.
+ response = new SatelliteEntitlementResponse(
+ getResponse(SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE));
+ assertEquals(SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE, response.getEntitlementStatus());
+ assertTrue(response.getPlmnAllowed().size() == 0);
+ assertTrue(response.getPlmnBarredList().size() == 0);
+
+ // Received the body with an entitlementStatus value of PROVISIONING.
+ response = new SatelliteEntitlementResponse(
+ getResponse(SATELLITE_ENTITLEMENT_STATUS_PROVISIONING));
+ assertEquals(SATELLITE_ENTITLEMENT_STATUS_PROVISIONING, response.getEntitlementStatus());
+ assertTrue(response.getPlmnAllowed().size() == 0);
+ assertTrue(response.getPlmnBarredList().size() == 0);
+ }
+
+ private String getResponse(int entitlementStatus) {
+ return "{\"VERS\":{\"version\":\"1\",\"validity\":\"172800\"},"
+ + "\"TOKEN\":{\"token\":\"ASH127AHHA88SF\"},\""
+ + ServiceEntitlement.APP_SATELLITE_ENTITLEMENT + "\":{"
+ + "\"EntitlementStatus\":\"" + entitlementStatus + "\""
+ + getPLMNListOrEmpty(entitlementStatus)
+ + "}}";
+ }
+
+ private String getPLMNListOrEmpty(int entitlementStatus) {
+ return entitlementStatus == SATELLITE_ENTITLEMENT_STATUS_ENABLED ? ","
+ + "\"PLMNAllowed\":[{\"PLMN\":\"31026\",\"DataPlanType\":\"unmetered\"},"
+ + "{\"PLMN\":\"302820\",\"DataPlanType\":\"metered\"}],"
+ + "\"PLMNBarred\":[{\"PLMN\":\"31017\"},"
+ + "{\"PLMN\":\"302020\"}]" : "";
+ }
+}