/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.cellbroadcastreceiver; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; import android.provider.Telephony; import android.telephony.SmsCbCmasInfo; import android.telephony.SmsCbEtwsInfo; import android.telephony.SmsCbMessage; import android.util.Log; import com.android.internal.telephony.gsm.SmsCbConstants; /** * Open, create, and upgrade the cell broadcast SQLite database. Previously an inner class of * {@code CellBroadcastDatabase}, this is now a top-level class. The column definitions in * {@code CellBroadcastDatabase} have been moved to {@link Telephony.CellBroadcasts} in the * framework, to simplify access to this database from third-party apps. */ public class CellBroadcastDatabaseHelper extends SQLiteOpenHelper { private static final String TAG = "CellBroadcastDatabaseHelper"; static final String DATABASE_NAME = "cell_broadcasts.db"; static final String TABLE_NAME = "broadcasts"; /** Temporary table for upgrading the database version. */ static final String TEMP_TABLE_NAME = "old_broadcasts"; /** * Database version 1: initial version * Database version 2-9: (reserved for OEM database customization) * Database version 10: adds ETWS and CMAS columns and CDMA support * Database version 11: adds delivery time index */ static final int DATABASE_VERSION = 11; CellBroadcastDatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE " + TABLE_NAME + " (" + Telephony.CellBroadcasts._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + Telephony.CellBroadcasts.GEOGRAPHICAL_SCOPE + " INTEGER," + Telephony.CellBroadcasts.PLMN + " TEXT," + Telephony.CellBroadcasts.LAC + " INTEGER," + Telephony.CellBroadcasts.CID + " INTEGER," + Telephony.CellBroadcasts.SERIAL_NUMBER + " INTEGER," + Telephony.CellBroadcasts.SERVICE_CATEGORY + " INTEGER," + Telephony.CellBroadcasts.LANGUAGE_CODE + " TEXT," + Telephony.CellBroadcasts.MESSAGE_BODY + " TEXT," + Telephony.CellBroadcasts.DELIVERY_TIME + " INTEGER," + Telephony.CellBroadcasts.MESSAGE_READ + " INTEGER," + Telephony.CellBroadcasts.MESSAGE_FORMAT + " INTEGER," + Telephony.CellBroadcasts.MESSAGE_PRIORITY + " INTEGER," + Telephony.CellBroadcasts.ETWS_WARNING_TYPE + " INTEGER," + Telephony.CellBroadcasts.CMAS_MESSAGE_CLASS + " INTEGER," + Telephony.CellBroadcasts.CMAS_CATEGORY + " INTEGER," + Telephony.CellBroadcasts.CMAS_RESPONSE_TYPE + " INTEGER," + Telephony.CellBroadcasts.CMAS_SEVERITY + " INTEGER," + Telephony.CellBroadcasts.CMAS_URGENCY + " INTEGER," + Telephony.CellBroadcasts.CMAS_CERTAINTY + " INTEGER);"); createDeliveryTimeIndex(db); } private void createDeliveryTimeIndex(SQLiteDatabase db) { db.execSQL("CREATE INDEX IF NOT EXISTS deliveryTimeIndex ON " + TABLE_NAME + " (" + Telephony.CellBroadcasts.DELIVERY_TIME + ");"); } /** Columns to copy on database upgrade. */ private static final String[] COLUMNS_V1 = { Telephony.CellBroadcasts.GEOGRAPHICAL_SCOPE, Telephony.CellBroadcasts.SERIAL_NUMBER, Telephony.CellBroadcasts.V1_MESSAGE_CODE, Telephony.CellBroadcasts.V1_MESSAGE_IDENTIFIER, Telephony.CellBroadcasts.LANGUAGE_CODE, Telephony.CellBroadcasts.MESSAGE_BODY, Telephony.CellBroadcasts.DELIVERY_TIME, Telephony.CellBroadcasts.MESSAGE_READ, }; private static final int COLUMN_V1_GEOGRAPHICAL_SCOPE = 0; private static final int COLUMN_V1_SERIAL_NUMBER = 1; private static final int COLUMN_V1_MESSAGE_CODE = 2; private static final int COLUMN_V1_MESSAGE_IDENTIFIER = 3; private static final int COLUMN_V1_LANGUAGE_CODE = 4; private static final int COLUMN_V1_MESSAGE_BODY = 5; private static final int COLUMN_V1_DELIVERY_TIME = 6; private static final int COLUMN_V1_MESSAGE_READ = 7; @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion == newVersion) { return; } // always log database upgrade log("Upgrading DB from version " + oldVersion + " to " + newVersion); // Upgrade from V1 to V10 if (oldVersion == 1) { db.beginTransaction(); try { // Step 1: rename original table db.execSQL("DROP TABLE IF EXISTS " + TEMP_TABLE_NAME); db.execSQL("ALTER TABLE " + TABLE_NAME + " RENAME TO " + TEMP_TABLE_NAME); // Step 2: create new table and indices onCreate(db); // Step 3: copy each message into the new table Cursor cursor = db.query(TEMP_TABLE_NAME, COLUMNS_V1, null, null, null, null, null); try { while (cursor.moveToNext()) { upgradeMessageV1ToV2(db, cursor); } } finally { cursor.close(); } // Step 4: drop the original table and commit transaction db.execSQL("DROP TABLE " + TEMP_TABLE_NAME); db.setTransactionSuccessful(); } finally { db.endTransaction(); } oldVersion = 10; // skip to version 10 } // Note to OEMs: if you have customized the database schema since V1, you will need to // add your own code here to convert from your version to version 10. if (oldVersion < 10) { throw new SQLiteException("CellBroadcastDatabase doesn't know how to upgrade " + " DB version " + oldVersion + " (customized by OEM?)"); } if (oldVersion == 10) { createDeliveryTimeIndex(db); oldVersion++; } } /** * Upgrades a single broadcast message from version 1 to version 2. */ private static void upgradeMessageV1ToV2(SQLiteDatabase db, Cursor cursor) { int geographicalScope = cursor.getInt(COLUMN_V1_GEOGRAPHICAL_SCOPE); int updateNumber = cursor.getInt(COLUMN_V1_SERIAL_NUMBER); int messageCode = cursor.getInt(COLUMN_V1_MESSAGE_CODE); int messageId = cursor.getInt(COLUMN_V1_MESSAGE_IDENTIFIER); String languageCode = cursor.getString(COLUMN_V1_LANGUAGE_CODE); String messageBody = cursor.getString(COLUMN_V1_MESSAGE_BODY); long deliveryTime = cursor.getLong(COLUMN_V1_DELIVERY_TIME); boolean isRead = (cursor.getInt(COLUMN_V1_MESSAGE_READ) != 0); int serialNumber = ((geographicalScope & 0x03) << 14) | ((messageCode & 0x3ff) << 4) | (updateNumber & 0x0f); ContentValues cv = new ContentValues(16); cv.put(Telephony.CellBroadcasts.GEOGRAPHICAL_SCOPE, geographicalScope); cv.put(Telephony.CellBroadcasts.SERIAL_NUMBER, serialNumber); cv.put(Telephony.CellBroadcasts.SERVICE_CATEGORY, messageId); cv.put(Telephony.CellBroadcasts.LANGUAGE_CODE, languageCode); cv.put(Telephony.CellBroadcasts.MESSAGE_BODY, messageBody); cv.put(Telephony.CellBroadcasts.DELIVERY_TIME, deliveryTime); cv.put(Telephony.CellBroadcasts.MESSAGE_READ, isRead); cv.put(Telephony.CellBroadcasts.MESSAGE_FORMAT, SmsCbMessage.MESSAGE_FORMAT_3GPP); int etwsWarningType = SmsCbEtwsInfo.ETWS_WARNING_TYPE_UNKNOWN; int cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_UNKNOWN; int cmasSeverity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN; int cmasUrgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN; int cmasCertainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN; switch (messageId) { case SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING: etwsWarningType = SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE; break; case SmsCbConstants.MESSAGE_ID_ETWS_TSUNAMI_WARNING: etwsWarningType = SmsCbEtwsInfo.ETWS_WARNING_TYPE_TSUNAMI; break; case SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_AND_TSUNAMI_WARNING: etwsWarningType = SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI; break; case SmsCbConstants.MESSAGE_ID_ETWS_TEST_MESSAGE: etwsWarningType = SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE; break; case SmsCbConstants.MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE: etwsWarningType = SmsCbEtwsInfo.ETWS_WARNING_TYPE_OTHER_EMERGENCY; break; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL: cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT; break; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED: cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT; cmasSeverity = SmsCbCmasInfo.CMAS_SEVERITY_EXTREME; cmasUrgency = SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE; cmasCertainty = SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED; break; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY: cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT; cmasSeverity = SmsCbCmasInfo.CMAS_SEVERITY_EXTREME; cmasUrgency = SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE; cmasCertainty = SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY; break; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED: cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT; cmasSeverity = SmsCbCmasInfo.CMAS_SEVERITY_EXTREME; cmasUrgency = SmsCbCmasInfo.CMAS_URGENCY_EXPECTED; cmasCertainty = SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED; break; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY: cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT; cmasSeverity = SmsCbCmasInfo.CMAS_SEVERITY_EXTREME; cmasUrgency = SmsCbCmasInfo.CMAS_URGENCY_EXPECTED; cmasCertainty = SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY; break; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED: cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT; cmasSeverity = SmsCbCmasInfo.CMAS_SEVERITY_SEVERE; cmasUrgency = SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE; cmasCertainty = SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED; break; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY: cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT; cmasSeverity = SmsCbCmasInfo.CMAS_SEVERITY_SEVERE; cmasUrgency = SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE; cmasCertainty = SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY; break; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED: cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT; cmasSeverity = SmsCbCmasInfo.CMAS_SEVERITY_SEVERE; cmasUrgency = SmsCbCmasInfo.CMAS_URGENCY_EXPECTED; cmasCertainty = SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED; break; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY: cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT; cmasSeverity = SmsCbCmasInfo.CMAS_SEVERITY_SEVERE; cmasUrgency = SmsCbCmasInfo.CMAS_URGENCY_EXPECTED; cmasCertainty = SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY; break; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY: cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY; break; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST: cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST; break; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE: cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE; break; case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE: cmasMessageClass = SmsCbCmasInfo.CMAS_CLASS_OPERATOR_DEFINED_USE; break; } if (etwsWarningType != SmsCbEtwsInfo.ETWS_WARNING_TYPE_UNKNOWN || cmasMessageClass != SmsCbCmasInfo.CMAS_CLASS_UNKNOWN) { cv.put(Telephony.CellBroadcasts.MESSAGE_PRIORITY, SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY); } else { cv.put(Telephony.CellBroadcasts.MESSAGE_PRIORITY, SmsCbMessage.MESSAGE_PRIORITY_NORMAL); } if (etwsWarningType != SmsCbEtwsInfo.ETWS_WARNING_TYPE_UNKNOWN) { cv.put(Telephony.CellBroadcasts.ETWS_WARNING_TYPE, etwsWarningType); } if (cmasMessageClass != SmsCbCmasInfo.CMAS_CLASS_UNKNOWN) { cv.put(Telephony.CellBroadcasts.CMAS_MESSAGE_CLASS, cmasMessageClass); } if (cmasSeverity != SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN) { cv.put(Telephony.CellBroadcasts.CMAS_SEVERITY, cmasSeverity); } if (cmasUrgency != SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN) { cv.put(Telephony.CellBroadcasts.CMAS_URGENCY, cmasUrgency); } if (cmasCertainty != SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN) { cv.put(Telephony.CellBroadcasts.CMAS_CERTAINTY, cmasCertainty); } db.insert(TABLE_NAME, null, cv); } private static void log(String msg) { Log.d(TAG, msg); } }