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