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