• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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.internal.telephony;
18 
19 import static android.os.PowerWhitelistManager.REASON_EVENT_MMS;
20 import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
21 
22 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.app.Activity;
27 import android.app.AppOpsManager;
28 import android.app.BroadcastOptions;
29 import android.compat.annotation.UnsupportedAppUsage;
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.ServiceConnection;
34 import android.content.pm.PackageManager;
35 import android.content.pm.ResolveInfo;
36 import android.os.Build;
37 import android.os.Bundle;
38 import android.os.IBinder;
39 import android.os.PowerWhitelistManager;
40 import android.os.RemoteException;
41 import android.os.UserHandle;
42 import android.os.UserManager;
43 import android.provider.Telephony.Sms.Intents;
44 import android.telephony.SmsManager;
45 import android.telephony.SubscriptionManager;
46 import android.text.TextUtils;
47 
48 import com.android.internal.telephony.uicc.IccUtils;
49 import com.android.internal.telephony.util.TelephonyUtils;
50 import com.android.telephony.Rlog;
51 
52 import com.google.android.mms.pdu.GenericPdu;
53 import com.google.android.mms.pdu.NotificationInd;
54 import com.google.android.mms.pdu.PduParser;
55 
56 import java.util.HashMap;
57 import java.util.List;
58 
59 /**
60  * WAP push handler class.
61  *
62  * @hide
63  */
64 public class WapPushOverSms implements ServiceConnection {
65     private static final String TAG = "WAP PUSH";
66     private static final boolean DBG = false;
67 
68     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
69     private final Context mContext;
70     PowerWhitelistManager mPowerWhitelistManager;
71 
72     private String mWapPushManagerPackage;
73 
74     /** Assigned from ServiceConnection callback on main threaad. */
75     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
76     private volatile IWapPushManager mWapPushManager;
77 
bindWapPushManagerService(Context context)78     private void bindWapPushManagerService(Context context) {
79         Intent intent = new Intent(IWapPushManager.class.getName());
80         ComponentName comp = resolveSystemService(context.getPackageManager(), intent);
81         intent.setComponent(comp);
82         if (comp == null || !context.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
83             Rlog.e(TAG, "bindService() for wappush manager failed");
84         } else {
85             synchronized (this) {
86                 mWapPushManagerPackage = comp.getPackageName();
87             }
88             if (DBG) Rlog.v(TAG, "bindService() for wappush manager succeeded");
89         }
90     }
91 
92     /**
93      * Special function for use by the system to resolve service
94      * intents to system apps.  Throws an exception if there are
95      * multiple potential matches to the Intent.  Returns null if
96      * there are no matches.
97      */
resolveSystemService(@onNull PackageManager pm, @NonNull Intent intent)98     private static @Nullable ComponentName resolveSystemService(@NonNull PackageManager pm,
99             @NonNull Intent intent) {
100         List<ResolveInfo> results = pm.queryIntentServices(
101                 intent, PackageManager.MATCH_SYSTEM_ONLY);
102         if (results == null) {
103             return null;
104         }
105         ComponentName comp = null;
106         for (int i = 0; i < results.size(); i++) {
107             ResolveInfo ri = results.get(i);
108             ComponentName foundComp = new ComponentName(ri.serviceInfo.applicationInfo.packageName,
109                     ri.serviceInfo.name);
110             if (comp != null) {
111                 throw new IllegalStateException("Multiple system services handle " + intent
112                     + ": " + comp + ", " + foundComp);
113             }
114             comp = foundComp;
115         }
116         return comp;
117     }
118 
119     @Override
onServiceConnected(ComponentName name, IBinder service)120     public void onServiceConnected(ComponentName name, IBinder service) {
121         mWapPushManager = IWapPushManager.Stub.asInterface(service);
122         if (DBG) Rlog.v(TAG, "wappush manager connected to " + hashCode());
123     }
124 
125     @Override
onServiceDisconnected(ComponentName name)126     public void onServiceDisconnected(ComponentName name) {
127         mWapPushManager = null;
128         if (DBG) Rlog.v(TAG, "wappush manager disconnected.");
129     }
130 
WapPushOverSms(Context context)131     public WapPushOverSms(Context context) {
132         mContext = context;
133         mPowerWhitelistManager = mContext.getSystemService(PowerWhitelistManager.class);
134 
135         UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
136 
137         bindWapPushManagerService(mContext);
138     }
139 
dispose()140     public void dispose() {
141         if (mWapPushManager != null) {
142             if (DBG) Rlog.v(TAG, "dispose: unbind wappush manager");
143             mContext.unbindService(this);
144         } else {
145             Rlog.e(TAG, "dispose: not bound to a wappush manager");
146         }
147     }
148 
149     /**
150      * Decodes the wap push pdu. The decoded result is wrapped inside the {@link DecodedResult}
151      * object. The caller of this method should check {@link DecodedResult#statusCode} for the
152      * decoding status. It  can have the following values.
153      *
154      * Activity.RESULT_OK - the wap push pdu is successfully decoded and should be further processed
155      * Intents.RESULT_SMS_HANDLED - the wap push pdu should be ignored.
156      * Intents.RESULT_SMS_GENERIC_ERROR - the pdu is invalid.
157      */
decodeWapPdu(byte[] pdu, InboundSmsHandler handler)158     private DecodedResult decodeWapPdu(byte[] pdu, InboundSmsHandler handler) {
159         DecodedResult result = new DecodedResult();
160         if (DBG) Rlog.d(TAG, "Rx: " + IccUtils.bytesToHexString(pdu));
161 
162         try {
163             int index = 0;
164             int transactionId = pdu[index++] & 0xFF;
165             int pduType = pdu[index++] & 0xFF;
166 
167             // Should we "abort" if no subId for now just no supplying extra param below
168             int phoneId = handler.getPhone().getPhoneId();
169 
170             if ((pduType != WspTypeDecoder.PDU_TYPE_PUSH) &&
171                     (pduType != WspTypeDecoder.PDU_TYPE_CONFIRMED_PUSH)) {
172                 index = mContext.getResources().getInteger(
173                         com.android.internal.R.integer.config_valid_wappush_index);
174                 if (index != -1) {
175                     transactionId = pdu[index++] & 0xff;
176                     pduType = pdu[index++] & 0xff;
177                     if (DBG)
178                         Rlog.d(TAG, "index = " + index + " PDU Type = " + pduType +
179                                 " transactionID = " + transactionId);
180 
181                     // recheck wap push pduType
182                     if ((pduType != WspTypeDecoder.PDU_TYPE_PUSH)
183                             && (pduType != WspTypeDecoder.PDU_TYPE_CONFIRMED_PUSH)) {
184                         if (DBG) Rlog.w(TAG, "Received non-PUSH WAP PDU. Type = " + pduType);
185                         result.statusCode = Intents.RESULT_SMS_HANDLED;
186                         return result;
187                     }
188                 } else {
189                     if (DBG) Rlog.w(TAG, "Received non-PUSH WAP PDU. Type = " + pduType);
190                     result.statusCode = Intents.RESULT_SMS_HANDLED;
191                     return result;
192                 }
193             }
194             WspTypeDecoder pduDecoder =
195                     TelephonyComponentFactory.getInstance().inject(WspTypeDecoder.class.getName())
196                             .makeWspTypeDecoder(pdu);
197 
198             /**
199              * Parse HeaderLen(unsigned integer).
200              * From wap-230-wsp-20010705-a section 8.1.2
201              * The maximum size of a uintvar is 32 bits.
202              * So it will be encoded in no more than 5 octets.
203              */
204             if (pduDecoder.decodeUintvarInteger(index) == false) {
205                 if (DBG) Rlog.w(TAG, "Received PDU. Header Length error.");
206                 result.statusCode = Intents.RESULT_SMS_GENERIC_ERROR;
207                 return result;
208             }
209             int headerLength = (int) pduDecoder.getValue32();
210             index += pduDecoder.getDecodedDataLength();
211 
212             int headerStartIndex = index;
213 
214             /**
215              * Parse Content-Type.
216              * From wap-230-wsp-20010705-a section 8.4.2.24
217              *
218              * Content-type-value = Constrained-media | Content-general-form
219              * Content-general-form = Value-length Media-type
220              * Media-type = (Well-known-media | Extension-Media) *(Parameter)
221              * Value-length = Short-length | (Length-quote Length)
222              * Short-length = <Any octet 0-30>   (octet <= WAP_PDU_SHORT_LENGTH_MAX)
223              * Length-quote = <Octet 31>         (WAP_PDU_LENGTH_QUOTE)
224              * Length = Uintvar-integer
225              */
226             if (pduDecoder.decodeContentType(index) == false) {
227                 if (DBG) Rlog.w(TAG, "Received PDU. Header Content-Type error.");
228                 result.statusCode = Intents.RESULT_SMS_GENERIC_ERROR;
229                 return result;
230             }
231 
232             String mimeType = pduDecoder.getValueString();
233             long binaryContentType = pduDecoder.getValue32();
234             index += pduDecoder.getDecodedDataLength();
235 
236             byte[] header = new byte[headerLength];
237             System.arraycopy(pdu, headerStartIndex, header, 0, header.length);
238 
239             byte[] intentData;
240 
241             if (mimeType != null && mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_PUSH_CO)) {
242                 intentData = pdu;
243             } else {
244                 int dataIndex = headerStartIndex + headerLength;
245                 intentData = new byte[pdu.length - dataIndex];
246                 System.arraycopy(pdu, dataIndex, intentData, 0, intentData.length);
247             }
248 
249             int subId = SubscriptionManager.getSubscriptionId(phoneId);
250             if (!SubscriptionManager.isValidSubscriptionId(subId)) {
251                 subId = SmsManager.getDefaultSmsSubscriptionId();
252             }
253 
254             // Continue if PDU parsing fails: the default messaging app may successfully parse the
255             // same PDU.
256             GenericPdu parsedPdu = null;
257             try {
258                 parsedPdu = new PduParser(intentData, shouldParseContentDisposition(subId)).parse();
259             } catch (Exception e) {
260                 Rlog.e(TAG, "Unable to parse PDU: " + e.toString());
261             }
262 
263             if (parsedPdu != null && parsedPdu.getMessageType() == MESSAGE_TYPE_NOTIFICATION_IND) {
264                 final NotificationInd nInd = (NotificationInd) parsedPdu;
265                 if (nInd.getFrom() != null
266                         && BlockChecker.isBlocked(mContext, nInd.getFrom().getString(), null)) {
267                     result.statusCode = Intents.RESULT_SMS_HANDLED;
268                     return result;
269                 }
270             }
271 
272             /**
273              * Seek for application ID field in WSP header.
274              * If application ID is found, WapPushManager substitute the message
275              * processing. Since WapPushManager is optional module, if WapPushManager
276              * is not found, legacy message processing will be continued.
277              */
278             if (pduDecoder.seekXWapApplicationId(index, index + headerLength - 1)) {
279                 index = (int) pduDecoder.getValue32();
280                 pduDecoder.decodeXWapApplicationId(index);
281                 String wapAppId = pduDecoder.getValueString();
282                 if (wapAppId == null) {
283                     wapAppId = Integer.toString((int) pduDecoder.getValue32());
284                 }
285                 result.wapAppId = wapAppId;
286                 String contentType = ((mimeType == null) ?
287                         Long.toString(binaryContentType) : mimeType);
288                 result.contentType = contentType;
289                 if (DBG) Rlog.v(TAG, "appid found: " + wapAppId + ":" + contentType);
290             }
291 
292             result.subId = subId;
293             result.phoneId = phoneId;
294             result.parsedPdu = parsedPdu;
295             result.mimeType = mimeType;
296             result.transactionId = transactionId;
297             result.pduType = pduType;
298             result.header = header;
299             result.intentData = intentData;
300             result.contentTypeParameters = pduDecoder.getContentParameters();
301             result.statusCode = Activity.RESULT_OK;
302         } catch (ArrayIndexOutOfBoundsException aie) {
303             // 0-byte WAP PDU or other unexpected WAP PDU contents can easily throw this;
304             // log exception string without stack trace and return false.
305             Rlog.e(TAG, "ignoring dispatchWapPdu() array index exception: " + aie);
306             result.statusCode = Intents.RESULT_SMS_GENERIC_ERROR;
307         }
308         return result;
309     }
310 
311     /**
312      * Dispatches inbound messages that are in the WAP PDU format. See
313      * wap-230-wsp-20010705-a section 8 for details on the WAP PDU format.
314      *
315      * @param pdu The WAP PDU, made up of one or more SMS PDUs
316      * @param address The originating address
317      * @return a result code from {@link android.provider.Telephony.Sms.Intents}, or
318      *         {@link Activity#RESULT_OK} if the message has been broadcast
319      *         to applications
320      */
dispatchWapPdu(byte[] pdu, InboundSmsHandler.SmsBroadcastReceiver receiver, InboundSmsHandler handler, String address, int subId, long messageId)321     public int dispatchWapPdu(byte[] pdu, InboundSmsHandler.SmsBroadcastReceiver receiver,
322             InboundSmsHandler handler, String address, int subId, long messageId) {
323         DecodedResult result = decodeWapPdu(pdu, handler);
324         if (result.statusCode != Activity.RESULT_OK) {
325             return result.statusCode;
326         }
327 
328         /**
329          * If the pdu has application ID, WapPushManager substitute the message
330          * processing. Since WapPushManager is optional module, if WapPushManager
331          * is not found, legacy message processing will be continued.
332          */
333         if (result.wapAppId != null) {
334             try {
335                 boolean processFurther = true;
336                 IWapPushManager wapPushMan = mWapPushManager;
337 
338                 if (wapPushMan == null) {
339                     if (DBG) Rlog.w(TAG, "wap push manager not found!");
340                 } else {
341                     synchronized (this) {
342                         mPowerWhitelistManager.whitelistAppTemporarilyForEvent(
343                                 mWapPushManagerPackage, PowerWhitelistManager.EVENT_MMS,
344                                 REASON_EVENT_MMS, "mms-mgr");
345                     }
346 
347                     Intent intent = new Intent();
348                     intent.putExtra("transactionId", result.transactionId);
349                     intent.putExtra("pduType", result.pduType);
350                     intent.putExtra("header", result.header);
351                     intent.putExtra("data", result.intentData);
352                     intent.putExtra("contentTypeParameters", result.contentTypeParameters);
353                     SubscriptionManager.putPhoneIdAndSubIdExtra(intent, result.phoneId);
354                     if (!TextUtils.isEmpty(address)) {
355                         intent.putExtra("address", address);
356                     }
357 
358                     int procRet = wapPushMan.processMessage(
359                         result.wapAppId, result.contentType, intent);
360                     if (DBG) Rlog.v(TAG, "procRet:" + procRet);
361                     if ((procRet & WapPushManagerParams.MESSAGE_HANDLED) > 0
362                             && (procRet & WapPushManagerParams.FURTHER_PROCESSING) == 0) {
363                         processFurther = false;
364                     }
365                 }
366                 if (!processFurther) {
367                     return Intents.RESULT_SMS_HANDLED;
368                 }
369             } catch (RemoteException e) {
370                 if (DBG) Rlog.w(TAG, "remote func failed...");
371             }
372         }
373         if (DBG) Rlog.v(TAG, "fall back to existing handler");
374 
375         if (result.mimeType == null) {
376             if (DBG) Rlog.w(TAG, "Header Content-Type error.");
377             return Intents.RESULT_SMS_GENERIC_ERROR;
378         }
379 
380         Intent intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION);
381         intent.setType(result.mimeType);
382         intent.putExtra("transactionId", result.transactionId);
383         intent.putExtra("pduType", result.pduType);
384         intent.putExtra("header", result.header);
385         intent.putExtra("data", result.intentData);
386         intent.putExtra("contentTypeParameters", result.contentTypeParameters);
387         if (!TextUtils.isEmpty(address)) {
388             intent.putExtra("address", address);
389         }
390         if (messageId != 0L) {
391             intent.putExtra("messageId", messageId);
392         }
393 
394         // Direct the intent to only the default MMS app. If we can't find a default MMS app
395         // then sent it to all broadcast receivers.
396         UserHandle userHandle = TelephonyUtils.getSubscriptionUserHandle(mContext, subId);
397         ComponentName componentName = SmsApplication.getDefaultMmsApplicationAsUser(mContext,
398                 true, userHandle);
399 
400         Bundle options = null;
401         if (componentName != null) {
402             // Deliver MMS message only to this receiver
403             intent.setComponent(componentName);
404             if (DBG) Rlog.v(TAG, "Delivering MMS to: " + componentName.getPackageName() +
405                     " " + componentName.getClassName());
406             long duration = mPowerWhitelistManager.whitelistAppTemporarilyForEvent(
407                     componentName.getPackageName(), PowerWhitelistManager.EVENT_MMS,
408                     REASON_EVENT_MMS, "mms-app");
409             BroadcastOptions bopts = BroadcastOptions.makeBasic();
410             bopts.setTemporaryAppAllowlist(duration,
411                     TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
412                     REASON_EVENT_MMS,
413                     "");
414             options = bopts.toBundle();
415         }
416 
417         if (userHandle == null) {
418             userHandle = UserHandle.SYSTEM;
419         }
420         handler.dispatchIntent(intent, getPermissionForType(result.mimeType),
421                 getAppOpsStringPermissionForIntent(result.mimeType), options, receiver,
422                 userHandle, subId);
423         return Activity.RESULT_OK;
424     }
425 
426     /**
427      * Check whether the pdu is a MMS WAP push pdu that should be dispatched to the SMS app.
428      */
429     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
isWapPushForMms(byte[] pdu, InboundSmsHandler handler)430     public boolean isWapPushForMms(byte[] pdu, InboundSmsHandler handler) {
431         DecodedResult result = decodeWapPdu(pdu, handler);
432         return result.statusCode == Activity.RESULT_OK
433             && WspTypeDecoder.CONTENT_TYPE_B_MMS.equals(result.mimeType);
434     }
435 
shouldParseContentDisposition(int subId)436     private static boolean shouldParseContentDisposition(int subId) {
437         return SmsManager
438                 .getSmsManagerForSubscriptionId(subId)
439                 .getCarrierConfigValues()
440                 .getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION, true);
441     }
442 
getPermissionForType(String mimeType)443     public static String getPermissionForType(String mimeType) {
444         String permission;
445         if (WspTypeDecoder.CONTENT_TYPE_B_MMS.equals(mimeType)) {
446             permission = android.Manifest.permission.RECEIVE_MMS;
447         } else {
448             permission = android.Manifest.permission.RECEIVE_WAP_PUSH;
449         }
450         return permission;
451     }
452 
453     /**
454      * Return a appOps String for the given MIME type.
455      * @param mimeType MIME type of the Intent
456      * @return The appOps String
457      */
getAppOpsStringPermissionForIntent(String mimeType)458     public static String getAppOpsStringPermissionForIntent(String mimeType) {
459         String appOp;
460         if (WspTypeDecoder.CONTENT_TYPE_B_MMS.equals(mimeType)) {
461             appOp = AppOpsManager.OPSTR_RECEIVE_MMS;
462         } else {
463             appOp = AppOpsManager.OPSTR_RECEIVE_WAP_PUSH;
464         }
465         return appOp;
466     }
467 
468     /**
469      * Place holder for decoded Wap pdu data.
470      */
471     private final class DecodedResult {
472         String mimeType;
473         String contentType;
474         int transactionId;
475         int pduType;
476         int phoneId;
477         int subId;
478         byte[] header;
479         String wapAppId;
480         byte[] intentData;
481         HashMap<String, String> contentTypeParameters;
482         GenericPdu parsedPdu;
483         int statusCode;
484     }
485 }
486