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