• 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.Ts43Constants.ResponseXmlNode;
37 import com.android.imsserviceentitlement.ts43.Ts43SmsOverIpStatus;
38 import com.android.imsserviceentitlement.ts43.Ts43VolteStatus;
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         // Set fake device info to avoid leaking
117         requestBuilder.setTerminalVendor("vendorX");
118         requestBuilder.setTerminalModel("modelY");
119         requestBuilder.setTerminalSoftwareVersion("versionZ");
120         requestBuilder.setAcceptContentType(ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_XML);
121         if (mNeedsImsProvisioning) {
122             requestBuilder.setConfigurationVersion(
123                     Integer.parseInt(mLastEntitlementConfiguration.getVersion()));
124         }
125         ServiceEntitlementRequest request = requestBuilder.build();
126 
127         XmlDoc entitlementXmlDoc = null;
128 
129         try {
130             String rawXml = mServiceEntitlement.queryEntitlementStatus(
131                     mNeedsImsProvisioning
132                             ? ImmutableList.of(
133                             ServiceEntitlement.APP_VOWIFI,
134                             ServiceEntitlement.APP_VOLTE,
135                             ServiceEntitlement.APP_SMSOIP)
136                             : ImmutableList.of(ServiceEntitlement.APP_VOWIFI),
137                     request);
138             entitlementXmlDoc = new XmlDoc(rawXml);
139             mLastEntitlementConfiguration.update(rawXml);
140             // Reset the retry count if no exception from queryEntitlementStatus()
141             mRetryFullAuthenticationCount = AUTHENTICATION_RETRIES;
142         } catch (ServiceEntitlementException e) {
143             if (e.getErrorCode() == ServiceEntitlementException.ERROR_HTTP_STATUS_NOT_SUCCESS) {
144                 if (e.getHttpStatus() == RESPONSE_TOKEN_EXPIRED) {
145                     if (mRetryFullAuthenticationCount <= 0) {
146                         Log.d(TAG, "Ran out of the retry count, stop query status.");
147                         return null;
148                     }
149                     Log.d(TAG, "Server asking for full authentication, retry the query.");
150                     // Clean up the cached data and perform full authentication next query.
151                     mLastEntitlementConfiguration.reset();
152                     mRetryFullAuthenticationCount--;
153                     return checkEntitlementStatus();
154                 } else if (e.getHttpStatus() == RESPONSE_RETRY_AFTER && !TextUtils.isEmpty(
155                         e.getRetryAfter())) {
156                     // For handling the case of HTTP_UNAVAILABLE(503), client would perform the
157                     // retry for the delay of Retry-After.
158                     Log.d(TAG, "Server asking for retry. retryAfter = " + e.getRetryAfter());
159                     return EntitlementResult
160                             .builder()
161                             .setRetryAfterSeconds(parseDelaySecondsByRetryAfter(e.getRetryAfter()))
162                             .build();
163                 }
164             }
165             Log.e(TAG, "queryEntitlementStatus failed", e);
166         }
167         return entitlementXmlDoc == null ? null : toEntitlementResult(entitlementXmlDoc);
168     }
169 
170     /**
171      * Parses the value of {@link HttpHeaders#RETRY_AFTER}. The possible formats could be a numeric
172      * value in second, or a HTTP-date in RFC-1123 date-time format.
173      */
parseDelaySecondsByRetryAfter(String retryAfter)174     private long parseDelaySecondsByRetryAfter(String retryAfter) {
175         try {
176             return Long.parseLong(retryAfter);
177         } catch (NumberFormatException numberFormatException) {
178         }
179 
180         try {
181             return SECONDS.between(
182                     Instant.now(sClock), RFC_1123_DATE_TIME.parse(retryAfter, Instant::from));
183         } catch (DateTimeParseException dateTimeParseException) {
184         }
185 
186         Log.w(TAG, "Unable to parse retry-after: " + retryAfter + ", ignore it.");
187         return -1;
188     }
189 
toEntitlementResult(XmlDoc doc)190     private EntitlementResult toEntitlementResult(XmlDoc doc) {
191         EntitlementResult.Builder builder = EntitlementResult.builder();
192         ClientBehavior clientBehavior = mLastEntitlementConfiguration.entitlementValidation();
193 
194         if (mNeedsImsProvisioning && isResetToDefault(clientBehavior)) {
195             // keep the entitlement result in default value and reset the configs.
196             if (clientBehavior == ClientBehavior.NEEDS_TO_RESET
197                     || clientBehavior == ClientBehavior.UNKNOWN_BEHAVIOR) {
198                 mLastEntitlementConfiguration.reset();
199             } else {
200                 mLastEntitlementConfiguration.resetConfigsExceptVers();
201             }
202         } else {
203             builder.setVowifiStatus(Ts43VowifiStatus.builder(doc).build())
204                     .setVolteStatus(Ts43VolteStatus.builder(doc).build())
205                     .setSmsoveripStatus(Ts43SmsOverIpStatus.builder(doc).build());
206             doc.get(
207                     ResponseXmlNode.APPLICATION,
208                     ResponseXmlAttributes.SERVER_FLOW_URL,
209                     ServiceEntitlement.APP_VOWIFI)
210                     .ifPresent(url -> builder.setEmergencyAddressWebUrl(url));
211             doc.get(
212                     ResponseXmlNode.APPLICATION,
213                     ResponseXmlAttributes.SERVER_FLOW_USER_DATA,
214                     ServiceEntitlement.APP_VOWIFI)
215                     .ifPresent(userData -> builder.setEmergencyAddressWebData(userData));
216         }
217         return builder.build();
218     }
219 
isResetToDefault(ClientBehavior clientBehavior)220     private boolean isResetToDefault(ClientBehavior clientBehavior) {
221         return clientBehavior == ClientBehavior.UNKNOWN_BEHAVIOR
222                 || clientBehavior == ClientBehavior.NEEDS_TO_RESET
223                 || clientBehavior == ClientBehavior.NEEDS_TO_RESET_EXCEPT_VERS
224                 || clientBehavior == ClientBehavior.NEEDS_TO_RESET_EXCEPT_VERS_UNTIL_SETTING_ON;
225     }
226 
getCarrierConfig(Context context)227     private CarrierConfig getCarrierConfig(Context context) {
228         String entitlementServiceUrl = TelephonyUtils.getEntitlementServerUrl(context, mSubId);
229         return CarrierConfig.builder().setServerUrl(entitlementServiceUrl).build();
230     }
231 }
232