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 com.android.imsserviceentitlement.entitlement.EntitlementConfiguration.ClientBehavior.NEEDS_TO_RESET;
20 import static com.android.imsserviceentitlement.entitlement.EntitlementConfiguration.ClientBehavior.VALID_DURING_VALIDITY;
21 import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_HTTP_STATUS_NOT_SUCCESS;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 
25 import static org.mockito.ArgumentMatchers.any;
26 import static org.mockito.ArgumentMatchers.eq;
27 import static org.mockito.Mockito.verify;
28 import static org.mockito.Mockito.when;
29 
30 import android.content.Context;
31 import android.os.PersistableBundle;
32 import android.telephony.CarrierConfigManager;
33 
34 import androidx.test.core.app.ApplicationProvider;
35 import androidx.test.runner.AndroidJUnit4;
36 
37 import com.android.imsserviceentitlement.entitlement.EntitlementConfiguration;
38 import com.android.imsserviceentitlement.entitlement.EntitlementResult;
39 import com.android.imsserviceentitlement.fcm.FcmTokenStore;
40 import com.android.imsserviceentitlement.utils.TelephonyUtils;
41 import com.android.libraries.entitlement.ServiceEntitlement;
42 import com.android.libraries.entitlement.ServiceEntitlementException;
43 import com.android.libraries.entitlement.ServiceEntitlementRequest;
44 
45 import com.google.common.collect.ImmutableList;
46 
47 import org.junit.Before;
48 import org.junit.Rule;
49 import org.junit.Test;
50 import org.junit.runner.RunWith;
51 import org.mockito.Mock;
52 import org.mockito.Spy;
53 import org.mockito.junit.MockitoJUnit;
54 import org.mockito.junit.MockitoRule;
55 
56 import java.text.SimpleDateFormat;
57 import java.time.Clock;
58 import java.time.Instant;
59 import java.time.ZoneOffset;
60 import java.util.Date;
61 import java.util.Locale;
62 import java.util.TimeZone;
63 
64 @RunWith(AndroidJUnit4.class)
65 public class ImsEntitlementApiTest {
66     @Rule public final MockitoRule rule = MockitoJUnit.rule();
67 
68     @Spy private Context mContext = ApplicationProvider.getApplicationContext();
69 
70     @Mock private ServiceEntitlement mMockServiceEntitlement;
71     @Mock private EntitlementConfiguration mMockEntitlementConfiguration;
72     @Mock private CarrierConfigManager mCarrierConfigManager;
73 
74     private static final int SUB_ID = 1;
75     private static final String FCM_TOKEN = "FCM_TOKEN";
76     private static final String RAW_XML =
77             "<wap-provisioningdoc version=\"1.1\">"
78                     + "  <characteristic type=\"VERS\">"
79                     + "    <parm name=\"version\" value=\"1\"/>"
80                     + "    <parm name=\"validity\" value=\"1728000\"/>"
81                     + "  </characteristic>"
82                     + "  <characteristic type=\"TOKEN\">"
83                     + "    <parm name=\"token\" value=\"kZYfCEpSsMr88KZVmab5UsZVzl+nWSsX\"/>"
84                     + "    <parm name=\"validity\" value=\"3600\"/>"
85                     + "  </characteristic>"
86                     + "  <characteristic type=\"APPLICATION\">"
87                     + "    <parm name=\"AppID\" value=\"ap2004\"/>"
88                     + "    <parm name=\"EntitlementStatus\" value=\"1\"/>"
89                     + "  </characteristic>"
90                     + "</wap-provisioningdoc>";
91     private static final String RAW_XML_NEW_TOKEN =
92             "<wap-provisioningdoc version=\"1.1\">"
93                     + "  <characteristic type=\"VERS\">"
94                     + "    <parm name=\"version\" value=\"1\"/>"
95                     + "    <parm name=\"validity\" value=\"1728000\"/>"
96                     + "  </characteristic>"
97                     + "  <characteristic type=\"TOKEN\">"
98                     + "    <parm name=\"token\" value=\"NEW_TOKEN\"/>"
99                     + "    <parm name=\"validity\" value=\"3600\"/>"
100                     + "  </characteristic>\n"
101                     + "  <characteristic type=\"APPLICATION\">"
102                     + "    <parm name=\"AppID\" value=\"ap2004\"/>"
103                     + "    <parm name=\"EntitlementStatus\" value=\"1\"/>"
104                     + "  </characteristic>"
105                     + "</wap-provisioningdoc>";
106 
107     private static final String MULTIPLE_APPIDS_RAW_XML =
108             "<wap-provisioningdoc version=\"1.1\">"
109                     + "  <characteristic type=\"VERS\">"
110                     + "    <parm name=\"version\" value=\"1\"/>"
111                     + "    <parm name=\"validity\" value=\"1728000\"/>"
112                     + "  </characteristic>"
113                     + "  <characteristic type=\"TOKEN\">"
114                     + "    <parm name=\"token\" value=\"kZYfCEpSsMr88KZVmab5UsZVzl+nWSsX\"/>"
115                     + "    <parm name=\"validity\" value=\"3600\"/>"
116                     + "  </characteristic>"
117                     + "  <characteristic type=\"APPLICATION\">"
118                     + "    <parm name=\"AppID\" value=\"ap2003\"/>"
119                     + "    <parm name=\"EntitlementStatus\" value=\"1\"/>"
120                     + "  </characteristic>\n"
121                     + "  <characteristic type=\"APPLICATION\">"
122                     + "    <parm name=\"AppID\" value=\"ap2004\"/>\n"
123                     + "    <parm name=\"EntitlementStatus\" value=\"1\"/>"
124                     + "  </characteristic>"
125                     + "  <characteristic type=\"APPLICATION\">"
126                     + "    <parm name=\"AppID\" value=\"ap2005\"/>"
127                     + "    <parm name=\"EntitlementStatus\" value=\"1\"/>"
128                     + "  </characteristic>"
129                     + "</wap-provisioningdoc>";
130 
131     private final EntitlementConfiguration mEntitlementConfiguration =
132             new EntitlementConfiguration(ApplicationProvider.getApplicationContext(), SUB_ID);
133 
134     private ImsEntitlementApi mImsEntitlementApi;
135 
136     @Before
setUp()137     public void setUp() {
138         setImsProvisioningBool(true);
139         FcmTokenStore.setToken(mContext, SUB_ID, FCM_TOKEN);
140         mEntitlementConfiguration.reset();
141     }
142 
143     @Test
checkEntitlementStatus_verifyVowifiStatus()144     public void checkEntitlementStatus_verifyVowifiStatus() throws Exception {
145         setImsProvisioningBool(false);
146         setupImsEntitlementApi(mEntitlementConfiguration);
147         when(mMockServiceEntitlement.queryEntitlementStatus(
148                 eq(ImmutableList.of(ServiceEntitlement.APP_VOWIFI)), any())).thenReturn(RAW_XML);
149 
150         EntitlementResult result = mImsEntitlementApi.checkEntitlementStatus();
151 
152         assertThat(result.getVowifiStatus().vowifiEntitled()).isTrue();
153     }
154 
155     @Test
checkEntitlementStatus_verifyImsAppsStatus()156     public void checkEntitlementStatus_verifyImsAppsStatus() throws Exception {
157         setupImsEntitlementApi(mEntitlementConfiguration);
158         when(mMockServiceEntitlement.queryEntitlementStatus(
159                 eq(ImmutableList.of(
160                         ServiceEntitlement.APP_VOWIFI,
161                         ServiceEntitlement.APP_VOLTE,
162                         ServiceEntitlement.APP_SMSOIP)), any())
163         ).thenReturn(MULTIPLE_APPIDS_RAW_XML);
164 
165         EntitlementResult result = mImsEntitlementApi.checkEntitlementStatus();
166 
167         assertThat(result.getVowifiStatus().vowifiEntitled()).isTrue();
168         assertThat(result.getVolteStatus().isActive()).isTrue();
169         assertThat(result.getSmsoveripStatus().isActive()).isTrue();
170     }
171 
172     @Test
checkEntitlementStatus_verifyConfigs()173     public void checkEntitlementStatus_verifyConfigs() throws Exception {
174         setImsProvisioningBool(false);
175         setupImsEntitlementApi(mEntitlementConfiguration);
176         when(mMockServiceEntitlement.queryEntitlementStatus(
177                 eq(ImmutableList.of(ServiceEntitlement.APP_VOWIFI)),
178                 any())).thenReturn(RAW_XML);
179 
180         EntitlementResult result = mImsEntitlementApi.checkEntitlementStatus();
181 
182         assertThat(mEntitlementConfiguration.getVoWifiStatus()).isEqualTo(1);
183         assertThat(mEntitlementConfiguration.getVolteStatus()).isEqualTo(2);
184         assertThat(mEntitlementConfiguration.getSmsOverIpStatus()).isEqualTo(2);
185         assertThat(mEntitlementConfiguration.getToken().get()).isEqualTo(
186                 "kZYfCEpSsMr88KZVmab5UsZVzl+nWSsX");
187         assertThat(mEntitlementConfiguration.getTokenValidity()).isEqualTo(3600);
188         assertThat(mEntitlementConfiguration.entitlementValidation()).isEqualTo(
189                 VALID_DURING_VALIDITY);
190     }
191 
192     @Test
checkEntitlementStatus_resultNull_verifyVowifiStatusAndConfigs()193     public void checkEntitlementStatus_resultNull_verifyVowifiStatusAndConfigs() throws Exception {
194         setImsProvisioningBool(false);
195         setupImsEntitlementApi(mEntitlementConfiguration);
196         when(mMockServiceEntitlement.queryEntitlementStatus(
197                 eq(ImmutableList.of(ServiceEntitlement.APP_VOWIFI)), any())).thenReturn(null);
198 
199         EntitlementResult result = mImsEntitlementApi.checkEntitlementStatus();
200 
201         assertThat(result.getVowifiStatus().vowifiEntitled()).isFalse();
202         assertThat(mEntitlementConfiguration.getVoWifiStatus()).isEqualTo(2);
203         assertThat(mEntitlementConfiguration.getVolteStatus()).isEqualTo(2);
204         assertThat(mEntitlementConfiguration.getSmsOverIpStatus()).isEqualTo(2);
205         assertThat(mEntitlementConfiguration.getToken().isPresent()).isFalse();
206         assertThat(mEntitlementConfiguration.getTokenValidity()).isEqualTo(0);
207         assertThat(mEntitlementConfiguration.entitlementValidation()).isEqualTo(NEEDS_TO_RESET);
208     }
209 
210     @Test
checkEntitlementStatus_httpResponse511_dataStoreReset()211     public void checkEntitlementStatus_httpResponse511_dataStoreReset() throws Exception {
212         setImsProvisioningBool(false);
213         setupImsEntitlementApi(mMockEntitlementConfiguration);
214         when(mMockServiceEntitlement.queryEntitlementStatus(
215                 eq(ImmutableList.of(ServiceEntitlement.APP_VOWIFI)), any()))
216                 .thenThrow(
217                         new ServiceEntitlementException(
218                                 ERROR_HTTP_STATUS_NOT_SUCCESS, 511, "Invalid connection response"));
219 
220         EntitlementResult result = mImsEntitlementApi.checkEntitlementStatus();
221 
222         verify(mMockEntitlementConfiguration).reset();
223         assertThat(result).isNull();
224     }
225 
226     @Test
checkEntitlementStatus_httpResponse511_fullAuthnDone()227     public void checkEntitlementStatus_httpResponse511_fullAuthnDone() throws Exception {
228         setImsProvisioningBool(false);
229         setupImsEntitlementApi(mEntitlementConfiguration);
230         mEntitlementConfiguration.update(RAW_XML);
231         // While perform fast-authn, throws exception with code 511
232         when(mMockServiceEntitlement.queryEntitlementStatus(
233                 ImmutableList.of(ServiceEntitlement.APP_VOWIFI),
234                 authenticationRequest("kZYfCEpSsMr88KZVmab5UsZVzl+nWSsX")))
235                 .thenThrow(
236                         new ServiceEntitlementException(
237                                 ERROR_HTTP_STATUS_NOT_SUCCESS, 511, "Invalid connection response"));
238         // While perform full-authn, return the result
239         when(mMockServiceEntitlement.queryEntitlementStatus(
240                 ImmutableList.of(ServiceEntitlement.APP_VOWIFI),
241                 authenticationRequest(null)))
242                 .thenReturn(RAW_XML_NEW_TOKEN);
243 
244         EntitlementResult result = mImsEntitlementApi.checkEntitlementStatus();
245 
246         assertThat(result).isNotNull();
247         assertThat(mEntitlementConfiguration.getToken().get()).isEqualTo("NEW_TOKEN");
248     }
249 
250     @Test
checkEntitlementStatus_httpResponse503WithDateTime_returnsRetryAfter()251     public void checkEntitlementStatus_httpResponse503WithDateTime_returnsRetryAfter()
252             throws Exception {
253         setImsProvisioningBool(false);
254         setupImsEntitlementApi(mEntitlementConfiguration);
255         mEntitlementConfiguration.update(RAW_XML);
256         Clock fixedClock = Clock.fixed(Instant.ofEpochSecond(0), ZoneOffset.UTC);
257         ImsEntitlementApi.sClock = fixedClock;
258 
259         // While perform fast-authn, throws exception with code 503
260         when(mMockServiceEntitlement.queryEntitlementStatus(
261                 ImmutableList.of(ServiceEntitlement.APP_VOWIFI),
262                 authenticationRequest("kZYfCEpSsMr88KZVmab5UsZVzl+nWSsX")))
263                 .thenThrow(
264                         new ServiceEntitlementException(
265                                 ERROR_HTTP_STATUS_NOT_SUCCESS,
266                                 503,
267                                 getDateTimeAfter(120, fixedClock),
268                                 "Invalid connection response"));
269 
270         EntitlementResult result = mImsEntitlementApi.checkEntitlementStatus();
271 
272         assertThat(result).isNotNull();
273         assertThat(result.getRetryAfterSeconds()).isEqualTo(120);
274     }
275 
276     @Test
checkEntitlementStatus_httpResponse503WithNumericValue_returnsRetryAfter()277     public void checkEntitlementStatus_httpResponse503WithNumericValue_returnsRetryAfter()
278             throws Exception {
279         setImsProvisioningBool(false);
280         setupImsEntitlementApi(mEntitlementConfiguration);
281         mEntitlementConfiguration.update(RAW_XML);
282         // While perform fast-authn, throws exception with code 503
283         when(mMockServiceEntitlement.queryEntitlementStatus(
284                 ImmutableList.of(ServiceEntitlement.APP_VOWIFI),
285                 authenticationRequest("kZYfCEpSsMr88KZVmab5UsZVzl+nWSsX")))
286                 .thenThrow(
287                         new ServiceEntitlementException(
288                                 ERROR_HTTP_STATUS_NOT_SUCCESS,
289                                 503,
290                                 "120",
291                                 "Invalid connection response"));
292 
293         EntitlementResult result = mImsEntitlementApi.checkEntitlementStatus();
294 
295         assertThat(result).isNotNull();
296         assertThat(result.getRetryAfterSeconds()).isEqualTo(120);
297     }
298 
authenticationRequest(String token)299     private ServiceEntitlementRequest authenticationRequest(String token) {
300         ServiceEntitlementRequest.Builder requestBuilder = ServiceEntitlementRequest.builder();
301         if (token != null) {
302             requestBuilder.setAuthenticationToken(token);
303         }
304         requestBuilder.setNotificationToken(FcmTokenStore.getToken(mContext, SUB_ID));
305         requestBuilder.setTerminalVendor("vendorX");
306         requestBuilder.setTerminalModel("modelY");
307         requestBuilder.setTerminalSoftwareVersion("versionZ");
308         requestBuilder.setAcceptContentType(ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_XML);
309         return requestBuilder.build();
310     }
311 
setupImsEntitlementApi(EntitlementConfiguration entitlementConfiguration)312     private void setupImsEntitlementApi(EntitlementConfiguration entitlementConfiguration) {
313         mImsEntitlementApi = new ImsEntitlementApi(
314                 mContext,
315                 SUB_ID,
316                 TelephonyUtils.isImsProvisioningRequired(mContext, SUB_ID),
317                 mMockServiceEntitlement,
318                 entitlementConfiguration);
319     }
320 
setImsProvisioningBool(boolean provisioning)321     private void setImsProvisioningBool(boolean provisioning) {
322         PersistableBundle carrierConfig = new PersistableBundle();
323         carrierConfig.putBoolean(
324                 CarrierConfigManager.ImsServiceEntitlement.KEY_IMS_PROVISIONING_BOOL,
325                 provisioning
326         );
327         when(mCarrierConfigManager.getConfigForSubId(SUB_ID)).thenReturn(carrierConfig);
328         when(mContext.getSystemService(CarrierConfigManager.class))
329                 .thenReturn(mCarrierConfigManager);
330     }
331 
getDateTimeAfter(long seconds, Clock fixedClock)332     private String getDateTimeAfter(long seconds, Clock fixedClock) {
333         SimpleDateFormat dateFormat = new SimpleDateFormat(
334                 "EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
335         dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
336         return dateFormat.format(Date.from(fixedClock.instant().plusSeconds(seconds)));
337     }
338 }
339