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 17package com.android.smspush; 18 19import android.app.Service; 20import android.content.ActivityNotFoundException; 21import android.content.ComponentName; 22import android.content.ContentValues; 23import android.content.Context; 24import android.content.Intent; 25import android.content.pm.PackageManager; 26import android.database.Cursor; 27import android.database.sqlite.SQLiteOpenHelper; 28import android.database.sqlite.SQLiteDatabase; 29import android.os.IBinder; 30import android.os.RemoteException; 31import android.util.Log; 32 33import com.android.internal.telephony.IWapPushManager; 34import 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 */ 50public 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