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