Home | History | Annotate | Download | only in cellbroadcastreceiver
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of 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,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.cellbroadcastreceiver;
     18 
     19 import android.content.ContentProvider;
     20 import android.content.ContentProviderClient;
     21 import android.content.ContentResolver;
     22 import android.content.ContentValues;
     23 import android.content.UriMatcher;
     24 import android.database.Cursor;
     25 import android.database.sqlite.SQLiteDatabase;
     26 import android.database.sqlite.SQLiteOpenHelper;
     27 import android.database.sqlite.SQLiteQueryBuilder;
     28 import android.net.Uri;
     29 import android.os.AsyncTask;
     30 import android.provider.Telephony;
     31 import android.telephony.CellBroadcastMessage;
     32 import android.text.TextUtils;
     33 import android.util.Log;
     34 
     35 /**
     36  * ContentProvider for the database of received cell broadcasts.
     37  */
     38 public class CellBroadcastContentProvider extends ContentProvider {
     39     private static final String TAG = "CellBroadcastContentProvider";
     40 
     41     /** URI matcher for ContentProvider queries. */
     42     private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
     43 
     44     /** Authority string for content URIs. */
     45     static final String CB_AUTHORITY = "cellbroadcasts";
     46 
     47     /** Content URI for notifying observers. */
     48     static final Uri CONTENT_URI = Uri.parse("content://cellbroadcasts/");
     49 
     50     /** URI matcher type to get all cell broadcasts. */
     51     private static final int CB_ALL = 0;
     52 
     53     /** URI matcher type to get a cell broadcast by ID. */
     54     private static final int CB_ALL_ID = 1;
     55 
     56     /** MIME type for the list of all cell broadcasts. */
     57     private static final String CB_LIST_TYPE = "vnd.android.cursor.dir/cellbroadcast";
     58 
     59     /** MIME type for an individual cell broadcast. */
     60     private static final String CB_TYPE = "vnd.android.cursor.item/cellbroadcast";
     61 
     62     static {
     63         sUriMatcher.addURI(CB_AUTHORITY, null, CB_ALL);
     64         sUriMatcher.addURI(CB_AUTHORITY, "#", CB_ALL_ID);
     65     }
     66 
     67     /** The database for this content provider. */
     68     private SQLiteOpenHelper mOpenHelper;
     69 
     70     /**
     71      * Initialize content provider.
     72      * @return true if the provider was successfully loaded, false otherwise
     73      */
     74     @Override
     75     public boolean onCreate() {
     76         mOpenHelper = new CellBroadcastDatabaseHelper(getContext());
     77         return true;
     78     }
     79 
     80     /**
     81      * Return a cursor for the cell broadcast table.
     82      * @param uri the URI to query.
     83      * @param projection the list of columns to put into the cursor, or null.
     84      * @param selection the selection criteria to apply when filtering rows, or null.
     85      * @param selectionArgs values to replace ?s in selection string.
     86      * @param sortOrder how the rows in the cursor should be sorted, or null to sort from most
     87      *  recently received to least recently received.
     88      * @return a Cursor or null.
     89      */
     90     @Override
     91     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
     92             String sortOrder) {
     93         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
     94         qb.setTables(CellBroadcastDatabaseHelper.TABLE_NAME);
     95 
     96         int match = sUriMatcher.match(uri);
     97         switch (match) {
     98             case CB_ALL:
     99                 // get all broadcasts
    100                 break;
    101 
    102             case CB_ALL_ID:
    103                 // get broadcast by ID
    104                 qb.appendWhere("(_id=" + uri.getPathSegments().get(0) + ')');
    105                 break;
    106 
    107             default:
    108                 Log.e(TAG, "Invalid query: " + uri);
    109                 throw new IllegalArgumentException("Unknown URI: " + uri);
    110         }
    111 
    112         String orderBy;
    113         if (!TextUtils.isEmpty(sortOrder)) {
    114             orderBy = sortOrder;
    115         } else {
    116             orderBy = Telephony.CellBroadcasts.DEFAULT_SORT_ORDER;
    117         }
    118 
    119         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    120         Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
    121         if (c != null) {
    122             c.setNotificationUri(getContext().getContentResolver(), CONTENT_URI);
    123         }
    124         return c;
    125     }
    126 
    127     /**
    128      * Return the MIME type of the data at the specified URI.
    129      * @param uri the URI to query.
    130      * @return a MIME type string, or null if there is no type.
    131      */
    132     @Override
    133     public String getType(Uri uri) {
    134         int match = sUriMatcher.match(uri);
    135         switch (match) {
    136             case CB_ALL:
    137                 return CB_LIST_TYPE;
    138 
    139             case CB_ALL_ID:
    140                 return CB_TYPE;
    141 
    142             default:
    143                 return null;
    144         }
    145     }
    146 
    147     /**
    148      * Insert a new row. This throws an exception, as the database can only be modified by
    149      * calling custom methods in this class, and not via the ContentProvider interface.
    150      * @param uri the content:// URI of the insertion request.
    151      * @param values a set of column_name/value pairs to add to the database.
    152      * @return the URI for the newly inserted item.
    153      */
    154     @Override
    155     public Uri insert(Uri uri, ContentValues values) {
    156         throw new UnsupportedOperationException("insert not supported");
    157     }
    158 
    159     /**
    160      * Delete one or more rows. This throws an exception, as the database can only be modified by
    161      * calling custom methods in this class, and not via the ContentProvider interface.
    162      * @param uri the full URI to query, including a row ID (if a specific record is requested).
    163      * @param selection an optional restriction to apply to rows when deleting.
    164      * @return the number of rows affected.
    165      */
    166     @Override
    167     public int delete(Uri uri, String selection, String[] selectionArgs) {
    168         throw new UnsupportedOperationException("delete not supported");
    169     }
    170 
    171     /**
    172      * Update one or more rows. This throws an exception, as the database can only be modified by
    173      * calling custom methods in this class, and not via the ContentProvider interface.
    174      * @param uri the URI to query, potentially including the row ID.
    175      * @param values a Bundle mapping from column names to new column values.
    176      * @param selection an optional filter to match rows to update.
    177      * @return the number of rows affected.
    178      */
    179     @Override
    180     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    181         throw new UnsupportedOperationException("update not supported");
    182     }
    183 
    184     /**
    185      * Internal method to insert a new Cell Broadcast into the database and notify observers.
    186      * @param message the message to insert
    187      * @return true if the broadcast is new, false if it's a duplicate broadcast.
    188      */
    189     boolean insertNewBroadcast(CellBroadcastMessage message) {
    190         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    191         ContentValues cv = message.getContentValues();
    192 
    193         // Note: this method previously queried the database for duplicate message IDs, but this
    194         // is not compatible with CMAS carrier requirements and could also cause other emergency
    195         // alerts, e.g. ETWS, to not display if the database is filled with old messages.
    196         // Use duplicate message ID detection in CellBroadcastAlertService instead of DB query.
    197 
    198         long rowId = db.insert(CellBroadcastDatabaseHelper.TABLE_NAME, null, cv);
    199         if (rowId == -1) {
    200             Log.e(TAG, "failed to insert new broadcast into database");
    201             // Return true on DB write failure because we still want to notify the user.
    202             // The CellBroadcastMessage will be passed with the intent, so the message will be
    203             // displayed in the emergency alert dialog, or the dialog that is displayed when
    204             // the user selects the notification for a non-emergency broadcast, even if the
    205             // broadcast could not be written to the database.
    206         }
    207         return true;    // broadcast is not a duplicate
    208     }
    209 
    210     /**
    211      * Internal method to delete a cell broadcast by row ID and notify observers.
    212      * @param rowId the row ID of the broadcast to delete
    213      * @return true if the database was updated, false otherwise
    214      */
    215     boolean deleteBroadcast(long rowId) {
    216         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    217 
    218         int rowCount = db.delete(CellBroadcastDatabaseHelper.TABLE_NAME,
    219                 Telephony.CellBroadcasts._ID + "=?",
    220                 new String[]{Long.toString(rowId)});
    221         if (rowCount != 0) {
    222             return true;
    223         } else {
    224             Log.e(TAG, "failed to delete broadcast at row " + rowId);
    225             return false;
    226         }
    227     }
    228 
    229     /**
    230      * Internal method to delete all cell broadcasts and notify observers.
    231      * @return true if the database was updated, false otherwise
    232      */
    233     boolean deleteAllBroadcasts() {
    234         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    235 
    236         int rowCount = db.delete(CellBroadcastDatabaseHelper.TABLE_NAME, null, null);
    237         if (rowCount != 0) {
    238             return true;
    239         } else {
    240             Log.e(TAG, "failed to delete all broadcasts");
    241             return false;
    242         }
    243     }
    244 
    245     /**
    246      * Internal method to mark a broadcast as read and notify observers. The broadcast can be
    247      * identified by delivery time (for new alerts) or by row ID. The caller is responsible for
    248      * decrementing the unread non-emergency alert count, if necessary.
    249      *
    250      * @param columnName the column name to query (ID or delivery time)
    251      * @param columnValue the ID or delivery time of the broadcast to mark read
    252      * @return true if the database was updated, false otherwise
    253      */
    254     boolean markBroadcastRead(String columnName, long columnValue) {
    255         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    256 
    257         ContentValues cv = new ContentValues(1);
    258         cv.put(Telephony.CellBroadcasts.MESSAGE_READ, 1);
    259 
    260         String whereClause = columnName + "=?";
    261         String[] whereArgs = new String[]{Long.toString(columnValue)};
    262 
    263         int rowCount = db.update(CellBroadcastDatabaseHelper.TABLE_NAME, cv, whereClause, whereArgs);
    264         if (rowCount != 0) {
    265             return true;
    266         } else {
    267             Log.e(TAG, "failed to mark broadcast read: " + columnName + " = " + columnValue);
    268             return false;
    269         }
    270     }
    271 
    272     /** Callback for users of AsyncCellBroadcastOperation. */
    273     interface CellBroadcastOperation {
    274         /**
    275          * Perform an operation using the specified provider.
    276          * @param provider the CellBroadcastContentProvider to use
    277          * @return true if any rows were changed, false otherwise
    278          */
    279         boolean execute(CellBroadcastContentProvider provider);
    280     }
    281 
    282     /**
    283      * Async task to call this content provider's internal methods on a background thread.
    284      * The caller supplies the CellBroadcastOperation object to call for this provider.
    285      */
    286     static class AsyncCellBroadcastTask extends AsyncTask<CellBroadcastOperation, Void, Void> {
    287         /** Reference to this app's content resolver. */
    288         private ContentResolver mContentResolver;
    289 
    290         AsyncCellBroadcastTask(ContentResolver contentResolver) {
    291             mContentResolver = contentResolver;
    292         }
    293 
    294         /**
    295          * Perform a generic operation on the CellBroadcastContentProvider.
    296          * @param params the CellBroadcastOperation object to call for this provider
    297          * @return void
    298          */
    299         @Override
    300         protected Void doInBackground(CellBroadcastOperation... params) {
    301             ContentProviderClient cpc = mContentResolver.acquireContentProviderClient(
    302                     CellBroadcastContentProvider.CB_AUTHORITY);
    303             CellBroadcastContentProvider provider = (CellBroadcastContentProvider)
    304                     cpc.getLocalContentProvider();
    305 
    306             if (provider != null) {
    307                 try {
    308                     boolean changed = params[0].execute(provider);
    309                     if (changed) {
    310                         Log.d(TAG, "database changed: notifying observers...");
    311                         mContentResolver.notifyChange(CONTENT_URI, null, false);
    312                     }
    313                 } finally {
    314                     cpc.release();
    315                 }
    316             } else {
    317                 Log.e(TAG, "getLocalContentProvider() returned null");
    318             }
    319 
    320             mContentResolver = null;    // free reference to content resolver
    321             return null;
    322         }
    323     }
    324 }
    325