1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package android.telephony;
18 
19 import android.content.ContentValues;
20 import android.content.Context;
21 import android.database.Cursor;
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 import android.provider.Telephony;
25 import android.text.format.DateUtils;
26 
27 /**
28  * Application wrapper for {@link SmsCbMessage}. This is Parcelable so that
29  * decoded broadcast message objects can be passed between running Services.
30  * New broadcasts are received by the CellBroadcastReceiver app, which exports
31  * the database of previously received broadcasts at "content://cellbroadcasts/".
32  * The "android.permission.READ_CELL_BROADCASTS" permission is required to read
33  * from the ContentProvider, and writes to the database are not allowed.<p>
34  *
35  * Use {@link #createFromCursor} to create CellBroadcastMessage objects from rows
36  * in the database cursor returned by the ContentProvider.
37  *
38  * {@hide}
39  */
40 public class CellBroadcastMessage implements Parcelable {
41 
42     /** Identifier for getExtra() when adding this object to an Intent. */
43     public static final String SMS_CB_MESSAGE_EXTRA =
44             "com.android.cellbroadcastreceiver.SMS_CB_MESSAGE";
45 
46     /** SmsCbMessage. */
47     private final SmsCbMessage mSmsCbMessage;
48 
49     private final long mDeliveryTime;
50     private boolean mIsRead;
51 
52     /**
53      * Indicates the subId
54      *
55      * @hide
56      */
57     private int mSubId = 0;
58 
59     /**
60      * set Subscription information
61      *
62      * @hide
63      */
setSubId(int subId)64     public void setSubId(int subId) {
65         mSubId = subId;
66     }
67 
68     /**
69      * get Subscription information
70      *
71      * @hide
72      */
getSubId()73     public int getSubId() {
74         return mSubId;
75     }
76 
CellBroadcastMessage(SmsCbMessage message)77     public CellBroadcastMessage(SmsCbMessage message) {
78         mSmsCbMessage = message;
79         mDeliveryTime = System.currentTimeMillis();
80         mIsRead = false;
81     }
82 
CellBroadcastMessage(SmsCbMessage message, long deliveryTime, boolean isRead)83     private CellBroadcastMessage(SmsCbMessage message, long deliveryTime, boolean isRead) {
84         mSmsCbMessage = message;
85         mDeliveryTime = deliveryTime;
86         mIsRead = isRead;
87     }
88 
CellBroadcastMessage(Parcel in)89     private CellBroadcastMessage(Parcel in) {
90         mSmsCbMessage = new SmsCbMessage(in);
91         mDeliveryTime = in.readLong();
92         mIsRead = (in.readInt() != 0);
93     }
94 
95     /** Parcelable: no special flags. */
96     @Override
describeContents()97     public int describeContents() {
98         return 0;
99     }
100 
101     @Override
writeToParcel(Parcel out, int flags)102     public void writeToParcel(Parcel out, int flags) {
103         mSmsCbMessage.writeToParcel(out, flags);
104         out.writeLong(mDeliveryTime);
105         out.writeInt(mIsRead ? 1 : 0);
106     }
107 
108     public static final Parcelable.Creator<CellBroadcastMessage> CREATOR
109             = new Parcelable.Creator<CellBroadcastMessage>() {
110         @Override
111         public CellBroadcastMessage createFromParcel(Parcel in) {
112             return new CellBroadcastMessage(in);
113         }
114 
115         @Override
116         public CellBroadcastMessage[] newArray(int size) {
117             return new CellBroadcastMessage[size];
118         }
119     };
120 
121     /**
122      * Create a CellBroadcastMessage from a row in the database.
123      * @param cursor an open SQLite cursor pointing to the row to read
124      * @return the new CellBroadcastMessage
125      * @throws IllegalArgumentException if one of the required columns is missing
126      */
createFromCursor(Cursor cursor)127     public static CellBroadcastMessage createFromCursor(Cursor cursor) {
128         int geoScope = cursor.getInt(
129                 cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.GEOGRAPHICAL_SCOPE));
130         int serialNum = cursor.getInt(
131                 cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.SERIAL_NUMBER));
132         int category = cursor.getInt(
133                 cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.SERVICE_CATEGORY));
134         String language = cursor.getString(
135                 cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.LANGUAGE_CODE));
136         String body = cursor.getString(
137                 cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.MESSAGE_BODY));
138         int format = cursor.getInt(
139                 cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.MESSAGE_FORMAT));
140         int priority = cursor.getInt(
141                 cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.MESSAGE_PRIORITY));
142 
143         String plmn;
144         int plmnColumn = cursor.getColumnIndex(Telephony.CellBroadcasts.PLMN);
145         if (plmnColumn != -1 && !cursor.isNull(plmnColumn)) {
146             plmn = cursor.getString(plmnColumn);
147         } else {
148             plmn = null;
149         }
150 
151         int lac;
152         int lacColumn = cursor.getColumnIndex(Telephony.CellBroadcasts.LAC);
153         if (lacColumn != -1 && !cursor.isNull(lacColumn)) {
154             lac = cursor.getInt(lacColumn);
155         } else {
156             lac = -1;
157         }
158 
159         int cid;
160         int cidColumn = cursor.getColumnIndex(Telephony.CellBroadcasts.CID);
161         if (cidColumn != -1 && !cursor.isNull(cidColumn)) {
162             cid = cursor.getInt(cidColumn);
163         } else {
164             cid = -1;
165         }
166 
167         SmsCbLocation location = new SmsCbLocation(plmn, lac, cid);
168 
169         SmsCbEtwsInfo etwsInfo;
170         int etwsWarningTypeColumn = cursor.getColumnIndex(
171                 Telephony.CellBroadcasts.ETWS_WARNING_TYPE);
172         if (etwsWarningTypeColumn != -1 && !cursor.isNull(etwsWarningTypeColumn)) {
173             int warningType = cursor.getInt(etwsWarningTypeColumn);
174             etwsInfo = new SmsCbEtwsInfo(warningType, false, false, null);
175         } else {
176             etwsInfo = null;
177         }
178 
179         SmsCbCmasInfo cmasInfo;
180         int cmasMessageClassColumn = cursor.getColumnIndex(
181                 Telephony.CellBroadcasts.CMAS_MESSAGE_CLASS);
182         if (cmasMessageClassColumn != -1 && !cursor.isNull(cmasMessageClassColumn)) {
183             int messageClass = cursor.getInt(cmasMessageClassColumn);
184 
185             int cmasCategory;
186             int cmasCategoryColumn = cursor.getColumnIndex(
187                     Telephony.CellBroadcasts.CMAS_CATEGORY);
188             if (cmasCategoryColumn != -1 && !cursor.isNull(cmasCategoryColumn)) {
189                 cmasCategory = cursor.getInt(cmasCategoryColumn);
190             } else {
191                 cmasCategory = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN;
192             }
193 
194             int responseType;
195             int cmasResponseTypeColumn = cursor.getColumnIndex(
196                     Telephony.CellBroadcasts.CMAS_RESPONSE_TYPE);
197             if (cmasResponseTypeColumn != -1 && !cursor.isNull(cmasResponseTypeColumn)) {
198                 responseType = cursor.getInt(cmasResponseTypeColumn);
199             } else {
200                 responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN;
201             }
202 
203             int severity;
204             int cmasSeverityColumn = cursor.getColumnIndex(
205                     Telephony.CellBroadcasts.CMAS_SEVERITY);
206             if (cmasSeverityColumn != -1 && !cursor.isNull(cmasSeverityColumn)) {
207                 severity = cursor.getInt(cmasSeverityColumn);
208             } else {
209                 severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
210             }
211 
212             int urgency;
213             int cmasUrgencyColumn = cursor.getColumnIndex(
214                     Telephony.CellBroadcasts.CMAS_URGENCY);
215             if (cmasUrgencyColumn != -1 && !cursor.isNull(cmasUrgencyColumn)) {
216                 urgency = cursor.getInt(cmasUrgencyColumn);
217             } else {
218                 urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
219             }
220 
221             int certainty;
222             int cmasCertaintyColumn = cursor.getColumnIndex(
223                     Telephony.CellBroadcasts.CMAS_CERTAINTY);
224             if (cmasCertaintyColumn != -1 && !cursor.isNull(cmasCertaintyColumn)) {
225                 certainty = cursor.getInt(cmasCertaintyColumn);
226             } else {
227                 certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
228             }
229 
230             cmasInfo = new SmsCbCmasInfo(messageClass, cmasCategory, responseType, severity,
231                     urgency, certainty);
232         } else {
233             cmasInfo = null;
234         }
235 
236         SmsCbMessage msg = new SmsCbMessage(format, geoScope, serialNum, location, category,
237                 language, body, priority, etwsInfo, cmasInfo);
238 
239         long deliveryTime = cursor.getLong(cursor.getColumnIndexOrThrow(
240                 Telephony.CellBroadcasts.DELIVERY_TIME));
241         boolean isRead = (cursor.getInt(cursor.getColumnIndexOrThrow(
242                 Telephony.CellBroadcasts.MESSAGE_READ)) != 0);
243 
244         return new CellBroadcastMessage(msg, deliveryTime, isRead);
245     }
246 
247     /**
248      * Return a ContentValues object for insertion into the database.
249      * @return a new ContentValues object containing this object's data
250      */
getContentValues()251     public ContentValues getContentValues() {
252         ContentValues cv = new ContentValues(16);
253         SmsCbMessage msg = mSmsCbMessage;
254         cv.put(Telephony.CellBroadcasts.GEOGRAPHICAL_SCOPE, msg.getGeographicalScope());
255         SmsCbLocation location = msg.getLocation();
256         if (location.getPlmn() != null) {
257             cv.put(Telephony.CellBroadcasts.PLMN, location.getPlmn());
258         }
259         if (location.getLac() != -1) {
260             cv.put(Telephony.CellBroadcasts.LAC, location.getLac());
261         }
262         if (location.getCid() != -1) {
263             cv.put(Telephony.CellBroadcasts.CID, location.getCid());
264         }
265         cv.put(Telephony.CellBroadcasts.SERIAL_NUMBER, msg.getSerialNumber());
266         cv.put(Telephony.CellBroadcasts.SERVICE_CATEGORY, msg.getServiceCategory());
267         cv.put(Telephony.CellBroadcasts.LANGUAGE_CODE, msg.getLanguageCode());
268         cv.put(Telephony.CellBroadcasts.MESSAGE_BODY, msg.getMessageBody());
269         cv.put(Telephony.CellBroadcasts.DELIVERY_TIME, mDeliveryTime);
270         cv.put(Telephony.CellBroadcasts.MESSAGE_READ, mIsRead);
271         cv.put(Telephony.CellBroadcasts.MESSAGE_FORMAT, msg.getMessageFormat());
272         cv.put(Telephony.CellBroadcasts.MESSAGE_PRIORITY, msg.getMessagePriority());
273 
274         SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo();
275         if (etwsInfo != null) {
276             cv.put(Telephony.CellBroadcasts.ETWS_WARNING_TYPE, etwsInfo.getWarningType());
277         }
278 
279         SmsCbCmasInfo cmasInfo = mSmsCbMessage.getCmasWarningInfo();
280         if (cmasInfo != null) {
281             cv.put(Telephony.CellBroadcasts.CMAS_MESSAGE_CLASS, cmasInfo.getMessageClass());
282             cv.put(Telephony.CellBroadcasts.CMAS_CATEGORY, cmasInfo.getCategory());
283             cv.put(Telephony.CellBroadcasts.CMAS_RESPONSE_TYPE, cmasInfo.getResponseType());
284             cv.put(Telephony.CellBroadcasts.CMAS_SEVERITY, cmasInfo.getSeverity());
285             cv.put(Telephony.CellBroadcasts.CMAS_URGENCY, cmasInfo.getUrgency());
286             cv.put(Telephony.CellBroadcasts.CMAS_CERTAINTY, cmasInfo.getCertainty());
287         }
288 
289         return cv;
290     }
291 
292     /**
293      * Set or clear the "read message" flag.
294      * @param isRead true if the message has been read; false if not
295      */
setIsRead(boolean isRead)296     public void setIsRead(boolean isRead) {
297         mIsRead = isRead;
298     }
299 
getLanguageCode()300     public String getLanguageCode() {
301         return mSmsCbMessage.getLanguageCode();
302     }
303 
getServiceCategory()304     public int getServiceCategory() {
305         return mSmsCbMessage.getServiceCategory();
306     }
307 
getDeliveryTime()308     public long getDeliveryTime() {
309         return mDeliveryTime;
310     }
311 
getMessageBody()312     public String getMessageBody() {
313         return mSmsCbMessage.getMessageBody();
314     }
315 
isRead()316     public boolean isRead() {
317         return mIsRead;
318     }
319 
getSerialNumber()320     public int getSerialNumber() {
321         return mSmsCbMessage.getSerialNumber();
322     }
323 
getCmasWarningInfo()324     public SmsCbCmasInfo getCmasWarningInfo() {
325         return mSmsCbMessage.getCmasWarningInfo();
326     }
327 
getEtwsWarningInfo()328     public SmsCbEtwsInfo getEtwsWarningInfo() {
329         return mSmsCbMessage.getEtwsWarningInfo();
330     }
331 
332     /**
333      * Return whether the broadcast is an emergency (PWS) message type.
334      * This includes lower priority test messages and Amber alerts.
335      *
336      * All public alerts show the flashing warning icon in the dialog,
337      * but only emergency alerts play the alert sound and speak the message.
338      *
339      * @return true if the message is PWS type; false otherwise
340      */
isPublicAlertMessage()341     public boolean isPublicAlertMessage() {
342         return mSmsCbMessage.isEmergencyMessage();
343     }
344 
345     /**
346      * Returns whether the broadcast is an emergency (PWS) message type,
347      * including test messages and AMBER alerts.
348      *
349      * @return true if the message is PWS type (ETWS or CMAS)
350      */
isEmergencyAlertMessage()351     public boolean isEmergencyAlertMessage() {
352         return mSmsCbMessage.isEmergencyMessage();
353     }
354 
355     /**
356      * Return whether the broadcast is an ETWS emergency message type.
357      * @return true if the message is ETWS emergency type; false otherwise
358      */
isEtwsMessage()359     public boolean isEtwsMessage() {
360         return mSmsCbMessage.isEtwsMessage();
361     }
362 
363     /**
364      * Return whether the broadcast is a CMAS emergency message type.
365      * @return true if the message is CMAS emergency type; false otherwise
366      */
isCmasMessage()367     public boolean isCmasMessage() {
368         return mSmsCbMessage.isCmasMessage();
369     }
370 
371     /**
372      * Return the CMAS message class.
373      * @return the CMAS message class, e.g. {@link SmsCbCmasInfo#CMAS_CLASS_SEVERE_THREAT}, or
374      *  {@link SmsCbCmasInfo#CMAS_CLASS_UNKNOWN} if this is not a CMAS alert
375      */
getCmasMessageClass()376     public int getCmasMessageClass() {
377         if (mSmsCbMessage.isCmasMessage()) {
378             return mSmsCbMessage.getCmasWarningInfo().getMessageClass();
379         } else {
380             return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
381         }
382     }
383 
384     /**
385      * Return whether the broadcast is an ETWS popup alert.
386      * This method checks the message ID and the message code.
387      * @return true if the message indicates an ETWS popup alert
388      */
isEtwsPopupAlert()389     public boolean isEtwsPopupAlert() {
390         SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo();
391         return etwsInfo != null && etwsInfo.isPopupAlert();
392     }
393 
394     /**
395      * Return whether the broadcast is an ETWS emergency user alert.
396      * This method checks the message ID and the message code.
397      * @return true if the message indicates an ETWS emergency user alert
398      */
isEtwsEmergencyUserAlert()399     public boolean isEtwsEmergencyUserAlert() {
400         SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo();
401         return etwsInfo != null && etwsInfo.isEmergencyUserAlert();
402     }
403 
404     /**
405      * Return whether the broadcast is an ETWS test message.
406      * @return true if the message is an ETWS test message; false otherwise
407      */
isEtwsTestMessage()408     public boolean isEtwsTestMessage() {
409         SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo();
410         return etwsInfo != null &&
411                 etwsInfo.getWarningType() == SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE;
412     }
413 
414     /**
415      * Return the abbreviated date string for the message delivery time.
416      * @param context the context object
417      * @return a String to use in the broadcast list UI
418      */
getDateString(Context context)419     public String getDateString(Context context) {
420         int flags = DateUtils.FORMAT_NO_NOON_MIDNIGHT | DateUtils.FORMAT_SHOW_TIME |
421                 DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE |
422                 DateUtils.FORMAT_CAP_AMPM;
423         return DateUtils.formatDateTime(context, mDeliveryTime, flags);
424     }
425 
426     /**
427      * Return the date string for the message delivery time, suitable for text-to-speech.
428      * @param context the context object
429      * @return a String for populating the list item AccessibilityEvent for TTS
430      */
getSpokenDateString(Context context)431     public String getSpokenDateString(Context context) {
432         int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE;
433         return DateUtils.formatDateTime(context, mDeliveryTime, flags);
434     }
435 }
436