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