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