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            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