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 database was updated, false otherwise
    188      */
    189     boolean insertNewBroadcast(CellBroadcastMessage message) {
    190         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    191         ContentValues cv = message.getContentValues();
    192 
    193         long rowId = db.insert(CellBroadcastDatabaseHelper.TABLE_NAME, null, cv);
    194         if (rowId != -1) {
    195             return true;
    196         } else {
    197             Log.e(TAG, "failed to insert new broadcast into database");
    198             return false;
    199         }
    200     }
    201 
    202     /**
    203      * Internal method to delete a cell broadcast by row ID and notify observers.
    204      * @param rowId the row ID of the broadcast to delete
    205      * @param decrementUnreadCount true to decrement the count of unread alerts
    206      * @return true if the database was updated, false otherwise
    207      */
    208     boolean deleteBroadcast(long rowId, boolean decrementUnreadCount) {
    209         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    210 
    211         int rowCount = db.delete(CellBroadcastDatabaseHelper.TABLE_NAME,
    212                 Telephony.CellBroadcasts._ID + "=?",
    213                 new String[]{Long.toString(rowId)});
    214         if (rowCount != 0) {
    215             if (decrementUnreadCount) {
    216                 CellBroadcastReceiverApp.decrementUnreadAlertCount();
    217             }
    218             return true;
    219         } else {
    220             Log.e(TAG, "failed to delete broadcast at row " + rowId);
    221             return false;
    222         }
    223     }
    224 
    225     /**
    226      * Internal method to delete all cell broadcasts and notify observers.
    227      * @return true if the database was updated, false otherwise
    228      */
    229     boolean deleteAllBroadcasts() {
    230         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    231 
    232         int rowCount = db.delete(CellBroadcastDatabaseHelper.TABLE_NAME, null, null);
    233         if (rowCount != 0) {
    234             CellBroadcastReceiverApp.resetUnreadAlertCount();
    235             return true;
    236         } else {
    237             Log.e(TAG, "failed to delete all broadcasts");
    238             return false;
    239         }
    240     }
    241 
    242     /**
    243      * Internal method to mark a broadcast as read and notify observers. The broadcast can be
    244      * identified by delivery time (for new alerts) or by row ID. The caller is responsible for
    245      * decrementing the unread non-emergency alert count, if necessary.
    246      *
    247      * @param columnName the column name to query (ID or delivery time)
    248      * @param columnValue the ID or delivery time of the broadcast to mark read
    249      * @return true if the database was updated, false otherwise
    250      */
    251     boolean markBroadcastRead(String columnName, long columnValue) {
    252         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    253 
    254         ContentValues cv = new ContentValues(1);
    255         cv.put(Telephony.CellBroadcasts.MESSAGE_READ, 1);
    256 
    257         String whereClause = columnName + "=?";
    258         String[] whereArgs = new String[]{Long.toString(columnValue)};
    259 
    260         int rowCount = db.update(CellBroadcastDatabaseHelper.TABLE_NAME, cv, whereClause, whereArgs);
    261         if (rowCount != 0) {
    262             return true;
    263         } else {
    264             Log.e(TAG, "failed to mark broadcast read: " + columnName + " = " + columnValue);
    265             return false;
    266         }
    267     }
    268 
    269     /** Callback for users of AsyncCellBroadcastOperation. */
    270     interface CellBroadcastOperation {
    271         /**
    272          * Perform an operation using the specified provider.
    273          * @param provider the CellBroadcastContentProvider to use
    274          * @return true if any rows were changed, false otherwise
    275          */
    276         boolean execute(CellBroadcastContentProvider provider);
    277     }
    278 
    279     /**
    280      * Async task to call this content provider's internal methods on a background thread.
    281      * The caller supplies the CellBroadcastOperation object to call for this provider.
    282      */
    283     static class AsyncCellBroadcastTask extends AsyncTask<CellBroadcastOperation, Void, Void> {
    284         /** Reference to this app's content resolver. */
    285         private ContentResolver mContentResolver;
    286 
    287         AsyncCellBroadcastTask(ContentResolver contentResolver) {
    288             mContentResolver = contentResolver;
    289         }
    290 
    291         /**
    292          * Perform a generic operation on the CellBroadcastContentProvider.
    293          * @param params the CellBroadcastOperation object to call for this provider
    294          * @return void
    295          */
    296         @Override
    297         protected Void doInBackground(CellBroadcastOperation... params) {
    298             ContentProviderClient cpc = mContentResolver.acquireContentProviderClient(
    299                     CellBroadcastContentProvider.CB_AUTHORITY);
    300             CellBroadcastContentProvider provider = (CellBroadcastContentProvider)
    301                     cpc.getLocalContentProvider();
    302 
    303             if (provider != null) {
    304                 try {
    305                     boolean changed = params[0].execute(provider);
    306                     if (changed) {
    307                         Log.d(TAG, "database changed: notifying observers...");
    308                         mContentResolver.notifyChange(CONTENT_URI, null, false);
    309                     }
    310                 } finally {
    311                     cpc.release();
    312                 }
    313             } else {
    314                 Log.e(TAG, "getLocalContentProvider() returned null");
    315             }
    316 
    317             mContentResolver = null;    // free reference to content resolver
    318             return null;
    319         }
    320     }
    321 }
    322