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