1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.phone.satellite.entitlement; 18 19 import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME; 20 import static java.time.temporal.ChronoUnit.SECONDS; 21 22 import android.annotation.NonNull; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.net.ConnectivityManager; 28 import android.net.Network; 29 import android.net.NetworkCapabilities; 30 import android.net.NetworkRequest; 31 import android.os.Handler; 32 import android.os.HandlerThread; 33 import android.os.Looper; 34 import android.os.Message; 35 import android.os.PersistableBundle; 36 import android.telephony.CarrierConfigManager; 37 import android.telephony.Rlog; 38 import android.telephony.SubscriptionManager; 39 40 import com.android.internal.annotations.GuardedBy; 41 import com.android.internal.annotations.VisibleForTesting; 42 import com.android.internal.telephony.ExponentialBackoff; 43 import com.android.internal.telephony.flags.FeatureFlags; 44 import com.android.internal.telephony.satellite.SatelliteConstants; 45 import com.android.internal.telephony.satellite.SatelliteController; 46 import com.android.internal.telephony.satellite.metrics.EntitlementMetricsStats; 47 import com.android.internal.telephony.subscription.SubscriptionManagerService; 48 import com.android.libraries.entitlement.ServiceEntitlementException; 49 50 import java.time.Instant; 51 import java.time.format.DateTimeParseException; 52 import java.util.ArrayList; 53 import java.util.HashMap; 54 import java.util.List; 55 import java.util.Map; 56 import java.util.concurrent.TimeUnit; 57 58 /** 59 * This class query the entitlement server to receive values for satellite services and passes the 60 * response to the {@link com.android.internal.telephony.satellite.SatelliteController}. 61 * @hide 62 */ 63 public class SatelliteEntitlementController extends Handler { 64 private static final String TAG = "SatelliteEntitlementController"; 65 @NonNull private static SatelliteEntitlementController sInstance; 66 /** Message code used in handleMessage() */ 67 private static final int CMD_START_QUERY_ENTITLEMENT = 1; 68 private static final int CMD_RETRY_QUERY_ENTITLEMENT = 2; 69 private static final int CMD_SIM_REFRESH = 3; 70 71 /** Retry on next trigger event. */ 72 private static final int HTTP_RESPONSE_500 = 500; 73 /** Retry after the time specified in the “Retry-After” header. After retry count doesn't exceed 74 * MAX_RETRY_COUNT. */ 75 private static final int HTTP_RESPONSE_503 = 503; 76 /** Default query refresh time is 1 month. */ 77 78 private static final int DEFAULT_QUERY_REFRESH_DAYS = 7; 79 private static final long INITIAL_DELAY_MILLIS = TimeUnit.MINUTES.toMillis(10); // 10 min 80 private static final long MAX_DELAY_MILLIS = TimeUnit.DAYS.toMillis(5); // 5 days 81 private static final int MULTIPLIER = 2; 82 private static final int MAX_RETRY_COUNT = 5; 83 @NonNull private final SubscriptionManagerService mSubscriptionManagerService; 84 @NonNull private final CarrierConfigManager mCarrierConfigManager; 85 @NonNull private final CarrierConfigManager.CarrierConfigChangeListener 86 mCarrierConfigChangeListener; 87 @NonNull private final ConnectivityManager mConnectivityManager; 88 @NonNull private final ConnectivityManager.NetworkCallback mNetworkCallback; 89 @NonNull private final BroadcastReceiver mReceiver; 90 @NonNull private final Context mContext; 91 private final Object mLock = new Object(); 92 /** Map key : subId, value : ExponentialBackoff. */ 93 @GuardedBy("mLock") 94 private Map<Integer, ExponentialBackoff> mExponentialBackoffPerSub = new HashMap<>(); 95 /** Map key : subId, value : SatelliteEntitlementResult. */ 96 @GuardedBy("mLock") 97 private Map<Integer, SatelliteEntitlementResult> mSatelliteEntitlementResultPerSub = 98 new HashMap<>(); 99 /** Map key : subId, value : the last query time to millis. */ 100 @GuardedBy("mLock") 101 private Map<Integer, Long> mLastQueryTimePerSub = new HashMap<>(); 102 /** Map key : subId, value : Count the number of retries caused by the 'ExponentialBackoff' and 103 * '503 error case with the Retry-After header'. */ 104 @GuardedBy("mLock") 105 private Map<Integer, Integer> mRetryCountPerSub = new HashMap<>(); 106 /** Map key : subId, value : Whether query is in progress. */ 107 @GuardedBy("mLock") 108 private Map<Integer, Boolean> mIsEntitlementInProgressPerSub = new HashMap<>(); 109 /** Map key : slotId, value : The last used subId. */ 110 @GuardedBy("mLock") 111 private Map<Integer, Integer> mSubIdPerSlot = new HashMap<>(); 112 @NonNull private final EntitlementMetricsStats mEntitlementMetricsStats; 113 114 /** 115 * Create the SatelliteEntitlementController singleton instance. 116 * @param context The Context to use to create the SatelliteEntitlementController. 117 * @param featureFlags The feature flag. 118 */ make(@onNull Context context, @NonNull FeatureFlags featureFlags)119 public static void make(@NonNull Context context, @NonNull FeatureFlags featureFlags) { 120 if (sInstance == null) { 121 HandlerThread handlerThread = new HandlerThread(TAG); 122 handlerThread.start(); 123 sInstance = 124 new SatelliteEntitlementController(context, handlerThread.getLooper()); 125 } 126 } 127 128 /** 129 * Create a SatelliteEntitlementController to request query to the entitlement server for 130 * satellite services and receive responses. 131 * 132 * @param context The Context for the SatelliteEntitlementController. 133 * @param looper The looper for the handler. It does not run on main thread. 134 */ 135 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) SatelliteEntitlementController(@onNull Context context, @NonNull Looper looper)136 public SatelliteEntitlementController(@NonNull Context context, @NonNull Looper looper) { 137 super(looper); 138 mContext = context; 139 mSubscriptionManagerService = SubscriptionManagerService.getInstance(); 140 mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class); 141 mCarrierConfigChangeListener = (slotIndex, subId, carrierId, specificCarrierId) -> 142 handleCarrierConfigChanged(slotIndex, subId, carrierId, specificCarrierId); 143 if (mCarrierConfigManager != null) { 144 mCarrierConfigManager.registerCarrierConfigChangeListener(this::post, 145 mCarrierConfigChangeListener); 146 } 147 mConnectivityManager = context.getSystemService(ConnectivityManager.class); 148 mNetworkCallback = new ConnectivityManager.NetworkCallback() { 149 @Override 150 public void onAvailable(Network network) { 151 handleInternetConnected(); 152 } 153 }; 154 NetworkRequest networkrequest = new NetworkRequest.Builder() 155 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build(); 156 mConnectivityManager.registerNetworkCallback(networkrequest, mNetworkCallback, this); 157 mReceiver = new SatelliteEntitlementControllerReceiver(); 158 IntentFilter intentFilter = new IntentFilter(); 159 intentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); 160 context.registerReceiver(mReceiver, intentFilter); 161 mEntitlementMetricsStats = EntitlementMetricsStats.getOrCreateInstance(); 162 SatelliteController.getInstance().registerIccRefresh(this, CMD_SIM_REFRESH); 163 } 164 165 @Override handleMessage(@onNull Message msg)166 public void handleMessage(@NonNull Message msg) { 167 switch (msg.what) { 168 case CMD_START_QUERY_ENTITLEMENT: 169 handleCmdStartQueryEntitlement(); 170 break; 171 case CMD_RETRY_QUERY_ENTITLEMENT: 172 handleCmdRetryQueryEntitlement(msg.arg1); 173 break; 174 case CMD_SIM_REFRESH: 175 handleSimRefresh(); 176 break; 177 default: 178 logd("do not used this message"); 179 } 180 } 181 handleCarrierConfigChanged(int slotIndex, int subId, int carrierId, int specificCarrierId)182 private void handleCarrierConfigChanged(int slotIndex, int subId, int carrierId, 183 int specificCarrierId) { 184 logd("handleCarrierConfigChanged(): slotIndex(" + slotIndex + "), subId(" 185 + subId + "), carrierId(" + carrierId + "), specificCarrierId(" 186 + specificCarrierId + ")"); 187 processSimChanged(slotIndex, subId); 188 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 189 return; 190 } 191 192 sendEmptyMessage(CMD_START_QUERY_ENTITLEMENT); 193 synchronized (mLock) { 194 mSubIdPerSlot.put(slotIndex, subId); 195 } 196 } 197 198 // When SIM is removed or changed, then reset the previous subId's retry related objects. processSimChanged(int slotIndex, int subId)199 private void processSimChanged(int slotIndex, int subId) { 200 int previousSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 201 synchronized (mLock) { 202 previousSubId = mSubIdPerSlot.getOrDefault(slotIndex, 203 SubscriptionManager.INVALID_SUBSCRIPTION_ID); 204 } 205 logd("processSimChanged prev subId:" + previousSubId); 206 if (previousSubId != subId) { 207 synchronized (mLock) { 208 mSubIdPerSlot.remove(slotIndex); 209 } 210 logd("processSimChanged resetEntitlementQueryPerSubId"); 211 resetEntitlementQueryPerSubId(previousSubId); 212 } 213 } 214 215 private class SatelliteEntitlementControllerReceiver extends BroadcastReceiver { 216 @Override onReceive(Context context, Intent intent)217 public void onReceive(Context context, Intent intent) { 218 String action = intent.getAction(); 219 if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) { 220 boolean airplaneMode = intent.getBooleanExtra("state", false); 221 handleAirplaneModeChange(airplaneMode); 222 } 223 } 224 } 225 handleAirplaneModeChange(boolean airplaneMode)226 private void handleAirplaneModeChange(boolean airplaneMode) { 227 if (!airplaneMode) { 228 resetEntitlementQueryCounts(Intent.ACTION_AIRPLANE_MODE_CHANGED); 229 } 230 } 231 handleSimRefresh()232 private void handleSimRefresh() { 233 resetEntitlementQueryCounts(cmdToString(CMD_SIM_REFRESH)); 234 sendMessageDelayed(obtainMessage(CMD_START_QUERY_ENTITLEMENT), 235 TimeUnit.SECONDS.toMillis(10)); 236 } 237 isInternetConnected()238 private boolean isInternetConnected() { 239 Network activeNetwork = mConnectivityManager.getActiveNetwork(); 240 NetworkCapabilities networkCapabilities = 241 mConnectivityManager.getNetworkCapabilities(activeNetwork); 242 // TODO b/319780796 Add checking if it is not a satellite. 243 return networkCapabilities != null 244 && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); 245 } 246 handleInternetConnected()247 private void handleInternetConnected() { 248 sendEmptyMessage(CMD_START_QUERY_ENTITLEMENT); 249 } 250 getServiceTypeForEntitlementMetrics(Map<String, List<Integer>> map)251 private int[] getServiceTypeForEntitlementMetrics(Map<String, List<Integer>> map) { 252 if (map == null || map.isEmpty()) { 253 return new int[]{}; 254 } 255 256 return map.entrySet().stream() 257 .findFirst() 258 .map(entry -> { 259 List<Integer> list = entry.getValue(); 260 if (list == null) { 261 return new int[]{}; // Return empty array if the list is null 262 } 263 return list.stream().mapToInt(Integer::intValue).toArray(); 264 }) 265 .orElse(new int[]{}); // Return empty array if no entry is found 266 } 267 getDataPolicyForEntitlementMetrics(Map<String, Integer> dataPolicyMap)268 private int getDataPolicyForEntitlementMetrics(Map<String, Integer> dataPolicyMap) { 269 if (dataPolicyMap != null && !dataPolicyMap.isEmpty()) { 270 return dataPolicyMap.values().stream().findFirst() 271 .orElse(-1); 272 } 273 return -1; 274 } 275 reportSuccessForEntitlement(int subId, SatelliteEntitlementResult entitlementResult)276 private void reportSuccessForEntitlement(int subId, SatelliteEntitlementResult 277 entitlementResult) { 278 // allowed service info entitlement status 279 boolean isAllowedServiceInfo = !entitlementResult 280 .getAvailableServiceTypeInfoForPlmnList().isEmpty(); 281 282 int[] serviceType = new int[0]; 283 int dataPolicy = 0; 284 if (isAllowedServiceInfo) { 285 serviceType = getServiceTypeForEntitlementMetrics( 286 entitlementResult.getAvailableServiceTypeInfoForPlmnList()); 287 dataPolicy = SatelliteController.getInstance().mapDataPolicyForMetrics( 288 getDataPolicyForEntitlementMetrics( 289 entitlementResult.getDataServicePolicyInfoForPlmnList())); 290 } 291 mEntitlementMetricsStats.reportSuccess(subId, 292 getEntitlementStatus(entitlementResult), true, isAllowedServiceInfo, 293 serviceType, dataPolicy); 294 } 295 296 /** 297 * Check if the device can request to entitlement server (if there is an internet connection and 298 * if the throttle time has passed since the last request), and then pass the response to 299 * SatelliteController if the response is received. 300 */ 301 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) handleCmdStartQueryEntitlement()302 public void handleCmdStartQueryEntitlement() { 303 for (int subId : mSubscriptionManagerService.getActiveSubIdList(true)) { 304 if (!shouldStartQueryEntitlement(subId)) { 305 continue; 306 } 307 308 // Check the satellite service query result from the entitlement server for the 309 // satellite service. 310 try { 311 synchronized (mLock) { 312 mIsEntitlementInProgressPerSub.put(subId, true); 313 SatelliteEntitlementResult entitlementResult = getSatelliteEntitlementApi( 314 subId).checkEntitlementStatus(); 315 mSatelliteEntitlementResultPerSub.put(subId, entitlementResult); 316 reportSuccessForEntitlement(subId, entitlementResult); 317 } 318 } catch (ServiceEntitlementException e) { 319 loge(e.toString()); 320 mEntitlementMetricsStats.reportError(subId, e.getErrorCode(), false); 321 if (!isInternetConnected()) { 322 logd("StartQuery: disconnected. " + e); 323 synchronized (mLock) { 324 mIsEntitlementInProgressPerSub.remove(subId); 325 } 326 return; 327 } 328 if (isPermanentError(e)) { 329 logd("StartQuery: shouldPermanentError."); 330 queryCompleted(subId); 331 continue; 332 } else if (isRetryAfterError(e)) { 333 long retryAfterSeconds = parseSecondsFromRetryAfter(e.getRetryAfter()); 334 logd("StartQuery: next retry will be in " + TimeUnit.SECONDS.toMillis( 335 retryAfterSeconds) + " sec"); 336 sendMessageDelayed(obtainMessage(CMD_RETRY_QUERY_ENTITLEMENT, subId, 0), 337 TimeUnit.SECONDS.toMillis(retryAfterSeconds)); 338 stopExponentialBackoff(subId); 339 continue; 340 } else { 341 startExponentialBackoff(subId); 342 continue; 343 } 344 } 345 queryCompleted(subId); 346 } 347 } 348 349 /** When airplane mode changes from on to off, reset the values required to start the first 350 * query. */ resetEntitlementQueryCounts(String event)351 private void resetEntitlementQueryCounts(String event) { 352 logd("resetEntitlementQueryCounts: " + event); 353 synchronized (mLock) { 354 mLastQueryTimePerSub = new HashMap<>(); 355 mExponentialBackoffPerSub = new HashMap<>(); 356 mRetryCountPerSub = new HashMap<>(); 357 mIsEntitlementInProgressPerSub = new HashMap<>(); 358 } 359 } 360 361 /** 362 * If the HTTP response does not receive a body containing the 200 ok with sat mode 363 * configuration, 364 * 365 * 1. If the 500 response received, then no more retry until next event occurred. 366 * 2. If the 503 response with Retry-After header received, then the query is retried until 367 * MAX_RETRY_COUNT. 368 * 3. If other response or exception is occurred, then the query is retried until 369 * MAX_RETRY_COUNT is reached using the ExponentialBackoff. 370 */ handleCmdRetryQueryEntitlement(int subId)371 private void handleCmdRetryQueryEntitlement(int subId) { 372 if (!shouldRetryQueryEntitlement(subId)) { 373 return; 374 } 375 try { 376 synchronized (mLock) { 377 int currentRetryCount = getRetryCount(subId); 378 mRetryCountPerSub.put(subId, currentRetryCount + 1); 379 logd("[" + subId + "] retry cnt:" + getRetryCount(subId)); 380 SatelliteEntitlementResult entitlementResult = getSatelliteEntitlementApi( 381 subId).checkEntitlementStatus(); 382 mSatelliteEntitlementResultPerSub.put(subId, entitlementResult); 383 reportSuccessForEntitlement(subId, entitlementResult); 384 385 } 386 } catch (ServiceEntitlementException e) { 387 loge(e.toString()); 388 mEntitlementMetricsStats.reportError(subId, e.getErrorCode(), true); 389 if (!isRetryAvailable(subId)) { 390 logd("retryQuery: unavailable."); 391 queryCompleted(subId); 392 return; 393 } 394 if (!isInternetConnected()) { 395 logd("retryQuery: Internet disconnected."); 396 stopExponentialBackoff(subId); 397 synchronized (mLock) { 398 mIsEntitlementInProgressPerSub.remove(subId); 399 } 400 return; 401 } 402 if (isPermanentError(e)) { 403 logd("retryQuery: shouldPermanentError."); 404 queryCompleted(subId); 405 return; 406 } else if (isRetryAfterError(e)) { 407 long retryAfterSeconds = parseSecondsFromRetryAfter(e.getRetryAfter()); 408 logd("retryQuery: next retry will be in " + TimeUnit.SECONDS.toMillis( 409 retryAfterSeconds) + " sec"); 410 sendMessageDelayed(obtainMessage(CMD_RETRY_QUERY_ENTITLEMENT, subId, 0), 411 TimeUnit.SECONDS.toMillis(retryAfterSeconds)); 412 stopExponentialBackoff(subId); 413 return; 414 } else { 415 ExponentialBackoff exponentialBackoff = null; 416 synchronized (mLock) { 417 exponentialBackoff = mExponentialBackoffPerSub.get(subId); 418 } 419 if (exponentialBackoff == null) { 420 startExponentialBackoff(subId); 421 } else { 422 exponentialBackoff.notifyFailed(); 423 logd("retryQuery: The next retry will be in " 424 + exponentialBackoff.getCurrentDelay() + " ms."); 425 } 426 return; 427 } 428 } 429 queryCompleted(subId); 430 } 431 432 // If the 500 response is received, no retry until the next trigger event occurs. isPermanentError(ServiceEntitlementException e)433 private boolean isPermanentError(ServiceEntitlementException e) { 434 return e.getHttpStatus() == HTTP_RESPONSE_500; 435 } 436 437 /** If the 503 response with Retry-After header, retry is attempted according to the value in 438 * the Retry-After header up to MAX_RETRY_COUNT. */ isRetryAfterError(ServiceEntitlementException e)439 private boolean isRetryAfterError(ServiceEntitlementException e) { 440 int responseCode = e.getHttpStatus(); 441 logd("shouldRetryAfterError: received the " + responseCode); 442 if (responseCode == HTTP_RESPONSE_503 && e.getRetryAfter() != null 443 && !e.getRetryAfter().isEmpty()) { 444 long retryAfterSeconds = parseSecondsFromRetryAfter(e.getRetryAfter()); 445 if (retryAfterSeconds == -1) { 446 logd("Unable parsing the retry-after. try to exponential backoff."); 447 return false; 448 } 449 return true; 450 } 451 return false; 452 } 453 454 /** Parse the HTTP-date or a number of seconds in the retry-after value. */ parseSecondsFromRetryAfter(String retryAfter)455 private long parseSecondsFromRetryAfter(String retryAfter) { 456 try { 457 return Long.parseLong(retryAfter); 458 } catch (NumberFormatException numberFormatException) { 459 } 460 461 try { 462 return SECONDS.between( 463 Instant.now(), RFC_1123_DATE_TIME.parse(retryAfter, Instant::from)); 464 } catch (DateTimeParseException dateTimeParseException) { 465 } 466 467 return -1; 468 } 469 startExponentialBackoff(int subId)470 private void startExponentialBackoff(int subId) { 471 ExponentialBackoff exponentialBackoff = null; 472 stopExponentialBackoff(subId); 473 synchronized (mLock) { 474 mExponentialBackoffPerSub.put(subId, 475 new ExponentialBackoff(INITIAL_DELAY_MILLIS, MAX_DELAY_MILLIS, 476 MULTIPLIER, this.getLooper(), () -> { 477 synchronized (mLock) { 478 sendMessage(obtainMessage(CMD_RETRY_QUERY_ENTITLEMENT, subId, 0)); 479 } 480 })); 481 exponentialBackoff = mExponentialBackoffPerSub.get(subId); 482 } 483 if (exponentialBackoff != null) { 484 exponentialBackoff.start(); 485 logd("start ExponentialBackoff, cnt: " + getRetryCount(subId) + ". Retrying in " 486 + exponentialBackoff.getCurrentDelay() + " ms."); 487 } 488 } 489 490 /** If the Internet connection is lost during the ExponentialBackoff, stop the 491 * ExponentialBackoff and reset it. */ stopExponentialBackoff(int subId)492 private void stopExponentialBackoff(int subId) { 493 synchronized (mLock) { 494 if (mExponentialBackoffPerSub.get(subId) != null) { 495 logd("stopExponentialBackoff: reset ExponentialBackoff"); 496 mExponentialBackoffPerSub.get(subId).stop(); 497 mExponentialBackoffPerSub.remove(subId); 498 } 499 } 500 } 501 502 /** 503 * No more query retry, update the result. If there is no response from the server, then used 504 * the default value - 'satellite disabled' and empty 'PLMN allowed list'. 505 * And then it send a delayed message to trigger the query again after A refresh day has passed. 506 */ queryCompleted(int subId)507 private void queryCompleted(int subId) { 508 SatelliteEntitlementResult entitlementResult; 509 synchronized (mLock) { 510 if (!mSatelliteEntitlementResultPerSub.containsKey(subId)) { 511 logd("queryCompleted: create default SatelliteEntitlementResult"); 512 mSatelliteEntitlementResultPerSub.put(subId, 513 SatelliteEntitlementResult.getDefaultResult()); 514 } 515 entitlementResult = mSatelliteEntitlementResultPerSub.get(subId); 516 stopExponentialBackoff(subId); 517 mIsEntitlementInProgressPerSub.remove(subId); 518 logd("reset retry count for refresh query"); 519 mRetryCountPerSub.remove(subId); 520 } 521 522 saveLastQueryTime(subId); 523 Message message = obtainMessage(); 524 message.what = CMD_START_QUERY_ENTITLEMENT; 525 message.arg1 = subId; 526 sendMessageDelayed(message, TimeUnit.DAYS.toMillis( 527 getSatelliteEntitlementStatusRefreshDays(subId))); 528 logd("queryCompleted: updateSatelliteEntitlementStatus"); 529 updateSatelliteEntitlementStatus(subId, entitlementResult.getEntitlementStatus() == 530 SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_ENABLED, 531 entitlementResult.getAllowedPLMNList(), entitlementResult.getBarredPLMNList(), 532 entitlementResult.getDataPlanInfoForPlmnList(), 533 entitlementResult.getAvailableServiceTypeInfoForPlmnList(), 534 entitlementResult.getDataServicePolicyInfoForPlmnList(), 535 entitlementResult.getVoiceServicePolicyInfoForPlmnList()); 536 } 537 shouldStartQueryEntitlement(int subId)538 private boolean shouldStartQueryEntitlement(int subId) { 539 logd("shouldStartQueryEntitlement " + subId); 540 if (!shouldRetryQueryEntitlement(subId)) { 541 return false; 542 } 543 544 synchronized (mLock) { 545 if (mIsEntitlementInProgressPerSub.getOrDefault(subId, false)) { 546 logd("In progress retry"); 547 return false; 548 } 549 } 550 return true; 551 } 552 shouldRetryQueryEntitlement(int subId)553 private boolean shouldRetryQueryEntitlement(int subId) { 554 if (!isSatelliteEntitlementSupported(subId)) { 555 logd("Doesn't support entitlement query for satellite."); 556 resetSatelliteEntitlementRestrictedReason(subId); 557 return false; 558 } 559 560 if (!isInternetConnected()) { 561 stopExponentialBackoff(subId); 562 synchronized (mLock) { 563 mIsEntitlementInProgressPerSub.remove(subId); 564 } 565 logd("Internet disconnected"); 566 return false; 567 } 568 569 if (!shouldRefreshEntitlementStatus(subId)) { 570 return false; 571 } 572 573 return isRetryAvailable(subId); 574 } 575 576 // update for removing the satellite entitlement restricted reason resetSatelliteEntitlementRestrictedReason(int subId)577 private void resetSatelliteEntitlementRestrictedReason(int subId) { 578 SatelliteEntitlementResult previousResult; 579 SatelliteEntitlementResult enabledResult = new SatelliteEntitlementResult( 580 SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_ENABLED, 581 new ArrayList<>(), new ArrayList<>()); 582 synchronized (mLock) { 583 previousResult = mSatelliteEntitlementResultPerSub.get(subId); 584 } 585 if (previousResult != null && previousResult.getEntitlementStatus() 586 != SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_ENABLED) { 587 logd("set enabled status for removing satellite entitlement restricted reason"); 588 synchronized (mLock) { 589 mSatelliteEntitlementResultPerSub.put(subId, enabledResult); 590 } 591 updateSatelliteEntitlementStatus(subId, true, enabledResult.getAllowedPLMNList(), 592 enabledResult.getBarredPLMNList(), enabledResult.getDataPlanInfoForPlmnList(), 593 enabledResult.getAvailableServiceTypeInfoForPlmnList(), 594 enabledResult.getDataServicePolicyInfoForPlmnList(), 595 enabledResult.getVoiceServicePolicyInfoForPlmnList()); 596 } 597 resetEntitlementQueryPerSubId(subId); 598 } 599 resetEntitlementQueryPerSubId(int subId)600 private void resetEntitlementQueryPerSubId(int subId) { 601 logd("resetEntitlementQueryPerSubId: " + subId); 602 stopExponentialBackoff(subId); 603 synchronized (mLock) { 604 mLastQueryTimePerSub.remove(subId); 605 mRetryCountPerSub.remove(subId); 606 mIsEntitlementInProgressPerSub.remove(subId); 607 } 608 removeMessages(CMD_RETRY_QUERY_ENTITLEMENT, 609 obtainMessage(CMD_RETRY_QUERY_ENTITLEMENT, subId, 0)); 610 } 611 612 /** 613 * Compare the last query time to the refresh time from the CarrierConfig to see if the device 614 * can query the entitlement server. 615 */ shouldRefreshEntitlementStatus(int subId)616 private boolean shouldRefreshEntitlementStatus(int subId) { 617 long lastQueryTimeMillis = getLastQueryTime(subId); 618 long refreshTimeMillis = TimeUnit.DAYS.toMillis( 619 getSatelliteEntitlementStatusRefreshDays(subId)); 620 boolean isAvailable = 621 (System.currentTimeMillis() - lastQueryTimeMillis) > refreshTimeMillis; 622 if (!isAvailable) { 623 logd("query is already done. can query after " + Instant.ofEpochMilli( 624 refreshTimeMillis + lastQueryTimeMillis)); 625 } 626 return isAvailable; 627 } 628 629 /** 630 * Get the SatelliteEntitlementApi. 631 * 632 * @param subId The subId of the subscription for creating SatelliteEntitlementApi 633 * @return A new SatelliteEntitlementApi object. 634 */ 635 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) getSatelliteEntitlementApi(int subId)636 public SatelliteEntitlementApi getSatelliteEntitlementApi(int subId) { 637 return new SatelliteEntitlementApi(mContext, getConfigForSubId(subId), subId); 638 } 639 640 /** If there is a value stored in the cache, it is used. If there is no value stored in the 641 * cache, it is considered the first query. */ getLastQueryTime(int subId)642 private long getLastQueryTime(int subId) { 643 synchronized (mLock) { 644 return mLastQueryTimePerSub.getOrDefault(subId, 0L); 645 } 646 } 647 648 /** Return the satellite entitlement status refresh days from carrier config. */ getSatelliteEntitlementStatusRefreshDays(int subId)649 private int getSatelliteEntitlementStatusRefreshDays(int subId) { 650 return getConfigForSubId(subId).getInt( 651 CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT, 652 DEFAULT_QUERY_REFRESH_DAYS); 653 } 654 isRetryAvailable(int subId)655 private boolean isRetryAvailable(int subId) { 656 if (getRetryCount(subId) >= MAX_RETRY_COUNT) { 657 logd("The retry will not be attempted until the next trigger event."); 658 return false; 659 } 660 return true; 661 } 662 663 /** Return the satellite entitlement supported bool from carrier config. */ isSatelliteEntitlementSupported(int subId)664 private boolean isSatelliteEntitlementSupported(int subId) { 665 return getConfigForSubId(subId).getBoolean( 666 CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL); 667 } 668 669 @NonNull getConfigForSubId(int subId)670 private PersistableBundle getConfigForSubId(int subId) { 671 PersistableBundle config = null; 672 if (mCarrierConfigManager != null) { 673 config = mCarrierConfigManager.getConfigForSubId(subId, 674 CarrierConfigManager.ImsServiceEntitlement.KEY_ENTITLEMENT_SERVER_URL_STRING, 675 CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT, 676 CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, 677 CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING); 678 } 679 if (config == null || config.isEmpty()) { 680 config = CarrierConfigManager.getDefaultConfig(); 681 } 682 return config; 683 } 684 saveLastQueryTime(int subId)685 private void saveLastQueryTime(int subId) { 686 long lastQueryTimeMillis = System.currentTimeMillis(); 687 synchronized (mLock) { 688 mLastQueryTimePerSub.put(subId, lastQueryTimeMillis); 689 } 690 } 691 getRetryCount(int subId)692 private int getRetryCount(int subId) { 693 synchronized (mLock) { 694 return mRetryCountPerSub.getOrDefault(subId, 0); 695 } 696 } 697 698 /** 699 * Send to satelliteController for update the satellite service enabled or not and plmn Allowed 700 * list. 701 */ 702 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) updateSatelliteEntitlementStatus(int subId, boolean enabled, List<String> plmnAllowedList, List<String> plmnBarredList, Map<String,Integer> plmnDataPlanMap, Map<String, List<Integer>>plmnAllowedServicesMap, Map<String,Integer>plmnDataServicePolicyMap, Map<String, Integer>plmnVoiceServicePolicyMap)703 public void updateSatelliteEntitlementStatus(int subId, boolean enabled, 704 List<String> plmnAllowedList, List<String> plmnBarredList, 705 Map<String,Integer> plmnDataPlanMap, 706 Map<String, List<Integer>>plmnAllowedServicesMap, 707 Map<String,Integer>plmnDataServicePolicyMap, 708 Map<String, Integer>plmnVoiceServicePolicyMap) { 709 SatelliteController.getInstance().onSatelliteEntitlementStatusUpdated(subId, enabled, 710 plmnAllowedList, plmnBarredList, plmnDataPlanMap, plmnAllowedServicesMap, 711 plmnDataServicePolicyMap, plmnVoiceServicePolicyMap, null); 712 } 713 getEntitlementStatus( SatelliteEntitlementResult entitlementResult)714 private @SatelliteConstants.SatelliteEntitlementStatus int getEntitlementStatus( 715 SatelliteEntitlementResult entitlementResult) { 716 switch (entitlementResult.getEntitlementStatus()) { 717 case SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_DISABLED: 718 return SatelliteConstants.SATELLITE_ENTITLEMENT_STATUS_DISABLED; 719 case SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_ENABLED: 720 return SatelliteConstants.SATELLITE_ENTITLEMENT_STATUS_ENABLED; 721 case SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE: 722 return SatelliteConstants.SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE; 723 case SatelliteEntitlementResult.SATELLITE_ENTITLEMENT_STATUS_PROVISIONING: 724 return SatelliteConstants.SATELLITE_ENTITLEMENT_STATUS_PROVISIONING; 725 default: 726 return SatelliteConstants.SATELLITE_ENTITLEMENT_STATUS_UNKNOWN; 727 } 728 } 729 cmdToString(int cmd)730 private static String cmdToString(int cmd) { 731 switch (cmd) { 732 case CMD_SIM_REFRESH: 733 return "SIM_REFRESH"; 734 default: 735 return "UNKNOWN(" + cmd + ")"; 736 } 737 } 738 logd(String log)739 private static void logd(String log) { 740 Rlog.d(TAG, log); 741 } 742 loge(String log)743 private static void loge(String log) { 744 Rlog.e(TAG, log); 745 } 746 } 747