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