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