1 /*
2  * Copyright (C) 2010 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 android.telephony;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.SystemApi;
23 import android.content.ContentValues;
24 import android.database.Cursor;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.provider.Telephony.CellBroadcasts;
28 import android.telephony.CbGeoUtils.Geometry;
29 
30 import java.lang.annotation.Retention;
31 import java.lang.annotation.RetentionPolicy;
32 import java.util.ArrayList;
33 import java.util.List;
34 
35 /**
36  * Parcelable object containing a received cell broadcast message. There are four different types
37  * of Cell Broadcast messages:
38  *
39  * <ul>
40  * <li>opt-in informational broadcasts, e.g. news, weather, stock quotes, sports scores</li>
41  * <li>cell information messages, broadcast on channel 50, indicating the current cell name for
42  *  roaming purposes (required to display on the idle screen in Brazil)</li>
43  * <li>emergency broadcasts for the Japanese Earthquake and Tsunami Warning System (ETWS)</li>
44  * <li>emergency broadcasts for the American Commercial Mobile Alert Service (CMAS)</li>
45  * </ul>
46  *
47  * <p>There are also four different CB message formats: GSM, ETWS Primary Notification (GSM only),
48  * UMTS, and CDMA. Some fields are only applicable for some message formats. Other fields were
49  * unified under a common name, avoiding some names, such as "Message Identifier", that refer to
50  * two completely different concepts in 3GPP and CDMA.
51  *
52  * <p>The GSM/UMTS Message Identifier field is available via {@link #getServiceCategory}, the name
53  * of the equivalent field in CDMA. In both cases the service category is a 16-bit value, but 3GPP
54  * and 3GPP2 have completely different meanings for the respective values. For ETWS and CMAS, the
55  * application should
56  *
57  * <p>The CDMA Message Identifier field is available via {@link #getSerialNumber}, which is used
58  * to detect the receipt of a duplicate message to be discarded. In CDMA, the message ID is
59  * unique to the current PLMN. In GSM/UMTS, there is a 16-bit serial number containing a 2-bit
60  * Geographical Scope field which indicates whether the 10-bit message code and 4-bit update number
61  * are considered unique to the PLMN, to the current cell, or to the current Location Area (or
62  * Service Area in UMTS). The relevant values are concatenated into a single String which will be
63  * unique if the messages are not duplicates.
64  *
65  * <p>The SMS dispatcher does not detect duplicate messages. However, it does concatenate the
66  * pages of a GSM multi-page cell broadcast into a single SmsCbMessage object.
67  *
68  * <p>Interested applications with {@code RECEIVE_SMS_PERMISSION} can register to receive
69  * {@code SMS_CB_RECEIVED_ACTION} broadcast intents for incoming non-emergency broadcasts.
70  * Only system applications such as the CellBroadcastReceiver may receive notifications for
71  * emergency broadcasts (ETWS and CMAS). This is intended to prevent any potential for delays or
72  * interference with the immediate display of the alert message and playing of the alert sound and
73  * vibration pattern, which could be caused by poorly written or malicious non-system code.
74  *
75  * @hide
76  */
77 @SystemApi
78 public final class SmsCbMessage implements Parcelable {
79 
80     /** @hide */
81     public static final String LOG_TAG = "SMSCB";
82 
83     /** Cell wide geographical scope with immediate display (GSM/UMTS only). */
84     public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0;
85 
86     /** PLMN wide geographical scope (GSM/UMTS and all CDMA broadcasts). */
87     public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1;
88 
89     /** Location / service area wide geographical scope (GSM/UMTS only). */
90     public static final int GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE = 2;
91 
92     /** Cell wide geographical scope (GSM/UMTS only). */
93     public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE = 3;
94 
95     /** @hide */
96     @IntDef(prefix = { "GEOGRAPHICAL_SCOPE_" }, value = {
97             GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE,
98             GEOGRAPHICAL_SCOPE_PLMN_WIDE,
99             GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE,
100             GEOGRAPHICAL_SCOPE_CELL_WIDE,
101     })
102     @Retention(RetentionPolicy.SOURCE)
103     public @interface GeographicalScope {}
104 
105     /** GSM or UMTS format cell broadcast. */
106     public static final int MESSAGE_FORMAT_3GPP = 1;
107 
108     /** CDMA format cell broadcast. */
109     public static final int MESSAGE_FORMAT_3GPP2 = 2;
110 
111     /** @hide */
112     @IntDef(prefix = { "MESSAGE_FORMAT_" }, value = {
113             MESSAGE_FORMAT_3GPP,
114             MESSAGE_FORMAT_3GPP2
115     })
116     @Retention(RetentionPolicy.SOURCE)
117     public @interface MessageFormat {}
118 
119     /** Normal message priority. */
120     public static final int MESSAGE_PRIORITY_NORMAL = 0;
121 
122     /** Interactive message priority. */
123     public static final int MESSAGE_PRIORITY_INTERACTIVE = 1;
124 
125     /** Urgent message priority. */
126     public static final int MESSAGE_PRIORITY_URGENT = 2;
127 
128     /** Emergency message priority. */
129     public static final int MESSAGE_PRIORITY_EMERGENCY = 3;
130 
131     /** @hide */
132     @IntDef(prefix = { "MESSAGE_PRIORITY_" }, value = {
133             MESSAGE_PRIORITY_NORMAL,
134             MESSAGE_PRIORITY_INTERACTIVE,
135             MESSAGE_PRIORITY_URGENT,
136             MESSAGE_PRIORITY_EMERGENCY,
137     })
138     @Retention(RetentionPolicy.SOURCE)
139     public @interface MessagePriority {}
140 
141     /**
142      * Integer indicating that the maximum wait time is not set.
143      * Based on ATIS-0700041 Section 5.2.8 WAC Geo-Fencing Maximum Wait Time Table 12.
144      */
145     public static final int MAXIMUM_WAIT_TIME_NOT_SET = 255;
146 
147     /** Format of this message (for interpretation of service category values). */
148     private final int mMessageFormat;
149 
150     /** Geographical scope of broadcast. */
151     private final int mGeographicalScope;
152 
153     /**
154      * Serial number of broadcast (message identifier for CDMA, geographical scope + message code +
155      * update number for GSM/UMTS). The serial number plus the location code uniquely identify
156      * a cell broadcast for duplicate detection.
157      */
158     private final int mSerialNumber;
159 
160     /**
161      * Location identifier for this message. It consists of the current operator MCC/MNC as a
162      * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the
163      * message is not binary 01, the Location Area is included for comparison. If the GS is
164      * 00 or 11, the Cell ID is also included. LAC and Cell ID are -1 if not specified.
165      */
166     @NonNull
167     private final SmsCbLocation mLocation;
168 
169     /**
170      * 16-bit CDMA service category or GSM/UMTS message identifier. For ETWS and CMAS warnings,
171      * the information provided by the category is also available via {@link #getEtwsWarningInfo()}
172      * or {@link #getCmasWarningInfo()}.
173      */
174     private final int mServiceCategory;
175 
176     /** Message language, as a two-character string, e.g. "en". */
177     @Nullable
178     private final String mLanguage;
179 
180     /** The 8-bit data coding scheme defined in 3GPP TS 23.038 section 4. */
181     private final int mDataCodingScheme;
182 
183     /** Message body, as a String. */
184     @Nullable
185     private final String mBody;
186 
187     /** Message priority (including emergency priority). */
188     private final int mPriority;
189 
190     /** ETWS warning notification information (ETWS warnings only). */
191     @Nullable
192     private final SmsCbEtwsInfo mEtwsWarningInfo;
193 
194     /** CMAS warning notification information (CMAS warnings only). */
195     @Nullable
196     private final SmsCbCmasInfo mCmasWarningInfo;
197 
198     /**
199      * Geo-Fencing Maximum Wait Time in second, a device shall allow to determine its position
200      * meeting operator policy. If the device is unable to determine its position meeting operator
201      * policy within the GeoFencing Maximum Wait Time, it shall present the alert to the user and
202      * discontinue further positioning determination for the alert.
203      */
204     private final int mMaximumWaitTimeSec;
205 
206     /** UNIX timestamp of when the message was received. */
207     private final long mReceivedTimeMillis;
208 
209     /** CMAS warning area coordinates. */
210     private final List<Geometry> mGeometries;
211 
212     private final int mSlotIndex;
213 
214     private final int mSubId;
215 
216     /**
217      * Create a new SmsCbMessage with the specified data.
218      * @hide
219      */
SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber, @NonNull SmsCbLocation location, int serviceCategory, @Nullable String language, @Nullable String body, int priority, @Nullable SmsCbEtwsInfo etwsWarningInfo, @Nullable SmsCbCmasInfo cmasWarningInfo, int slotIndex, int subId)220     public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber,
221             @NonNull SmsCbLocation location, int serviceCategory, @Nullable String language,
222             @Nullable String body, int priority, @Nullable SmsCbEtwsInfo etwsWarningInfo,
223             @Nullable SmsCbCmasInfo cmasWarningInfo, int slotIndex, int subId) {
224 
225         this(messageFormat, geographicalScope, serialNumber, location, serviceCategory, language,
226                 0, body, priority, etwsWarningInfo, cmasWarningInfo, 0 /* maximumWaitingTime */,
227                 null /* geometries */, System.currentTimeMillis(), slotIndex, subId);
228     }
229 
230     /**
231      * Create a new {@link SmsCbMessage} with the specified data, including warning area
232      * coordinates information.
233      */
SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber, @NonNull SmsCbLocation location, int serviceCategory, @Nullable String language, int dataCodingScheme, @Nullable String body, int priority, @Nullable SmsCbEtwsInfo etwsWarningInfo, @Nullable SmsCbCmasInfo cmasWarningInfo, int maximumWaitTimeSec, @Nullable List<Geometry> geometries, long receivedTimeMillis, int slotIndex, int subId)234     public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber,
235                         @NonNull SmsCbLocation location, int serviceCategory,
236                         @Nullable String language, int dataCodingScheme, @Nullable String body,
237                         int priority, @Nullable SmsCbEtwsInfo etwsWarningInfo,
238                         @Nullable SmsCbCmasInfo cmasWarningInfo, int maximumWaitTimeSec,
239                         @Nullable List<Geometry> geometries, long receivedTimeMillis, int slotIndex,
240                         int subId) {
241         mMessageFormat = messageFormat;
242         mGeographicalScope = geographicalScope;
243         mSerialNumber = serialNumber;
244         mLocation = location;
245         mServiceCategory = serviceCategory;
246         mLanguage = language;
247         mDataCodingScheme = dataCodingScheme;
248         mBody = body;
249         mPriority = priority;
250         mEtwsWarningInfo = etwsWarningInfo;
251         mCmasWarningInfo = cmasWarningInfo;
252         mReceivedTimeMillis = receivedTimeMillis;
253         mGeometries = geometries;
254         mMaximumWaitTimeSec = maximumWaitTimeSec;
255         mSlotIndex = slotIndex;
256         mSubId = subId;
257     }
258 
259     /**
260      * Create a new SmsCbMessage object from a Parcel.
261      * @hide
262      */
SmsCbMessage(@onNull Parcel in)263     public SmsCbMessage(@NonNull Parcel in) {
264         mMessageFormat = in.readInt();
265         mGeographicalScope = in.readInt();
266         mSerialNumber = in.readInt();
267         mLocation = new SmsCbLocation(in);
268         mServiceCategory = in.readInt();
269         mLanguage = in.readString();
270         mDataCodingScheme = in.readInt();
271         mBody = in.readString();
272         mPriority = in.readInt();
273         int type = in.readInt();
274         switch (type) {
275             case 'E':
276                 // unparcel ETWS warning information
277                 mEtwsWarningInfo = new SmsCbEtwsInfo(in);
278                 mCmasWarningInfo = null;
279                 break;
280 
281             case 'C':
282                 // unparcel CMAS warning information
283                 mEtwsWarningInfo = null;
284                 mCmasWarningInfo = new SmsCbCmasInfo(in);
285                 break;
286 
287             default:
288                 mEtwsWarningInfo = null;
289                 mCmasWarningInfo = null;
290         }
291         mReceivedTimeMillis = in.readLong();
292         String geoStr = in.readString();
293         mGeometries = geoStr != null ? CbGeoUtils.parseGeometriesFromString(geoStr) : null;
294         mMaximumWaitTimeSec = in.readInt();
295         mSlotIndex = in.readInt();
296         mSubId = in.readInt();
297     }
298 
299     /**
300      * Flatten this object into a Parcel.
301      *
302      * @param dest  The Parcel in which the object should be written.
303      * @param flags Additional flags about how the object should be written (ignored).
304      */
305     @Override
writeToParcel(Parcel dest, int flags)306     public void writeToParcel(Parcel dest, int flags) {
307         dest.writeInt(mMessageFormat);
308         dest.writeInt(mGeographicalScope);
309         dest.writeInt(mSerialNumber);
310         mLocation.writeToParcel(dest, flags);
311         dest.writeInt(mServiceCategory);
312         dest.writeString(mLanguage);
313         dest.writeInt(mDataCodingScheme);
314         dest.writeString(mBody);
315         dest.writeInt(mPriority);
316         if (mEtwsWarningInfo != null) {
317             // parcel ETWS warning information
318             dest.writeInt('E');
319             mEtwsWarningInfo.writeToParcel(dest, flags);
320         } else if (mCmasWarningInfo != null) {
321             // parcel CMAS warning information
322             dest.writeInt('C');
323             mCmasWarningInfo.writeToParcel(dest, flags);
324         } else {
325             // no ETWS or CMAS warning information
326             dest.writeInt('0');
327         }
328         dest.writeLong(mReceivedTimeMillis);
329         dest.writeString(
330                 mGeometries != null ? CbGeoUtils.encodeGeometriesToString(mGeometries) : null);
331         dest.writeInt(mMaximumWaitTimeSec);
332         dest.writeInt(mSlotIndex);
333         dest.writeInt(mSubId);
334     }
335 
336     @NonNull
337     public static final Parcelable.Creator<SmsCbMessage> CREATOR =
338             new Parcelable.Creator<SmsCbMessage>() {
339         @Override
340         public SmsCbMessage createFromParcel(Parcel in) {
341             return new SmsCbMessage(in);
342         }
343 
344         @Override
345         public SmsCbMessage[] newArray(int size) {
346             return new SmsCbMessage[size];
347         }
348     };
349 
350     /**
351      * Return the geographical scope of this message (GSM/UMTS only).
352      *
353      * @return Geographical scope
354      */
getGeographicalScope()355     public @GeographicalScope int getGeographicalScope() {
356         return mGeographicalScope;
357     }
358 
359     /**
360      * Return the broadcast serial number of broadcast (message identifier for CDMA, or
361      * geographical scope + message code + update number for GSM/UMTS). The serial number plus
362      * the location code uniquely identify a cell broadcast for duplicate detection.
363      *
364      * @return the 16-bit CDMA message identifier or GSM/UMTS serial number
365      */
getSerialNumber()366     public int getSerialNumber() {
367         return mSerialNumber;
368     }
369 
370     /**
371      * Return the location identifier for this message, consisting of the MCC/MNC as a
372      * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the
373      * message is not binary 01, the Location Area is included. If the GS is 00 or 11, the
374      * cell ID is also included. The {@link SmsCbLocation} object includes a method to test
375      * if the location is included within another location area or within a PLMN and CellLocation.
376      *
377      * @return the geographical location code for duplicate message detection
378      */
379     @NonNull
getLocation()380     public android.telephony.SmsCbLocation getLocation() {
381         return mLocation;
382     }
383 
384     /**
385      * Return the 16-bit CDMA service category or GSM/UMTS message identifier. The interpretation
386      * of the category is radio technology specific. For ETWS and CMAS warnings, the information
387      * provided by the category is available via {@link #getEtwsWarningInfo()} or
388      * {@link #getCmasWarningInfo()} in a radio technology independent format.
389      *
390      * @return the radio technology specific service category
391      */
getServiceCategory()392     public int getServiceCategory() {
393         return mServiceCategory;
394     }
395 
396     /**
397      * Get the ISO-639-1 language code for this message, or null if unspecified
398      *
399      * @return Language code
400      */
401     @Nullable
getLanguageCode()402     public String getLanguageCode() {
403         return mLanguage;
404     }
405 
406     /**
407      * Get data coding scheme of the message
408      *
409      * @return The 8-bit data coding scheme defined in 3GPP TS 23.038 section 4.
410      */
getDataCodingScheme()411     public int getDataCodingScheme() {
412         return mDataCodingScheme;
413     }
414 
415     /**
416      * Get the body of this message, or null if no body available
417      *
418      * @return Body, or null
419      */
420     @Nullable
getMessageBody()421     public String getMessageBody() {
422         return mBody;
423     }
424 
425     /**
426      * Get the warning area coordinates information represented by polygons and circles.
427      * @return a list of geometries, or an empty list if there is no coordinate information
428      * associated with this message.
429      * @hide
430      */
431     @SystemApi
432     @NonNull
getGeometries()433     public List<Geometry> getGeometries() {
434         if (mGeometries == null) {
435             return new ArrayList<>();
436         }
437         return mGeometries;
438     }
439 
440     /**
441      * Get the Geo-Fencing Maximum Wait Time.
442      * @return the time in second.
443      */
getMaximumWaitingDuration()444     public int getMaximumWaitingDuration() {
445         return mMaximumWaitTimeSec;
446     }
447 
448     /**
449      * Get the time when this message was received.
450      * @return the time in millisecond
451      */
getReceivedTime()452     public long getReceivedTime() {
453         return mReceivedTimeMillis;
454     }
455 
456     /**
457      * Get the slot index associated with this message.
458      * @return the slot index associated with this message
459      */
getSlotIndex()460     public int getSlotIndex() {
461         return mSlotIndex;
462     }
463 
464     /**
465      * Get the subscription id associated with this message.
466      * @return the subscription id associated with this message
467      */
getSubscriptionId()468     public int getSubscriptionId() {
469         return mSubId;
470     }
471 
472     /**
473      * Get the message format ({@link #MESSAGE_FORMAT_3GPP} or {@link #MESSAGE_FORMAT_3GPP2}).
474      * @return an integer representing 3GPP or 3GPP2 message format
475      */
getMessageFormat()476     public @MessageFormat int getMessageFormat() {
477         return mMessageFormat;
478     }
479 
480     /**
481      * Get the message priority. Normal broadcasts return {@link #MESSAGE_PRIORITY_NORMAL}
482      * and emergency broadcasts return {@link #MESSAGE_PRIORITY_EMERGENCY}. CDMA also may return
483      * {@link #MESSAGE_PRIORITY_INTERACTIVE} or {@link #MESSAGE_PRIORITY_URGENT}.
484      * @return an integer representing the message priority
485      */
getMessagePriority()486     public @MessagePriority int getMessagePriority() {
487         return mPriority;
488     }
489 
490     /**
491      * If this is an ETWS warning notification then this method will return an object containing
492      * the ETWS warning type, the emergency user alert flag, and the popup flag. If this is an
493      * ETWS primary notification (GSM only), there will also be a 7-byte timestamp and 43-byte
494      * digital signature. As of Release 10, 3GPP TS 23.041 states that the UE shall ignore the
495      * ETWS primary notification timestamp and digital signature if received.
496      *
497      * @return an SmsCbEtwsInfo object, or null if this is not an ETWS warning notification
498      */
499     @Nullable
getEtwsWarningInfo()500     public SmsCbEtwsInfo getEtwsWarningInfo() {
501         return mEtwsWarningInfo;
502     }
503 
504     /**
505      * If this is a CMAS warning notification then this method will return an object containing
506      * the CMAS message class, category, response type, severity, urgency and certainty.
507      * The message class is always present. Severity, urgency and certainty are present for CDMA
508      * warning notifications containing a type 1 elements record and for GSM and UMTS warnings
509      * except for the Presidential-level alert category. Category and response type are only
510      * available for CDMA notifications containing a type 1 elements record.
511      *
512      * @return an SmsCbCmasInfo object, or null if this is not a CMAS warning notification
513      */
514     @Nullable
getCmasWarningInfo()515     public SmsCbCmasInfo getCmasWarningInfo() {
516         return mCmasWarningInfo;
517     }
518 
519     /**
520      * Return whether this message is an emergency (PWS) message type.
521      * @return true if the message is an emergency notification; false otherwise
522      */
isEmergencyMessage()523     public boolean isEmergencyMessage() {
524         return mPriority == MESSAGE_PRIORITY_EMERGENCY;
525     }
526 
527     /**
528      * Return whether this message is an ETWS warning alert.
529      * @return true if the message is an ETWS warning notification; false otherwise
530      */
isEtwsMessage()531     public boolean isEtwsMessage() {
532         return mEtwsWarningInfo != null;
533     }
534 
535     /**
536      * Return whether this message is a CMAS warning alert.
537      * @return true if the message is a CMAS warning notification; false otherwise
538      */
isCmasMessage()539     public boolean isCmasMessage() {
540         return mCmasWarningInfo != null;
541     }
542 
543     @Override
toString()544     public String toString() {
545         return "SmsCbMessage{geographicalScope=" + mGeographicalScope + ", serialNumber="
546                 + mSerialNumber + ", location=" + mLocation + ", serviceCategory="
547                 + mServiceCategory + ", language=" + mLanguage + ", body=" + mBody
548                 + ", priority=" + mPriority
549                 + (mEtwsWarningInfo != null ? (", " + mEtwsWarningInfo.toString()) : "")
550                 + (mCmasWarningInfo != null ? (", " + mCmasWarningInfo.toString()) : "")
551                 + ", maximumWaitingTime=" + mMaximumWaitTimeSec
552                 + ", received time=" + mReceivedTimeMillis
553                 + ", slotIndex = " + mSlotIndex
554                 + ", geo=" + (mGeometries != null
555                 ? CbGeoUtils.encodeGeometriesToString(mGeometries) : "null")
556                 + '}';
557     }
558 
559     /**
560      * Describe the kinds of special objects contained in the marshalled representation.
561      * @return a bitmask indicating this Parcelable contains no special objects
562      */
563     @Override
describeContents()564     public int describeContents() {
565         return 0;
566     }
567 
568     /**
569      * @return the {@link ContentValues} instance that includes the cell broadcast data.
570      */
571     @NonNull
getContentValues()572     public ContentValues getContentValues() {
573         ContentValues cv = new ContentValues(16);
574         cv.put(CellBroadcasts.SLOT_INDEX, mSlotIndex);
575         cv.put(CellBroadcasts.SUBSCRIPTION_ID, mSubId);
576         cv.put(CellBroadcasts.GEOGRAPHICAL_SCOPE, mGeographicalScope);
577         if (mLocation.getPlmn() != null) {
578             cv.put(CellBroadcasts.PLMN, mLocation.getPlmn());
579         }
580         if (mLocation.getLac() != -1) {
581             cv.put(CellBroadcasts.LAC, mLocation.getLac());
582         }
583         if (mLocation.getCid() != -1) {
584             cv.put(CellBroadcasts.CID, mLocation.getCid());
585         }
586         cv.put(CellBroadcasts.SERIAL_NUMBER, getSerialNumber());
587         cv.put(CellBroadcasts.SERVICE_CATEGORY, getServiceCategory());
588         cv.put(CellBroadcasts.LANGUAGE_CODE, getLanguageCode());
589         cv.put(CellBroadcasts.DATA_CODING_SCHEME, getDataCodingScheme());
590         cv.put(CellBroadcasts.MESSAGE_BODY, getMessageBody());
591         cv.put(CellBroadcasts.MESSAGE_FORMAT, getMessageFormat());
592         cv.put(CellBroadcasts.MESSAGE_PRIORITY, getMessagePriority());
593 
594         SmsCbEtwsInfo etwsInfo = getEtwsWarningInfo();
595         if (etwsInfo != null) {
596             cv.put(CellBroadcasts.ETWS_WARNING_TYPE, etwsInfo.getWarningType());
597         }
598 
599         SmsCbCmasInfo cmasInfo = getCmasWarningInfo();
600         if (cmasInfo != null) {
601             cv.put(CellBroadcasts.CMAS_MESSAGE_CLASS, cmasInfo.getMessageClass());
602             cv.put(CellBroadcasts.CMAS_CATEGORY, cmasInfo.getCategory());
603             cv.put(CellBroadcasts.CMAS_RESPONSE_TYPE, cmasInfo.getResponseType());
604             cv.put(CellBroadcasts.CMAS_SEVERITY, cmasInfo.getSeverity());
605             cv.put(CellBroadcasts.CMAS_URGENCY, cmasInfo.getUrgency());
606             cv.put(CellBroadcasts.CMAS_CERTAINTY, cmasInfo.getCertainty());
607         }
608 
609         cv.put(CellBroadcasts.RECEIVED_TIME, mReceivedTimeMillis);
610 
611         if (mGeometries != null) {
612             cv.put(CellBroadcasts.GEOMETRIES, CbGeoUtils.encodeGeometriesToString(mGeometries));
613         } else {
614             cv.put(CellBroadcasts.GEOMETRIES, (String) null);
615         }
616 
617         cv.put(CellBroadcasts.MAXIMUM_WAIT_TIME, mMaximumWaitTimeSec);
618 
619         return cv;
620     }
621 
622     /**
623      * Create a {@link SmsCbMessage} instance from a row in the cell broadcast database.
624      * @param cursor an open SQLite cursor pointing to the row to read
625      * @return a {@link SmsCbMessage} instance.
626      * @throws IllegalArgumentException if one of the required columns is missing
627      */
628     @NonNull
createFromCursor(@onNull Cursor cursor)629     public static SmsCbMessage createFromCursor(@NonNull Cursor cursor) {
630         int geoScope = cursor.getInt(
631                 cursor.getColumnIndexOrThrow(CellBroadcasts.GEOGRAPHICAL_SCOPE));
632         int serialNum = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SERIAL_NUMBER));
633         int category = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SERVICE_CATEGORY));
634         String language = cursor.getString(
635                 cursor.getColumnIndexOrThrow(CellBroadcasts.LANGUAGE_CODE));
636         String body = cursor.getString(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_BODY));
637         int format = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_FORMAT));
638         int priority = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_PRIORITY));
639         int slotIndex = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SLOT_INDEX));
640         int subId = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SUBSCRIPTION_ID));
641 
642         String plmn;
643         int plmnColumn = cursor.getColumnIndex(CellBroadcasts.PLMN);
644         if (plmnColumn != -1 && !cursor.isNull(plmnColumn)) {
645             plmn = cursor.getString(plmnColumn);
646         } else {
647             plmn = null;
648         }
649 
650         int lac;
651         int lacColumn = cursor.getColumnIndex(CellBroadcasts.LAC);
652         if (lacColumn != -1 && !cursor.isNull(lacColumn)) {
653             lac = cursor.getInt(lacColumn);
654         } else {
655             lac = -1;
656         }
657 
658         int cid;
659         int cidColumn = cursor.getColumnIndex(CellBroadcasts.CID);
660         if (cidColumn != -1 && !cursor.isNull(cidColumn)) {
661             cid = cursor.getInt(cidColumn);
662         } else {
663             cid = -1;
664         }
665 
666         SmsCbLocation location = new SmsCbLocation(plmn, lac, cid);
667 
668         SmsCbEtwsInfo etwsInfo;
669         int etwsWarningTypeColumn = cursor.getColumnIndex(CellBroadcasts.ETWS_WARNING_TYPE);
670         if (etwsWarningTypeColumn != -1 && !cursor.isNull(etwsWarningTypeColumn)) {
671             int warningType = cursor.getInt(etwsWarningTypeColumn);
672             etwsInfo = new SmsCbEtwsInfo(warningType, false, false, false, null);
673         } else {
674             etwsInfo = null;
675         }
676 
677         SmsCbCmasInfo cmasInfo = null;
678         int cmasMessageClassColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_MESSAGE_CLASS);
679         if (cmasMessageClassColumn != -1 && !cursor.isNull(cmasMessageClassColumn)) {
680             int messageClass = cursor.getInt(cmasMessageClassColumn);
681 
682             int cmasCategory;
683             int cmasCategoryColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_CATEGORY);
684             if (cmasCategoryColumn != -1 && !cursor.isNull(cmasCategoryColumn)) {
685                 cmasCategory = cursor.getInt(cmasCategoryColumn);
686             } else {
687                 cmasCategory = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN;
688             }
689 
690             int responseType;
691             int cmasResponseTypeColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_RESPONSE_TYPE);
692             if (cmasResponseTypeColumn != -1 && !cursor.isNull(cmasResponseTypeColumn)) {
693                 responseType = cursor.getInt(cmasResponseTypeColumn);
694             } else {
695                 responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN;
696             }
697 
698             int severity;
699             int cmasSeverityColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_SEVERITY);
700             if (cmasSeverityColumn != -1 && !cursor.isNull(cmasSeverityColumn)) {
701                 severity = cursor.getInt(cmasSeverityColumn);
702             } else {
703                 severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
704             }
705 
706             int urgency;
707             int cmasUrgencyColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_URGENCY);
708             if (cmasUrgencyColumn != -1 && !cursor.isNull(cmasUrgencyColumn)) {
709                 urgency = cursor.getInt(cmasUrgencyColumn);
710             } else {
711                 urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
712             }
713 
714             int certainty;
715             int cmasCertaintyColumn = cursor.getColumnIndex(CellBroadcasts.CMAS_CERTAINTY);
716             if (cmasCertaintyColumn != -1 && !cursor.isNull(cmasCertaintyColumn)) {
717                 certainty = cursor.getInt(cmasCertaintyColumn);
718             } else {
719                 certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
720             }
721 
722             cmasInfo = new SmsCbCmasInfo(messageClass, cmasCategory, responseType, severity,
723                     urgency, certainty);
724         }
725 
726         String geoStr = cursor.getString(cursor.getColumnIndex(CellBroadcasts.GEOMETRIES));
727         List<Geometry> geometries =
728                 geoStr != null ? CbGeoUtils.parseGeometriesFromString(geoStr) : null;
729 
730         long receivedTimeMillis = cursor.getLong(
731                 cursor.getColumnIndexOrThrow(CellBroadcasts.RECEIVED_TIME));
732 
733         int maximumWaitTimeSec = cursor.getInt(
734                 cursor.getColumnIndexOrThrow(CellBroadcasts.MAXIMUM_WAIT_TIME));
735 
736         return new SmsCbMessage(format, geoScope, serialNum, location, category,
737                 language, 0, body, priority, etwsInfo, cmasInfo, maximumWaitTimeSec, geometries,
738                 receivedTimeMillis, slotIndex, subId);
739     }
740 
741     /**
742      * @return {@code True} if this message needs geo-fencing check.
743      */
needGeoFencingCheck()744     public boolean needGeoFencingCheck() {
745         return mMaximumWaitTimeSec > 0 && mGeometries != null && !mGeometries.isEmpty();
746     }
747 }
748