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