Fixed incorrect cell identity for geo scope support The geographical scope support defined by 3GPP TS 23.041 9.4.1.2.1 requires the cell identity including MCC/MNC, LAC/TAC, and CID from the registered network. The cell identity cell broadcast service retrieved is not correct. This fixed several issues in cell broadcast service. 1. Cell broadcast service used the MCC/MNC from the SIM, not from current registered network. 2. Cell broadcast service uses a ramdom cell info return by telephony manager's getAllCellInfo, which returns both registered and nearby cells. We should get cell identity from CS network, PS network, then nearby cells. 3. Cell broadcast service can only handle GSM cell identity, but not UMTS/LTE/NR cell identity. Fix: 157618105 Test: Manual & unit tests Change-Id: Id9310efc0bda7d5fa158d2be84782ce178c3a299
diff --git a/src/com/android/cellbroadcastservice/GsmCellBroadcastHandler.java b/src/com/android/cellbroadcastservice/GsmCellBroadcastHandler.java index 8449166..dec379d 100644 --- a/src/com/android/cellbroadcastservice/GsmCellBroadcastHandler.java +++ b/src/com/android/cellbroadcastservice/GsmCellBroadcastHandler.java
@@ -20,6 +20,7 @@ import static com.android.cellbroadcastservice.CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__UNEXPECTED_GSM_MESSAGE_TYPE_FROM_FWK; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; @@ -32,11 +33,18 @@ import android.os.SystemClock; import android.os.UserHandle; import android.provider.Telephony.CellBroadcasts; +import android.telephony.AccessNetworkConstants; import android.telephony.CbGeoUtils.Geometry; import android.telephony.CellBroadcastIntents; import android.telephony.CellIdentity; import android.telephony.CellIdentityGsm; +import android.telephony.CellIdentityLte; +import android.telephony.CellIdentityNr; +import android.telephony.CellIdentityTdscdma; +import android.telephony.CellIdentityWcdma; import android.telephony.CellInfo; +import android.telephony.NetworkRegistrationInfo; +import android.telephony.ServiceState; import android.telephony.SmsCbLocation; import android.telephony.SmsCbMessage; import android.telephony.SubscriptionManager; @@ -55,6 +63,8 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; import java.util.stream.IntStream; /** @@ -328,23 +338,93 @@ } } - // return the GSM cell location from the first GSM cell info - private Pair<Integer, Integer> getGsmLacAndCid() { - TelephonyManager tm = - (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); - List<CellInfo> infos = tm.getAllCellInfo(); - for (CellInfo info : infos) { - CellIdentity ci = info.getCellIdentity(); - if (ci instanceof CellIdentityGsm) { - CellIdentityGsm ciGsm = (CellIdentityGsm) ci; - int lac = ciGsm.getLac() != CellInfo.UNAVAILABLE ? ciGsm.getLac() : -1; - int cid = ciGsm.getCid() != CellInfo.UNAVAILABLE ? ciGsm.getCid() : -1; - return Pair.create(lac, cid); - } + /** + * Get LAC (location area code for GSM/UMTS) / TAC (tracking area code for LTE/NR) and CID + * (Cell id) from the cell identity + * + * @param ci Cell identity + * @return Pair of LAC and CID. {@code null} if not available. + */ + private @Nullable Pair<Integer, Integer> getLacAndCid(CellIdentity ci) { + if (ci == null) return null; + int lac = CellInfo.UNAVAILABLE; + int cid = CellInfo.UNAVAILABLE; + if (ci instanceof CellIdentityGsm) { + lac = ((CellIdentityGsm) ci).getLac(); + cid = ((CellIdentityGsm) ci).getCid(); + } else if (ci instanceof CellIdentityWcdma) { + lac = ((CellIdentityWcdma) ci).getLac(); + cid = ((CellIdentityWcdma) ci).getCid(); + } else if ((ci instanceof CellIdentityTdscdma)) { + lac = ((CellIdentityTdscdma) ci).getLac(); + cid = ((CellIdentityTdscdma) ci).getCid(); + } else if (ci instanceof CellIdentityLte) { + lac = ((CellIdentityLte) ci).getTac(); + cid = ((CellIdentityLte) ci).getCi(); + } else if (ci instanceof CellIdentityNr) { + lac = ((CellIdentityNr) ci).getTac(); + cid = ((CellIdentityNr) ci).getPci(); } + + if (lac != CellInfo.UNAVAILABLE || cid != CellInfo.UNAVAILABLE) { + return Pair.create(lac, cid); + } + + // When both LAC and CID are not available. return null; } + /** + * Get LAC (location area code for GSM/UMTS) / TAC (tracking area code for LTE/NR) and CID + * (Cell id) of the registered network. + * + * @param slotIndex SIM slot index + * + * @return lac and cid. {@code null} if cell identity is not available from the registered + * network. + */ + private @Nullable Pair<Integer, Integer> getLacAndCid(int slotIndex) { + TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); + tm.createForSubscriptionId(getSubIdForPhone(mContext, slotIndex)); + + ServiceState serviceState = tm.getServiceState(); + + if (serviceState == null) return null; + + // The list of cell identity to extract LAC and CID. The higher priority one will be added + // into the top of list. + List<CellIdentity> cellIdentityList = new ArrayList<>(); + + // CS network + NetworkRegistrationInfo nri = serviceState.getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + if (nri != null) { + cellIdentityList.add(nri.getCellIdentity()); + } + + // PS network + nri = serviceState.getNetworkRegistrationInfo( + NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); + if (nri != null) { + cellIdentityList.add(nri.getCellIdentity()); + } + + // When SIM is not inserted, we use the cell identity from the nearby cell. This is + // best effort. + List<CellInfo> infos = tm.getAllCellInfo(); + if (infos != null) { + cellIdentityList.addAll( + infos.stream().map(CellInfo::getCellIdentity).collect(Collectors.toList())); + } + + // Return the first valid LAC and CID from the list. + return cellIdentityList.stream() + .map(this::getLacAndCid) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } + /** * Handle 3GPP format SMS-CB message. @@ -373,35 +453,17 @@ TelephonyManager tm = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); tm.createForSubscriptionId(getSubIdForPhone(mContext, slotIndex)); - // TODO make a systemAPI for getNetworkOperatorForSlotIndex - String plmn = tm.getSimOperator(); + String plmn = tm.getNetworkOperator(); int lac = -1; int cid = -1; - Pair<Integer, Integer> lacAndCid = getGsmLacAndCid(); - // Check if GSM lac and cid are available. This is required to support - // dual-mode devices such as CDMA/LTE devices that require support for - // both 3GPP and 3GPP2 format messages + // Get LAC and CID of the current camped cell. + Pair<Integer, Integer> lacAndCid = getLacAndCid(slotIndex); if (lacAndCid != null) { lac = lacAndCid.first; cid = lacAndCid.second; } - SmsCbLocation location; - switch (header.getGeographicalScope()) { - case SmsCbMessage.GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE: - location = new SmsCbLocation(plmn, lac, -1); - break; - - case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE: - case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE: - location = new SmsCbLocation(plmn, lac, cid); - break; - - case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE: - default: - location = new SmsCbLocation(plmn, -1, -1); - break; - } + SmsCbLocation location = new SmsCbLocation(plmn, lac, cid); byte[][] pdus; int pageCount = header.getNumberOfPages(); @@ -456,7 +518,8 @@ return GsmSmsCbMessage.createSmsCbMessage(mContext, header, location, pdus, slotIndex); } catch (RuntimeException e) { - final String errorMessage = "Error in decoding SMS CB pdu" + e.toString(); + final String errorMessage = "Error in decoding SMS CB pdu: " + e.toString(); + e.printStackTrace(); loge(errorMessage); CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR, CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_INVALID_PDU, errorMessage);
diff --git a/tests/src/com/android/cellbroadcastservice/tests/CellBroadcastServiceTestBase.java b/tests/src/com/android/cellbroadcastservice/tests/CellBroadcastServiceTestBase.java index f1c7430..dfdb2e2 100644 --- a/tests/src/com/android/cellbroadcastservice/tests/CellBroadcastServiceTestBase.java +++ b/tests/src/com/android/cellbroadcastservice/tests/CellBroadcastServiceTestBase.java
@@ -126,6 +126,8 @@ doReturn(powerManager).when(mMockedContext).getSystemService(Context.POWER_SERVICE); doReturn(mMockedTelephonyManager).when(mMockedContext) .getSystemService(Context.TELEPHONY_SERVICE); + doReturn(Context.TELEPHONY_SERVICE).when(mMockedContext) + .getSystemServiceName(TelephonyManager.class); doReturn(mMockedSubscriptionManager).when(mMockedContext) .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); doReturn(mMockedLocationManager).when(mMockedContext)
diff --git a/tests/src/com/android/cellbroadcastservice/tests/GsmCellBroadcastHandlerTest.java b/tests/src/com/android/cellbroadcastservice/tests/GsmCellBroadcastHandlerTest.java index a124faa..e94aabb 100644 --- a/tests/src/com/android/cellbroadcastservice/tests/GsmCellBroadcastHandlerTest.java +++ b/tests/src/com/android/cellbroadcastservice/tests/GsmCellBroadcastHandlerTest.java
@@ -34,8 +34,14 @@ import android.os.Bundle; import android.provider.Settings; import android.provider.Telephony; +import android.telephony.AccessNetworkConstants; +import android.telephony.CellIdentityLte; +import android.telephony.NetworkRegistrationInfo; +import android.telephony.ServiceState; import android.telephony.SmsCbCmasInfo; +import android.telephony.SmsCbLocation; import android.telephony.SmsCbMessage; +import android.telephony.TelephonyManager; import android.test.mock.MockContentProvider; import android.test.mock.MockContentResolver; import android.test.suitebuilder.annotation.SmallTest; @@ -244,4 +250,43 @@ verify(mMockedContext, never()).sendOrderedBroadcast(any(), anyString(), anyString(), any(), any(), anyInt(), any(), any()); } + + @Test + @SmallTest + public void testSmsCbLocation() { + final byte[] pdu = hexStringToBytes("01111B40110101C366701A09368545692408000000000000000" + + "00000000000000000000000000000000000000000000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000B"); + + final String fakePlmn = "310999"; + final int fakeTac = 1234; + final int fakeCid = 5678; + + doReturn(fakePlmn).when(mMockedTelephonyManager).getNetworkOperator(); + ServiceState ss = Mockito.mock(ServiceState.class); + doReturn(ss).when(mMockedTelephonyManager).getServiceState(); + NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder() + .setDomain(NetworkRegistrationInfo.DOMAIN_CS) + .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE) + .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN) + .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME) + .setCellIdentity(new CellIdentityLte(0, 0, fakeCid, 0, fakeTac)) + .build(); + doReturn(nri).when(ss).getNetworkRegistrationInfo(anyInt(), anyInt()); + + mGsmCellBroadcastHandler.onGsmCellBroadcastSms(0, pdu); + mTestableLooper.processAllMessages(); + + ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mMockedContext).sendOrderedBroadcast(intentCaptor.capture(), any(), + (Bundle) any(), any(), any(), anyInt(), any(), any()); + Intent intent = intentCaptor.getValue(); + assertEquals(Telephony.Sms.Intents.ACTION_SMS_EMERGENCY_CB_RECEIVED, intent.getAction()); + SmsCbMessage msg = intent.getParcelableExtra("message"); + + SmsCbLocation location = msg.getLocation(); + assertEquals(fakePlmn, location.getPlmn()); + assertEquals(fakeTac, location.getLac()); + assertEquals(fakeCid, location.getCid()); + } }