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