• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
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