blob: 6682a8b7a886eaa186023438ebe6dd482bd1a3c2 [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;
Jack Yud634e502020-01-29 23:45:58 -080044import android.text.TextUtils;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070045import android.text.format.DateUtils;
Jordan Liu8d7d0a12019-10-21 12:30:56 -070046import android.util.Pair;
Jack Yu90af67b2020-01-24 11:45:59 -080047import android.util.SparseArray;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070048
49import com.android.cellbroadcastservice.GsmSmsCbMessage.GeoFencingTriggerMessage;
50import com.android.cellbroadcastservice.GsmSmsCbMessage.GeoFencingTriggerMessage.CellBroadcastIdentity;
Jack Yu7afa1c02019-11-07 22:59:46 -080051import com.android.internal.annotations.VisibleForTesting;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070052
Jack Yu7afa1c02019-11-07 22:59:46 -080053import java.text.DateFormat;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070054import java.util.ArrayList;
55import java.util.HashMap;
56import java.util.Iterator;
57import java.util.List;
Jack Yu90af67b2020-01-24 11:45:59 -080058import java.util.stream.IntStream;
Jordan Liudfcbfaf2019-10-11 11:42:03 -070059
60/**
61 * Handler for 3GPP format Cell Broadcasts. Parent class can also handle CDMA Cell Broadcasts.
62 */
63public class GsmCellBroadcastHandler extends CellBroadcastHandler {
64 private static final boolean VDBG = false; // log CB PDU data
65
66 /** Indicates that a message is not being broadcasted. */
67 private static final String MESSAGE_NOT_BROADCASTED = "0";
68
Jack Yud634e502020-01-29 23:45:58 -080069 private final SparseArray<String> mAreaInfos = new SparseArray<>();
Jack Yu90af67b2020-01-24 11:45:59 -080070
Jordan Liudfcbfaf2019-10-11 11:42:03 -070071 /** This map holds incomplete concatenated messages waiting for assembly. */
Jordan Liudfcbfaf2019-10-11 11:42:03 -070072 private final HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap =
73 new HashMap<>(4);
74
Jack Yu7afa1c02019-11-07 22:59:46 -080075 @VisibleForTesting
Jordan Liu997ab0a2019-11-14 14:23:49 -080076 public GsmCellBroadcastHandler(Context context, Looper looper) {
Jack Yu7afa1c02019-11-07 22:59:46 -080077 super("GsmCellBroadcastHandler", context, looper);
Jordan Liudfcbfaf2019-10-11 11:42:03 -070078 }
79
80 @Override
81 protected void onQuitting() {
82 super.onQuitting(); // release wakelock
83 }
84
85 /**
86 * Handle a GSM cell broadcast message passed from the telephony framework.
87 * @param message
88 */
89 public void onGsmCellBroadcastSms(int slotIndex, byte[] message) {
90 sendMessage(EVENT_NEW_SMS_MESSAGE, slotIndex, -1, message);
91 }
92
93 /**
Jack Yu90af67b2020-01-24 11:45:59 -080094 * Get the area information
95 *
96 * @param slotIndex SIM slot index
97 * @return The area information
98 */
99 @NonNull
100 public String getCellBroadcastAreaInfo(int slotIndex) {
Jack Yud634e502020-01-29 23:45:58 -0800101 String info;
102 synchronized (mAreaInfos) {
103 info = mAreaInfos.get(slotIndex);
104 }
Jack Yu90af67b2020-01-24 11:45:59 -0800105 return info == null ? "" : info;
106 }
107
108 /**
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700109 * Create a new CellBroadcastHandler.
110 * @param context the context to use for dispatching Intents
111 * @return the new handler
112 */
113 public static GsmCellBroadcastHandler makeGsmCellBroadcastHandler(Context context) {
Jack Yu7afa1c02019-11-07 22:59:46 -0800114 GsmCellBroadcastHandler handler = new GsmCellBroadcastHandler(context, Looper.myLooper());
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700115 handler.start();
116 return handler;
117 }
118
119 /**
120 * Find the cell broadcast messages specify by the geo-fencing trigger message and perform a
121 * geo-fencing check for these messages.
122 * @param geoFencingTriggerMessage the trigger message
123 *
124 * @return {@code True} if geo-fencing is need for some cell broadcast message.
125 */
126 private boolean handleGeoFencingTriggerMessage(
127 GeoFencingTriggerMessage geoFencingTriggerMessage, int slotIndex) {
128 final List<SmsCbMessage> cbMessages = new ArrayList<>();
129 final List<Uri> cbMessageUris = new ArrayList<>();
130
Jack Yu7afa1c02019-11-07 22:59:46 -0800131 SubscriptionManager subMgr = (SubscriptionManager) mContext.getSystemService(
132 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
133 int[] subIds = subMgr.getSubscriptionIds(slotIndex);
134 Resources res;
135 if (subIds != null) {
136 res = getResources(subIds[0]);
137 } else {
138 res = getResources(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
139 }
140
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700141 // Only consider the cell broadcast received within 24 hours.
142 long lastReceivedTime = System.currentTimeMillis() - DateUtils.DAY_IN_MILLIS;
143
Jack Yu7afa1c02019-11-07 22:59:46 -0800144 // Some carriers require reset duplication detection after airplane mode or reboot.
145 if (res.getBoolean(R.bool.reset_on_power_cycle_or_airplane_mode)) {
146 lastReceivedTime = Long.max(lastReceivedTime, mLastAirplaneModeTime);
147 lastReceivedTime = Long.max(lastReceivedTime,
148 System.currentTimeMillis() - SystemClock.elapsedRealtime());
149 }
150
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700151 // Find the cell broadcast message identify by the message identifier and serial number
152 // and is not broadcasted.
153 String where = CellBroadcasts.SERVICE_CATEGORY + "=? AND "
154 + CellBroadcasts.SERIAL_NUMBER + "=? AND "
155 + CellBroadcasts.MESSAGE_BROADCASTED + "=? AND "
156 + CellBroadcasts.RECEIVED_TIME + ">?";
157
158 ContentResolver resolver = mContext.getContentResolver();
159 for (CellBroadcastIdentity identity : geoFencingTriggerMessage.cbIdentifiers) {
Chen Xu34c71672019-10-21 22:56:37 -0700160 try (Cursor cursor = resolver.query(CellBroadcasts.CONTENT_URI,
Jordan Liub51f6932019-11-15 11:13:59 -0800161 CellBroadcastProvider.QUERY_COLUMNS,
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700162 where,
163 new String[] { Integer.toString(identity.messageIdentifier),
164 Integer.toString(identity.serialNumber), MESSAGE_NOT_BROADCASTED,
165 Long.toString(lastReceivedTime) },
166 null /* sortOrder */)) {
167 if (cursor != null) {
168 while (cursor.moveToNext()) {
169 cbMessages.add(SmsCbMessage.createFromCursor(cursor));
Chen Xu34c71672019-10-21 22:56:37 -0700170 cbMessageUris.add(ContentUris.withAppendedId(CellBroadcasts.CONTENT_URI,
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700171 cursor.getInt(cursor.getColumnIndex(CellBroadcasts._ID))));
172 }
173 }
174 }
175 }
176
Jack Yu7afa1c02019-11-07 22:59:46 -0800177 log("Found " + cbMessages.size() + " not broadcasted messages since "
178 + DateFormat.getDateTimeInstance().format(lastReceivedTime));
179
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700180 List<Geometry> commonBroadcastArea = new ArrayList<>();
181 if (geoFencingTriggerMessage.shouldShareBroadcastArea()) {
182 for (SmsCbMessage msg : cbMessages) {
183 if (msg.getGeometries() != null) {
184 commonBroadcastArea.addAll(msg.getGeometries());
185 }
186 }
187 }
188
189 // ATIS doesn't specify the geo fencing maximum wait time for the cell broadcasts specified
190 // in geo fencing trigger message. We will pick the largest maximum wait time among these
191 // cell broadcasts.
192 int maximumWaitTimeSec = 0;
193 for (SmsCbMessage msg : cbMessages) {
Jordan Liu8d7d0a12019-10-21 12:30:56 -0700194 maximumWaitTimeSec = Math.max(maximumWaitTimeSec, msg.getMaximumWaitingDuration());
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700195 }
196
197 if (DBG) {
198 logd("Geo-fencing trigger message = " + geoFencingTriggerMessage);
199 for (SmsCbMessage msg : cbMessages) {
200 logd(msg.toString());
201 }
202 }
203
204 if (cbMessages.isEmpty()) {
205 if (DBG) logd("No CellBroadcast message need to be broadcasted");
206 return false;
207 }
208
209 requestLocationUpdate(location -> {
210 if (location == null) {
211 // If the location is not available, broadcast the messages directly.
Jack Yu8cb5d152019-10-23 11:35:32 -0700212 for (int i = 0; i < cbMessages.size(); i++) {
213 broadcastMessage(cbMessages.get(i), cbMessageUris.get(i), slotIndex);
214 }
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700215 } else {
216 for (int i = 0; i < cbMessages.size(); i++) {
217 List<Geometry> broadcastArea = !commonBroadcastArea.isEmpty()
218 ? commonBroadcastArea : cbMessages.get(i).getGeometries();
219 if (broadcastArea == null || broadcastArea.isEmpty()) {
220 broadcastMessage(cbMessages.get(i), cbMessageUris.get(i), slotIndex);
221 } else {
222 performGeoFencing(cbMessages.get(i), cbMessageUris.get(i), broadcastArea,
223 location, slotIndex);
224 }
225 }
226 }
227 }, maximumWaitTimeSec);
228 return true;
229 }
230
231 /**
Jack Yu90af67b2020-01-24 11:45:59 -0800232 * Process area info message.
233 *
234 * @param slotIndex SIM slot index
235 * @param message Cell broadcast message
236 * @return {@code true} if the mssage is an area info message and got processed correctly,
237 * otherwise {@code false}.
238 */
239 private boolean handleAreaInfoMessage(int slotIndex, SmsCbMessage message) {
240 SubscriptionManager subMgr = (SubscriptionManager) mContext.getSystemService(
241 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
242
243 // Check area info message
244 int[] subIds = subMgr.getSubscriptionIds(slotIndex);
245 Resources res;
246 if (subIds != null) {
247 res = getResources(subIds[0]);
248 } else {
249 res = getResources(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
250 }
251 int[] areaInfoChannels = res.getIntArray(R.array.area_info_channels);
252
253 if (IntStream.of(areaInfoChannels).anyMatch(
254 x -> x == message.getServiceCategory())) {
Jack Yud634e502020-01-29 23:45:58 -0800255 synchronized (mAreaInfos) {
256 String info = mAreaInfos.get(slotIndex);
257 if (TextUtils.equals(info, message.getMessageBody())) {
258 // Message is a duplicate
259 return true;
260 }
261 mAreaInfos.put(slotIndex, message.getMessageBody());
262 }
Jack Yu90af67b2020-01-24 11:45:59 -0800263
264 String[] pkgs = mContext.getResources().getStringArray(
265 R.array.config_area_info_receiver_packages);
266 for (String pkg : pkgs) {
267 Intent intent = new Intent(CellBroadcastIntents.ACTION_AREA_INFO_UPDATED);
268 intent.setPackage(pkg);
269 mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
270 android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
271 }
272 return true;
273 }
274
275 // This is not an area info message.
276 return false;
277 }
278
279 /**
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700280 * Handle 3GPP-format Cell Broadcast messages sent from radio.
281 *
282 * @param message the message to process
283 * @return true if need to wait for geo-fencing or an ordered broadcast was sent.
284 */
285 @Override
286 protected boolean handleSmsMessage(Message message) {
287 // For GSM, message.obj should be a byte[]
288 int slotIndex = message.arg1;
289 if (message.obj instanceof byte[]) {
290 byte[] pdu = (byte[]) message.obj;
291 SmsCbHeader header = createSmsCbHeader(pdu);
292 if (header == null) return false;
293
Jack Yu7afa1c02019-11-07 22:59:46 -0800294 log("header=" + header);
Jordan Liu5e174552019-11-07 11:54:10 -0800295 if (header.getServiceCategory() == SmsCbConstants.MESSAGE_ID_CMAS_GEO_FENCING_TRIGGER) {
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700296 GeoFencingTriggerMessage triggerMessage =
297 GsmSmsCbMessage.createGeoFencingTriggerMessage(pdu);
298 if (triggerMessage != null) {
299 return handleGeoFencingTriggerMessage(triggerMessage, slotIndex);
300 }
301 } else {
302 SmsCbMessage cbMessage = handleGsmBroadcastSms(header, pdu, slotIndex);
303 if (cbMessage != null) {
Jack Yu2390cad2019-10-24 00:19:49 -0700304 if (isDuplicate(cbMessage)) {
305 return false;
306 }
Jack Yu90af67b2020-01-24 11:45:59 -0800307
308 if (handleAreaInfoMessage(slotIndex, cbMessage)) {
309 log("Channel " + cbMessage.getServiceCategory() + " message processed");
310 return false;
311 }
312
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700313 handleBroadcastSms(cbMessage);
314 return true;
315 }
316 if (VDBG) log("Not handled GSM broadcasts.");
317 }
Jordan Liu62e183f2019-12-20 15:09:44 -0800318 } else {
319 loge("handleSmsMessage for GSM got object of type: "
320 + message.obj.getClass().getName());
321 CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
322 CELL_BROADCAST_MESSAGE_ERROR__TYPE__UNEXPECTED_GSM_MESSAGE_TYPE_FROM_FWK,
323 message.obj.getClass().getName());
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700324 }
Jordan Liu62e183f2019-12-20 15:09:44 -0800325 if (message.obj instanceof SmsCbMessage) {
326 return super.handleSmsMessage(message);
327 } else {
328 return false;
329 }
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700330 }
331
Jordan Liu8d7d0a12019-10-21 12:30:56 -0700332 // return the GSM cell location from the first GSM cell info
333 private Pair<Integer, Integer> getGsmLacAndCid() {
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700334 TelephonyManager tm =
335 (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
336 List<CellInfo> infos = tm.getAllCellInfo();
337 for (CellInfo info : infos) {
Jordan Liu8d7d0a12019-10-21 12:30:56 -0700338 CellIdentity ci = info.getCellIdentity();
339 if (ci instanceof CellIdentityGsm) {
340 CellIdentityGsm ciGsm = (CellIdentityGsm) ci;
341 int lac = ciGsm.getLac() != CellInfo.UNAVAILABLE ? ciGsm.getLac() : -1;
342 int cid = ciGsm.getCid() != CellInfo.UNAVAILABLE ? ciGsm.getCid() : -1;
343 return Pair.create(lac, cid);
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700344 }
345 }
Jordan Liu8d7d0a12019-10-21 12:30:56 -0700346 return null;
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700347 }
348
349
350 /**
351 * Handle 3GPP format SMS-CB message.
352 * @param header the cellbroadcast header.
353 * @param receivedPdu the received PDUs as a byte[]
354 */
355 private SmsCbMessage handleGsmBroadcastSms(SmsCbHeader header, byte[] receivedPdu,
356 int slotIndex) {
357 try {
358 if (VDBG) {
359 int pduLength = receivedPdu.length;
360 for (int i = 0; i < pduLength; i += 8) {
361 StringBuilder sb = new StringBuilder("SMS CB pdu data: ");
362 for (int j = i; j < i + 8 && j < pduLength; j++) {
363 int b = receivedPdu[j] & 0xff;
364 if (b < 0x10) {
365 sb.append('0');
366 }
367 sb.append(Integer.toHexString(b)).append(' ');
368 }
369 log(sb.toString());
370 }
371 }
372
373 if (VDBG) log("header=" + header);
374 TelephonyManager tm =
375 (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
Jordan Liuac12bae2019-11-21 11:59:07 -0800376 tm.createForSubscriptionId(getSubIdForPhone(mContext, slotIndex));
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700377 // TODO make a systemAPI for getNetworkOperatorForSlotIndex
Jordan Liu8d7d0a12019-10-21 12:30:56 -0700378 String plmn = tm.getSimOperator();
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700379 int lac = -1;
380 int cid = -1;
Jordan Liu8d7d0a12019-10-21 12:30:56 -0700381 Pair<Integer, Integer> lacAndCid = getGsmLacAndCid();
382 // Check if GSM lac and cid are available. This is required to support
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700383 // dual-mode devices such as CDMA/LTE devices that require support for
384 // both 3GPP and 3GPP2 format messages
Jordan Liu8d7d0a12019-10-21 12:30:56 -0700385 if (lacAndCid != null) {
386 lac = lacAndCid.first;
387 cid = lacAndCid.second;
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700388 }
389
390 SmsCbLocation location;
391 switch (header.getGeographicalScope()) {
392 case SmsCbMessage.GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE:
393 location = new SmsCbLocation(plmn, lac, -1);
394 break;
395
396 case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE:
397 case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE:
398 location = new SmsCbLocation(plmn, lac, cid);
399 break;
400
401 case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE:
402 default:
Jordan Liuac12bae2019-11-21 11:59:07 -0800403 location = new SmsCbLocation(plmn, -1, -1);
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700404 break;
405 }
406
407 byte[][] pdus;
408 int pageCount = header.getNumberOfPages();
409 if (pageCount > 1) {
410 // Multi-page message
411 SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, location);
412
413 // Try to find other pages of the same message
414 pdus = mSmsCbPageMap.get(concatInfo);
415
416 if (pdus == null) {
417 // This is the first page of this message, make room for all
418 // pages and keep until complete
419 pdus = new byte[pageCount][];
420
421 mSmsCbPageMap.put(concatInfo, pdus);
422 }
423
424 if (VDBG) log("pdus size=" + pdus.length);
425 // Page parameter is one-based
426 pdus[header.getPageIndex() - 1] = receivedPdu;
427
428 for (byte[] pdu : pdus) {
429 if (pdu == null) {
430 // Still missing pages, exit
431 log("still missing pdu");
432 return null;
433 }
434 }
435
436 // Message complete, remove and dispatch
437 mSmsCbPageMap.remove(concatInfo);
438 } else {
439 // Single page message
440 pdus = new byte[1][];
441 pdus[0] = receivedPdu;
442 }
443
444 // Remove messages that are out of scope to prevent the map from
445 // growing indefinitely, containing incomplete messages that were
446 // never assembled
447 Iterator<SmsCbConcatInfo> iter = mSmsCbPageMap.keySet().iterator();
448
449 while (iter.hasNext()) {
450 SmsCbConcatInfo info = iter.next();
451
452 if (!info.matchesLocation(plmn, lac, cid)) {
453 iter.remove();
454 }
455 }
456
457 return GsmSmsCbMessage.createSmsCbMessage(mContext, header, location, pdus, slotIndex);
458
459 } catch (RuntimeException e) {
460 loge("Error in decoding SMS CB pdu", e);
Jordan Liu62e183f2019-12-20 15:09:44 -0800461 CellBroadcastStatsLog.write(CellBroadcastStatsLog.CB_MESSAGE_ERROR,
462 CELL_BROADCAST_MESSAGE_ERROR__TYPE__GSM_INVALID_PDU, e.toString());
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700463 return null;
464 }
465 }
466
467 private SmsCbHeader createSmsCbHeader(byte[] bytes) {
468 try {
469 return new SmsCbHeader(bytes);
470 } catch (Exception ex) {
471 loge("Can't create SmsCbHeader, ex = " + ex.toString());
472 return null;
473 }
474 }
475
476 /**
477 * Holds all info about a message page needed to assemble a complete concatenated message.
478 */
479 private static final class SmsCbConcatInfo {
480
481 private final SmsCbHeader mHeader;
482 private final SmsCbLocation mLocation;
483
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700484 SmsCbConcatInfo(SmsCbHeader header, SmsCbLocation location) {
485 mHeader = header;
486 mLocation = location;
487 }
488
489 @Override
490 public int hashCode() {
491 return (mHeader.getSerialNumber() * 31) + mLocation.hashCode();
492 }
493
494 @Override
495 public boolean equals(Object obj) {
496 if (obj instanceof SmsCbConcatInfo) {
497 SmsCbConcatInfo other = (SmsCbConcatInfo) obj;
498
499 // Two pages match if they have the same serial number (which includes the
500 // geographical scope and update number), and both pages belong to the same
501 // location (PLMN, plus LAC and CID if these are part of the geographical scope).
502 return mHeader.getSerialNumber() == other.mHeader.getSerialNumber()
503 && mLocation.equals(other.mLocation);
504 }
505
506 return false;
507 }
508
509 /**
510 * Compare the location code for this message to the current location code. The match is
511 * relative to the geographical scope of the message, which determines whether the LAC
512 * and Cell ID are saved in mLocation or set to -1 to match all values.
513 *
514 * @param plmn the current PLMN
515 * @param lac the current Location Area (GSM) or Service Area (UMTS)
516 * @param cid the current Cell ID
517 * @return true if this message is valid for the current location; false otherwise
518 */
Jordan Liudfcbfaf2019-10-11 11:42:03 -0700519 public boolean matchesLocation(String plmn, int lac, int cid) {
520 return mLocation.isInLocationArea(plmn, lac, cid);
521 }
522 }
523}