1 /* 2 * Copyright (C) 2013 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; 18 19 import static com.android.cellbroadcastservice.CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_INVALID_PDU; 20 import static com.android.cellbroadcastservice.CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__UNEXPECTED_GSM_MESSAGE_TYPE_FROM_FWK; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.content.BroadcastReceiver; 25 import android.content.ContentResolver; 26 import android.content.ContentUris; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.content.res.Resources; 31 import android.database.Cursor; 32 import android.net.Uri; 33 import android.os.Looper; 34 import android.os.Message; 35 import android.os.SystemClock; 36 import android.os.UserHandle; 37 import android.provider.Telephony.CellBroadcasts; 38 import android.telephony.AccessNetworkConstants; 39 import android.telephony.CarrierConfigManager; 40 import android.telephony.CbGeoUtils; 41 import android.telephony.CbGeoUtils.Geometry; 42 import android.telephony.CellBroadcastIntents; 43 import android.telephony.CellIdentity; 44 import android.telephony.CellIdentityGsm; 45 import android.telephony.CellIdentityLte; 46 import android.telephony.CellIdentityNr; 47 import android.telephony.CellIdentityTdscdma; 48 import android.telephony.CellIdentityWcdma; 49 import android.telephony.CellInfo; 50 import android.telephony.NetworkRegistrationInfo; 51 import android.telephony.PhoneStateListener; 52 import android.telephony.ServiceState; 53 import android.telephony.SmsCbLocation; 54 import android.telephony.SmsCbMessage; 55 import android.telephony.SubscriptionInfo; 56 import android.telephony.SubscriptionManager; 57 import android.telephony.TelephonyManager; 58 import android.text.TextUtils; 59 import android.text.format.DateUtils; 60 import android.util.Pair; 61 import android.util.SparseArray; 62 63 import com.android.cellbroadcastservice.GsmSmsCbMessage.GeoFencingTriggerMessage; 64 import com.android.cellbroadcastservice.GsmSmsCbMessage.GeoFencingTriggerMessage.CellBroadcastIdentity; 65 import com.android.internal.annotations.VisibleForTesting; 66 67 import java.text.DateFormat; 68 import java.util.ArrayList; 69 import java.util.Arrays; 70 import java.util.HashMap; 71 import java.util.Iterator; 72 import java.util.List; 73 import java.util.Objects; 74 import java.util.stream.Collectors; 75 import java.util.stream.IntStream; 76 77 /** 78 * Handler for 3GPP format Cell Broadcasts. Parent class can also handle CDMA Cell Broadcasts. 79 */ 80 public class GsmCellBroadcastHandler extends CellBroadcastHandler { 81 private static final boolean VDBG = false; // log CB PDU data 82 83 /** Indicates that a message is not displayed. */ 84 private static final String MESSAGE_NOT_DISPLAYED = "0"; 85 86 /** 87 * Intent sent from cellbroadcastreceiver to notify cellbroadcastservice that area info update 88 * is disabled/enabled. 89 */ 90 private static final String ACTION_AREA_UPDATE_ENABLED = 91 "com.android.cellbroadcastreceiver.action.AREA_UPDATE_INFO_ENABLED"; 92 93 /** 94 * The extra for cell ACTION_AREA_UPDATE_ENABLED enable/disable 95 */ 96 private static final String EXTRA_ENABLE = "enable"; 97 98 /** 99 * This permission is only granted to the cellbroadcast mainline module and thus can be 100 * used for permission check within CBR and CBS. 101 */ 102 private static final String CBR_MODULE_PERMISSION = 103 "com.android.cellbroadcastservice.FULL_ACCESS_CELL_BROADCAST_HISTORY"; 104 105 private final SparseArray<String> mAreaInfos = new SparseArray<>(); 106 107 /** 108 * Used to store ServiceStateListeners for each active slot 109 */ 110 private final SparseArray<ServiceStateListener> mServiceStateListener = new SparseArray<>(); 111 112 /** This map holds incomplete concatenated messages waiting for assembly. */ 113 private final HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap = 114 new HashMap<>(4); 115 116 @VisibleForTesting GsmCellBroadcastHandler(Context context, Looper looper, CbSendMessageCalculatorFactory cbSendMessageCalculatorFactory, CellBroadcastHandler.HandlerHelper handlerHelper)117 public GsmCellBroadcastHandler(Context context, Looper looper, 118 CbSendMessageCalculatorFactory cbSendMessageCalculatorFactory, 119 CellBroadcastHandler.HandlerHelper handlerHelper) { 120 super("GsmCellBroadcastHandler", context, looper, cbSendMessageCalculatorFactory, 121 handlerHelper); 122 mContext.registerReceiver(mGsmReceiver, new IntentFilter(ACTION_AREA_UPDATE_ENABLED), 123 CBR_MODULE_PERMISSION, null); 124 // Some OEMs want us to reset the area info updates when going out of service 125 // (Okay to use res for slot 0 here because this should not be carrier specific) 126 if (getResourcesForSlot(0).getBoolean(R.bool.reset_area_info_on_oos)) { 127 mContext.registerReceiver(mGsmReceiver, 128 new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED), null, 129 null); 130 } 131 } 132 133 /** 134 * Constructor used only for tests. This constructor allows the caller to pass in resources 135 * and a subId to be put into the resources cache before getResourcesForSlot called (this is 136 * needed for unit tests to prevent 137 */ 138 @VisibleForTesting GsmCellBroadcastHandler(Context context, Looper looper, CbSendMessageCalculatorFactory cbSendMessageCalculatorFactory, CellBroadcastHandler.HandlerHelper handlerHelper, Resources resources, int subId)139 public GsmCellBroadcastHandler(Context context, Looper looper, 140 CbSendMessageCalculatorFactory cbSendMessageCalculatorFactory, 141 CellBroadcastHandler.HandlerHelper handlerHelper, Resources resources, int subId) { 142 super("GsmCellBroadcastHandler", context, looper, cbSendMessageCalculatorFactory, 143 handlerHelper); 144 mContext.registerReceiver(mGsmReceiver, new IntentFilter(ACTION_AREA_UPDATE_ENABLED), 145 CBR_MODULE_PERMISSION, null); 146 147 // set the resources cache here for unit tests 148 mResourcesCache.put(subId, resources); 149 150 // Some OEMs want us to reset the area info updates when going out of service 151 // (Okay to use res for slot 0 here because this should not be carrier specific) 152 if (getResourcesForSlot(0).getBoolean(R.bool.reset_area_info_on_oos)) { 153 mContext.registerReceiver(mGsmReceiver, 154 new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED), null, 155 null); 156 } 157 } 158 159 @Override cleanup()160 public void cleanup() { 161 log("cleanup"); 162 TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); 163 int size = mServiceStateListener.size(); 164 for (int i = 0; i < size; i++) { 165 tm.listen(mServiceStateListener.valueAt(i), PhoneStateListener.LISTEN_NONE); 166 } 167 mContext.unregisterReceiver(mGsmReceiver); 168 super.cleanup(); 169 } 170 registerServiceStateListeners()171 private void registerServiceStateListeners() { 172 // register for all active slots 173 TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); 174 SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class); 175 for (int slotId = 0; slotId < tm.getActiveModemCount(); slotId++) { 176 SubscriptionInfo info = sm.getActiveSubscriptionInfoForSimSlotIndex(slotId); 177 if (info != null) { 178 int subId = info.getSubscriptionId(); 179 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 180 mServiceStateListener.put(slotId, new ServiceStateListener(subId, slotId)); 181 tm.createForSubscriptionId(subId).listen(mServiceStateListener.get(slotId), 182 PhoneStateListener.LISTEN_SERVICE_STATE); 183 } 184 } 185 } 186 } 187 188 private class ServiceStateListener extends PhoneStateListener { 189 // subId is not needed for clearing area info, only used for debugging purposes 190 private int mSubId; 191 private int mSlotId; 192 ServiceStateListener(int subId, int slotId)193 ServiceStateListener(int subId, int slotId) { 194 mSubId = subId; 195 mSlotId = slotId; 196 } 197 198 @Override onServiceStateChanged(@onNull ServiceState serviceState)199 public void onServiceStateChanged(@NonNull ServiceState serviceState) { 200 int state = serviceState.getState(); 201 if (state == ServiceState.STATE_POWER_OFF 202 || state == ServiceState.STATE_OUT_OF_SERVICE) { 203 synchronized (mAreaInfos) { 204 if (mAreaInfos.contains(mSlotId)) { 205 log("OOS mSubId=" + mSubId + " mSlotId=" + mSlotId 206 + ", clearing area infos"); 207 mAreaInfos.remove(mSlotId); 208 } 209 } 210 } 211 } 212 } 213 214 @Override onQuitting()215 protected void onQuitting() { 216 super.onQuitting(); // release wakelock 217 } 218 219 /** 220 * Handle a GSM cell broadcast message passed from the telephony framework. 221 * @param message 222 */ onGsmCellBroadcastSms(int slotIndex, byte[] message)223 public void onGsmCellBroadcastSms(int slotIndex, byte[] message) { 224 sendMessage(EVENT_NEW_SMS_MESSAGE, slotIndex, -1, message); 225 } 226 227 /** 228 * Get the area information 229 * 230 * @param slotIndex SIM slot index 231 * @return The area information 232 */ 233 @NonNull getCellBroadcastAreaInfo(int slotIndex)234 public String getCellBroadcastAreaInfo(int slotIndex) { 235 String info; 236 synchronized (mAreaInfos) { 237 info = mAreaInfos.get(slotIndex); 238 } 239 return info == null ? "" : info; 240 } 241 242 /** 243 * Create a new CellBroadcastHandler. 244 * @param context the context to use for dispatching Intents 245 * @return the new handler 246 */ makeGsmCellBroadcastHandler(Context context)247 public static GsmCellBroadcastHandler makeGsmCellBroadcastHandler(Context context) { 248 GsmCellBroadcastHandler handler = new GsmCellBroadcastHandler(context, Looper.myLooper(), 249 new CbSendMessageCalculatorFactory(), null); 250 handler.start(); 251 return handler; 252 } 253 getResourcesForSlot(int slotIndex)254 private Resources getResourcesForSlot(int slotIndex) { 255 SubscriptionManager subMgr = mContext.getSystemService(SubscriptionManager.class); 256 int[] subIds = subMgr.getSubscriptionIds(slotIndex); 257 Resources res; 258 if (subIds != null) { 259 res = getResources(subIds[0]); 260 } else { 261 res = getResources(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID); 262 } 263 return res; 264 } 265 266 /** 267 * Find the cell broadcast messages specify by the geo-fencing trigger message and perform a 268 * geo-fencing check for these messages. 269 * @param geoFencingTriggerMessage the trigger message 270 * 271 * @return {@code True} if geo-fencing is need for some cell broadcast message. 272 */ handleGeoFencingTriggerMessage( GeoFencingTriggerMessage geoFencingTriggerMessage, int slotIndex)273 private boolean handleGeoFencingTriggerMessage( 274 GeoFencingTriggerMessage geoFencingTriggerMessage, int slotIndex) { 275 final List<SmsCbMessage> cbMessages = new ArrayList<>(); 276 final List<Uri> cbMessageUris = new ArrayList<>(); 277 278 Resources res = getResourcesForSlot(slotIndex); 279 280 // Only consider the cell broadcast received within 24 hours. 281 long lastReceivedTime = System.currentTimeMillis() - DateUtils.DAY_IN_MILLIS; 282 283 // Some carriers require reset duplication detection after airplane mode or reboot. 284 if (res.getBoolean(R.bool.reset_on_power_cycle_or_airplane_mode)) { 285 lastReceivedTime = Long.max(lastReceivedTime, mLastAirplaneModeTime); 286 lastReceivedTime = Long.max(lastReceivedTime, 287 System.currentTimeMillis() - SystemClock.elapsedRealtime()); 288 } 289 290 // Find the cell broadcast message identify by the message identifier and serial number 291 // and was not displayed. 292 String where = CellBroadcasts.SERVICE_CATEGORY + "=? AND " 293 + CellBroadcasts.SERIAL_NUMBER + "=? AND " 294 + CellBroadcasts.MESSAGE_DISPLAYED + "=? AND " 295 + CellBroadcasts.RECEIVED_TIME + ">?"; 296 297 ContentResolver resolver = mContext.getContentResolver(); 298 for (CellBroadcastIdentity identity : geoFencingTriggerMessage.cbIdentifiers) { 299 try (Cursor cursor = resolver.query(CellBroadcasts.CONTENT_URI, 300 CellBroadcastProvider.QUERY_COLUMNS, 301 where, 302 new String[] { Integer.toString(identity.messageIdentifier), 303 Integer.toString(identity.serialNumber), MESSAGE_NOT_DISPLAYED, 304 Long.toString(lastReceivedTime) }, 305 null /* sortOrder */)) { 306 if (cursor != null) { 307 while (cursor.moveToNext()) { 308 cbMessages.add(SmsCbMessage.createFromCursor(cursor)); 309 cbMessageUris.add(ContentUris.withAppendedId(CellBroadcasts.CONTENT_URI, 310 cursor.getInt(cursor.getColumnIndex(CellBroadcasts._ID)))); 311 } 312 } 313 } 314 } 315 316 log("Found " + cbMessages.size() + " not broadcasted messages since " 317 + DateFormat.getDateTimeInstance().format(lastReceivedTime)); 318 319 List<Geometry> commonBroadcastArea = new ArrayList<>(); 320 if (geoFencingTriggerMessage.shouldShareBroadcastArea()) { 321 for (SmsCbMessage msg : cbMessages) { 322 if (msg.getGeometries() != null) { 323 commonBroadcastArea.addAll(msg.getGeometries()); 324 } 325 } 326 } 327 328 // ATIS doesn't specify the geo fencing maximum wait time for the cell broadcasts specified 329 // in geo fencing trigger message. We will pick the largest maximum wait time among these 330 // cell broadcasts. 331 int maxWaitingTimeSec = 0; 332 for (SmsCbMessage msg : cbMessages) { 333 maxWaitingTimeSec = Math.max(maxWaitingTimeSec, getMaxLocationWaitingTime(msg)); 334 } 335 336 if (DBG) { 337 logd("Geo-fencing trigger message = " + geoFencingTriggerMessage); 338 for (SmsCbMessage msg : cbMessages) { 339 logd(msg.toString()); 340 } 341 } 342 343 if (cbMessages.isEmpty()) { 344 if (DBG) logd("No CellBroadcast message need to be broadcasted"); 345 return false; 346 } 347 348 //Create calculators for each message that will be reused on every location update. 349 CbSendMessageCalculator[] calculators = new CbSendMessageCalculator[cbMessages.size()]; 350 for (int i = 0; i < cbMessages.size(); i++) { 351 List<Geometry> broadcastArea = !commonBroadcastArea.isEmpty() 352 ? commonBroadcastArea : cbMessages.get(i).getGeometries(); 353 if (broadcastArea == null) { 354 broadcastArea = new ArrayList<>(); 355 } 356 calculators[i] = mCbSendMessageCalculatorFactory.createNew(mContext, broadcastArea); 357 } 358 359 requestLocationUpdate(new LocationUpdateCallback() { 360 @Override 361 public void onLocationUpdate(@NonNull CbGeoUtils.LatLng location, 362 float accuracy) { 363 if (VDBG) { 364 logd("onLocationUpdate: location=" + location 365 + ", acc=" + accuracy + ". "); 366 } 367 for (int i = 0; i < cbMessages.size(); i++) { 368 CbSendMessageCalculator calculator = calculators[i]; 369 if (calculator.getFences().isEmpty()) { 370 broadcastGeofenceMessage(cbMessages.get(i), cbMessageUris.get(i), 371 slotIndex, calculator); 372 } else { 373 performGeoFencing(cbMessages.get(i), cbMessageUris.get(i), 374 calculator, location, slotIndex, accuracy); 375 } 376 } 377 378 boolean containsAnyAmbiguousMessages = Arrays.stream(calculators) 379 .anyMatch(c -> isMessageInAmbiguousState(c)); 380 if (!containsAnyAmbiguousMessages) { 381 cancelLocationRequest(); 382 } 383 } 384 385 @Override 386 public void onLocationUnavailable() { 387 for (int i = 0; i < cbMessages.size(); i++) { 388 GsmCellBroadcastHandler.this.onLocationUnavailable(calculators[i], 389 cbMessages.get(i), cbMessageUris.get(i), slotIndex); 390 } 391 } 392 }, maxWaitingTimeSec); 393 return true; 394 } 395 396 /** 397 * Process area info message. 398 * 399 * @param slotIndex SIM slot index 400 * @param message Cell broadcast message 401 * @return {@code true} if the mssage is an area info message and got processed correctly, 402 * otherwise {@code false}. 403 */ handleAreaInfoMessage(int slotIndex, SmsCbMessage message)404 private boolean handleAreaInfoMessage(int slotIndex, SmsCbMessage message) { 405 Resources res = getResources(message.getSubscriptionId()); 406 int[] areaInfoChannels = res.getIntArray(R.array.area_info_channels); 407 408 // Check area info message 409 if (IntStream.of(areaInfoChannels).anyMatch( 410 x -> x == message.getServiceCategory())) { 411 synchronized (mAreaInfos) { 412 String info = mAreaInfos.get(slotIndex); 413 if (TextUtils.equals(info, message.getMessageBody())) { 414 // Message is a duplicate 415 return true; 416 } 417 mAreaInfos.put(slotIndex, message.getMessageBody()); 418 } 419 420 String[] pkgs = mContext.getResources().getStringArray( 421 R.array.config_area_info_receiver_packages); 422 for (String pkg : pkgs) { 423 Intent intent = new Intent(CellBroadcastIntents.ACTION_AREA_INFO_UPDATED); 424 intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, slotIndex); 425 intent.setPackage(pkg); 426 mContext.sendBroadcastAsUser(intent, UserHandle.ALL, 427 android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE); 428 } 429 return true; 430 } 431 432 // This is not an area info message. 433 return false; 434 } 435 436 /** 437 * Handle 3GPP-format Cell Broadcast messages sent from radio. 438 * 439 * @param message the message to process 440 * @return true if need to wait for geo-fencing or an ordered broadcast was sent. 441 */ 442 @Override handleSmsMessage(Message message)443 protected boolean handleSmsMessage(Message message) { 444 // For GSM, message.obj should be a byte[] 445 int slotIndex = message.arg1; 446 if (message.obj instanceof byte[]) { 447 byte[] pdu = (byte[]) message.obj; 448 SmsCbHeader header = createSmsCbHeader(pdu); 449 if (header == null) return false; 450 451 log("header=" + header); 452 if (header.getServiceCategory() == SmsCbConstants.MESSAGE_ID_CMAS_GEO_FENCING_TRIGGER) { 453 GeoFencingTriggerMessage triggerMessage = 454 GsmSmsCbMessage.createGeoFencingTriggerMessage(pdu); 455 if (triggerMessage != null) { 456 return handleGeoFencingTriggerMessage(triggerMessage, slotIndex); 457 } 458 } else { 459 SmsCbMessage cbMessage = handleGsmBroadcastSms(header, pdu, slotIndex); 460 if (cbMessage != null) { 461 if (isDuplicate(cbMessage)) { 462 CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_FILTERED, 463 CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_FILTERED__TYPE__GSM, 464 CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_FILTERED__FILTER__DUPLICATE_MESSAGE); 465 return false; 466 } 467 468 if (handleAreaInfoMessage(slotIndex, cbMessage)) { 469 log("Channel " + cbMessage.getServiceCategory() + " message processed"); 470 CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_FILTERED, 471 CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_FILTERED__TYPE__GSM, 472 CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_FILTERED__FILTER__AREA_INFO_MESSAGE); 473 return false; 474 } 475 476 handleBroadcastSms(cbMessage); 477 return true; 478 } 479 if (VDBG) log("Not handled GSM broadcasts."); 480 } 481 } else { 482 final String errorMessage = "handleSmsMessage for GSM got object of type: " 483 + message.obj.getClass().getName(); 484 loge(errorMessage); 485 CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR, 486 CELL_BROADCAST_MESSAGE_ERROR__TYPE__UNEXPECTED_GSM_MESSAGE_TYPE_FROM_FWK, 487 errorMessage); 488 } 489 if (message.obj instanceof SmsCbMessage) { 490 return super.handleSmsMessage(message); 491 } else { 492 return false; 493 } 494 } 495 496 /** 497 * Get LAC (location area code for GSM/UMTS) / TAC (tracking area code for LTE/NR) and CID 498 * (Cell id) from the cell identity 499 * 500 * @param ci Cell identity 501 * @return Pair of LAC and CID. {@code null} if not available. 502 */ getLacAndCid(CellIdentity ci)503 private @Nullable Pair<Integer, Integer> getLacAndCid(CellIdentity ci) { 504 if (ci == null) return null; 505 int lac = CellInfo.UNAVAILABLE; 506 int cid = CellInfo.UNAVAILABLE; 507 if (ci instanceof CellIdentityGsm) { 508 lac = ((CellIdentityGsm) ci).getLac(); 509 cid = ((CellIdentityGsm) ci).getCid(); 510 } else if (ci instanceof CellIdentityWcdma) { 511 lac = ((CellIdentityWcdma) ci).getLac(); 512 cid = ((CellIdentityWcdma) ci).getCid(); 513 } else if ((ci instanceof CellIdentityTdscdma)) { 514 lac = ((CellIdentityTdscdma) ci).getLac(); 515 cid = ((CellIdentityTdscdma) ci).getCid(); 516 } else if (ci instanceof CellIdentityLte) { 517 lac = ((CellIdentityLte) ci).getTac(); 518 cid = ((CellIdentityLte) ci).getCi(); 519 } else if (ci instanceof CellIdentityNr) { 520 lac = ((CellIdentityNr) ci).getTac(); 521 cid = ((CellIdentityNr) ci).getPci(); 522 } 523 524 if (lac != CellInfo.UNAVAILABLE || cid != CellInfo.UNAVAILABLE) { 525 return Pair.create(lac, cid); 526 } 527 528 // When both LAC and CID are not available. 529 return null; 530 } 531 532 /** 533 * Get LAC (location area code for GSM/UMTS) / TAC (tracking area code for LTE/NR) and CID 534 * (Cell id) of the registered network. 535 * 536 * @param slotIndex SIM slot index 537 * 538 * @return lac and cid. {@code null} if cell identity is not available from the registered 539 * network. 540 */ getLacAndCid(int slotIndex)541 private @Nullable Pair<Integer, Integer> getLacAndCid(int slotIndex) { 542 TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); 543 tm.createForSubscriptionId(getSubIdForPhone(mContext, slotIndex)); 544 545 ServiceState serviceState = tm.getServiceState(); 546 547 if (serviceState == null) return null; 548 549 // The list of cell identity to extract LAC and CID. The higher priority one will be added 550 // into the top of list. 551 List<CellIdentity> cellIdentityList = new ArrayList<>(); 552 553 // CS network 554 NetworkRegistrationInfo nri = serviceState.getNetworkRegistrationInfo( 555 NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); 556 if (nri != null) { 557 cellIdentityList.add(nri.getCellIdentity()); 558 } 559 560 // PS network 561 nri = serviceState.getNetworkRegistrationInfo( 562 NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); 563 if (nri != null) { 564 cellIdentityList.add(nri.getCellIdentity()); 565 } 566 567 // When SIM is not inserted, we use the cell identity from the nearby cell. This is 568 // best effort. 569 List<CellInfo> infos = tm.getAllCellInfo(); 570 if (infos != null) { 571 cellIdentityList.addAll( 572 infos.stream().map(CellInfo::getCellIdentity).collect(Collectors.toList())); 573 } 574 575 // Return the first valid LAC and CID from the list. 576 return cellIdentityList.stream() 577 .map(this::getLacAndCid) 578 .filter(Objects::nonNull) 579 .findFirst() 580 .orElse(null); 581 } 582 583 584 /** 585 * Handle 3GPP format SMS-CB message. 586 * @param header the cellbroadcast header. 587 * @param receivedPdu the received PDUs as a byte[] 588 */ handleGsmBroadcastSms(SmsCbHeader header, byte[] receivedPdu, int slotIndex)589 private SmsCbMessage handleGsmBroadcastSms(SmsCbHeader header, byte[] receivedPdu, 590 int slotIndex) { 591 try { 592 if (VDBG) { 593 int pduLength = receivedPdu.length; 594 for (int i = 0; i < pduLength; i += 8) { 595 StringBuilder sb = new StringBuilder("SMS CB pdu data: "); 596 for (int j = i; j < i + 8 && j < pduLength; j++) { 597 int b = receivedPdu[j] & 0xff; 598 if (b < 0x10) { 599 sb.append('0'); 600 } 601 sb.append(Integer.toHexString(b)).append(' '); 602 } 603 log(sb.toString()); 604 } 605 } 606 607 if (VDBG) log("header=" + header); 608 TelephonyManager tm = 609 (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); 610 tm.createForSubscriptionId(getSubIdForPhone(mContext, slotIndex)); 611 String plmn = tm.getNetworkOperator(); 612 int lac = -1; 613 int cid = -1; 614 // Get LAC and CID of the current camped cell. 615 Pair<Integer, Integer> lacAndCid = getLacAndCid(slotIndex); 616 if (lacAndCid != null) { 617 lac = lacAndCid.first; 618 cid = lacAndCid.second; 619 } 620 621 SmsCbLocation location = new SmsCbLocation(plmn, lac, cid); 622 623 byte[][] pdus; 624 int pageCount = header.getNumberOfPages(); 625 if (pageCount > 1) { 626 // Multi-page message 627 SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, location); 628 629 // Try to find other pages of the same message 630 pdus = mSmsCbPageMap.get(concatInfo); 631 632 if (pdus == null) { 633 // This is the first page of this message, make room for all 634 // pages and keep until complete 635 pdus = new byte[pageCount][]; 636 637 mSmsCbPageMap.put(concatInfo, pdus); 638 } 639 640 if (VDBG) log("pdus size=" + pdus.length); 641 // Page parameter is one-based 642 pdus[header.getPageIndex() - 1] = receivedPdu; 643 644 for (byte[] pdu : pdus) { 645 if (pdu == null) { 646 // Still missing pages, exit 647 log("still missing pdu"); 648 return null; 649 } 650 } 651 652 // Message complete, remove and dispatch 653 mSmsCbPageMap.remove(concatInfo); 654 } else { 655 // Single page message 656 pdus = new byte[1][]; 657 pdus[0] = receivedPdu; 658 } 659 660 // Remove messages that are out of scope to prevent the map from 661 // growing indefinitely, containing incomplete messages that were 662 // never assembled 663 Iterator<SmsCbConcatInfo> iter = mSmsCbPageMap.keySet().iterator(); 664 665 while (iter.hasNext()) { 666 SmsCbConcatInfo info = iter.next(); 667 668 if (!info.matchesLocation(plmn, lac, cid)) { 669 iter.remove(); 670 } 671 } 672 673 return GsmSmsCbMessage.createSmsCbMessage(mContext, header, location, pdus, slotIndex); 674 675 } catch (RuntimeException e) { 676 final String errorMessage = "Error in decoding SMS CB pdu: " + e.toString(); 677 e.printStackTrace(); 678 loge(errorMessage); 679 CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR, 680 CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_INVALID_PDU, errorMessage); 681 return null; 682 } 683 } 684 createSmsCbHeader(byte[] bytes)685 private SmsCbHeader createSmsCbHeader(byte[] bytes) { 686 try { 687 return new SmsCbHeader(bytes); 688 } catch (Exception ex) { 689 loge("Can't create SmsCbHeader, ex = " + ex.toString()); 690 return null; 691 } 692 } 693 694 private BroadcastReceiver mGsmReceiver = new BroadcastReceiver() { 695 @Override 696 public void onReceive(Context context, Intent intent) { 697 switch (intent.getAction()) { 698 case ACTION_AREA_UPDATE_ENABLED: 699 boolean enabled = intent.getBooleanExtra(EXTRA_ENABLE, false); 700 log("Area update info enabled: " + enabled); 701 String[] pkgs = mContext.getResources().getStringArray( 702 R.array.config_area_info_receiver_packages); 703 // set mAreaInfo to null before sending the broadcast to listeners to avoid 704 // possible race condition. 705 if (!enabled) { 706 mAreaInfos.clear(); 707 log("Area update info disabled, clear areaInfo"); 708 } 709 // notify receivers. the setting is singleton for msim devices, if areaInfo 710 // toggle was off/on, it will applies for all slots/subscriptions. 711 TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); 712 for(int i = 0; i < tm.getActiveModemCount(); i++) { 713 for (String pkg : pkgs) { 714 Intent areaInfoIntent = new Intent( 715 CellBroadcastIntents.ACTION_AREA_INFO_UPDATED); 716 areaInfoIntent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, i); 717 areaInfoIntent.putExtra(EXTRA_ENABLE, enabled); 718 areaInfoIntent.setPackage(pkg); 719 mContext.sendBroadcastAsUser(areaInfoIntent, UserHandle.ALL, 720 android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE); 721 } 722 } 723 break; 724 case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED: 725 // (Okay to use res for slot 0 here because this should not be carrier specific) 726 if (getResourcesForSlot(0).getBoolean(R.bool.reset_area_info_on_oos)) { 727 registerServiceStateListeners(); 728 } 729 break; 730 default: 731 log("Unhandled broadcast " + intent.getAction()); 732 } 733 } 734 }; 735 736 /** 737 * Holds all info about a message page needed to assemble a complete concatenated message. 738 */ 739 @VisibleForTesting 740 public static final class SmsCbConcatInfo { 741 742 private final SmsCbHeader mHeader; 743 private final SmsCbLocation mLocation; 744 745 @VisibleForTesting SmsCbConcatInfo(SmsCbHeader header, SmsCbLocation location)746 public SmsCbConcatInfo(SmsCbHeader header, SmsCbLocation location) { 747 mHeader = header; 748 mLocation = location; 749 } 750 751 @Override hashCode()752 public int hashCode() { 753 return (mHeader.getSerialNumber() * 31) + mLocation.hashCode(); 754 } 755 756 @Override equals(Object obj)757 public boolean equals(Object obj) { 758 if (obj instanceof SmsCbConcatInfo) { 759 SmsCbConcatInfo other = (SmsCbConcatInfo) obj; 760 761 // Two pages match if they have the same serial number (which includes the 762 // geographical scope and update number), and both pages belong to the same 763 // location (PLMN, plus LAC and CID if these are part of the geographical scope). 764 return mHeader.getSerialNumber() == other.mHeader.getSerialNumber() 765 && mLocation.equals(other.mLocation); 766 } 767 768 return false; 769 } 770 771 /** 772 * Compare the location code for this message to the current location code. The match is 773 * relative to the geographical scope of the message, which determines whether the LAC 774 * and Cell ID are saved in mLocation or set to -1 to match all values. 775 * 776 * @param plmn the current PLMN 777 * @param lac the current Location Area (GSM) or Service Area (UMTS) 778 * @param cid the current Cell ID 779 * @return true if this message is valid for the current location; false otherwise 780 */ matchesLocation(String plmn, int lac, int cid)781 public boolean matchesLocation(String plmn, int lac, int cid) { 782 return mLocation.isInLocationArea(plmn, lac, cid); 783 } 784 } 785 } 786