• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.SQLiteDatabase;
30 import android.database.sqlite.SQLiteOpenHelper;
31 import android.os.Build;
32 import android.os.IBinder;
33 import android.os.PowerManager;
34 import android.os.RemoteException;
35 import android.os.UserManager;
36 import android.util.Log;
37 
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.internal.telephony.IWapPushManager;
40 import com.android.internal.telephony.WapPushManagerParams;
41 
42 import java.io.File;
43 
44 /**
45  * The WapPushManager service is implemented to process incoming
46  * WAP Push messages and to maintain the Receiver Application/Application
47  * ID mapping. The WapPushManager runs as a system service, and only the
48  * WapPushManager can update the WAP Push message and Receiver Application
49  * mapping (Application ID Table). When the device receives an SMS WAP Push
50  * message, the WapPushManager looks up the Receiver Application name in
51  * Application ID Table. If an application is found, the application is
52  * launched using its full component name instead of broadcasting an implicit
53  * Intent. If a Receiver Application is not found in the Application ID
54  * Table or the WapPushManager returns a process-further value, the
55  * telephony stack will process the message using existing message processing
56  * flow, and broadcast an implicit Intent.
57  */
58 public class WapPushManager extends Service {
59 
60     private static final String LOG_TAG = "WAP PUSH";
61     private static final String DATABASE_NAME = "wappush.db";
62     private static final String APPID_TABLE_NAME = "appid_tbl";
63 
64     /**
65      * Version number must be incremented when table structure is changed.
66      */
67     private static final int WAP_PUSH_MANAGER_VERSION = 1;
68     private static final boolean DEBUG_SQL = false;
69     private static final boolean LOCAL_LOGV = false;
70 
71     /**
72      * Inner class that deals with application ID table
73      */
74     @VisibleForTesting
75     public static class WapPushManDBHelper extends SQLiteOpenHelper {
76         /**
77          * Constructor
78          */
79         @VisibleForTesting
WapPushManDBHelper(Context context)80         public WapPushManDBHelper(Context context) {
81             super(context, DATABASE_NAME, null, WAP_PUSH_MANAGER_VERSION);
82             if (LOCAL_LOGV) Log.v(LOG_TAG, "helper instance created.");
83         }
84 
85         @Override
onCreate(SQLiteDatabase db)86         public void onCreate(SQLiteDatabase db) {
87             if (LOCAL_LOGV) Log.v(LOG_TAG, "db onCreate.");
88             String sql = "CREATE TABLE " + APPID_TABLE_NAME + " ("
89                     + "id INTEGER PRIMARY KEY, "
90                     + "x_wap_application TEXT, "
91                     + "content_type TEXT, "
92                     + "package_name TEXT, "
93                     + "class_name TEXT, "
94                     + "app_type INTEGER, "
95                     + "need_signature INTEGER, "
96                     + "further_processing INTEGER, "
97                     + "install_order INTEGER "
98                     + ")";
99 
100             if (DEBUG_SQL) Log.v(LOG_TAG, "sql: " + sql);
101             db.execSQL(sql);
102         }
103 
104         @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)105         public void onUpgrade(SQLiteDatabase db,
106                     int oldVersion, int newVersion) {
107             // TODO: when table structure is changed, need to dump and restore data.
108             /*
109               db.execSQL(
110               "drop table if exists "+APPID_TABLE_NAME);
111               onCreate(db);
112             */
113             Log.w(LOG_TAG, "onUpgrade is not implemented yet. do nothing.");
114         }
115 
116         protected class queryData {
117             public String packageName;
118             public String className;
119             int appType;
120             int needSignature;
121             int furtherProcessing;
122             int installOrder;
123         }
124 
125         /**
126          * Query the latest receiver application info with supplied application ID and
127          * content type.
128          * @param app_id    application ID to look up
129          * @param content_type    content type to look up
130          */
queryLastApp(SQLiteDatabase db, String app_id, String content_type)131         protected queryData queryLastApp(SQLiteDatabase db,
132                 String app_id, String content_type) {
133             if (LOCAL_LOGV) Log.v(LOG_TAG, "queryLastApp app_id: " + app_id
134                     + " content_type: " +  content_type);
135 
136             Cursor cur = db.query(APPID_TABLE_NAME,
137                     new String[] {"install_order", "package_name", "class_name",
138                     "app_type", "need_signature", "further_processing"},
139                     "x_wap_application=? and content_type=?",
140                     new String[] {app_id, content_type},
141                     null /* groupBy */,
142                     null /* having */,
143                     "install_order desc" /* orderBy */);
144 
145             queryData ret = null;
146 
147             if (cur.moveToNext()) {
148                 ret = new queryData();
149                 ret.installOrder = cur.getInt(cur.getColumnIndex("install_order"));
150                 ret.packageName = cur.getString(cur.getColumnIndex("package_name"));
151                 ret.className = cur.getString(cur.getColumnIndex("class_name"));
152                 ret.appType = cur.getInt(cur.getColumnIndex("app_type"));
153                 ret.needSignature = cur.getInt(cur.getColumnIndex("need_signature"));
154                 ret.furtherProcessing = cur.getInt(cur.getColumnIndex("further_processing"));
155             }
156             cur.close();
157             return ret;
158         }
159 
160     }
161 
162     /**
163      * The exported API implementations class
164      */
165     private class IWapPushManagerStub extends IWapPushManager.Stub {
166         public Context mContext;
167 
IWapPushManagerStub()168         public IWapPushManagerStub() {
169 
170         }
171 
172         /**
173          * Compare the package signature with WapPushManager package
174          */
signatureCheck(String package_name)175         protected boolean signatureCheck(String package_name) {
176             PackageManager pm = mContext.getPackageManager();
177             int match = pm.checkSignatures(mContext.getPackageName(), package_name);
178 
179             if (LOCAL_LOGV) Log.v(LOG_TAG, "compare signature " + mContext.getPackageName()
180                     + " and " +  package_name + ", match=" + match);
181 
182             return match == PackageManager.SIGNATURE_MATCH;
183         }
184 
185         /**
186          * Returns the status value of the message processing.
187          * The message will be processed as follows:
188          * 1.Look up Application ID Table with x-wap-application-id + content type
189          * 2.Check the signature of package name that is found in the
190          *   Application ID Table by using PackageManager.checkSignature
191          * 3.Trigger the Application
192          * 4.Returns the process status value.
193          */
processMessage(String app_id, String content_type, Intent intent)194         public int processMessage(String app_id, String content_type, Intent intent)
195             throws RemoteException {
196             Log.d(LOG_TAG, "wpman processMsg " + app_id + ":" + content_type);
197 
198             WapPushManDBHelper dbh = getDatabase(mContext);
199             SQLiteDatabase db = dbh.getReadableDatabase();
200             WapPushManDBHelper.queryData lastapp = dbh.queryLastApp(db, app_id, content_type);
201             db.close();
202 
203             if (lastapp == null) {
204                 Log.w(LOG_TAG, "no receiver app found for " + app_id + ":" + content_type);
205                 return WapPushManagerParams.APP_QUERY_FAILED;
206             }
207             if (LOCAL_LOGV) Log.v(LOG_TAG, "starting " + lastapp.packageName
208                     + "/" + lastapp.className);
209 
210             if (lastapp.needSignature != 0) {
211                 if (!signatureCheck(lastapp.packageName)) {
212                     return WapPushManagerParams.SIGNATURE_NO_MATCH;
213                 }
214             }
215 
216             if (lastapp.appType == WapPushManagerParams.APP_TYPE_ACTIVITY) {
217                 //Intent intent = new Intent(Intent.ACTION_MAIN);
218                 intent.setClassName(lastapp.packageName, lastapp.className);
219                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
220 
221                 try {
222                     mContext.startActivity(intent);
223                 } catch (ActivityNotFoundException e) {
224                     Log.w(LOG_TAG, "invalid name " +
225                             lastapp.packageName + "/" + lastapp.className);
226                     return WapPushManagerParams.INVALID_RECEIVER_NAME;
227                 }
228             } else {
229                 intent.setClassName(mContext, lastapp.className);
230                 intent.setComponent(new ComponentName(lastapp.packageName,
231                         lastapp.className));
232                 PackageManager pm = mContext.getPackageManager();
233                 PowerManager powerManager =
234                         (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
235                 try {
236                     ApplicationInfo appInfo = pm.getApplicationInfo(lastapp.packageName, 0);
237                     if (appInfo.targetSdkVersion < Build.VERSION_CODES.O ||
238                             powerManager.isIgnoringBatteryOptimizations(lastapp.packageName)) {
239                         if (mContext.startService(intent) == null) {
240                             Log.w(LOG_TAG, "invalid name " +
241                                     lastapp.packageName + "/" + lastapp.className);
242                             return WapPushManagerParams.INVALID_RECEIVER_NAME;
243                         }
244                     } else {
245                         if (mContext.startForegroundService(intent) == null) {
246                             Log.w(LOG_TAG, "invalid name " +
247                                     lastapp.packageName + "/" + lastapp.className);
248                             return WapPushManagerParams.INVALID_RECEIVER_NAME;
249                         }
250                     }
251 
252                 } catch (NameNotFoundException e) {
253                     Log.w(LOG_TAG, "invalid name " +
254                             lastapp.packageName + "/" + lastapp.className);
255                     return WapPushManagerParams.INVALID_RECEIVER_NAME;
256                 }
257             }
258 
259             return WapPushManagerParams.MESSAGE_HANDLED
260                     | (lastapp.furtherProcessing == 1 ?
261                             WapPushManagerParams.FURTHER_PROCESSING : 0);
262         }
263 
appTypeCheck(int app_type)264         protected boolean appTypeCheck(int app_type) {
265             if (app_type == WapPushManagerParams.APP_TYPE_ACTIVITY ||
266                     app_type == WapPushManagerParams.APP_TYPE_SERVICE) {
267                 return true;
268             } else {
269                 return false;
270             }
271         }
272 
273         /**
274          * Returns true if adding the package succeeded.
275          */
addPackage(String x_app_id, String content_type, String package_name, String class_name, int app_type, boolean need_signature, boolean further_processing)276         public boolean addPackage(String x_app_id, String content_type,
277                 String package_name, String class_name,
278                 int app_type, boolean need_signature, boolean further_processing) {
279             WapPushManDBHelper dbh = getDatabase(mContext);
280             SQLiteDatabase db = dbh.getWritableDatabase();
281 
282             if (!appTypeCheck(app_type)) {
283                 Log.w(LOG_TAG, "invalid app_type " + app_type + ". app_type must be "
284                         + WapPushManagerParams.APP_TYPE_ACTIVITY + " or "
285                         + WapPushManagerParams.APP_TYPE_SERVICE);
286                 return false;
287             }
288             boolean ret = insertPackage(dbh, db, x_app_id, content_type, package_name, class_name,
289                     app_type, need_signature, further_processing);
290             db.close();
291 
292             return ret;
293         }
294 
295         /**
296          * Returns true if updating the package succeeded.
297          */
updatePackage(String x_app_id, String content_type, String package_name, String class_name, int app_type, boolean need_signature, boolean further_processing)298         public boolean updatePackage(String x_app_id, String content_type,
299                 String package_name, String class_name,
300                 int app_type, boolean need_signature, boolean further_processing) {
301 
302             if (!appTypeCheck(app_type)) {
303                 Log.w(LOG_TAG, "invalid app_type " + app_type + ". app_type must be "
304                         + WapPushManagerParams.APP_TYPE_ACTIVITY + " or "
305                         + WapPushManagerParams.APP_TYPE_SERVICE);
306                 return false;
307             }
308 
309             WapPushManDBHelper dbh = getDatabase(mContext);
310             SQLiteDatabase db = dbh.getWritableDatabase();
311             WapPushManDBHelper.queryData lastapp = dbh.queryLastApp(db, x_app_id, content_type);
312 
313             if (lastapp == null) {
314                 db.close();
315                 return false;
316             }
317 
318             ContentValues values = new ContentValues();
319             String where = "x_wap_application=\'" + x_app_id + "\'"
320                     + " and content_type=\'" + content_type + "\'"
321                     + " and install_order=" + lastapp.installOrder;
322 
323             values.put("package_name", package_name);
324             values.put("class_name", class_name);
325             values.put("app_type", app_type);
326             values.put("need_signature", need_signature ? 1 : 0);
327             values.put("further_processing", further_processing ? 1 : 0);
328 
329             int num = db.update(APPID_TABLE_NAME, values, where, null);
330             if (LOCAL_LOGV) Log.v(LOG_TAG, "update:" + x_app_id + ":" + content_type + " "
331                     + package_name + "." + class_name
332                     + ", sq:" + lastapp.installOrder);
333 
334             db.close();
335 
336             return num > 0;
337         }
338 
339         /**
340          * Returns true if deleting the package succeeded.
341          */
deletePackage(String x_app_id, String content_type, String package_name, String class_name)342         public boolean deletePackage(String x_app_id, String content_type,
343                 String package_name, String class_name) {
344             WapPushManDBHelper dbh = getDatabase(mContext);
345             SQLiteDatabase db = dbh.getWritableDatabase();
346             String where = "x_wap_application=\'" + x_app_id + "\'"
347                     + " and content_type=\'" + content_type + "\'"
348                     + " and package_name=\'" + package_name + "\'"
349                     + " and class_name=\'" + class_name + "\'";
350             int num_removed = db.delete(APPID_TABLE_NAME, where, null);
351 
352             db.close();
353             if (LOCAL_LOGV) Log.v(LOG_TAG, "deleted " + num_removed + " rows:"
354                     + x_app_id + ":" + content_type + " "
355                     + package_name + "." + class_name);
356             return num_removed > 0;
357         }
358     };
359 
360 
361     /**
362      * Linux IPC Binder
363      */
364     private final IWapPushManagerStub mBinder = new IWapPushManagerStub();
365 
366     /**
367      * Default constructor
368      */
WapPushManager()369     public WapPushManager() {
370         super();
371         mBinder.mContext = this;
372     }
373 
374     @Override
onBind(Intent arg0)375     public IBinder onBind(Intent arg0) {
376         return mBinder;
377     }
378 
379     /**
380      * Application ID database instance
381      */
382     private WapPushManDBHelper mDbHelper = null;
getDatabase(Context context)383     protected WapPushManDBHelper getDatabase(Context context) {
384         if (mDbHelper == null) {
385             if (LOCAL_LOGV) Log.v(LOG_TAG, "create new db inst.");
386             mDbHelper = new WapPushManDBHelper(context.createDeviceProtectedStorageContext());
387         }
388         // Migrate existing legacy database into the device encrypted storage.
389         migrateWapPushManDBIfNeeded(context);
390         return mDbHelper;
391     }
392 
393     /**
394      * Inserts a package information into a database
395      */
396     @VisibleForTesting
insertPackage(WapPushManDBHelper dbh, SQLiteDatabase db, String appId, String contentType, String packageName, String className, int appType, boolean needSignature, boolean furtherProcessing)397     public boolean insertPackage(WapPushManDBHelper dbh, SQLiteDatabase db, String appId,
398             String contentType, String packageName, String className, int appType,
399             boolean needSignature, boolean furtherProcessing) {
400 
401         WapPushManDBHelper.queryData lastapp = dbh.queryLastApp(db, appId, contentType);
402         boolean insert = false;
403         int sq = 0;
404 
405         if (lastapp == null) {
406             insert = true;
407             sq = 0;
408         } else if (!lastapp.packageName.equals(packageName)
409                 || !lastapp.className.equals(className)) {
410             insert = true;
411             sq = lastapp.installOrder + 1;
412         }
413 
414         if (insert) {
415             ContentValues values = new ContentValues();
416 
417             values.put("x_wap_application", appId);
418             values.put("content_type", contentType);
419             values.put("package_name", packageName);
420             values.put("class_name", className);
421             values.put("app_type", appType);
422             values.put("need_signature", needSignature ? 1 : 0);
423             values.put("further_processing", furtherProcessing ? 1 : 0);
424             values.put("install_order", sq);
425             db.insert(APPID_TABLE_NAME, null, values);
426             if (LOCAL_LOGV) {
427                 Log.v(LOG_TAG, "add:" + appId + ":" + contentType + " " + packageName
428                         + "." + className + ", newsq:" + sq);
429             }
430             return true;
431         }
432         return false;
433     }
434 
435     /**
436      * Migrates a legacy database into the device encrypted storage
437      */
migrateWapPushManDBIfNeeded(Context context)438     private void migrateWapPushManDBIfNeeded(Context context) {
439         UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
440         File file = context.getDatabasePath(DATABASE_NAME);
441         if (!userManager.isUserUnlocked() || !file.exists()) {
442             // Check if the device is unlocked because a legacy database can't access during
443             // DirectBoot.
444             return;
445         }
446 
447         // Migration steps below:
448         // 1. Merge the package info to legacy database if there is any package info which is
449         // registered during DirectBoot.
450         // 2. Move the data base to the device encryped storage.
451         WapPushManDBHelper legacyDbHelper = new WapPushManDBHelper(context);
452         SQLiteDatabase legacyDb = legacyDbHelper.getWritableDatabase();
453         SQLiteDatabase db = mDbHelper.getWritableDatabase();
454         Cursor cur = db.query(APPID_TABLE_NAME, null, null, null, null, null, null);
455         while (cur.moveToNext()) {
456             insertPackage(legacyDbHelper, legacyDb,
457                     cur.getString(cur.getColumnIndex("x_wap_application")),
458                     cur.getString(cur.getColumnIndex("content_type")),
459                     cur.getString(cur.getColumnIndex("package_name")),
460                     cur.getString(cur.getColumnIndex("class_name")),
461                     cur.getInt(cur.getColumnIndex("app_type")),
462                     cur.getInt(cur.getColumnIndex("need_signature")) == 1,
463                     cur.getInt(cur.getColumnIndex("further_processing")) == 1);
464         }
465         cur.close();
466         legacyDb.close();
467         db.close();
468         context.createDeviceProtectedStorageContext().moveDatabaseFrom(context, DATABASE_NAME);
469         Log.i(LOG_TAG, "Migrated the legacy database.");
470     }
471 
472     /**
473      * This method is used for testing
474      */
verifyData(String x_app_id, String content_type, String package_name, String class_name, int app_type, boolean need_signature, boolean further_processing)475     public boolean verifyData(String x_app_id, String content_type,
476             String package_name, String class_name,
477             int app_type, boolean need_signature, boolean further_processing) {
478         WapPushManDBHelper dbh = getDatabase(this);
479         SQLiteDatabase db = dbh.getReadableDatabase();
480         WapPushManDBHelper.queryData lastapp = dbh.queryLastApp(db, x_app_id, content_type);
481 
482         if (LOCAL_LOGV) Log.v(LOG_TAG, "verifyData app id: " + x_app_id + " content type: " +
483                 content_type + " lastapp: " + lastapp);
484 
485         db.close();
486 
487         if (lastapp == null) return false;
488 
489         if (LOCAL_LOGV) Log.v(LOG_TAG, "verifyData lastapp.packageName: " + lastapp.packageName +
490                 " lastapp.className: " + lastapp.className +
491                 " lastapp.appType: " + lastapp.appType +
492                 " lastapp.needSignature: " + lastapp.needSignature +
493                 " lastapp.furtherProcessing: " + lastapp.furtherProcessing);
494 
495 
496         if (lastapp.packageName.equals(package_name)
497                 && lastapp.className.equals(class_name)
498                 && lastapp.appType == app_type
499                 &&  lastapp.needSignature == (need_signature ? 1 : 0)
500                 &&  lastapp.furtherProcessing == (further_processing ? 1 : 0)) {
501             return true;
502         } else {
503             return false;
504         }
505     }
506 
507     /**
508      * This method is used for testing
509      */
isDataExist(String x_app_id, String content_type, String package_name, String class_name)510     public boolean isDataExist(String x_app_id, String content_type,
511             String package_name, String class_name) {
512         WapPushManDBHelper dbh = getDatabase(this);
513         SQLiteDatabase db = dbh.getReadableDatabase();
514         boolean ret = dbh.queryLastApp(db, x_app_id, content_type) != null;
515 
516         db.close();
517         return ret;
518     }
519 
520 }
521 
522