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.ContentResolver;
25 import android.content.ContentUris;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.res.Resources;
29 import android.database.Cursor;
30 import android.net.Uri;
31 import android.os.Looper;
32 import android.os.Message;
33 import android.os.SystemClock;
34 import android.os.UserHandle;
35 import android.provider.Telephony.CellBroadcasts;
36 import android.telephony.AccessNetworkConstants;
37 import android.telephony.CbGeoUtils.Geometry;
38 import android.telephony.CellBroadcastIntents;
39 import android.telephony.CellIdentity;
40 import android.telephony.CellIdentityGsm;
41 import android.telephony.CellIdentityLte;
42 import android.telephony.CellIdentityNr;
43 import android.telephony.CellIdentityTdscdma;
44 import android.telephony.CellIdentityWcdma;
45 import android.telephony.CellInfo;
46 import android.telephony.NetworkRegistrationInfo;
47 import android.telephony.ServiceState;
48 import android.telephony.SmsCbLocation;
49 import android.telephony.SmsCbMessage;
50 import android.telephony.SubscriptionManager;
51 import android.telephony.TelephonyManager;
52 import android.text.TextUtils;
53 import android.text.format.DateUtils;
54 import android.util.Pair;
55 import android.util.SparseArray;
56 
57 import com.android.cellbroadcastservice.GsmSmsCbMessage.GeoFencingTriggerMessage;
58 import com.android.cellbroadcastservice.GsmSmsCbMessage.GeoFencingTriggerMessage.CellBroadcastIdentity;
59 import com.android.internal.annotations.VisibleForTesting;
60 
61 import java.text.DateFormat;
62 import java.util.ArrayList;
63 import java.util.HashMap;
64 import java.util.Iterator;
65 import java.util.List;
66 import java.util.Objects;
67 import java.util.stream.Collectors;
68 import java.util.stream.IntStream;
69 
70 /**
71  * Handler for 3GPP format Cell Broadcasts. Parent class can also handle CDMA Cell Broadcasts.
72  */
73 public class GsmCellBroadcastHandler extends CellBroadcastHandler {
74     private static final boolean VDBG = false;  // log CB PDU data
75 
76     /** Indicates that a message is not displayed. */
77     private static final String MESSAGE_NOT_DISPLAYED = "0";
78 
79     private final SparseArray<String> mAreaInfos = new SparseArray<>();
80 
81     /** This map holds incomplete concatenated messages waiting for assembly. */
82     private final HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap =
83             new HashMap<>(4);
84 
85     @VisibleForTesting
GsmCellBroadcastHandler(Context context, Looper looper)86     public GsmCellBroadcastHandler(Context context, Looper looper) {
87         super("GsmCellBroadcastHandler", context, looper);
88     }
89 
90     @Override
onQuitting()91     protected void onQuitting() {
92         super.onQuitting();     // release wakelock
93     }
94 
95     /**
96      * Handle a GSM cell broadcast message passed from the telephony framework.
97      * @param message
98      */
onGsmCellBroadcastSms(int slotIndex, byte[] message)99     public void onGsmCellBroadcastSms(int slotIndex, byte[] message) {
100         sendMessage(EVENT_NEW_SMS_MESSAGE, slotIndex, -1, message);
101     }
102 
103     /**
104      * Get the area information
105      *
106      * @param slotIndex SIM slot index
107      * @return The area information
108      */
109     @NonNull
getCellBroadcastAreaInfo(int slotIndex)110     public String getCellBroadcastAreaInfo(int slotIndex) {
111         String info;
112         synchronized (mAreaInfos) {
113             info = mAreaInfos.get(slotIndex);
114         }
115         return info == null ? "" : info;
116     }
117 
118     /**
119      * Create a new CellBroadcastHandler.
120      * @param context the context to use for dispatching Intents
121      * @return the new handler
122      */
makeGsmCellBroadcastHandler(Context context)123     public static GsmCellBroadcastHandler makeGsmCellBroadcastHandler(Context context) {
124         GsmCellBroadcastHandler handler = new GsmCellBroadcastHandler(context, Looper.myLooper());
125         handler.start();
126         return handler;
127     }
128 
129     /**
130      * Find the cell broadcast messages specify by the geo-fencing trigger message and perform a
131      * geo-fencing check for these messages.
132      * @param geoFencingTriggerMessage the trigger message
133      *
134      * @return {@code True} if geo-fencing is need for some cell broadcast message.
135      */
handleGeoFencingTriggerMessage( GeoFencingTriggerMessage geoFencingTriggerMessage, int slotIndex)136     private boolean handleGeoFencingTriggerMessage(
137             GeoFencingTriggerMessage geoFencingTriggerMessage, int slotIndex) {
138         final List<SmsCbMessage> cbMessages = new ArrayList<>();
139         final List<Uri> cbMessageUris = new ArrayList<>();
140 
141         SubscriptionManager subMgr = (SubscriptionManager) mContext.getSystemService(
142                 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
143         int[] subIds = subMgr.getSubscriptionIds(slotIndex);
144         Resources res;
145         if (subIds != null) {
146             res = getResources(subIds[0]);
147         } else {
148             res = getResources(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
149         }
150 
151         // Only consider the cell broadcast received within 24 hours.
152         long lastReceivedTime = System.currentTimeMillis() - DateUtils.DAY_IN_MILLIS;
153 
154         // Some carriers require reset duplication detection after airplane mode or reboot.
155         if (res.getBoolean(R.bool.reset_on_power_cycle_or_airplane_mode)) {
156             lastReceivedTime = Long.max(lastReceivedTime, mLastAirplaneModeTime);
157             lastReceivedTime = Long.max(lastReceivedTime,
158                     System.currentTimeMillis() - SystemClock.elapsedRealtime());
159         }
160 
161         // Find the cell broadcast message identify by the message identifier and serial number
162         // and was not displayed.
163         String where = CellBroadcasts.SERVICE_CATEGORY + "=? AND "
164                 + CellBroadcasts.SERIAL_NUMBER + "=? AND "
165                 + CellBroadcasts.MESSAGE_DISPLAYED + "=? AND "
166                 + CellBroadcasts.RECEIVED_TIME + ">?";
167 
168         ContentResolver resolver = mContext.getContentResolver();
169         for (CellBroadcastIdentity identity : geoFencingTriggerMessage.cbIdentifiers) {
170             try (Cursor cursor = resolver.query(CellBroadcasts.CONTENT_URI,
171                     CellBroadcastProvider.QUERY_COLUMNS,
172                     where,
173                     new String[] { Integer.toString(identity.messageIdentifier),
174                             Integer.toString(identity.serialNumber), MESSAGE_NOT_DISPLAYED,
175                             Long.toString(lastReceivedTime) },
176                     null /* sortOrder */)) {
177                 if (cursor != null) {
178                     while (cursor.moveToNext()) {
179                         cbMessages.add(SmsCbMessage.createFromCursor(cursor));
180                         cbMessageUris.add(ContentUris.withAppendedId(CellBroadcasts.CONTENT_URI,
181                                 cursor.getInt(cursor.getColumnIndex(CellBroadcasts._ID))));
182                     }
183                 }
184             }
185         }
186 
187         log("Found " + cbMessages.size() + " not broadcasted messages since "
188                 + DateFormat.getDateTimeInstance().format(lastReceivedTime));
189 
190         List<Geometry> commonBroadcastArea = new ArrayList<>();
191         if (geoFencingTriggerMessage.shouldShareBroadcastArea()) {
192             for (SmsCbMessage msg : cbMessages) {
193                 if (msg.getGeometries() != null) {
194                     commonBroadcastArea.addAll(msg.getGeometries());
195                 }
196             }
197         }
198 
199         // ATIS doesn't specify the geo fencing maximum wait time for the cell broadcasts specified
200         // in geo fencing trigger message. We will pick the largest maximum wait time among these
201         // cell broadcasts.
202         int maxWaitingTimeSec = 0;
203         for (SmsCbMessage msg : cbMessages) {
204             maxWaitingTimeSec = Math.max(maxWaitingTimeSec, getMaxLocationWaitingTime(msg));
205         }
206 
207         if (DBG) {
208             logd("Geo-fencing trigger message = " + geoFencingTriggerMessage);
209             for (SmsCbMessage msg : cbMessages) {
210                 logd(msg.toString());
211             }
212         }
213 
214         if (cbMessages.isEmpty()) {
215             if (DBG) logd("No CellBroadcast message need to be broadcasted");
216             return false;
217         }
218 
219         requestLocationUpdate(location -> {
220             if (location == null) {
221                 // If the location is not available, broadcast the messages directly.
222                 for (int i = 0; i < cbMessages.size(); i++) {
223                     broadcastMessage(cbMessages.get(i), cbMessageUris.get(i), slotIndex);
224                 }
225             } else {
226                 for (int i = 0; i < cbMessages.size(); i++) {
227                     List<Geometry> broadcastArea = !commonBroadcastArea.isEmpty()
228                             ? commonBroadcastArea : cbMessages.get(i).getGeometries();
229                     if (broadcastArea == null || broadcastArea.isEmpty()) {
230                         broadcastMessage(cbMessages.get(i), cbMessageUris.get(i), slotIndex);
231                     } else {
232                         performGeoFencing(cbMessages.get(i), cbMessageUris.get(i), broadcastArea,
233                                 location, slotIndex);
234                     }
235                 }
236             }
237         }, maxWaitingTimeSec);
238         return true;
239     }
240 
241     /**
242      * Process area info message.
243      *
244      * @param slotIndex SIM slot index
245      * @param message Cell broadcast message
246      * @return {@code true} if the mssage is an area info message and got processed correctly,
247      * otherwise {@code false}.
248      */
handleAreaInfoMessage(int slotIndex, SmsCbMessage message)249     private boolean handleAreaInfoMessage(int slotIndex, SmsCbMessage message) {
250         Resources res = getResources(message.getSubscriptionId());
251         int[] areaInfoChannels = res.getIntArray(R.array.area_info_channels);
252 
253         // Check area info message
254         if (IntStream.of(areaInfoChannels).anyMatch(
255                 x -> x == message.getServiceCategory())) {
256             synchronized (mAreaInfos) {
257                 String info = mAreaInfos.get(slotIndex);
258                 if (TextUtils.equals(info, message.getMessageBody())) {
259                     // Message is a duplicate
260                     return true;
261                 }
262                 mAreaInfos.put(slotIndex, message.getMessageBody());
263             }
264 
265             String[] pkgs = mContext.getResources().getStringArray(
266                     R.array.config_area_info_receiver_packages);
267             for (String pkg : pkgs) {
268                 Intent intent = new Intent(CellBroadcastIntents.ACTION_AREA_INFO_UPDATED);
269                 intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, slotIndex);
270                 intent.setPackage(pkg);
271                 mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
272                         android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
273             }
274             return true;
275         }
276 
277         // This is not an area info message.
278         return false;
279     }
280 
281     /**
282      * Handle 3GPP-format Cell Broadcast messages sent from radio.
283      *
284      * @param message the message to process
285      * @return true if need to wait for geo-fencing or an ordered broadcast was sent.
286      */
287     @Override
handleSmsMessage(Message message)288     protected boolean handleSmsMessage(Message message) {
289         // For GSM, message.obj should be a byte[]
290         int slotIndex = message.arg1;
291         if (message.obj instanceof byte[]) {
292             byte[] pdu = (byte[]) message.obj;
293             SmsCbHeader header = createSmsCbHeader(pdu);
294             if (header == null) return false;
295 
296             log("header=" + header);
297             if (header.getServiceCategory() == SmsCbConstants.MESSAGE_ID_CMAS_GEO_FENCING_TRIGGER) {
298                 GeoFencingTriggerMessage triggerMessage =
299                         GsmSmsCbMessage.createGeoFencingTriggerMessage(pdu);
300                 if (triggerMessage != null) {
301                     return handleGeoFencingTriggerMessage(triggerMessage, slotIndex);
302                 }
303             } else {
304                 SmsCbMessage cbMessage = handleGsmBroadcastSms(header, pdu, slotIndex);
305                 if (cbMessage != null) {
306                     if (isDuplicate(cbMessage)) {
307                         CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_FILTERED,
308                                 CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_FILTERED__TYPE__GSM,
309                                 CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_FILTERED__FILTER__DUPLICATE_MESSAGE);
310                         return false;
311                     }
312 
313                     if (handleAreaInfoMessage(slotIndex, cbMessage)) {
314                         log("Channel " + cbMessage.getServiceCategory() + " message processed");
315                         CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_FILTERED,
316                                 CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_FILTERED__TYPE__GSM,
317                                 CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_FILTERED__FILTER__AREA_INFO_MESSAGE);
318                         return false;
319                     }
320 
321                     handleBroadcastSms(cbMessage);
322                     return true;
323                 }
324                 if (VDBG) log("Not handled GSM broadcasts.");
325             }
326         } else {
327             final String errorMessage = "handleSmsMessage for GSM got object of type: "
328                     + message.obj.getClass().getName();
329             loge(errorMessage);
330             CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
331                     CELL_BROADCAST_MESSAGE_ERROR__TYPE__UNEXPECTED_GSM_MESSAGE_TYPE_FROM_FWK,
332                     errorMessage);
333         }
334         if (message.obj instanceof SmsCbMessage) {
335             return super.handleSmsMessage(message);
336         } else {
337             return false;
338         }
339     }
340 
341     /**
342      * Get LAC (location area code for GSM/UMTS) / TAC (tracking area code for LTE/NR) and CID
343      * (Cell id) from the cell identity
344      *
345      * @param ci Cell identity
346      * @return Pair of LAC and CID. {@code null} if not available.
347      */
getLacAndCid(CellIdentity ci)348     private @Nullable Pair<Integer, Integer> getLacAndCid(CellIdentity ci) {
349         if (ci == null) return null;
350         int lac = CellInfo.UNAVAILABLE;
351         int cid = CellInfo.UNAVAILABLE;
352         if (ci instanceof CellIdentityGsm) {
353             lac = ((CellIdentityGsm) ci).getLac();
354             cid = ((CellIdentityGsm) ci).getCid();
355         } else if (ci instanceof CellIdentityWcdma) {
356             lac = ((CellIdentityWcdma) ci).getLac();
357             cid = ((CellIdentityWcdma) ci).getCid();
358         } else if ((ci instanceof CellIdentityTdscdma)) {
359             lac = ((CellIdentityTdscdma) ci).getLac();
360             cid = ((CellIdentityTdscdma) ci).getCid();
361         } else if (ci instanceof CellIdentityLte) {
362             lac = ((CellIdentityLte) ci).getTac();
363             cid = ((CellIdentityLte) ci).getCi();
364         } else if (ci instanceof CellIdentityNr) {
365             lac = ((CellIdentityNr) ci).getTac();
366             cid = ((CellIdentityNr) ci).getPci();
367         }
368 
369         if (lac != CellInfo.UNAVAILABLE || cid != CellInfo.UNAVAILABLE) {
370             return Pair.create(lac, cid);
371         }
372 
373         // When both LAC and CID are not available.
374         return null;
375     }
376 
377     /**
378      * Get LAC (location area code for GSM/UMTS) / TAC (tracking area code for LTE/NR) and CID
379      * (Cell id) of the registered network.
380      *
381      * @param slotIndex SIM slot index
382      *
383      * @return lac and cid. {@code null} if cell identity is not available from the registered
384      * network.
385      */
getLacAndCid(int slotIndex)386     private @Nullable Pair<Integer, Integer> getLacAndCid(int slotIndex) {
387         TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
388         tm.createForSubscriptionId(getSubIdForPhone(mContext, slotIndex));
389 
390         ServiceState serviceState = tm.getServiceState();
391 
392         if (serviceState == null) return null;
393 
394         // The list of cell identity to extract LAC and CID. The higher priority one will be added
395         // into the top of list.
396         List<CellIdentity> cellIdentityList = new ArrayList<>();
397 
398         // CS network
399         NetworkRegistrationInfo nri = serviceState.getNetworkRegistrationInfo(
400                 NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
401         if (nri != null) {
402             cellIdentityList.add(nri.getCellIdentity());
403         }
404 
405         // PS network
406         nri = serviceState.getNetworkRegistrationInfo(
407                 NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
408         if (nri != null) {
409             cellIdentityList.add(nri.getCellIdentity());
410         }
411 
412         // When SIM is not inserted, we use the cell identity from the nearby cell. This is
413         // best effort.
414         List<CellInfo> infos = tm.getAllCellInfo();
415         if (infos != null) {
416             cellIdentityList.addAll(
417                     infos.stream().map(CellInfo::getCellIdentity).collect(Collectors.toList()));
418         }
419 
420         // Return the first valid LAC and CID from the list.
421         return cellIdentityList.stream()
422                 .map(this::getLacAndCid)
423                 .filter(Objects::nonNull)
424                 .findFirst()
425                 .orElse(null);
426     }
427 
428 
429     /**
430      * Handle 3GPP format SMS-CB message.
431      * @param header the cellbroadcast header.
432      * @param receivedPdu the received PDUs as a byte[]
433      */
handleGsmBroadcastSms(SmsCbHeader header, byte[] receivedPdu, int slotIndex)434     private SmsCbMessage handleGsmBroadcastSms(SmsCbHeader header, byte[] receivedPdu,
435             int slotIndex) {
436         try {
437             if (VDBG) {
438                 int pduLength = receivedPdu.length;
439                 for (int i = 0; i < pduLength; i += 8) {
440                     StringBuilder sb = new StringBuilder("SMS CB pdu data: ");
441                     for (int j = i; j < i + 8 && j < pduLength; j++) {
442                         int b = receivedPdu[j] & 0xff;
443                         if (b < 0x10) {
444                             sb.append('0');
445                         }
446                         sb.append(Integer.toHexString(b)).append(' ');
447                     }
448                     log(sb.toString());
449                 }
450             }
451 
452             if (VDBG) log("header=" + header);
453             TelephonyManager tm =
454                     (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
455             tm.createForSubscriptionId(getSubIdForPhone(mContext, slotIndex));
456             String plmn = tm.getNetworkOperator();
457             int lac = -1;
458             int cid = -1;
459             // Get LAC and CID of the current camped cell.
460             Pair<Integer, Integer> lacAndCid = getLacAndCid(slotIndex);
461             if (lacAndCid != null) {
462                 lac = lacAndCid.first;
463                 cid = lacAndCid.second;
464             }
465 
466             SmsCbLocation location = new SmsCbLocation(plmn, lac, cid);
467 
468             byte[][] pdus;
469             int pageCount = header.getNumberOfPages();
470             if (pageCount > 1) {
471                 // Multi-page message
472                 SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, location);
473 
474                 // Try to find other pages of the same message
475                 pdus = mSmsCbPageMap.get(concatInfo);
476 
477                 if (pdus == null) {
478                     // This is the first page of this message, make room for all
479                     // pages and keep until complete
480                     pdus = new byte[pageCount][];
481 
482                     mSmsCbPageMap.put(concatInfo, pdus);
483                 }
484 
485                 if (VDBG) log("pdus size=" + pdus.length);
486                 // Page parameter is one-based
487                 pdus[header.getPageIndex() - 1] = receivedPdu;
488 
489                 for (byte[] pdu : pdus) {
490                     if (pdu == null) {
491                         // Still missing pages, exit
492                         log("still missing pdu");
493                         return null;
494                     }
495                 }
496 
497                 // Message complete, remove and dispatch
498                 mSmsCbPageMap.remove(concatInfo);
499             } else {
500                 // Single page message
501                 pdus = new byte[1][];
502                 pdus[0] = receivedPdu;
503             }
504 
505             // Remove messages that are out of scope to prevent the map from
506             // growing indefinitely, containing incomplete messages that were
507             // never assembled
508             Iterator<SmsCbConcatInfo> iter = mSmsCbPageMap.keySet().iterator();
509 
510             while (iter.hasNext()) {
511                 SmsCbConcatInfo info = iter.next();
512 
513                 if (!info.matchesLocation(plmn, lac, cid)) {
514                     iter.remove();
515                 }
516             }
517 
518             return GsmSmsCbMessage.createSmsCbMessage(mContext, header, location, pdus, slotIndex);
519 
520         } catch (RuntimeException e) {
521             final String errorMessage = "Error in decoding SMS CB pdu: " + e.toString();
522             e.printStackTrace();
523             loge(errorMessage);
524             CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
525                     CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_INVALID_PDU, errorMessage);
526             return null;
527         }
528     }
529 
createSmsCbHeader(byte[] bytes)530     private SmsCbHeader createSmsCbHeader(byte[] bytes) {
531         try {
532             return new SmsCbHeader(bytes);
533         } catch (Exception ex) {
534             loge("Can't create SmsCbHeader, ex = " + ex.toString());
535             return null;
536         }
537     }
538 
539     /**
540      * Holds all info about a message page needed to assemble a complete concatenated message.
541      */
542     private static final class SmsCbConcatInfo {
543 
544         private final SmsCbHeader mHeader;
545         private final SmsCbLocation mLocation;
546 
SmsCbConcatInfo(SmsCbHeader header, SmsCbLocation location)547         SmsCbConcatInfo(SmsCbHeader header, SmsCbLocation location) {
548             mHeader = header;
549             mLocation = location;
550         }
551 
552         @Override
hashCode()553         public int hashCode() {
554             return (mHeader.getSerialNumber() * 31) + mLocation.hashCode();
555         }
556 
557         @Override
equals(Object obj)558         public boolean equals(Object obj) {
559             if (obj instanceof SmsCbConcatInfo) {
560                 SmsCbConcatInfo other = (SmsCbConcatInfo) obj;
561 
562                 // Two pages match if they have the same serial number (which includes the
563                 // geographical scope and update number), and both pages belong to the same
564                 // location (PLMN, plus LAC and CID if these are part of the geographical scope).
565                 return mHeader.getSerialNumber() == other.mHeader.getSerialNumber()
566                         && mLocation.equals(other.mLocation);
567             }
568 
569             return false;
570         }
571 
572         /**
573          * Compare the location code for this message to the current location code. The match is
574          * relative to the geographical scope of the message, which determines whether the LAC
575          * and Cell ID are saved in mLocation or set to -1 to match all values.
576          *
577          * @param plmn the current PLMN
578          * @param lac the current Location Area (GSM) or Service Area (UMTS)
579          * @param cid the current Cell ID
580          * @return true if this message is valid for the current location; false otherwise
581          */
matchesLocation(String plmn, int lac, int cid)582         public boolean matchesLocation(String plmn, int lac, int cid) {
583             return mLocation.isInLocationArea(plmn, lac, cid);
584         }
585     }
586 }
587