1 /* 2 * Copyright (C) 2019 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.cellbroadcastservice.tests; 18 19 import static android.telephony.SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE; 20 import static android.telephony.SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE; 21 22 import static org.mockito.ArgumentMatchers.any; 23 import static org.mockito.ArgumentMatchers.anyInt; 24 import static org.mockito.ArgumentMatchers.anyString; 25 import static org.mockito.ArgumentMatchers.eq; 26 import static org.mockito.ArgumentMatchers.nullable; 27 import static org.mockito.Mockito.clearInvocations; 28 import static org.mockito.Mockito.doAnswer; 29 import static org.mockito.Mockito.doReturn; 30 import static org.mockito.Mockito.mock; 31 import static org.mockito.Mockito.never; 32 import static org.mockito.Mockito.times; 33 import static org.mockito.Mockito.verify; 34 35 import android.app.ActivityManager; 36 import android.app.IActivityManager; 37 import android.content.ContentValues; 38 import android.content.Context; 39 import android.content.IIntentSender; 40 import android.content.Intent; 41 import android.content.res.Configuration; 42 import android.database.Cursor; 43 import android.database.MatrixCursor; 44 import android.net.Uri; 45 import android.os.Bundle; 46 import android.os.IBinder; 47 import android.os.ServiceManager; 48 import android.os.SystemProperties; 49 import android.provider.Telephony; 50 import android.telephony.CbGeoUtils; 51 import android.telephony.SmsCbCmasInfo; 52 import android.telephony.SmsCbLocation; 53 import android.telephony.SmsCbMessage; 54 import android.telephony.SubscriptionInfo; 55 import android.telephony.SubscriptionManager; 56 import android.telephony.TelephonyManager; 57 import android.test.mock.MockContentProvider; 58 import android.test.mock.MockContentResolver; 59 import android.testing.AndroidTestingRunner; 60 import android.testing.TestableLooper; 61 import android.text.format.DateUtils; 62 import android.util.Singleton; 63 64 import androidx.annotation.NonNull; 65 import androidx.test.filters.SmallTest; 66 67 import com.android.cellbroadcastservice.CbSendMessageCalculator; 68 import com.android.cellbroadcastservice.CellBroadcastHandler; 69 import com.android.cellbroadcastservice.CellBroadcastProvider; 70 import com.android.cellbroadcastservice.SmsCbConstants; 71 import com.android.internal.telephony.ISub; 72 import com.android.modules.utils.build.SdkLevel; 73 74 import org.junit.After; 75 import org.junit.Before; 76 import org.junit.Test; 77 import org.junit.runner.RunWith; 78 import org.mockito.Mock; 79 80 import java.io.IOException; 81 import java.io.OutputStream; 82 import java.io.PrintWriter; 83 import java.util.HashMap; 84 import java.util.List; 85 86 @RunWith(AndroidTestingRunner.class) 87 @TestableLooper.RunWithLooper 88 public class CellBroadcastHandlerTest extends CellBroadcastServiceTestBase { 89 90 private CellBroadcastHandler mCellBroadcastHandler; 91 92 private TestableLooper mTestbleLooper; 93 94 private CbSendMessageCalculatorFactoryFacade mSendMessageFactory; 95 96 private CellBroadcastHandler.HandlerHelper mHandlerHelper; 97 98 protected HashMap<String, IBinder> mServiceManagerMockedServices = new HashMap<>(); 99 100 @Mock 101 private IBinder mIBinder; 102 103 @Mock 104 private IActivityManager mIActivityManager; 105 106 @Mock 107 private IIntentSender mIIntentSender; 108 109 @Mock 110 private Singleton<IActivityManager> mIActivityManagerSingleton; 111 112 @Mock 113 private ISub mISub; 114 115 private Configuration mConfiguration; 116 117 private class CellBroadcastContentProvider extends MockContentProvider { 118 String mPlmn = "311480"; 119 int mGeographicalScope = 0; 120 setPlmn(String plmn)121 public void setPlmn(String plmn) { 122 mPlmn = plmn; 123 } 124 setGeographicalScope(int geographicalScope)125 public void setGeographicalScope(int geographicalScope) { 126 mGeographicalScope = geographicalScope; 127 } 128 129 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)130 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 131 String sortOrder) { 132 133 if (uri.compareTo(Telephony.CellBroadcasts.CONTENT_URI) == 0) { 134 MatrixCursor mc = new MatrixCursor(CellBroadcastProvider.QUERY_COLUMNS); 135 136 mc.addRow(new Object[]{ 137 1, // _ID 138 0, // SLOT_INDEX 139 1, // SUBSCRIPTION_ID 140 mGeographicalScope, // GEOGRAPHICAL_SCOPE 141 mPlmn, // PLMN 142 0, // LAC 143 0, // CID 144 1234, // SERIAL_NUMBER 145 SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL, 146 "en", // LANGUAGE_CODE 147 1, // DATA_CODING_SCHEME 148 "Test Message", // MESSAGE_BODY 149 1, // MESSAGE_FORMAT 150 3, // MESSAGE_PRIORITY 151 0, // ETWS_WARNING_TYPE 152 0, // ETWS_IS_PRIMARY 153 SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT, // CMAS_MESSAGE_CLASS 154 0, // CMAS_CATEGORY 155 0, // CMAS_RESPONSE_TYPE 156 0, // CMAS_SEVERITY 157 0, // CMAS_URGENCY 158 0, // CMAS_CERTAINTY 159 System.currentTimeMillis() - DateUtils.HOUR_IN_MILLIS * 2, 160 System.currentTimeMillis() - DateUtils.HOUR_IN_MILLIS * 2, 161 true, // MESSAGE_BROADCASTED 162 true, // MESSAGE_DISPLAYED 163 "", // GEOMETRIES 164 5, // MAXIMUM_WAIT_TIME 165 }); 166 return mc; 167 } 168 169 return null; 170 } 171 172 @Override update(Uri url, ContentValues values, String where, String[] whereArgs)173 public int update(Uri url, ContentValues values, String where, String[] whereArgs) { 174 return 1; 175 } 176 } 177 178 179 @Before setUp()180 public void setUp() throws Exception { 181 super.setUp(); 182 183 mTestbleLooper = TestableLooper.get(CellBroadcastHandlerTest.this); 184 mSendMessageFactory = new CbSendMessageCalculatorFactoryFacade(); 185 mHandlerHelper = mock(CellBroadcastHandler.HandlerHelper.class); 186 187 mCellBroadcastHandler = new CellBroadcastHandler("CellBroadcastHandlerUT", 188 mMockedContext, mTestbleLooper.getLooper(), mSendMessageFactory, mHandlerHelper); 189 190 doAnswer(invocation -> { 191 Runnable r = invocation.getArgument(0); 192 mCellBroadcastHandler.getHandler().post(r); 193 return null; 194 }).when(mHandlerHelper).post(any()); 195 doReturn(mCellBroadcastHandler.getHandler()).when(mHandlerHelper).getHandler(); 196 197 ((MockContentResolver) mMockedContext.getContentResolver()).addProvider( 198 Telephony.CellBroadcasts.CONTENT_URI.getAuthority(), 199 new CellBroadcastContentProvider()); 200 doReturn("com.android.cellbroadcastservice").when(mMockedContext).getPackageName(); 201 doReturn(mMockedContext).when(mMockedContext).createConfigurationContext(any()); 202 doReturn(mMockedResources).when(mMockedContext).getResources(); 203 mConfiguration = new Configuration(); 204 doReturn(mConfiguration).when(mMockedResources).getConfiguration(); 205 putResources(com.android.cellbroadcastservice.R.integer.message_expiration_time, 206 (int) DateUtils.DAY_IN_MILLIS); 207 putResources(com.android.cellbroadcastservice.R.bool.duplicate_compare_service_category, 208 true); 209 210 replaceInstance(ActivityManager.class, "IActivityManagerSingleton", null, 211 mIActivityManagerSingleton); 212 213 replaceInstance(Singleton.class, "mInstance", mIActivityManagerSingleton, 214 mIActivityManager); 215 replaceInstance(ServiceManager.class, "sCache", null, mServiceManagerMockedServices); 216 217 doReturn(mIIntentSender).when(mIActivityManager).getIntentSenderWithFeature(anyInt(), 218 nullable(String.class), nullable(String.class), nullable(IBinder.class), 219 nullable(String.class), anyInt(), nullable(Intent[].class), 220 nullable(String[].class), anyInt(), nullable(Bundle.class), anyInt()); 221 doReturn(mIBinder).when(mIIntentSender).asBinder(); 222 doReturn(mISub).when(mIBinder).queryLocalInterface(anyString()); 223 mServiceManagerMockedServices.put("isub", mIBinder); 224 TelephonyManager.disableServiceHandleCaching(); 225 SubscriptionManager.disableCaching(); 226 } 227 228 @After tearDown()229 public void tearDown() throws Exception { 230 super.tearDown(); 231 } 232 createSmsCbMessage(int serialNumber, int serviceCategory, String messageBody)233 private SmsCbMessage createSmsCbMessage(int serialNumber, int serviceCategory, 234 String messageBody) { 235 return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, 236 0, serialNumber, new SmsCbLocation("311480", 0, 0), 237 serviceCategory, "en", messageBody, 3, 238 null, null, 0, 1); 239 } 240 241 @Test 242 @SmallTest testDuplicate()243 public void testDuplicate() throws Exception { 244 SmsCbMessage msg = createSmsCbMessage(1234, 4370, "msg"); 245 assertTrue(mCellBroadcastHandler.isDuplicate(msg)); 246 } 247 248 @Test 249 @SmallTest testNotDuplicateSerialDifferent()250 public void testNotDuplicateSerialDifferent() throws Exception { 251 SmsCbMessage msg = createSmsCbMessage(1235, 4370, "msg"); 252 assertFalse(mCellBroadcastHandler.isDuplicate(msg)); 253 } 254 255 @Test 256 @SmallTest testNotDuplicateServiceCategoryDifferent()257 public void testNotDuplicateServiceCategoryDifferent() throws Exception { 258 SmsCbMessage msg = createSmsCbMessage(1234, 4371, "msg"); 259 assertFalse(mCellBroadcastHandler.isDuplicate(msg)); 260 } 261 262 @Test 263 @SmallTest testNotDuplicateMessageBodyDifferent()264 public void testNotDuplicateMessageBodyDifferent() throws Exception { 265 putResources(com.android.cellbroadcastservice.R.bool.duplicate_compare_body, true); 266 SmsCbMessage msg = createSmsCbMessage(1234, 4370, "msg"); 267 assertFalse(mCellBroadcastHandler.isDuplicate(msg)); 268 } 269 270 @Test 271 @SmallTest testNotDuplicateCellLocationDifferent()272 public void testNotDuplicateCellLocationDifferent() throws Exception { 273 SmsCbMessage msg = new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, 274 0, 1234, new SmsCbLocation("311480", 0, 1), 275 4370, "en", "Test Message", 3, 276 null, null, 0, 1); 277 assertFalse(mCellBroadcastHandler.isDuplicate(msg)); 278 } 279 280 @Test 281 @SmallTest testMakeCellBroadcastHandler()282 public void testMakeCellBroadcastHandler() throws Exception { 283 CellBroadcastHandler cellBroadcastHandler = 284 CellBroadcastHandler.makeCellBroadcastHandler(mMockedContext); 285 // sanity test for make, just assert that returned object is not null 286 assertTrue(cellBroadcastHandler != null); 287 cellBroadcastHandler.cleanup(); 288 } 289 290 @Test 291 @SmallTest testDump()292 public void testDump() throws Exception { 293 mCellBroadcastHandler.dump(null, new PrintWriter(new OutputStream() { 294 @Override 295 public void write(int b) throws IOException { 296 // no implementation needed for sanity test 297 } 298 }), null); 299 } 300 301 @Test 302 @SmallTest testPutPhoneIdAndSubIdExtra()303 public void testPutPhoneIdAndSubIdExtra() throws Exception { 304 Intent intent = new Intent(); 305 int phoneId = 0; 306 if (SdkLevel.isAtLeastU()) { 307 doReturn(FAKE_SUBID).when(mISub).getSubId(phoneId); 308 } 309 CellBroadcastHandler.putPhoneIdAndSubIdExtra(mMockedContext, intent, phoneId); 310 assertEquals(FAKE_SUBID, intent.getIntExtra( 311 SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, FAKE_SUBID + 1)); 312 assertEquals(FAKE_SUBID, intent.getIntExtra( 313 "subscription", FAKE_SUBID + 1)); 314 assertEquals(phoneId, intent.getIntExtra( 315 SubscriptionManager.EXTRA_SLOT_INDEX, phoneId + 1)); 316 assertEquals(phoneId, intent.getIntExtra("phone", phoneId + 1)); 317 318 // if subId is not available, subscription extras should not be added 319 Intent intentNoSubId = new Intent(); 320 if (SdkLevel.isAtLeastU()) { 321 doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mISub).getSubId(phoneId); 322 } else { 323 doReturn(null).when(mMockedSubscriptionManager).getSubscriptionIds(anyInt()); 324 } 325 CellBroadcastHandler.putPhoneIdAndSubIdExtra(mMockedContext, intentNoSubId, phoneId); 326 assertEquals(FAKE_SUBID + 1, intentNoSubId.getIntExtra( 327 SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, FAKE_SUBID + 1)); 328 assertEquals(FAKE_SUBID + 1, intentNoSubId.getIntExtra( 329 "subscription", FAKE_SUBID + 1)); 330 assertEquals(phoneId, intentNoSubId.getIntExtra( 331 SubscriptionManager.EXTRA_SLOT_INDEX, phoneId + 1)); 332 assertEquals(phoneId, intentNoSubId.getIntExtra( 333 "phone", phoneId + 1)); 334 } 335 336 @Test 337 @SmallTest testDuplicateDetection()338 public void testDuplicateDetection() throws Exception { 339 // if IS_DEBUGGABLE 340 if (SystemProperties.getInt("ro.debuggable", 0) == 1) { 341 SmsCbMessage msg = createSmsCbMessage(1234, 4370, "msg"); 342 // message should be detected as duplicate initially 343 assertTrue(mCellBroadcastHandler.isDuplicate(msg)); 344 345 // disable duplicate detection 346 Intent intent = 347 new Intent("com.android.cellbroadcastservice.action.DUPLICATE_DETECTION"); 348 intent.putExtra("enable", false); 349 sendBroadcast(intent); 350 351 // message should not be detected as duplicate 352 assertFalse(mCellBroadcastHandler.isDuplicate(msg)); 353 354 // enable duplicate detection 355 intent.putExtra("enable", true); 356 sendBroadcast(intent); 357 358 // message should be detected as duplicate again 359 assertTrue(mCellBroadcastHandler.isDuplicate(msg)); 360 } 361 } 362 363 @Test 364 @SmallTest testEmptyPlmnDuplicateDetection()365 public void testEmptyPlmnDuplicateDetection() throws Exception { 366 367 // case (GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE + plmn exist) 368 SmsCbMessage msg1 = new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, 369 GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE, 1234, new SmsCbLocation("311480", 0, 0), 370 4370, "en", "Test Message1", 3, 371 null, null, 0, 1); 372 assertTrue(mCellBroadcastHandler.isDuplicate(msg1)); 373 374 // case (GEOGRAPHICAL_SCOPE_PLMN_WIDE + plmn exist) 375 SmsCbMessage msg2 = new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, 376 GEOGRAPHICAL_SCOPE_PLMN_WIDE, 1234, new SmsCbLocation("311480", 0, 0), 377 4370, "en", "Test Message2", 3, 378 null, null, 0, 1); 379 assertFalse(mCellBroadcastHandler.isDuplicate(msg2)); 380 381 // case (GEOGRAPHICAL_SCOPE_PLMN_WIDE + plmn "") 382 SmsCbMessage msg3 = new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, 383 GEOGRAPHICAL_SCOPE_PLMN_WIDE, 1234, new SmsCbLocation("", 0, 0), 384 4370, "en", "Test Message2", 3, 385 null, null, 0, 1); 386 assertFalse(mCellBroadcastHandler.isDuplicate(msg3)); 387 388 CellBroadcastContentProvider CBContentProviderForEmptyPlmn = 389 new CellBroadcastContentProvider(); 390 CBContentProviderForEmptyPlmn.setPlmn(""); 391 CBContentProviderForEmptyPlmn.setGeographicalScope(GEOGRAPHICAL_SCOPE_PLMN_WIDE); 392 mMockedContentResolver.addProvider(Telephony.CellBroadcasts.CONTENT_URI.getAuthority(), 393 CBContentProviderForEmptyPlmn); 394 395 // case (GEOGRAPHICAL_SCOPE_PLMN_WIDE + plmn "") with already empty plmn message saved 396 SmsCbMessage msg4 = new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, 397 GEOGRAPHICAL_SCOPE_PLMN_WIDE, 1234, new SmsCbLocation("", 0, 0), 398 4370, "en", "Test Message3", 3, 399 null, null, 0, 1); 400 assertTrue(mCellBroadcastHandler.isDuplicate(msg4)); 401 } 402 403 @Test 404 @SmallTest testCrossSimDuplicateDetection()405 public void testCrossSimDuplicateDetection() throws Exception { 406 int differentSlotID = 1; 407 int differentSubID = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; 408 409 // enable cross_sim_duplicate_detection 410 putResources(com.android.cellbroadcastservice.R.bool.cross_sim_duplicate_detection, true); 411 412 // The message with different subId will be detected as duplication. 413 SmsCbMessage msg1 = new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, 414 0, 1234, new SmsCbLocation("311480", 0, 0), 415 4370, "en", "Test Message", 3, 416 null, null, 0, differentSubID); 417 assertTrue(mCellBroadcastHandler.isDuplicate(msg1)); 418 419 // The message with different body won't be detected as a duplication. 420 SmsCbMessage msg2 = new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, 421 0, 1234, new SmsCbLocation("311480", 0, 0), 422 4370, "en", "Different Message", 3, 423 null, null, 0, differentSubID); 424 assertFalse(mCellBroadcastHandler.isDuplicate(msg2)); 425 426 // The message with different slotId will be detected as a duplication. 427 SmsCbMessage msg3 = new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, 428 0, 1234, new SmsCbLocation("311480", 0, 0), 429 4370, "en", "Test Message", 3, 430 null, null, differentSlotID, 1); 431 assertTrue(mCellBroadcastHandler.isDuplicate(msg3)); 432 433 // The message with different slotId and body will be detected as a duplication. 434 SmsCbMessage msg4 = new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, 435 0, 1234, new SmsCbLocation("311480", 0, 0), 436 4370, "en", "Different Message", 3, 437 null, null, differentSlotID, 1); 438 assertTrue(mCellBroadcastHandler.isDuplicate(msg4)); 439 } 440 441 @Test 442 @SmallTest testGetResources()443 public void testGetResources() throws Exception { 444 SubscriptionInfo mockSubInfo = mock(SubscriptionInfo.class); 445 doReturn(mockSubInfo).when(mMockedSubscriptionManager).getActiveSubscriptionInfo(anyInt()); 446 doReturn(001).when(mockSubInfo).getMcc(); 447 doReturn(01).when(mockSubInfo).getMnc(); 448 449 // verify not to call SubscriptionManager#getResourcesForSubId for DEFAULT ID 450 mCellBroadcastHandler.getResources(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID); 451 452 verify(mMockedContext, never()).createConfigurationContext(any()); 453 verify(mMockedContext, times(1)).getResources(); 454 455 // verify not to call SubscriptionManager#getResourcesForSubId for INVALID ID 456 mCellBroadcastHandler.getResources(SubscriptionManager.INVALID_SUBSCRIPTION_ID); 457 458 verify(mMockedContext, never()).createConfigurationContext(any()); 459 verify(mMockedContext, times(2)).getResources(); 460 461 // verify to call SubscriptionManager#getResourcesForSubId for normal sub id 462 mCellBroadcastHandler.getResources(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID - 1); 463 464 verify(mMockedContext, times(1)).createConfigurationContext(any()); 465 466 // verify to call SubscriptionManager#getResourcesForSubId again for the same sub 467 mCellBroadcastHandler.getResources(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID - 1); 468 469 verify(mMockedContext, times(1)).createConfigurationContext(any()); 470 } 471 472 @Test 473 @SmallTest testConstructorRegistersReceiverWithExpectedFlag()474 public void testConstructorRegistersReceiverWithExpectedFlag() { 475 int expectedFlag = SdkLevel.isAtLeastT() ? Context.RECEIVER_EXPORTED : 0; 476 clearInvocations(mMockedContext); 477 478 CellBroadcastHandler cellBroadcastHandler = new CellBroadcastHandler( 479 "CellBroadcastHandlerUT", mMockedContext, mTestbleLooper.getLooper(), 480 mSendMessageFactory, mHandlerHelper); 481 482 verify(mMockedContext, times(1)).registerReceiver(any(), any(), eq(expectedFlag)); 483 cellBroadcastHandler.cleanup(); 484 } 485 486 /** 487 * Makes injecting a mock factory easy. 488 */ 489 static class CbSendMessageCalculatorFactoryFacade extends 490 CellBroadcastHandler.CbSendMessageCalculatorFactory { 491 492 @NonNull 493 private CellBroadcastHandler.CbSendMessageCalculatorFactory mUnderlyingFactory; 494 getUnderlyingFactory()495 @NonNull CellBroadcastHandler.CbSendMessageCalculatorFactory getUnderlyingFactory() { 496 return mUnderlyingFactory; 497 } 498 setUnderlyingFactory( @onNull final CellBroadcastHandler.CbSendMessageCalculatorFactory factory)499 void setUnderlyingFactory( 500 @NonNull final CellBroadcastHandler.CbSendMessageCalculatorFactory factory) { 501 mUnderlyingFactory = factory; 502 } 503 CbSendMessageCalculatorFactoryFacade()504 CbSendMessageCalculatorFactoryFacade() { 505 mUnderlyingFactory = new CellBroadcastHandler.CbSendMessageCalculatorFactory(); 506 } 507 508 @Override createNew(@onNull Context context, @NonNull List<CbGeoUtils.Geometry> fences)509 public CbSendMessageCalculator createNew(@NonNull Context context, 510 @NonNull List<CbGeoUtils.Geometry> fences) { 511 return mUnderlyingFactory.createNew(context, fences); 512 } 513 } 514 } 515