• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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