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