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