blob: b7026074e0de521f24eed497e781283b97b86675 [file] [log] [blame]
Jordan Liudfcbfaf2019-10-11 11:42:03 -07001/*
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
17package com.android.cellbroadcastservice;
18
Jordan Liu62e183f2019-12-20 15:09:44 -080019import static com.android.cellbroadcastservice.CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_INVALID_PDU;
20import static com.android.cellbroadcastservice.CellBroadcastStatsLog.CELL_BROADCAST_MESSAGE_ERROR__TYPE__UNEXPECTED_GSM_MESSAGE_TYPE_FROM_FWK;
21
Jack Yu90af67b2020-01-24 11:45:59 -080022import android.annotation.NonNull;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070023import android.content.ContentResolver;
24import android.content.ContentUris;
25import android.content.Context;
Jack Yu90af67b2020-01-24 11:45:59 -080026import android.content.Intent;
Jack Yu7afa1c02019-11-07 22:59:46 -080027import android.content.res.Resources;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070028import android.database.Cursor;
29import android.net.Uri;
Jack Yu7afa1c02019-11-07 22:59:46 -080030import android.os.Looper;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070031import android.os.Message;
Jack Yu7afa1c02019-11-07 22:59:46 -080032import android.os.SystemClock;
Jack Yu90af67b2020-01-24 11:45:59 -080033import android.os.UserHandle;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070034import android.provider.Telephony.CellBroadcasts;
Jordan Liu8d7d0a12019-10-21 12:30:56 -070035import android.telephony.CbGeoUtils.Geometry;
Jack Yu90af67b2020-01-24 11:45:59 -080036import android.telephony.CellBroadcastIntents;
Jordan Liu8d7d0a12019-10-21 12:30:56 -070037import android.telephony.CellIdentity;
38import android.telephony.CellIdentityGsm;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070039import android.telephony.CellInfo;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070040import android.telephony.SmsCbLocation;
41import android.telephony.SmsCbMessage;
Jack Yu7afa1c02019-11-07 22:59:46 -080042import android.telephony.SubscriptionManager;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070043import android.telephony.TelephonyManager;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070044import android.text.format.DateUtils;
Jordan Liu8d7d0a12019-10-21 12:30:56 -070045import android.util.Pair;
Jack Yu90af67b2020-01-24 11:45:59 -080046import android.util.SparseArray;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070047
48import com.android.cellbroadcastservice.GsmSmsCbMessage.GeoFencingTriggerMessage;
49import com.android.cellbroadcastservice.GsmSmsCbMessage.GeoFencingTriggerMessage.CellBroadcastIdentity;
Jack Yu7afa1c02019-11-07 22:59:46 -080050import com.android.internal.annotations.VisibleForTesting;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070051
Jack Yu7afa1c02019-11-07 22:59:46 -080052import java.text.DateFormat;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070053import java.util.ArrayList;
54import java.util.HashMap;
55import java.util.Iterator;
56import java.util.List;
Jack Yu90af67b2020-01-24 11:45:59 -080057import java.util.stream.IntStream;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070058
59/**
60 * Handler for 3GPP format Cell Broadcasts. Parent class can also handle CDMA Cell Broadcasts.
61 */
62public class GsmCellBroadcastHandler extends CellBroadcastHandler {
63 private static final boolean VDBG = false; // log CB PDU data
64
65 /** Indicates that a message is not being broadcasted. */
66 private static final String MESSAGE_NOT_BROADCASTED = "0";
67
Jack Yu90af67b2020-01-24 11:45:59 -080068 private SparseArray<String> mAreaInfos = new SparseArray<>();
69
Jordan Liudfcbfaf2019-10-11 11:42:03 -070070 /** This map holds incomplete concatenated messages waiting for assembly. */
Jordan Liudfcbfaf2019-10-11 11:42:03 -070071 private final HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap =
72 new HashMap<>(4);
73
Jack Yu7afa1c02019-11-07 22:59:46 -080074 @VisibleForTesting
Jordan Liu997ab0a2019-11-14 14:23:49 -080075 public GsmCellBroadcastHandler(Context context, Looper looper) {
Jack Yu7afa1c02019-11-07 22:59:46 -080076 super("GsmCellBroadcastHandler", context, looper);
Jordan Liudfcbfaf2019-10-11 11:42:03 -070077 }
78
79 @Override
80 protected void onQuitting() {
81 super.onQuitting(); // release wakelock
82 }
83
84 /**
85 * Handle a GSM cell broadcast message passed from the telephony framework.
86 * @param message
87 */
88 public void onGsmCellBroadcastSms(int slotIndex, byte[] message) {
89 sendMessage(EVENT_NEW_SMS_MESSAGE, slotIndex, -1, message);
90 }
91
92 /**
Jack Yu90af67b2020-01-24 11:45:59 -080093 * Get the area information
94 *
95 * @param slotIndex SIM slot index
96 * @return The area information
97 */
98 @NonNull
99 public String getCellBroadcastAreaInfo(int slotIndex) {
100 String info = mAreaInfos.get(slotIndex);
101 return info == null ? "" : info;
102 }
103
104 /**
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700105 * Create a new CellBroadcastHandler.
106 * @param context the context to use for dispatching Intents
107 * @return the new handler
108 */
109 public static GsmCellBroadcastHandler makeGsmCellBroadcastHandler(Context context) {
Jack Yu7afa1c02019-11-07 22:59:46 -0800110 GsmCellBroadcastHandler handler = new GsmCellBroadcastHandler(context, Looper.myLooper());
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700111 handler.start();
112 return handler;
113 }
114
115 /**
116 * Find the cell broadcast messages specify by the geo-fencing trigger message and perform a
117 * geo-fencing check for these messages.
118 * @param geoFencingTriggerMessage the trigger message
119 *
120 * @return {@code True} if geo-fencing is need for some cell broadcast message.
121 */
122 private boolean handleGeoFencingTriggerMessage(
123 GeoFencingTriggerMessage geoFencingTriggerMessage, int slotIndex) {
124 final List<SmsCbMessage> cbMessages = new ArrayList<>();
125 final List<Uri> cbMessageUris = new ArrayList<>();
126
Jack Yu7afa1c02019-11-07 22:59:46 -0800127 SubscriptionManager subMgr = (SubscriptionManager) mContext.getSystemService(
128 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
129 int[] subIds = subMgr.getSubscriptionIds(slotIndex);
130 Resources res;
131 if (subIds != null) {
132 res = getResources(subIds[0]);
133 } else {
134 res = getResources(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
135 }
136
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700137 // Only consider the cell broadcast received within 24 hours.
138 long lastReceivedTime = System.currentTimeMillis() - DateUtils.DAY_IN_MILLIS;
139
Jack Yu7afa1c02019-11-07 22:59:46 -0800140 // Some carriers require reset duplication detection after airplane mode or reboot.
141 if (res.getBoolean(R.bool.reset_on_power_cycle_or_airplane_mode)) {
142 lastReceivedTime = Long.max(lastReceivedTime, mLastAirplaneModeTime);
143 lastReceivedTime = Long.max(lastReceivedTime,
144 System.currentTimeMillis() - SystemClock.elapsedRealtime());
145 }
146
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700147 // Find the cell broadcast message identify by the message identifier and serial number
148 // and is not broadcasted.
149 String where = CellBroadcasts.SERVICE_CATEGORY + "=? AND "
150 + CellBroadcasts.SERIAL_NUMBER + "=? AND "
151 + CellBroadcasts.MESSAGE_BROADCASTED + "=? AND "
152 + CellBroadcasts.RECEIVED_TIME + ">?";
153
154 ContentResolver resolver = mContext.getContentResolver();
155 for (CellBroadcastIdentity identity : geoFencingTriggerMessage.cbIdentifiers) {
Chen Xu34c71672019-10-21 22:56:37 -0700156 try (Cursor cursor = resolver.query(CellBroadcasts.CONTENT_URI,
Jordan Liub51f6932019-11-15 11:13:59 -0800157 CellBroadcastProvider.QUERY_COLUMNS,
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700158 where,
159 new String[] { Integer.toString(identity.messageIdentifier),
160 Integer.toString(identity.serialNumber), MESSAGE_NOT_BROADCASTED,
161 Long.toString(lastReceivedTime) },
162 null /* sortOrder */)) {
163 if (cursor != null) {
164 while (cursor.moveToNext()) {
165 cbMessages.add(SmsCbMessage.createFromCursor(cursor));
Chen Xu34c71672019-10-21 22:56:37 -0700166 cbMessageUris.add(ContentUris.withAppendedId(CellBroadcasts.CONTENT_URI,
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700167 cursor.getInt(cursor.getColumnIndex(CellBroadcasts._ID))));
168 }
169 }
170 }
171 }
172
Jack Yu7afa1c02019-11-07 22:59:46 -0800173 log("Found " + cbMessages.size() + " not broadcasted messages since "
174 + DateFormat.getDateTimeInstance().format(lastReceivedTime));
175
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700176 List<Geometry> commonBroadcastArea = new ArrayList<>();
177 if (geoFencingTriggerMessage.shouldShareBroadcastArea()) {
178 for (SmsCbMessage msg : cbMessages) {
179 if (msg.getGeometries() != null) {
180 commonBroadcastArea.addAll(msg.getGeometries());
181 }
182 }
183 }
184
185 // ATIS doesn't specify the geo fencing maximum wait time for the cell broadcasts specified
186 // in geo fencing trigger message. We will pick the largest maximum wait time among these
187 // cell broadcasts.
188 int maximumWaitTimeSec = 0;
189 for (SmsCbMessage msg : cbMessages) {
Jordan Liu8d7d0a12019-10-21 12:30:56 -0700190 maximumWaitTimeSec = Math.max(maximumWaitTimeSec, msg.getMaximumWaitingDuration());
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700191 }
192
193 if (DBG) {
194 logd("Geo-fencing trigger message = " + geoFencingTriggerMessage);
195 for (SmsCbMessage msg : cbMessages) {
196 logd(msg.toString());
197 }
198 }
199
200 if (cbMessages.isEmpty()) {
201 if (DBG) logd("No CellBroadcast message need to be broadcasted");
202 return false;
203 }
204
205 requestLocationUpdate(location -> {
206 if (location == null) {
207 // If the location is not available, broadcast the messages directly.
Jack Yu8cb5d152019-10-23 11:35:32 -0700208 for (int i = 0; i < cbMessages.size(); i++) {
209 broadcastMessage(cbMessages.get(i), cbMessageUris.get(i), slotIndex);
210 }
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700211 } else {
212 for (int i = 0; i < cbMessages.size(); i++) {
213 List<Geometry> broadcastArea = !commonBroadcastArea.isEmpty()
214 ? commonBroadcastArea : cbMessages.get(i).getGeometries();
215 if (broadcastArea == null || broadcastArea.isEmpty()) {
216 broadcastMessage(cbMessages.get(i), cbMessageUris.get(i), slotIndex);
217 } else {
218 performGeoFencing(cbMessages.get(i), cbMessageUris.get(i), broadcastArea,
219 location, slotIndex);
220 }
221 }
222 }
223 }, maximumWaitTimeSec);
224 return true;
225 }
226
227 /**
Jack Yu90af67b2020-01-24 11:45:59 -0800228 * Process area info message.
229 *
230 * @param slotIndex SIM slot index
231 * @param message Cell broadcast message
232 * @return {@code true} if the mssage is an area info message and got processed correctly,
233 * otherwise {@code false}.
234 */
235 private boolean handleAreaInfoMessage(int slotIndex, SmsCbMessage message) {
236 SubscriptionManager subMgr = (SubscriptionManager) mContext.getSystemService(
237 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
238
239 // Check area info message
240 int[] subIds = subMgr.getSubscriptionIds(slotIndex);
241 Resources res;
242 if (subIds != null) {
243 res = getResources(subIds[0]);
244 } else {
245 res = getResources(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
246 }
247 int[] areaInfoChannels = res.getIntArray(R.array.area_info_channels);
248
249 if (IntStream.of(areaInfoChannels).anyMatch(
250 x -> x == message.getServiceCategory())) {
251 mAreaInfos.put(slotIndex, message.getMessageBody());
252
253 String[] pkgs = mContext.getResources().getStringArray(
254 R.array.config_area_info_receiver_packages);
255 for (String pkg : pkgs) {
256 Intent intent = new Intent(CellBroadcastIntents.ACTION_AREA_INFO_UPDATED);
257 intent.setPackage(pkg);
258 mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
259 android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
260 }
261 return true;
262 }
263
264 // This is not an area info message.
265 return false;
266 }
267
268 /**
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700269 * Handle 3GPP-format Cell Broadcast messages sent from radio.
270 *
271 * @param message the message to process
272 * @return true if need to wait for geo-fencing or an ordered broadcast was sent.
273 */
274 @Override
275 protected boolean handleSmsMessage(Message message) {
276 // For GSM, message.obj should be a byte[]
277 int slotIndex = message.arg1;
278 if (message.obj instanceof byte[]) {
279 byte[] pdu = (byte[]) message.obj;
280 SmsCbHeader header = createSmsCbHeader(pdu);
281 if (header == null) return false;
282
Jack Yu7afa1c02019-11-07 22:59:46 -0800283 log("header=" + header);
Jordan Liu5e174552019-11-07 11:54:10 -0800284 if (header.getServiceCategory() == SmsCbConstants.MESSAGE_ID_CMAS_GEO_FENCING_TRIGGER) {
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700285 GeoFencingTriggerMessage triggerMessage =
286 GsmSmsCbMessage.createGeoFencingTriggerMessage(pdu);
287 if (triggerMessage != null) {
288 return handleGeoFencingTriggerMessage(triggerMessage, slotIndex);
289 }
290 } else {
291 SmsCbMessage cbMessage = handleGsmBroadcastSms(header, pdu, slotIndex);
292 if (cbMessage != null) {
Jack Yu2390cad2019-10-24 00:19:49 -0700293 if (isDuplicate(cbMessage)) {
294 return false;
295 }
Jack Yu90af67b2020-01-24 11:45:59 -0800296
297 if (handleAreaInfoMessage(slotIndex, cbMessage)) {
298 log("Channel " + cbMessage.getServiceCategory() + " message processed");
299 return false;
300 }
301
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700302 handleBroadcastSms(cbMessage);
303 return true;
304 }
305 if (VDBG) log("Not handled GSM broadcasts.");
306 }
Jordan Liu62e183f2019-12-20 15:09:44 -0800307 } else {
308 loge("handleSmsMessage for GSM got object of type: "
309 + message.obj.getClass().getName());
310 CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
311 CELL_BROADCAST_MESSAGE_ERROR__TYPE__UNEXPECTED_GSM_MESSAGE_TYPE_FROM_FWK,
312 message.obj.getClass().getName());
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700313 }
Jordan Liu62e183f2019-12-20 15:09:44 -0800314 if (message.obj instanceof SmsCbMessage) {
315 return super.handleSmsMessage(message);
316 } else {
317 return false;
318 }
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700319 }
320
Jordan Liu8d7d0a12019-10-21 12:30:56 -0700321 // return the GSM cell location from the first GSM cell info
322 private Pair<Integer, Integer> getGsmLacAndCid() {
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700323 TelephonyManager tm =
324 (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
325 List<CellInfo> infos = tm.getAllCellInfo();
326 for (CellInfo info : infos) {
Jordan Liu8d7d0a12019-10-21 12:30:56 -0700327 CellIdentity ci = info.getCellIdentity();
328 if (ci instanceof CellIdentityGsm) {
329 CellIdentityGsm ciGsm = (CellIdentityGsm) ci;
330 int lac = ciGsm.getLac() != CellInfo.UNAVAILABLE ? ciGsm.getLac() : -1;
331 int cid = ciGsm.getCid() != CellInfo.UNAVAILABLE ? ciGsm.getCid() : -1;
332 return Pair.create(lac, cid);
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700333 }
334 }
Jordan Liu8d7d0a12019-10-21 12:30:56 -0700335 return null;
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700336 }
337
338
339 /**
340 * Handle 3GPP format SMS-CB message.
341 * @param header the cellbroadcast header.
342 * @param receivedPdu the received PDUs as a byte[]
343 */
344 private SmsCbMessage handleGsmBroadcastSms(SmsCbHeader header, byte[] receivedPdu,
345 int slotIndex) {
346 try {
347 if (VDBG) {
348 int pduLength = receivedPdu.length;
349 for (int i = 0; i < pduLength; i += 8) {
350 StringBuilder sb = new StringBuilder("SMS CB pdu data: ");
351 for (int j = i; j < i + 8 && j < pduLength; j++) {
352 int b = receivedPdu[j] & 0xff;
353 if (b < 0x10) {
354 sb.append('0');
355 }
356 sb.append(Integer.toHexString(b)).append(' ');
357 }
358 log(sb.toString());
359 }
360 }
361
362 if (VDBG) log("header=" + header);
363 TelephonyManager tm =
364 (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
Jordan Liuac12bae2019-11-21 11:59:07 -0800365 tm.createForSubscriptionId(getSubIdForPhone(mContext, slotIndex));
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700366 // TODO make a systemAPI for getNetworkOperatorForSlotIndex
Jordan Liu8d7d0a12019-10-21 12:30:56 -0700367 String plmn = tm.getSimOperator();
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700368 int lac = -1;
369 int cid = -1;
Jordan Liu8d7d0a12019-10-21 12:30:56 -0700370 Pair<Integer, Integer> lacAndCid = getGsmLacAndCid();
371 // Check if GSM lac and cid are available. This is required to support
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700372 // dual-mode devices such as CDMA/LTE devices that require support for
373 // both 3GPP and 3GPP2 format messages
Jordan Liu8d7d0a12019-10-21 12:30:56 -0700374 if (lacAndCid != null) {
375 lac = lacAndCid.first;
376 cid = lacAndCid.second;
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700377 }
378
379 SmsCbLocation location;
380 switch (header.getGeographicalScope()) {
381 case SmsCbMessage.GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE:
382 location = new SmsCbLocation(plmn, lac, -1);
383 break;
384
385 case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE:
386 case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE:
387 location = new SmsCbLocation(plmn, lac, cid);
388 break;
389
390 case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE:
391 default:
Jordan Liuac12bae2019-11-21 11:59:07 -0800392 location = new SmsCbLocation(plmn, -1, -1);
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700393 break;
394 }
395
396 byte[][] pdus;
397 int pageCount = header.getNumberOfPages();
398 if (pageCount > 1) {
399 // Multi-page message
400 SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, location);
401
402 // Try to find other pages of the same message
403 pdus = mSmsCbPageMap.get(concatInfo);
404
405 if (pdus == null) {
406 // This is the first page of this message, make room for all
407 // pages and keep until complete
408 pdus = new byte[pageCount][];
409
410 mSmsCbPageMap.put(concatInfo, pdus);
411 }
412
413 if (VDBG) log("pdus size=" + pdus.length);
414 // Page parameter is one-based
415 pdus[header.getPageIndex() - 1] = receivedPdu;
416
417 for (byte[] pdu : pdus) {
418 if (pdu == null) {
419 // Still missing pages, exit
420 log("still missing pdu");
421 return null;
422 }
423 }
424
425 // Message complete, remove and dispatch
426 mSmsCbPageMap.remove(concatInfo);
427 } else {
428 // Single page message
429 pdus = new byte[1][];
430 pdus[0] = receivedPdu;
431 }
432
433 // Remove messages that are out of scope to prevent the map from
434 // growing indefinitely, containing incomplete messages that were
435 // never assembled
436 Iterator<SmsCbConcatInfo> iter = mSmsCbPageMap.keySet().iterator();
437
438 while (iter.hasNext()) {
439 SmsCbConcatInfo info = iter.next();
440
441 if (!info.matchesLocation(plmn, lac, cid)) {
442 iter.remove();
443 }
444 }
445
446 return GsmSmsCbMessage.createSmsCbMessage(mContext, header, location, pdus, slotIndex);
447
448 } catch (RuntimeException e) {
449 loge("Error in decoding SMS CB pdu", e);
Jordan Liu62e183f2019-12-20 15:09:44 -0800450 CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
451 CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_INVALID_PDU, e.toString());
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700452 return null;
453 }
454 }
455
456 private SmsCbHeader createSmsCbHeader(byte[] bytes) {
457 try {
458 return new SmsCbHeader(bytes);
459 } catch (Exception ex) {
460 loge("Can't create SmsCbHeader, ex = " + ex.toString());
461 return null;
462 }
463 }
464
465 /**
466 * Holds all info about a message page needed to assemble a complete concatenated message.
467 */
468 private static final class SmsCbConcatInfo {
469
470 private final SmsCbHeader mHeader;
471 private final SmsCbLocation mLocation;
472
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700473 SmsCbConcatInfo(SmsCbHeader header, SmsCbLocation location) {
474 mHeader = header;
475 mLocation = location;
476 }
477
478 @Override
479 public int hashCode() {
480 return (mHeader.getSerialNumber() * 31) + mLocation.hashCode();
481 }
482
483 @Override
484 public boolean equals(Object obj) {
485 if (obj instanceof SmsCbConcatInfo) {
486 SmsCbConcatInfo other = (SmsCbConcatInfo) obj;
487
488 // Two pages match if they have the same serial number (which includes the
489 // geographical scope and update number), and both pages belong to the same
490 // location (PLMN, plus LAC and CID if these are part of the geographical scope).
491 return mHeader.getSerialNumber() == other.mHeader.getSerialNumber()
492 && mLocation.equals(other.mLocation);
493 }
494
495 return false;
496 }
497
498 /**
499 * Compare the location code for this message to the current location code. The match is
500 * relative to the geographical scope of the message, which determines whether the LAC
501 * and Cell ID are saved in mLocation or set to -1 to match all values.
502 *
503 * @param plmn the current PLMN
504 * @param lac the current Location Area (GSM) or Service Area (UMTS)
505 * @param cid the current Cell ID
506 * @return true if this message is valid for the current location; false otherwise
507 */
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700508 public boolean matchesLocation(String plmn, int lac, int cid) {
509 return mLocation.isInLocationArea(plmn, lac, cid);
510 }
511 }
512}