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