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