Home | History | Annotate | Download | only in smspush
      1 /*
      2  * Copyright (C) 2010 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.smspush;
     18 
     19 import android.app.Service;
     20 import android.content.ActivityNotFoundException;
     21 import android.content.ComponentName;
     22 import android.content.ContentValues;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.pm.PackageManager;
     26 import android.database.Cursor;
     27 import android.database.sqlite.SQLiteOpenHelper;
     28 import android.database.sqlite.SQLiteDatabase;
     29 import android.os.IBinder;
     30 import android.os.RemoteException;
     31 import android.util.Log;
     32 
     33 import com.android.internal.telephony.IWapPushManager;
     34 import com.android.internal.telephony.WapPushManagerParams;
     35 
     36 /**
     37  * The WapPushManager service is implemented to process incoming
     38  * WAP Push messages and to maintain the Receiver Application/Application
     39  * ID mapping. The WapPushManager runs as a system service, and only the
     40  * WapPushManager can update the WAP Push message and Receiver Application
     41  * mapping (Application ID Table). When the device receives an SMS WAP Push
     42  * message, the WapPushManager looks up the Receiver Application name in
     43  * Application ID Table. If an application is found, the application is
     44  * launched using its full component name instead of broadcasting an implicit
     45  * Intent. If a Receiver Application is not found in the Application ID
     46  * Table or the WapPushManager returns a process-further value, the
     47  * telephony stack will process the message using existing message processing
     48  * flow, and broadcast an implicit Intent.
     49  */
     50 public class WapPushManager extends Service {
     51 
     52     private static final String LOG_TAG = "WAP PUSH";
     53     private static final String DATABASE_NAME = "wappush.db";
     54     private static final String APPID_TABLE_NAME = "appid_tbl";
     55 
     56     /**
     57      * Version number must be incremented when table structure is changed.
     58      */
     59     private static final int WAP_PUSH_MANAGER_VERSION = 1;
     60     private static final boolean DEBUG_SQL = false;
     61     private static final boolean LOCAL_LOGV = false;
     62 
     63     /**
     64      * Inner class that deals with application ID table
     65      */
     66     private class WapPushManDBHelper extends SQLiteOpenHelper {
     67         WapPushManDBHelper(Context context) {
     68             super(context, DATABASE_NAME, null, WAP_PUSH_MANAGER_VERSION);
     69             if (LOCAL_LOGV) Log.v(LOG_TAG, "helper instance created.");
     70         }
     71 
     72         @Override
     73         public void onCreate(SQLiteDatabase db) {
     74             if (LOCAL_LOGV) Log.v(LOG_TAG, "db onCreate.");
     75             String sql = "CREATE TABLE " + APPID_TABLE_NAME + " ("
     76                     + "id INTEGER PRIMARY KEY, "
     77                     + "x_wap_application TEXT, "
     78                     + "content_type TEXT, "
     79                     + "package_name TEXT, "
     80                     + "class_name TEXT, "
     81                     + "app_type INTEGER, "
     82                     + "need_signature INTEGER, "
     83                     + "further_processing INTEGER, "
     84                     + "install_order INTEGER "
     85                     + ")";
     86 
     87             if (DEBUG_SQL) Log.v(LOG_TAG, "sql: " + sql);
     88             db.execSQL(sql);
     89         }
     90 
     91         @Override
     92         public void onUpgrade(SQLiteDatabase db,
     93                     int oldVersion, int newVersion) {
     94             // TODO: when table structure is changed, need to dump and restore data.
     95             /*
     96               db.execSQL(
     97               "drop table if exists "+APPID_TABLE_NAME);
     98               onCreate(db);
     99             */
    100             Log.w(LOG_TAG, "onUpgrade is not implemented yet. do nothing.");
    101         }
    102 
    103         protected class queryData {
    104             public String packageName;
    105             public String className;
    106             int appType;
    107             int needSignature;
    108             int furtherProcessing;
    109             int installOrder;
    110         }
    111 
    112         /**
    113          * Query the latest receiver application info with supplied application ID and
    114          * content type.
    115          * @param app_id    application ID to look up
    116          * @param content_type    content type to look up
    117          */
    118         protected queryData queryLastApp(SQLiteDatabase db,
    119                 String app_id, String content_type) {
    120             String sql = "select install_order, package_name, class_name, "
    121                     + " app_type, need_signature, further_processing"
    122                     + " from " + APPID_TABLE_NAME
    123                     + " where x_wap_application=\'" + app_id + "\'"
    124                     + " and content_type=\'" + content_type + "\'"
    125                     + " order by install_order desc";
    126             if (DEBUG_SQL) Log.v(LOG_TAG, "sql: " + sql);
    127             Cursor cur = db.rawQuery(sql, null);
    128             queryData ret = null;
    129 
    130             if (cur.moveToNext()) {
    131                 ret = new queryData();
    132                 ret.installOrder = cur.getInt(cur.getColumnIndex("install_order"));
    133                 ret.packageName = cur.getString(cur.getColumnIndex("package_name"));
    134                 ret.className = cur.getString(cur.getColumnIndex("class_name"));
    135                 ret.appType = cur.getInt(cur.getColumnIndex("app_type"));
    136                 ret.needSignature = cur.getInt(cur.getColumnIndex("need_signature"));
    137                 ret.furtherProcessing = cur.getInt(cur.getColumnIndex("further_processing"));
    138             }
    139             cur.close();
    140             return ret;
    141         }
    142 
    143     }
    144 
    145     /**
    146      * The exported API implementations class
    147      */
    148     private class IWapPushManagerStub extends IWapPushManager.Stub {
    149         public Context mContext;
    150 
    151         public IWapPushManagerStub() {
    152 
    153         }
    154 
    155         /**
    156          * Compare the package signature with WapPushManager package
    157          */
    158         protected boolean signatureCheck(String package_name) {
    159             PackageManager pm = mContext.getPackageManager();
    160             int match = pm.checkSignatures(mContext.getPackageName(), package_name);
    161 
    162             if (LOCAL_LOGV) Log.v(LOG_TAG, "compare signature " + mContext.getPackageName()
    163                     + " and " +  package_name + ", match=" + match);
    164 
    165             return match == PackageManager.SIGNATURE_MATCH;
    166         }
    167 
    168         /**
    169          * Returns the status value of the message processing.
    170          * The message will be processed as follows:
    171          * 1.Look up Application ID Table with x-wap-application-id + content type
    172          * 2.Check the signature of package name that is found in the
    173          *   Application ID Table by using PackageManager.checkSignature
    174          * 3.Trigger the Application
    175          * 4.Returns the process status value.
    176          */
    177         public int processMessage(String app_id, String content_type, Intent intent)
    178             throws RemoteException {
    179             Log.d(LOG_TAG, "wpman processMsg " + app_id + ":" + content_type);
    180 
    181             WapPushManDBHelper dbh = getDatabase(mContext);
    182             SQLiteDatabase db = dbh.getReadableDatabase();
    183             WapPushManDBHelper.queryData lastapp = dbh.queryLastApp(db, app_id, content_type);
    184             db.close();
    185 
    186             if (lastapp == null) {
    187                 Log.w(LOG_TAG, "no receiver app found for " + app_id + ":" + content_type);
    188                 return WapPushManagerParams.APP_QUERY_FAILED;
    189             }
    190             if (LOCAL_LOGV) Log.v(LOG_TAG, "starting " + lastapp.packageName
    191                     + "/" + lastapp.className);
    192 
    193             if (lastapp.needSignature != 0) {
    194                 if (!signatureCheck(lastapp.packageName)) {
    195                     return WapPushManagerParams.SIGNATURE_NO_MATCH;
    196                 }
    197             }
    198 
    199             if (lastapp.appType == WapPushManagerParams.APP_TYPE_ACTIVITY) {
    200                 //Intent intent = new Intent(Intent.ACTION_MAIN);
    201                 intent.setClassName(lastapp.packageName, lastapp.className);
    202                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    203 
    204                 try {
    205                     mContext.startActivity(intent);
    206                 } catch (ActivityNotFoundException e) {
    207                     Log.w(LOG_TAG, "invalid name " +
    208                             lastapp.packageName + "/" + lastapp.className);
    209                     return WapPushManagerParams.INVALID_RECEIVER_NAME;
    210                 }
    211             } else {
    212                 intent.setClassName(mContext, lastapp.className);
    213                 intent.setComponent(new ComponentName(lastapp.packageName,
    214                         lastapp.className));
    215                 if (mContext.startService(intent) == null) {
    216                     Log.w(LOG_TAG, "invalid name " +
    217                             lastapp.packageName + "/" + lastapp.className);
    218                     return WapPushManagerParams.INVALID_RECEIVER_NAME;
    219                 }
    220             }
    221 
    222             return WapPushManagerParams.MESSAGE_HANDLED
    223                     | (lastapp.furtherProcessing == 1 ?
    224                             WapPushManagerParams.FURTHER_PROCESSING : 0);
    225         }
    226 
    227         protected boolean appTypeCheck(int app_type) {
    228             if (app_type == WapPushManagerParams.APP_TYPE_ACTIVITY ||
    229                     app_type == WapPushManagerParams.APP_TYPE_SERVICE) {
    230                 return true;
    231             } else {
    232                 return false;
    233             }
    234         }
    235 
    236         /**
    237          * Returns true if adding the package succeeded.
    238          */
    239         public boolean addPackage(String x_app_id, String content_type,
    240                 String package_name, String class_name,
    241                 int app_type, boolean need_signature, boolean further_processing) {
    242             WapPushManDBHelper dbh = getDatabase(mContext);
    243             SQLiteDatabase db = dbh.getWritableDatabase();
    244             WapPushManDBHelper.queryData lastapp = dbh.queryLastApp(db, x_app_id, content_type);
    245             boolean ret = false;
    246             boolean insert = false;
    247             int sq = 0;
    248 
    249             if (!appTypeCheck(app_type)) {
    250                 Log.w(LOG_TAG, "invalid app_type " + app_type + ". app_type must be "
    251                         + WapPushManagerParams.APP_TYPE_ACTIVITY + " or "
    252                         + WapPushManagerParams.APP_TYPE_SERVICE);
    253                 return false;
    254             }
    255 
    256             if (lastapp == null) {
    257                 insert = true;
    258                 sq = 0;
    259             } else if (!lastapp.packageName.equals(package_name) ||
    260                     !lastapp.className.equals(class_name)) {
    261                 insert = true;
    262                 sq = lastapp.installOrder + 1;
    263             }
    264 
    265             if (insert) {
    266                 ContentValues values = new ContentValues();
    267 
    268                 values.put("x_wap_application", x_app_id);
    269                 values.put("content_type", content_type);
    270                 values.put("package_name", package_name);
    271                 values.put("class_name", class_name);
    272                 values.put("app_type", app_type);
    273                 values.put("need_signature", need_signature ? 1 : 0);
    274                 values.put("further_processing", further_processing ? 1 : 0);
    275                 values.put("install_order", sq);
    276                 db.insert(APPID_TABLE_NAME, null, values);
    277                 if (LOCAL_LOGV) Log.v(LOG_TAG, "add:" + x_app_id + ":" + content_type
    278                         + " " + package_name + "." + class_name
    279                         + ", newsq:" + sq);
    280                 ret = true;
    281             }
    282 
    283             db.close();
    284 
    285             return ret;
    286         }
    287 
    288         /**
    289          * Returns true if updating the package succeeded.
    290          */
    291         public boolean updatePackage(String x_app_id, String content_type,
    292                 String package_name, String class_name,
    293                 int app_type, boolean need_signature, boolean further_processing) {
    294 
    295             if (!appTypeCheck(app_type)) {
    296                 Log.w(LOG_TAG, "invalid app_type " + app_type + ". app_type must be "
    297                         + WapPushManagerParams.APP_TYPE_ACTIVITY + " or "
    298                         + WapPushManagerParams.APP_TYPE_SERVICE);
    299                 return false;
    300             }
    301 
    302             WapPushManDBHelper dbh = getDatabase(mContext);
    303             SQLiteDatabase db = dbh.getWritableDatabase();
    304             WapPushManDBHelper.queryData lastapp = dbh.queryLastApp(db, x_app_id, content_type);
    305 
    306             if (lastapp == null) {
    307                 db.close();
    308                 return false;
    309             }
    310 
    311             ContentValues values = new ContentValues();
    312             String where = "x_wap_application=\'" + x_app_id + "\'"
    313                     + " and content_type=\'" + content_type + "\'"
    314                     + " and install_order=" + lastapp.installOrder;
    315 
    316             values.put("package_name", package_name);
    317             values.put("class_name", class_name);
    318             values.put("app_type", app_type);
    319             values.put("need_signature", need_signature ? 1 : 0);
    320             values.put("further_processing", further_processing ? 1 : 0);
    321 
    322             int num = db.update(APPID_TABLE_NAME, values, where, null);
    323             if (LOCAL_LOGV) Log.v(LOG_TAG, "update:" + x_app_id + ":" + content_type + " "
    324                     + package_name + "." + class_name
    325                     + ", sq:" + lastapp.installOrder);
    326 
    327             db.close();
    328 
    329             return num > 0;
    330         }
    331 
    332         /**
    333          * Returns true if deleting the package succeeded.
    334          */
    335         public boolean deletePackage(String x_app_id, String content_type,
    336                 String package_name, String class_name) {
    337             WapPushManDBHelper dbh = getDatabase(mContext);
    338             SQLiteDatabase db = dbh.getWritableDatabase();
    339             String where = "x_wap_application=\'" + x_app_id + "\'"
    340                     + " and content_type=\'" + content_type + "\'"
    341                     + " and package_name=\'" + package_name + "\'"
    342                     + " and class_name=\'" + class_name + "\'";
    343             int num_removed = db.delete(APPID_TABLE_NAME, where, null);
    344 
    345             db.close();
    346             if (LOCAL_LOGV) Log.v(LOG_TAG, "deleted " + num_removed + " rows:"
    347                     + x_app_id + ":" + content_type + " "
    348                     + package_name + "." + class_name);
    349             return num_removed > 0;
    350         }
    351     };
    352 
    353 
    354     /**
    355      * Linux IPC Binder
    356      */
    357     private final IWapPushManagerStub mBinder = new IWapPushManagerStub();
    358 
    359     /**
    360      * Default constructor
    361      */
    362     public WapPushManager() {
    363         super();
    364         mBinder.mContext = this;
    365     }
    366 
    367     @Override
    368     public IBinder onBind(Intent arg0) {
    369         return mBinder;
    370     }
    371 
    372     /**
    373      * Application ID database instance
    374      */
    375     private WapPushManDBHelper mDbHelper = null;
    376     protected WapPushManDBHelper getDatabase(Context context) {
    377         if (mDbHelper == null) {
    378             if (LOCAL_LOGV) Log.v(LOG_TAG, "create new db inst.");
    379             mDbHelper = new WapPushManDBHelper(context);
    380         }
    381         return mDbHelper;
    382     }
    383 
    384 
    385     /**
    386      * This method is used for testing
    387      */
    388     public boolean verifyData(String x_app_id, String content_type,
    389             String package_name, String class_name,
    390             int app_type, boolean need_signature, boolean further_processing) {
    391         WapPushManDBHelper dbh = getDatabase(this);
    392         SQLiteDatabase db = dbh.getReadableDatabase();
    393         WapPushManDBHelper.queryData lastapp = dbh.queryLastApp(db, x_app_id, content_type);
    394 
    395         db.close();
    396 
    397         if (lastapp == null) return false;
    398 
    399         if (lastapp.packageName.equals(package_name)
    400                 && lastapp.className.equals(class_name)
    401                 && lastapp.appType == app_type
    402                 &&  lastapp.needSignature == (need_signature ? 1 : 0)
    403                 &&  lastapp.furtherProcessing == (further_processing ? 1 : 0)) {
    404             return true;
    405         } else {
    406             return false;
    407         }
    408     }
    409 
    410     /**
    411      * This method is used for testing
    412      */
    413     public boolean isDataExist(String x_app_id, String content_type,
    414             String package_name, String class_name) {
    415         WapPushManDBHelper dbh = getDatabase(this);
    416         SQLiteDatabase db = dbh.getReadableDatabase();
    417         boolean ret = dbh.queryLastApp(db, x_app_id, content_type) != null;
    418 
    419         db.close();
    420         return ret;
    421     }
    422 
    423 }
    424 
    425