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.graphics.Typeface; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.provider.Telephony; 26 import android.telephony.SmsCbCmasInfo; 27 import android.telephony.SmsCbEtwsInfo; 28 import android.telephony.SmsCbLocation; 29 import android.telephony.SmsCbMessage; 30 import android.text.Spannable; 31 import android.text.SpannableStringBuilder; 32 import android.text.format.DateUtils; 33 import 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 */ 48 public 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