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 { WapPushManDBHelper(Context context)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 onCreate(SQLiteDatabase db)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 onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)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 */ queryLastApp(SQLiteDatabase db, String app_id, String content_type)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 IWapPushManagerStub()159 public IWapPushManagerStub() { 160 161 } 162 163 /** 164 * Compare the package signature with WapPushManager package 165 */ signatureCheck(String package_name)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 */ processMessage(String app_id, String content_type, Intent intent)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 appTypeCheck(int app_type)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 */ addPackage(String x_app_id, String content_type, String package_name, String class_name, int app_type, boolean need_signature, boolean further_processing)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 */ updatePackage(String x_app_id, String content_type, String package_name, String class_name, int app_type, boolean need_signature, boolean further_processing)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 */ deletePackage(String x_app_id, String content_type, String package_name, String class_name)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 */ WapPushManager()390 public WapPushManager() { 391 super(); 392 mBinder.mContext = this; 393 } 394 395 @Override onBind(Intent arg0)396 public IBinder onBind(Intent arg0) { 397 return mBinder; 398 } 399 400 /** 401 * Application ID database instance 402 */ 403 private WapPushManDBHelper mDbHelper = null; getDatabase(Context context)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 */ verifyData(String x_app_id, String content_type, String package_name, String class_name, int app_type, boolean need_signature, boolean further_processing)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 */ isDataExist(String x_app_id, String content_type, String package_name, String class_name)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