1 /* 2 * Copyright (C) 2022 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.cellbroadcastreceiver.compliancetests; 18 19 import static org.junit.Assert.assertTrue; 20 import static org.junit.Assume.assumeTrue; 21 22 import android.app.Instrumentation; 23 import android.app.UiAutomation; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.content.pm.PackageManager; 29 import android.hardware.radio.network.Domain; 30 import android.os.Build; 31 import android.os.Handler; 32 import android.os.HandlerThread; 33 import android.os.SystemProperties; 34 import android.support.test.uiautomator.UiDevice; 35 import android.telephony.ServiceState; 36 import android.telephony.SubscriptionInfo; 37 import android.telephony.SubscriptionManager; 38 import android.telephony.TelephonyCallback; 39 import android.telephony.TelephonyManager; 40 import android.telephony.mockmodem.IRadioMessagingImpl; 41 import android.telephony.mockmodem.MockModemConfigBase.SimInfoChangedResult; 42 import android.telephony.mockmodem.MockModemManager; 43 import android.telephony.mockmodem.MockSimService; 44 import android.util.Log; 45 46 import androidx.test.platform.app.InstrumentationRegistry; 47 48 import com.android.compatibility.common.util.ShellIdentityUtils; 49 import com.android.internal.telephony.CellBroadcastUtils; 50 import com.android.modules.utils.build.SdkLevel; 51 52 import org.json.JSONArray; 53 import org.json.JSONObject; 54 import org.junit.AfterClass; 55 import org.junit.Before; 56 import org.junit.BeforeClass; 57 import org.junit.Rule; 58 import org.junit.rules.TestName; 59 60 import java.io.IOException; 61 import java.io.InputStream; 62 import java.util.ArrayList; 63 import java.util.Iterator; 64 import java.util.concurrent.CountDownLatch; 65 import java.util.concurrent.TimeUnit; 66 67 68 public class CellBroadcastBaseTest { 69 private static final String TAG = "CellBroadcastBaseTest"; 70 protected static MockModemManager sMockModemManager; 71 protected static int sSlotId = 0; 72 protected static JSONObject sCarriersObject; 73 protected static JSONObject sChannelsObject; 74 protected static JSONObject sSettingsObject; 75 protected static int sPreconditionError = 0; 76 protected static final int ERROR_SDK_VERSION = 1; 77 protected static final int ERROR_NO_TELEPHONY = 2; 78 protected static final int ERROR_MULTI_SIM = 3; 79 protected static final int ERROR_MOCK_MODEM_DISABLE = 4; 80 81 protected static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem"; 82 protected static final boolean DEBUG = !"user".equals(Build.TYPE); 83 84 protected static final String EXPECTED_RESULT_CHANNELS_JSON = "emergency_alert_channels.json"; 85 protected static final String CARRIER_LISTS_JSON = "region_plmn_list.json"; 86 protected static final String EXPECTED_RESULT_SETTINGS_JSON = "emergency_alert_settings.json"; 87 protected static final String CARRIER_MCCMNC_FIELD = "mccmnc"; 88 protected static final String CHANNEL_DEFAULT_VALUE_FIELD = "default_value"; 89 90 protected static final String ACTION_SET_CHANNELS_DONE = 91 "android.cellbroadcast.compliancetest.SET_CHANNELS_DONE"; 92 protected static CountDownLatch sSetChannelIsDone = new CountDownLatch(1); 93 protected static String sInputMccMnc = null; 94 protected static BroadcastReceiver sReceiver = null; 95 96 protected static final int MAX_WAIT_TIME = 15 * 1000; 97 98 protected static Instrumentation sInstrumentation = null; 99 protected static UiDevice sDevice = null; 100 protected static String sPackageName = null; 101 protected static IRadioMessagingImpl.CallBackWithExecutor sCallBackWithExecutor = null; 102 private static ServiceStateListener sServiceStateCallback; 103 private static int sServiceState = ServiceState.STATE_OUT_OF_SERVICE; 104 private static int sDataServiceState = ServiceState.STATE_OUT_OF_SERVICE; 105 private static final Object OBJECT = new Object(); 106 private static final int SERVICE_STATE_MAX_WAIT = 20 * 1000; 107 protected static CountDownLatch sServiceStateLatch = new CountDownLatch(1); 108 protected static CountDownLatch sDataServiceStateLatch = new CountDownLatch(1); 109 110 private static class ServiceStateListener extends TelephonyCallback 111 implements TelephonyCallback.ServiceStateListener { 112 @Override onServiceStateChanged(ServiceState serviceState)113 public void onServiceStateChanged(ServiceState serviceState) { 114 Log.d(TAG, "Callback: service state = " + serviceState.getVoiceRegState()); 115 Log.d(TAG, "Callback: service data state = " + serviceState.getDataRegState()); 116 synchronized (OBJECT) { 117 sServiceState = serviceState.getVoiceRegState(); 118 sDataServiceState = serviceState.getDataRegState(); 119 if (sServiceState == ServiceState.STATE_IN_SERVICE) { 120 sServiceStateLatch.countDown(); 121 logd("countdown sServiceStateLatch"); 122 } 123 if (sDataServiceState == ServiceState.STATE_OUT_OF_SERVICE) { 124 sDataServiceStateLatch.countDown(); 125 logd("countdown sDataServiceStateLatch"); 126 } 127 } 128 } 129 } 130 getContext()131 protected static Context getContext() { 132 return InstrumentationRegistry.getInstrumentation().getContext(); 133 } 134 135 private static class BroadcastChannelListener 136 implements IRadioMessagingImpl.BroadcastCallback { 137 @Override onGsmBroadcastActivated()138 public void onGsmBroadcastActivated() { 139 TelephonyManager tm = getContext().getSystemService(TelephonyManager.class); 140 String mccmnc = tm.getSimOperator(SubscriptionManager.getDefaultSubscriptionId()); 141 logd("onGsmBroadcastActivated, mccmnc = " + mccmnc); 142 if (sInputMccMnc != null && sInputMccMnc.equals(mccmnc)) { 143 sSetChannelIsDone.countDown(); 144 logd("wait is released"); 145 addSubIdToBeRemoved(SubscriptionManager.getDefaultSubscriptionId()); 146 } 147 } 148 149 @Override onCdmaBroadcastActivated()150 public void onCdmaBroadcastActivated() { 151 } 152 } 153 154 @BeforeClass beforeAllTests()155 public static void beforeAllTests() throws Exception { 156 logd("CellBroadcastBaseTest#beforeAllTests()"); 157 if (!SdkLevel.isAtLeastT()) { 158 Log.i(TAG, "sdk level is below the latest platform"); 159 sPreconditionError = ERROR_SDK_VERSION; 160 return; 161 } 162 163 final PackageManager pm = getContext().getPackageManager(); 164 boolean hasTelephonyFeature = pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY); 165 if (!hasTelephonyFeature) { 166 Log.i(TAG, "Not have Telephony Feature"); 167 sPreconditionError = ERROR_NO_TELEPHONY; 168 return; 169 } 170 171 TelephonyManager tm = 172 (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE); 173 boolean isMultiSim = tm != null && tm.getPhoneCount() > 1; 174 if (!SdkLevel.isAtLeastU() && isMultiSim) { 175 Log.i(TAG, "Not support Multi-Sim"); 176 sPreconditionError = ERROR_MULTI_SIM; 177 return; 178 } 179 180 if (!isMockModemAllowed()) { 181 Log.i(TAG, "Mock Modem is not allowed"); 182 sPreconditionError = ERROR_MOCK_MODEM_DISABLE; 183 return; 184 } 185 186 if (!SdkLevel.isAtLeastU()) { 187 sReceiver = new BroadcastReceiver() { 188 @Override 189 public void onReceive(Context context, Intent intent) { 190 String action = intent.getAction(); 191 if (ACTION_SET_CHANNELS_DONE.equals(action)) { 192 int subId = intent.getIntExtra("sub_id", -1); 193 logd("INTENT_SET_CHANNELS_DONE is received, subId=" + subId); 194 TelephonyManager tm = getContext().getSystemService(TelephonyManager.class) 195 .createForSubscriptionId(subId); 196 if (tm != null) { 197 String mccMncOfIntent = tm.getSimOperator(); 198 logd("mccMncOfIntent = " + mccMncOfIntent); 199 if (sInputMccMnc != null && sInputMccMnc.equals(mccMncOfIntent)) { 200 sSetChannelIsDone.countDown(); 201 logd("wait is released"); 202 addSubIdToBeRemoved(SubscriptionManager.getDefaultSubscriptionId()); 203 } 204 } 205 } 206 } 207 }; 208 IntentFilter filter = new IntentFilter(); 209 filter.addAction(ACTION_SET_CHANNELS_DONE); 210 getContext().registerReceiver(sReceiver, filter, Context.RECEIVER_EXPORTED); 211 } 212 213 sInstrumentation = InstrumentationRegistry.getInstrumentation(); 214 sDevice = UiDevice.getInstance(sInstrumentation); 215 216 sMockModemManager = new MockModemManager(); 217 assertTrue(sMockModemManager.connectMockModemService( 218 MockSimService.MOCK_SIM_PROFILE_ID_TWN_CHT)); 219 220 if (SdkLevel.isAtLeastU()) { 221 BroadcastChannelListener broadcastCallback = new BroadcastChannelListener(); 222 sCallBackWithExecutor = new IRadioMessagingImpl.CallBackWithExecutor( 223 Runnable::run, broadcastCallback); 224 sMockModemManager.registerBroadcastCallback(sSlotId, sCallBackWithExecutor); 225 } 226 waitForNotify(); 227 228 enterService(); 229 230 String jsonCarrier = loadJsonFile(CARRIER_LISTS_JSON); 231 sCarriersObject = new JSONObject(jsonCarrier); 232 String jsonChannels = loadJsonFile(EXPECTED_RESULT_CHANNELS_JSON); 233 sChannelsObject = new JSONObject(jsonChannels); 234 String jsonSettings = loadJsonFile(EXPECTED_RESULT_SETTINGS_JSON); 235 sSettingsObject = new JSONObject(jsonSettings); 236 sPackageName = CellBroadcastUtils 237 .getDefaultCellBroadcastReceiverPackageName(getContext()); 238 } 239 waitForNotify()240 private static void waitForNotify() { 241 try { 242 sSetChannelIsDone.await(MAX_WAIT_TIME, TimeUnit.MILLISECONDS); 243 } catch (InterruptedException e) { 244 // do nothing 245 } 246 } 247 248 @AfterClass afterAllTests()249 public static void afterAllTests() throws Exception { 250 logd("CellBroadcastBaseTest#afterAllTests()"); 251 252 if (sIccIdForDummySub != null) { 253 deleteDummySubscriptionIds(); 254 } 255 256 if (sReceiver != null) { 257 getContext().unregisterReceiver(sReceiver); 258 } 259 if (sCallBackWithExecutor != null && sMockModemManager != null) { 260 sMockModemManager.unregisterBroadcastCallback(sSlotId, sCallBackWithExecutor); 261 } 262 if (sMockModemManager != null) { 263 // Rebind all interfaces which is binding to MockModemService to default. 264 assertTrue(sMockModemManager.disconnectMockModemService()); 265 sMockModemManager = null; 266 } 267 sInputMccMnc = null; 268 } 269 270 @Rule 271 public final TestName mTestNameRule = new TestName(); 272 @Before beforeTest()273 public void beforeTest() throws Exception { 274 assumeTrue(getErrorMessage(sPreconditionError), sPreconditionError == 0); 275 } 276 loadJsonFile(String jsonFile)277 protected static String loadJsonFile(String jsonFile) { 278 String json = null; 279 try { 280 InputStream inputStream = getContext().getAssets().open(jsonFile); 281 int size = inputStream.available(); 282 byte[] byteArray = new byte[size]; 283 inputStream.read(byteArray); 284 inputStream.close(); 285 json = new String(byteArray, "UTF-8"); 286 } catch (IOException e) { 287 e.printStackTrace(); 288 return null; 289 } 290 return json; 291 } 292 paramsForTest()293 protected String[] paramsForTest() throws Throwable { 294 logd("paramsForTest"); 295 String jsonCarrier = loadJsonFile(CARRIER_LISTS_JSON); 296 JSONObject carriersObject = new JSONObject(jsonCarrier); 297 Iterator<String> carrierList = carriersObject.keys(); 298 299 ArrayList<String> carrierLists = new ArrayList<>(); 300 for (Iterator<String> it = carrierList; it.hasNext();) { 301 carrierLists.add(it.next()); 302 } 303 return carrierLists.toArray(new String[]{}); 304 } 305 paramsCarrierAndMccMncForTest()306 protected Object[] paramsCarrierAndMccMncForTest() throws Throwable { 307 logd("paramsCarrierAndMccMncForTest"); 308 String jsonCarrier = loadJsonFile(CARRIER_LISTS_JSON); 309 JSONObject carriersObject = new JSONObject(jsonCarrier); 310 Iterator<String> carrierList = carriersObject.keys(); 311 312 ArrayList<Object> result = new ArrayList<Object>(); 313 for (Iterator<String> it = carrierList; it.hasNext();) { 314 String carrierName = it.next(); 315 JSONObject carrierObject = carriersObject.getJSONObject(carrierName); 316 JSONArray mccMncList = carrierObject.getJSONArray(CARRIER_MCCMNC_FIELD); 317 for (int i = 0; i < mccMncList.length(); i++) { 318 String mccMnc = mccMncList.getString(i); 319 result.add(new String[]{carrierName, mccMnc}); 320 } 321 } 322 return result.toArray(new Object[]{}); 323 } 324 paramsCarrierAndChannelForTest()325 protected Object[] paramsCarrierAndChannelForTest() throws Throwable { 326 logd("paramsCarrierAndChannelForTest"); 327 String jsonCarrier = loadJsonFile(CARRIER_LISTS_JSON); 328 JSONObject carriersObject = new JSONObject(jsonCarrier); 329 Iterator<String> carrierList = carriersObject.keys(); 330 331 ArrayList<Object> result = new ArrayList<Object>(); 332 for (Iterator<String> it = carrierList; it.hasNext();) { 333 String carrierName = it.next(); 334 String jsonChannels = loadJsonFile(EXPECTED_RESULT_CHANNELS_JSON); 335 JSONObject channelsObject = new JSONObject(jsonChannels); 336 JSONObject channelsForCarrier = channelsObject.getJSONObject(carrierName); 337 for (Iterator<String> iterator = channelsForCarrier.keys(); iterator.hasNext();) { 338 String channelId = iterator.next(); 339 result.add(new String[]{carrierName, channelId}); 340 } 341 } 342 return result.toArray(new Object[]{}); 343 } 344 setSimInfo(String carrierName, String inputMccMnc)345 protected void setSimInfo(String carrierName, String inputMccMnc) throws Throwable { 346 String mcc = inputMccMnc.substring(0, 3); 347 String mnc = inputMccMnc.substring(3); 348 sInputMccMnc = inputMccMnc; 349 sSetChannelIsDone = new CountDownLatch(1); 350 351 String[] mccMnc = new String[] {mcc, mnc}; 352 logd("carrierName = " + carrierName 353 + ", mcc = " + mccMnc[0] + ", mnc = " + mccMnc[1]); 354 355 int slotId = 0; 356 357 boolean isSuccessful = sMockModemManager.setSimInfo(slotId, 358 SimInfoChangedResult.SIM_INFO_TYPE_MCC_MNC, mccMnc); 359 assertTrue(isSuccessful); 360 waitForNotify(); 361 } 362 isMockModemAllowed()363 private static boolean isMockModemAllowed() { 364 boolean isAllowed = SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false); 365 // Check for developer settings for user build. Always allow for debug builds 366 return isAllowed || DEBUG; 367 } 368 getErrorMessage(int error)369 protected String getErrorMessage(int error) { 370 String errorMessage = "Precondition Error"; 371 switch (error) { 372 case ERROR_SDK_VERSION: 373 errorMessage = "SDK level is below T"; 374 break; 375 case ERROR_NO_TELEPHONY: 376 errorMessage = "Not have Telephony Feature"; 377 break; 378 case ERROR_MULTI_SIM: 379 errorMessage = "Multi-sim is not supported in Mock Modem"; 380 break; 381 case ERROR_MOCK_MODEM_DISABLE: 382 errorMessage = "Please enable mock modem to run the test! The option can be " 383 + "updated in Settings -> System -> Developer options -> Allow Mock Modem"; 384 break; 385 } 386 return errorMessage; 387 } 388 logd(String msg)389 protected static void logd(String msg) { 390 if (DEBUG) Log.d(TAG, msg); 391 } 392 enterService()393 protected static void enterService() throws Exception { 394 logd("enterService"); 395 HandlerThread serviceStateChangeCallbackHandlerThread = 396 new HandlerThread("ServiceStateChangeCallback"); 397 serviceStateChangeCallbackHandlerThread.start(); 398 Handler serviceStateChangeCallbackHandler = 399 new Handler(serviceStateChangeCallbackHandlerThread.getLooper()); 400 TelephonyManager telephonyManager = 401 (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE); 402 sServiceStateLatch = new CountDownLatch(1); 403 sDataServiceStateLatch = new CountDownLatch(1); 404 // Register service state change callback 405 synchronized (OBJECT) { 406 sServiceState = ServiceState.STATE_OUT_OF_SERVICE; 407 sDataServiceState = ServiceState.STATE_OUT_OF_SERVICE; 408 } 409 410 serviceStateChangeCallbackHandler.post( 411 () -> { 412 sServiceStateCallback = new ServiceStateListener(); 413 ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn( 414 telephonyManager, 415 (tm) -> tm.registerTelephonyCallback( 416 Runnable::run, sServiceStateCallback)); 417 }); 418 419 logd("Disable Data Service"); 420 sMockModemManager.changeNetworkService(sSlotId, MockSimService.MOCK_SIM_PROFILE_ID_TWN_CHT, 421 false, Domain.PS); 422 423 logd("Wait for data service state change to out of service"); 424 waitForNotifyForDataServiceState(); 425 426 // Enter Service 427 logd("Enter Voice Service"); 428 sMockModemManager.changeNetworkService(sSlotId, MockSimService.MOCK_SIM_PROFILE_ID_TWN_CHT, 429 true, Domain.CS); 430 431 // Expect: Home State 432 logd("Wait for service state change to in service"); 433 waitForNotifyForServiceState(); 434 435 // Unregister service state change callback 436 telephonyManager.unregisterTelephonyCallback(sServiceStateCallback); 437 sServiceStateCallback = null; 438 } 439 waitForNotifyForServiceState()440 private static void waitForNotifyForServiceState() { 441 try { 442 sServiceStateLatch.await(SERVICE_STATE_MAX_WAIT, TimeUnit.MILLISECONDS); 443 } catch (InterruptedException e) { 444 // do nothing 445 } 446 } 447 waitForNotifyForDataServiceState()448 private static void waitForNotifyForDataServiceState() { 449 try { 450 sDataServiceStateLatch.await(SERVICE_STATE_MAX_WAIT, TimeUnit.MILLISECONDS); 451 } catch (InterruptedException e) { 452 // do nothing 453 } 454 } 455 456 private static int sSubIdForDummySub; 457 private static String sIccIdForDummySub; 458 private static int sSubTypeForDummySub; 459 addSubIdToBeRemoved(int subId)460 private static void addSubIdToBeRemoved(int subId) { 461 logd("addSubIdToBeRemoved, subId = " + subId 462 + " subIdToBeRemoved = " + sSubIdForDummySub); 463 deleteDummySubscriptionIds(); 464 UiAutomation uiAutomation = sInstrumentation.getUiAutomation(); 465 uiAutomation.adoptShellPermissionIdentity(); 466 try { 467 SubscriptionManager subManager = 468 getContext().getSystemService(SubscriptionManager.class); 469 SubscriptionInfo subInfo = subManager.getActiveSubscriptionInfo(subId); 470 sSubIdForDummySub = subId; 471 sIccIdForDummySub = subInfo.getIccId(); 472 sSubTypeForDummySub = subInfo.getSubscriptionType(); 473 logd("addSubIdToBeRemoved, subId = " + sSubIdForDummySub 474 + " iccId=" + sIccIdForDummySub + " subType=" + sSubTypeForDummySub); 475 } catch (SecurityException e) { 476 logd("runWithShellPermissionIdentity exception = " + e); 477 } finally { 478 uiAutomation.dropShellPermissionIdentity(); 479 } 480 } 481 deleteDummySubscriptionIds()482 private static void deleteDummySubscriptionIds() { 483 if (sIccIdForDummySub != null) { 484 UiAutomation uiAutomation = sInstrumentation.getUiAutomation(); 485 uiAutomation.adoptShellPermissionIdentity(); 486 try { 487 SubscriptionManager subManager = 488 getContext().getSystemService(SubscriptionManager.class); 489 logd("deleteDummySubscriptionIds " 490 + " subId =" + sSubIdForDummySub 491 + " iccId=" + sIccIdForDummySub 492 + " subType=" + sSubTypeForDummySub); 493 subManager.removeSubscriptionInfoRecord(sIccIdForDummySub, sSubTypeForDummySub); 494 } catch (SecurityException e) { 495 logd("runWithShellPermissionIdentity exception = " + e); 496 } catch (IllegalArgumentException e) { 497 logd("catch IllegalArgumentException during removing subscriptionId = " + e); 498 } catch (NullPointerException e) { 499 logd("catch NullPointerException during removing subscriptionId = " + e); 500 } finally { 501 uiAutomation.dropShellPermissionIdentity(); 502 } 503 } 504 } 505 } 506