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