• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.imsserviceentitlement;
18 
19 import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME;
20 import static java.time.temporal.ChronoUnit.SECONDS;
21 
22 import android.content.Context;
23 import android.text.TextUtils;
24 import android.util.Log;
25 
26 import androidx.annotation.Nullable;
27 import androidx.annotation.VisibleForTesting;
28 
29 import com.android.imsserviceentitlement.debug.DebugUtils;
30 import com.android.imsserviceentitlement.entitlement.EntitlementConfiguration;
31 import com.android.imsserviceentitlement.entitlement.EntitlementConfiguration.ClientBehavior;
32 import com.android.imsserviceentitlement.entitlement.EntitlementResult;
33 import com.android.imsserviceentitlement.fcm.FcmTokenStore;
34 import com.android.imsserviceentitlement.fcm.FcmUtils;
35 import com.android.imsserviceentitlement.ts43.Ts43Constants.ResponseXmlAttributes;
36 import com.android.imsserviceentitlement.ts43.Ts43SmsOverIpStatus;
37 import com.android.imsserviceentitlement.ts43.Ts43VolteStatus;
38 import com.android.imsserviceentitlement.ts43.Ts43VonrStatus;
39 import com.android.imsserviceentitlement.ts43.Ts43VowifiStatus;
40 import com.android.imsserviceentitlement.utils.TelephonyUtils;
41 import com.android.imsserviceentitlement.utils.XmlDoc;
42 import com.android.libraries.entitlement.CarrierConfig;
43 import com.android.libraries.entitlement.ServiceEntitlement;
44 import com.android.libraries.entitlement.ServiceEntitlementException;
45 import com.android.libraries.entitlement.ServiceEntitlementRequest;
46 
47 import com.google.common.collect.ImmutableList;
48 import com.google.common.net.HttpHeaders;
49 
50 import java.time.Clock;
51 import java.time.Instant;
52 import java.time.format.DateTimeParseException;
53 
54 /** Implementation of the entitlement API. */
55 public class ImsEntitlementApi {
56     private static final String TAG = "IMSSE-ImsEntitlementApi";
57 
58     private static final int RESPONSE_RETRY_AFTER = 503;
59     private static final int RESPONSE_TOKEN_EXPIRED = 511;
60 
61     private static final int AUTHENTICATION_RETRIES = 1;
62 
63     private final Context mContext;
64     private final int mSubId;
65     private final ServiceEntitlement mServiceEntitlement;
66     private final EntitlementConfiguration mLastEntitlementConfiguration;
67 
68     private int mRetryFullAuthenticationCount = AUTHENTICATION_RETRIES;
69     private boolean mNeedsImsProvisioning;
70 
71     @VisibleForTesting
72     static Clock sClock = Clock.systemUTC();
73 
ImsEntitlementApi(Context context, int subId)74     public ImsEntitlementApi(Context context, int subId) {
75         this.mContext = context;
76         this.mSubId = subId;
77         CarrierConfig carrierConfig = getCarrierConfig(context);
78         this.mNeedsImsProvisioning = TelephonyUtils.isImsProvisioningRequired(context, subId);
79         this.mServiceEntitlement =
80                 new ServiceEntitlement(
81                         context,
82                         carrierConfig,
83                         subId,
84                         /* saveHttpHistory = */ false,
85                         DebugUtils.getBypassEapAkaResponse());
86         this.mLastEntitlementConfiguration = new EntitlementConfiguration(context, subId);
87     }
88 
89     @VisibleForTesting
ImsEntitlementApi( Context context, int subId, boolean needsImsProvisioning, ServiceEntitlement serviceEntitlement, EntitlementConfiguration lastEntitlementConfiguration)90     ImsEntitlementApi(
91             Context context,
92             int subId,
93             boolean needsImsProvisioning,
94             ServiceEntitlement serviceEntitlement,
95             EntitlementConfiguration lastEntitlementConfiguration) {
96         this.mContext = context;
97         this.mSubId = subId;
98         this.mNeedsImsProvisioning = needsImsProvisioning;
99         this.mServiceEntitlement = serviceEntitlement;
100         this.mLastEntitlementConfiguration = lastEntitlementConfiguration;
101     }
102 
103     /**
104      * Returns WFC entitlement check result from carrier API (over network), or {@code null} on
105      * unrecoverable network issue or malformed server response. This is blocking call so should
106      * not be called on main thread.
107      */
108     @Nullable
checkEntitlementStatus()109     public EntitlementResult checkEntitlementStatus() {
110         Log.d(TAG, "checkEntitlementStatus subId=" + mSubId);
111         ServiceEntitlementRequest.Builder requestBuilder = ServiceEntitlementRequest.builder();
112         mLastEntitlementConfiguration.getToken().ifPresent(
113                 token -> requestBuilder.setAuthenticationToken(token));
114         FcmUtils.fetchFcmToken(mContext, mSubId);
115         requestBuilder.setNotificationToken(FcmTokenStore.getToken(mContext, mSubId));
116         int entitlementVersion = TelephonyUtils.getEntitlementVersion(mContext, mSubId);
117         requestBuilder.setEntitlementVersion(entitlementVersion + ".0");
118         requestBuilder.setAcceptContentType(ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_XML);
119         if (mNeedsImsProvisioning) {
120             requestBuilder.setConfigurationVersion(
121                     Integer.parseInt(mLastEntitlementConfiguration.getVersion()));
122         }
123         ServiceEntitlementRequest request = requestBuilder.build();
124 
125         XmlDoc entitlementXmlDoc = null;
126 
127         try {
128             String rawXml = mServiceEntitlement.queryEntitlementStatus(
129                     mNeedsImsProvisioning
130                             ? ImmutableList.of(
131                             ServiceEntitlement.APP_VOWIFI,
132                             ServiceEntitlement.APP_VOLTE,
133                             ServiceEntitlement.APP_SMSOIP)
134                             : ImmutableList.of(ServiceEntitlement.APP_VOWIFI),
135                     request);
136             entitlementXmlDoc = new XmlDoc(rawXml);
137             mLastEntitlementConfiguration.update(entitlementVersion, rawXml);
138             // Reset the retry count if no exception from queryEntitlementStatus()
139             mRetryFullAuthenticationCount = AUTHENTICATION_RETRIES;
140         } catch (ServiceEntitlementException e) {
141             if (e.getErrorCode() == ServiceEntitlementException.ERROR_HTTP_STATUS_NOT_SUCCESS) {
142                 if (e.getHttpStatus() == RESPONSE_TOKEN_EXPIRED) {
143                     if (mRetryFullAuthenticationCount <= 0) {
144                         Log.d(TAG, "Ran out of the retry count, stop query status.");
145                         return null;
146                     }
147                     Log.d(TAG, "Server asking for full authentication, retry the query.");
148                     // Clean up the cached data and perform full authentication next query.
149                     mLastEntitlementConfiguration.reset();
150                     mRetryFullAuthenticationCount--;
151                     return checkEntitlementStatus();
152                 } else if (e.getHttpStatus() == RESPONSE_RETRY_AFTER && !TextUtils.isEmpty(
153                         e.getRetryAfter())) {
154                     // For handling the case of HTTP_UNAVAILABLE(503), client would perform the
155                     // retry for the delay of Retry-After.
156                     Log.d(TAG, "Server asking for retry. retryAfter = " + e.getRetryAfter());
157                     boolean isDefaultActive = TelephonyUtils.getDefaultStatus(mContext, mSubId);
158                     return EntitlementResult.builder(isDefaultActive)
159                             .setRetryAfterSeconds(parseDelaySecondsByRetryAfter(e.getRetryAfter()))
160                             .build();
161                 }
162             }
163             Log.e(TAG, "queryEntitlementStatus failed", e);
164         }
165         return entitlementXmlDoc == null ? null : toEntitlementResult(entitlementXmlDoc);
166     }
167 
168     /**
169      * Parses the value of {@link HttpHeaders#RETRY_AFTER}. The possible formats could be a numeric
170      * value in second, or a HTTP-date in RFC-1123 date-time format.
171      */
parseDelaySecondsByRetryAfter(String retryAfter)172     private long parseDelaySecondsByRetryAfter(String retryAfter) {
173         try {
174             return Long.parseLong(retryAfter);
175         } catch (NumberFormatException numberFormatException) {
176         }
177 
178         try {
179             return SECONDS.between(
180                     Instant.now(sClock), RFC_1123_DATE_TIME.parse(retryAfter, Instant::from));
181         } catch (DateTimeParseException dateTimeParseException) {
182         }
183 
184         Log.w(TAG, "Unable to parse retry-after: " + retryAfter + ", ignore it.");
185         return -1;
186     }
187 
toEntitlementResult(XmlDoc doc)188     private EntitlementResult toEntitlementResult(XmlDoc doc) {
189         boolean isDefaultActive = TelephonyUtils.getDefaultStatus(mContext, mSubId);
190         EntitlementResult.Builder builder = EntitlementResult.builder(isDefaultActive);
191         ClientBehavior clientBehavior = mLastEntitlementConfiguration.entitlementValidation();
192 
193         if (mNeedsImsProvisioning && isResetToDefault(clientBehavior)) {
194             // keep the entitlement result in default value and reset the configs.
195             mLastEntitlementConfiguration.reset(clientBehavior);
196         } else {
197             builder.setVowifiStatus(Ts43VowifiStatus.builder(doc).build())
198                     .setVolteStatus(Ts43VolteStatus.builder(doc).build())
199                     .setVonrStatus(Ts43VonrStatus.builder(doc).build())
200                     .setSmsoveripStatus(Ts43SmsOverIpStatus.builder(doc).build());
201             doc.getFromVowifi(ResponseXmlAttributes.SERVER_FLOW_URL)
202                     .ifPresent(url -> builder.setEmergencyAddressWebUrl(url));
203             doc.getFromVowifi(ResponseXmlAttributes.SERVER_FLOW_USER_DATA)
204                     .ifPresent(userData -> builder.setEmergencyAddressWebData(userData));
205         }
206         return builder.build();
207     }
208 
isResetToDefault(ClientBehavior clientBehavior)209     private boolean isResetToDefault(ClientBehavior clientBehavior) {
210         return clientBehavior == ClientBehavior.UNKNOWN_BEHAVIOR
211                 || clientBehavior == ClientBehavior.NEEDS_TO_RESET
212                 || clientBehavior == ClientBehavior.NEEDS_TO_RESET_EXCEPT_VERS
213                 || clientBehavior == ClientBehavior.NEEDS_TO_RESET_EXCEPT_VERS_UNTIL_SETTING_ON;
214     }
215 
getCarrierConfig(Context context)216     private CarrierConfig getCarrierConfig(Context context) {
217         String entitlementServiceUrl = TelephonyUtils.getEntitlementServerUrl(context, mSubId);
218         return CarrierConfig.builder()
219                 .setClientTs43(CarrierConfig.CLIENT_TS_43_IMS_ENTITLEMENT)
220                 .setServerUrl(entitlementServiceUrl)
221                 .build();
222     }
223 }
224