/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.smspush;
import android.app.Service;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteDatabase;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.telephony.IWapPushManager;
import com.android.internal.telephony.WapPushManagerParams;
/**
* The WapPushManager service is implemented to process incoming
* WAP Push messages and to maintain the Receiver Application/Application
* ID mapping. The WapPushManager runs as a system service, and only the
* WapPushManager can update the WAP Push message and Receiver Application
* mapping (Application ID Table). When the device receives an SMS WAP Push
* message, the WapPushManager looks up the Receiver Application name in
* Application ID Table. If an application is found, the application is
* launched using its full component name instead of broadcasting an implicit
* Intent. If a Receiver Application is not found in the Application ID
* Table or the WapPushManager returns a process-further value, the
* telephony stack will process the message using existing message processing
* flow, and broadcast an implicit Intent.
*/
public class WapPushManager extends Service {
private static final String LOG_TAG = "WAP PUSH";
private static final String DATABASE_NAME = "wappush.db";
private static final String APPID_TABLE_NAME = "appid_tbl";
/**
* Version number must be incremented when table structure is changed.
*/
private static final int WAP_PUSH_MANAGER_VERSION = 1;
private static final boolean DEBUG_SQL = false;
private static final boolean LOCAL_LOGV = false;
/**
* Inner class that deals with application ID table
*/
private class WapPushManDBHelper extends SQLiteOpenHelper {
WapPushManDBHelper(Context context) {
super(context, DATABASE_NAME, null, WAP_PUSH_MANAGER_VERSION);
if (LOCAL_LOGV) Log.v(LOG_TAG, "helper instance created.");
}
@Override
public void onCreate(SQLiteDatabase db) {
if (LOCAL_LOGV) Log.v(LOG_TAG, "db onCreate.");
String sql = "CREATE TABLE " + APPID_TABLE_NAME + " ("
+ "id INTEGER PRIMARY KEY, "
+ "x_wap_application TEXT, "
+ "content_type TEXT, "
+ "package_name TEXT, "
+ "class_name TEXT, "
+ "app_type INTEGER, "
+ "need_signature INTEGER, "
+ "further_processing INTEGER, "
+ "install_order INTEGER "
+ ")";
if (DEBUG_SQL) Log.v(LOG_TAG, "sql: " + sql);
db.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase db,
int oldVersion, int newVersion) {
// TODO: when table structure is changed, need to dump and restore data.
/*
db.execSQL(
"drop table if exists "+APPID_TABLE_NAME);
onCreate(db);
*/
Log.w(LOG_TAG, "onUpgrade is not implemented yet. do nothing.");
}
protected class queryData {
public String packageName;
public String className;
int appType;
int needSignature;
int furtherProcessing;
int installOrder;
}
/**
* Query the latest receiver application info with supplied application ID and
* content type.
* @param app_id application ID to look up
* @param content_type content type to look up
*/
protected queryData queryLastApp(SQLiteDatabase db,
String app_id, String content_type) {
if (LOCAL_LOGV) Log.v(LOG_TAG, "queryLastApp app_id: " + app_id
+ " content_type: " + content_type);
Cursor cur = db.query(APPID_TABLE_NAME,
new String[] {"install_order", "package_name", "class_name",
"app_type", "need_signature", "further_processing"},
"x_wap_application=? and content_type=?",
new String[] {app_id, content_type},
null /* groupBy */,
null /* having */,
"install_order desc" /* orderBy */);
queryData ret = null;
if (cur.moveToNext()) {
ret = new queryData();
ret.installOrder = cur.getInt(cur.getColumnIndex("install_order"));
ret.packageName = cur.getString(cur.getColumnIndex("package_name"));
ret.className = cur.getString(cur.getColumnIndex("class_name"));
ret.appType = cur.getInt(cur.getColumnIndex("app_type"));
ret.needSignature = cur.getInt(cur.getColumnIndex("need_signature"));
ret.furtherProcessing = cur.getInt(cur.getColumnIndex("further_processing"));
}
cur.close();
return ret;
}
}
/**
* The exported API implementations class
*/
private class IWapPushManagerStub extends IWapPushManager.Stub {
public Context mContext;
public IWapPushManagerStub() {
}
/**
* Compare the package signature with WapPushManager package
*/
protected boolean signatureCheck(String package_name) {
PackageManager pm = mContext.getPackageManager();
int match = pm.checkSignatures(mContext.getPackageName(), package_name);
if (LOCAL_LOGV) Log.v(LOG_TAG, "compare signature " + mContext.getPackageName()
+ " and " + package_name + ", match=" + match);
return match == PackageManager.SIGNATURE_MATCH;
}
/**
* Returns the status value of the message processing.
* The message will be processed as follows:
* 1.Look up Application ID Table with x-wap-application-id + content type
* 2.Check the signature of package name that is found in the
* Application ID Table by using PackageManager.checkSignature
* 3.Trigger the Application
* 4.Returns the process status value.
*/
public int processMessage(String app_id, String content_type, Intent intent)
throws RemoteException {
Log.d(LOG_TAG, "wpman processMsg " + app_id + ":" + content_type);
WapPushManDBHelper dbh = getDatabase(mContext);
SQLiteDatabase db = dbh.getReadableDatabase();
WapPushManDBHelper.queryData lastapp = dbh.queryLastApp(db, app_id, content_type);
db.close();
if (lastapp == null) {
Log.w(LOG_TAG, "no receiver app found for " + app_id + ":" + content_type);
return WapPushManagerParams.APP_QUERY_FAILED;
}
if (LOCAL_LOGV) Log.v(LOG_TAG, "starting " + lastapp.packageName
+ "/" + lastapp.className);
if (lastapp.needSignature != 0) {
if (!signatureCheck(lastapp.packageName)) {
return WapPushManagerParams.SIGNATURE_NO_MATCH;
}
}
if (lastapp.appType == WapPushManagerParams.APP_TYPE_ACTIVITY) {
//Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClassName(lastapp.packageName, lastapp.className);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
mContext.startActivity(intent);
} catch (ActivityNotFoundException e) {
Log.w(LOG_TAG, "invalid name " +
lastapp.packageName + "/" + lastapp.className);
return WapPushManagerParams.INVALID_RECEIVER_NAME;
}
} else {
intent.setClassName(mContext, lastapp.className);
intent.setComponent(new ComponentName(lastapp.packageName,
lastapp.className));
if (mContext.startService(intent) == null) {
Log.w(LOG_TAG, "invalid name " +
lastapp.packageName + "/" + lastapp.className);
return WapPushManagerParams.INVALID_RECEIVER_NAME;
}
}
return WapPushManagerParams.MESSAGE_HANDLED
| (lastapp.furtherProcessing == 1 ?
WapPushManagerParams.FURTHER_PROCESSING : 0);
}
protected boolean appTypeCheck(int app_type) {
if (app_type == WapPushManagerParams.APP_TYPE_ACTIVITY ||
app_type == WapPushManagerParams.APP_TYPE_SERVICE) {
return true;
} else {
return false;
}
}
/**
* Returns true if adding the package succeeded.
*/
public boolean addPackage(String x_app_id, String content_type,
String package_name, String class_name,
int app_type, boolean need_signature, boolean further_processing) {
WapPushManDBHelper dbh = getDatabase(mContext);
SQLiteDatabase db = dbh.getWritableDatabase();
WapPushManDBHelper.queryData lastapp = dbh.queryLastApp(db, x_app_id, content_type);
boolean ret = false;
boolean insert = false;
int sq = 0;
if (!appTypeCheck(app_type)) {
Log.w(LOG_TAG, "invalid app_type " + app_type + ". app_type must be "
+ WapPushManagerParams.APP_TYPE_ACTIVITY + " or "
+ WapPushManagerParams.APP_TYPE_SERVICE);
return false;
}
if (lastapp == null) {
insert = true;
sq = 0;
} else if (!lastapp.packageName.equals(package_name) ||
!lastapp.className.equals(class_name)) {
insert = true;
sq = lastapp.installOrder + 1;
}
if (insert) {
ContentValues values = new ContentValues();
values.put("x_wap_application", x_app_id);
values.put("content_type", content_type);
values.put("package_name", package_name);
values.put("class_name", class_name);
values.put("app_type", app_type);
values.put("need_signature", need_signature ? 1 : 0);
values.put("further_processing", further_processing ? 1 : 0);
values.put("install_order", sq);
db.insert(APPID_TABLE_NAME, null, values);
if (LOCAL_LOGV) Log.v(LOG_TAG, "add:" + x_app_id + ":" + content_type
+ " " + package_name + "." + class_name
+ ", newsq:" + sq);
ret = true;
}
db.close();
return ret;
}
/**
* Returns true if updating the package succeeded.
*/
public boolean updatePackage(String x_app_id, String content_type,
String package_name, String class_name,
int app_type, boolean need_signature, boolean further_processing) {
if (!appTypeCheck(app_type)) {
Log.w(LOG_TAG, "invalid app_type " + app_type + ". app_type must be "
+ WapPushManagerParams.APP_TYPE_ACTIVITY + " or "
+ WapPushManagerParams.APP_TYPE_SERVICE);
return false;
}
WapPushManDBHelper dbh = getDatabase(mContext);
SQLiteDatabase db = dbh.getWritableDatabase();
WapPushManDBHelper.queryData lastapp = dbh.queryLastApp(db, x_app_id, content_type);
if (lastapp == null) {
db.close();
return false;
}
ContentValues values = new ContentValues();
String where = "x_wap_application=\'" + x_app_id + "\'"
+ " and content_type=\'" + content_type + "\'"
+ " and install_order=" + lastapp.installOrder;
values.put("package_name", package_name);
values.put("class_name", class_name);
values.put("app_type", app_type);
values.put("need_signature", need_signature ? 1 : 0);
values.put("further_processing", further_processing ? 1 : 0);
int num = db.update(APPID_TABLE_NAME, values, where, null);
if (LOCAL_LOGV) Log.v(LOG_TAG, "update:" + x_app_id + ":" + content_type + " "
+ package_name + "." + class_name
+ ", sq:" + lastapp.installOrder);
db.close();
return num > 0;
}
/**
* Returns true if deleting the package succeeded.
*/
public boolean deletePackage(String x_app_id, String content_type,
String package_name, String class_name) {
WapPushManDBHelper dbh = getDatabase(mContext);
SQLiteDatabase db = dbh.getWritableDatabase();
String where = "x_wap_application=\'" + x_app_id + "\'"
+ " and content_type=\'" + content_type + "\'"
+ " and package_name=\'" + package_name + "\'"
+ " and class_name=\'" + class_name + "\'";
int num_removed = db.delete(APPID_TABLE_NAME, where, null);
db.close();
if (LOCAL_LOGV) Log.v(LOG_TAG, "deleted " + num_removed + " rows:"
+ x_app_id + ":" + content_type + " "
+ package_name + "." + class_name);
return num_removed > 0;
}
};
/**
* Linux IPC Binder
*/
private final IWapPushManagerStub mBinder = new IWapPushManagerStub();
/**
* Default constructor
*/
public WapPushManager() {
super();
mBinder.mContext = this;
}
@Override
public IBinder onBind(Intent arg0) {
return mBinder;
}
/**
* Application ID database instance
*/
private WapPushManDBHelper mDbHelper = null;
protected WapPushManDBHelper getDatabase(Context context) {
if (mDbHelper == null) {
if (LOCAL_LOGV) Log.v(LOG_TAG, "create new db inst.");
mDbHelper = new WapPushManDBHelper(context);
}
return mDbHelper;
}
/**
* This method is used for testing
*/
public boolean verifyData(String x_app_id, String content_type,
String package_name, String class_name,
int app_type, boolean need_signature, boolean further_processing) {
WapPushManDBHelper dbh = getDatabase(this);
SQLiteDatabase db = dbh.getReadableDatabase();
WapPushManDBHelper.queryData lastapp = dbh.queryLastApp(db, x_app_id, content_type);
if (LOCAL_LOGV) Log.v(LOG_TAG, "verifyData app id: " + x_app_id + " content type: " +
content_type + " lastapp: " + lastapp);
db.close();
if (lastapp == null) return false;
if (LOCAL_LOGV) Log.v(LOG_TAG, "verifyData lastapp.packageName: " + lastapp.packageName +
" lastapp.className: " + lastapp.className +
" lastapp.appType: " + lastapp.appType +
" lastapp.needSignature: " + lastapp.needSignature +
" lastapp.furtherProcessing: " + lastapp.furtherProcessing);
if (lastapp.packageName.equals(package_name)
&& lastapp.className.equals(class_name)
&& lastapp.appType == app_type
&& lastapp.needSignature == (need_signature ? 1 : 0)
&& lastapp.furtherProcessing == (further_processing ? 1 : 0)) {
return true;
} else {
return false;
}
}
/**
* This method is used for testing
*/
public boolean isDataExist(String x_app_id, String content_type,
String package_name, String class_name) {
WapPushManDBHelper dbh = getDatabase(this);
SQLiteDatabase db = dbh.getReadableDatabase();
boolean ret = dbh.queryLastApp(db, x_app_id, content_type) != null;
db.close();
return ret;
}
}