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             if (LOCAL_LOGV) Log.v(LOG_TAG, "queryLastApp app_id: " + app_id
    121                     + " content_type: " +  content_type);
    122 
    123             Cursor cur = db.query(APPID_TABLE_NAME,
    124                     new String[] {"install_order", "package_name", "class_name",
    125                     "app_type", "need_signature", "further_processing"},
    126                     "x_wap_application=? and content_type=?",
    127                     new String[] {app_id, content_type},
    128                     null /* groupBy */,
    129                     null /* having */,
    130                     "install_order desc" /* orderBy */);
    131 
    132             queryData ret = null;
    133 
    134             if (cur.moveToNext()) {
    135                 ret = new queryData();
    136                 ret.installOrder = cur.getInt(cur.getColumnIndex("install_order"));
    137                 ret.packageName = cur.getString(cur.getColumnIndex("package_name"));
    138                 ret.className = cur.getString(cur.getColumnIndex("class_name"));
    139                 ret.appType = cur.getInt(cur.getColumnIndex("app_type"));
    140                 ret.needSignature = cur.getInt(cur.getColumnIndex("need_signature"));
    141                 ret.furtherProcessing = cur.getInt(cur.getColumnIndex("further_processing"));
    142             }
    143             cur.close();
    144             return ret;
    145         }
    146 
    147     }
    148 
    149     /**
    150      * The exported API implementations class
    151      */
    152     private class IWapPushManagerStub extends IWapPushManager.Stub {
    153         public Context mContext;
    154 
    155         public IWapPushManagerStub() {
    156 
    157         }
    158 
    159         /**
    160          * Compare the package signature with WapPushManager package
    161          */
    162         protected boolean signatureCheck(String package_name) {
    163             PackageManager pm = mContext.getPackageManager();
    164             int match = pm.checkSignatures(mContext.getPackageName(), package_name);
    165 
    166             if (LOCAL_LOGV) Log.v(LOG_TAG, "compare signature " + mContext.getPackageName()
    167                     + " and " +  package_name + ", match=" + match);
    168 
    169             return match == PackageManager.SIGNATURE_MATCH;
    170         }
    171 
    172         /**
    173          * Returns the status value of the message processing.
    174          * The message will be processed as follows:
    175          * 1.Look up Application ID Table with x-wap-application-id + content type
    176          * 2.Check the signature of package name that is found in the
    177          *   Application ID Table by using PackageManager.checkSignature
    178          * 3.Trigger the Application
    179          * 4.Returns the process status value.
    180          */
    181         public int processMessage(String app_id, String content_type, Intent intent)
    182             throws RemoteException {
    183             Log.d(LOG_TAG, "wpman processMsg " + app_id + ":" + content_type);
    184 
    185             WapPushManDBHelper dbh = getDatabase(mContext);
    186             SQLiteDatabase db = dbh.getReadableDatabase();
    187             WapPushManDBHelper.queryData lastapp = dbh.queryLastApp(db, app_id, content_type);
    188             db.close();
    189 
    190             if (lastapp == null) {
    191                 Log.w(LOG_TAG, "no receiver app found for " + app_id + ":" + content_type);
    192                 return WapPushManagerParams.APP_QUERY_FAILED;
    193             }
    194             if (LOCAL_LOGV) Log.v(LOG_TAG, "starting " + lastapp.packageName
    195                     + "/" + lastapp.className);
    196 
    197             if (lastapp.needSignature != 0) {
    198                 if (!signatureCheck(lastapp.packageName)) {
    199                     return WapPushManagerParams.SIGNATURE_NO_MATCH;
    200                 }
    201             }
    202 
    203             if (lastapp.appType == WapPushManagerParams.APP_TYPE_ACTIVITY) {
    204                 //Intent intent = new Intent(Intent.ACTION_MAIN);
    205                 intent.setClassName(lastapp.packageName, lastapp.className);
    206                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    207 
    208                 try {
    209                     mContext.startActivity(intent);
    210                 } catch (ActivityNotFoundException e) {
    211                     Log.w(LOG_TAG, "invalid name " +
    212                             lastapp.packageName + "/" + lastapp.className);
    213                     return WapPushManagerParams.INVALID_RECEIVER_NAME;
    214                 }
    215             } else {
    216                 intent.setClassName(mContext, lastapp.className);
    217                 intent.setComponent(new ComponentName(lastapp.packageName,
    218                         lastapp.className));
    219                 if (mContext.startService(intent) == null) {
    220                     Log.w(LOG_TAG, "invalid name " +
    221                             lastapp.packageName + "/" + lastapp.className);
    222                     return WapPushManagerParams.INVALID_RECEIVER_NAME;
    223                 }
    224             }
    225 
    226             return WapPushManagerParams.MESSAGE_HANDLED
    227                     | (lastapp.furtherProcessing == 1 ?
    228                             WapPushManagerParams.FURTHER_PROCESSING : 0);
    229         }
    230 
    231         protected boolean appTypeCheck(int app_type) {
    232             if (app_type == WapPushManagerParams.APP_TYPE_ACTIVITY ||
    233                     app_type == WapPushManagerParams.APP_TYPE_SERVICE) {
    234                 return true;
    235             } else {
    236                 return false;
    237             }
    238         }
    239 
    240         /**
    241          * Returns true if adding the package succeeded.
    242          */
    243         public boolean addPackage(String x_app_id, String content_type,
    244                 String package_name, String class_name,
    245                 int app_type, boolean need_signature, boolean further_processing) {
    246             WapPushManDBHelper dbh = getDatabase(mContext);
    247             SQLiteDatabase db = dbh.getWritableDatabase();
    248             WapPushManDBHelper.queryData lastapp = dbh.queryLastApp(db, x_app_id, content_type);
    249             boolean ret = false;
    250             boolean insert = false;
    251             int sq = 0;
    252 
    253             if (!appTypeCheck(app_type)) {
    254                 Log.w(LOG_TAG, "invalid app_type " + app_type + ". app_type must be "
    255                         + WapPushManagerParams.APP_TYPE_ACTIVITY + " or "
    256                         + WapPushManagerParams.APP_TYPE_SERVICE);
    257                 return false;
    258             }
    259 
    260             if (lastapp == null) {
    261                 insert = true;
    262                 sq = 0;
    263             } else if (!lastapp.packageName.equals(package_name) ||
    264                     !lastapp.className.equals(class_name)) {
    265                 insert = true;
    266                 sq = lastapp.installOrder + 1;
    267             }
    268 
    269             if (insert) {
    270                 ContentValues values = new ContentValues();
    271 
    272                 values.put("x_wap_application", x_app_id);
    273                 values.put("content_type", content_type);
    274                 values.put("package_name", package_name);
    275                 values.put("class_name", class_name);
    276                 values.put("app_type", app_type);
    277                 values.put("need_signature", need_signature ? 1 : 0);
    278                 values.put("further_processing", further_processing ? 1 : 0);
    279                 values.put("install_order", sq);
    280                 db.insert(APPID_TABLE_NAME, null, values);
    281                 if (LOCAL_LOGV) Log.v(LOG_TAG, "add:" + x_app_id + ":" + content_type
    282                         + " " + package_name + "." + class_name
    283                         + ", newsq:" + sq);
    284                 ret = true;
    285             }
    286 
    287             db.close();
    288 
    289             return ret;
    290         }
    291 
    292         /**
    293          * Returns true if updating the package succeeded.
    294          */
    295         public boolean updatePackage(String x_app_id, String content_type,
    296                 String package_name, String class_name,
    297                 int app_type, boolean need_signature, boolean further_processing) {
    298 
    299             if (!appTypeCheck(app_type)) {
    300                 Log.w(LOG_TAG, "invalid app_type " + app_type + ". app_type must be "
    301                         + WapPushManagerParams.APP_TYPE_ACTIVITY + " or "
    302                         + WapPushManagerParams.APP_TYPE_SERVICE);
    303                 return false;
    304             }
    305 
    306             WapPushManDBHelper dbh = getDatabase(mContext);
    307             SQLiteDatabase db = dbh.getWritableDatabase();
    308             WapPushManDBHelper.queryData lastapp = dbh.queryLastApp(db, x_app_id, content_type);
    309 
    310             if (lastapp == null) {
    311                 db.close();
    312                 return false;
    313             }
    314 
    315             ContentValues values = new ContentValues();
    316             String where = "x_wap_application=\'" + x_app_id + "\'"
    317                     + " and content_type=\'" + content_type + "\'"
    318                     + " and install_order=" + lastapp.installOrder;
    319 
    320             values.put("package_name", package_name);
    321             values.put("class_name", class_name);
    322             values.put("app_type", app_type);
    323             values.put("need_signature", need_signature ? 1 : 0);
    324             values.put("further_processing", further_processing ? 1 : 0);
    325 
    326             int num = db.update(APPID_TABLE_NAME, values, where, null);
    327             if (LOCAL_LOGV) Log.v(LOG_TAG, "update:" + x_app_id + ":" + content_type + " "
    328                     + package_name + "." + class_name
    329                     + ", sq:" + lastapp.installOrder);
    330 
    331             db.close();
    332 
    333             return num > 0;
    334         }
    335 
    336         /**
    337          * Returns true if deleting the package succeeded.
    338          */
    339         public boolean deletePackage(String x_app_id, String content_type,
    340                 String package_name, String class_name) {
    341             WapPushManDBHelper dbh = getDatabase(mContext);
    342             SQLiteDatabase db = dbh.getWritableDatabase();
    343             String where = "x_wap_application=\'" + x_app_id + "\'"
    344                     + " and content_type=\'" + content_type + "\'"
    345                     + " and package_name=\'" + package_name + "\'"
    346                     + " and class_name=\'" + class_name + "\'";
    347             int num_removed = db.delete(APPID_TABLE_NAME, where, null);
    348 
    349             db.close();
    350             if (LOCAL_LOGV) Log.v(LOG_TAG, "deleted " + num_removed + " rows:"
    351                     + x_app_id + ":" + content_type + " "
    352                     + package_name + "." + class_name);
    353             return num_removed > 0;
    354         }
    355     };
    356 
    357 
    358     /**
    359      * Linux IPC Binder
    360      */
    361     private final IWapPushManagerStub mBinder = new IWapPushManagerStub();
    362 
    363     /**
    364      * Default constructor
    365      */
    366     public WapPushManager() {
    367         super();
    368         mBinder.mContext = this;
    369     }
    370 
    371     @Override
    372     public IBinder onBind(Intent arg0) {
    373         return mBinder;
    374     }
    375 
    376     /**
    377      * Application ID database instance
    378      */
    379     private WapPushManDBHelper mDbHelper = null;
    380     protected WapPushManDBHelper getDatabase(Context context) {
    381         if (mDbHelper == null) {
    382             if (LOCAL_LOGV) Log.v(LOG_TAG, "create new db inst.");
    383             mDbHelper = new WapPushManDBHelper(context);
    384         }
    385         return mDbHelper;
    386     }
    387 
    388 
    389     /**
    390      * This method is used for testing
    391      */
    392     public boolean verifyData(String x_app_id, String content_type,
    393             String package_name, String class_name,
    394             int app_type, boolean need_signature, boolean further_processing) {
    395         WapPushManDBHelper dbh = getDatabase(this);
    396         SQLiteDatabase db = dbh.getReadableDatabase();
    397         WapPushManDBHelper.queryData lastapp = dbh.queryLastApp(db, x_app_id, content_type);
    398 
    399         if (LOCAL_LOGV) Log.v(LOG_TAG, "verifyData app id: " + x_app_id + " content type: " +
    400                 content_type + " lastapp: " + lastapp);
    401 
    402         db.close();
    403 
    404         if (lastapp == null) return false;
    405 
    406         if (LOCAL_LOGV) Log.v(LOG_TAG, "verifyData lastapp.packageName: " + lastapp.packageName +
    407                 " lastapp.className: " + lastapp.className +
    408                 " lastapp.appType: " + lastapp.appType +
    409                 " lastapp.needSignature: " + lastapp.needSignature +
    410                 " lastapp.furtherProcessing: " + lastapp.furtherProcessing);
    411 
    412 
    413         if (lastapp.packageName.equals(package_name)
    414                 && lastapp.className.equals(class_name)
    415                 && lastapp.appType == app_type
    416                 &&  lastapp.needSignature == (need_signature ? 1 : 0)
    417                 &&  lastapp.furtherProcessing == (further_processing ? 1 : 0)) {
    418             return true;
    419         } else {
    420             return false;
    421         }
    422     }
    423 
    424     /**
    425      * This method is used for testing
    426      */
    427     public boolean isDataExist(String x_app_id, String content_type,
    428             String package_name, String class_name) {
    429         WapPushManDBHelper dbh = getDatabase(this);
    430         SQLiteDatabase db = dbh.getReadableDatabase();
    431         boolean ret = dbh.queryLastApp(db, x_app_id, content_type) != null;
    432 
    433         db.close();
    434         return ret;
    435     }
    436 
    437 }
    438 
    439