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