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