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                 // save the WAP push message size so that if a download request is made for it
266                 // while on a satellite connection we can check if the size is under the threshold
267                 WapPushCache.putWapMessageSize(
268                         nInd.getContentLocation(),
269                         nInd.getTransactionId(),
270                         nInd.getMessageSize()
271                 );
272                 if (nInd.getFrom() != null
273                         && BlockChecker.isBlocked(mContext, nInd.getFrom().getString(), null)) {
274                     result.statusCode = Intents.RESULT_SMS_HANDLED;
275                     return result;
276                 }
277             }
278 
279             /**
280              * Seek for application ID field in WSP header.
281              * If application ID is found, WapPushManager substitute the message
282              * processing. Since WapPushManager is optional module, if WapPushManager
283              * is not found, legacy message processing will be continued.
284              */
285             if (pduDecoder.seekXWapApplicationId(index, index + headerLength - 1)) {
286                 index = (int) pduDecoder.getValue32();
287                 pduDecoder.decodeXWapApplicationId(index);
288                 String wapAppId = pduDecoder.getValueString();
289                 if (wapAppId == null) {
290                     wapAppId = Integer.toString((int) pduDecoder.getValue32());
291                 }
292                 result.wapAppId = wapAppId;
293                 String contentType = ((mimeType == null) ?
294                         Long.toString(binaryContentType) : mimeType);
295                 result.contentType = contentType;
296                 if (DBG) Rlog.v(TAG, "appid found: " + wapAppId + ":" + contentType);
297             }
298 
299             result.subId = subId;
300             result.phoneId = phoneId;
301             result.parsedPdu = parsedPdu;
302             result.mimeType = mimeType;
303             result.transactionId = transactionId;
304             result.pduType = pduType;
305             result.header = header;
306             result.intentData = intentData;
307             result.contentTypeParameters = pduDecoder.getContentParameters();
308             result.statusCode = Activity.RESULT_OK;
309         } catch (ArrayIndexOutOfBoundsException aie) {
310             // 0-byte WAP PDU or other unexpected WAP PDU contents can easily throw this;
311             // log exception string without stack trace and return false.
312             Rlog.e(TAG, "ignoring dispatchWapPdu() array index exception: " + aie);
313             result.statusCode = Intents.RESULT_SMS_GENERIC_ERROR;
314         }
315         return result;
316     }
317 
318     /**
319      * Dispatches inbound messages that are in the WAP PDU format. See
320      * wap-230-wsp-20010705-a section 8 for details on the WAP PDU format.
321      *
322      * @param pdu The WAP PDU, made up of one or more SMS PDUs
323      * @param address The originating address
324      * @return a result code from {@link android.provider.Telephony.Sms.Intents}, or
325      *         {@link Activity#RESULT_OK} if the message has been broadcast
326      *         to applications
327      */
dispatchWapPdu(byte[] pdu, InboundSmsHandler.SmsBroadcastReceiver receiver, InboundSmsHandler handler, String address, int subId, long messageId)328     public int dispatchWapPdu(byte[] pdu, InboundSmsHandler.SmsBroadcastReceiver receiver,
329             InboundSmsHandler handler, String address, int subId, long messageId) {
330         DecodedResult result = decodeWapPdu(pdu, handler);
331         if (result.statusCode != Activity.RESULT_OK) {
332             return result.statusCode;
333         }
334 
335         /**
336          * If the pdu has application ID, WapPushManager substitute the message
337          * processing. Since WapPushManager is optional module, if WapPushManager
338          * is not found, legacy message processing will be continued.
339          */
340         if (result.wapAppId != null) {
341             try {
342                 boolean processFurther = true;
343                 IWapPushManager wapPushMan = mWapPushManager;
344 
345                 if (wapPushMan == null) {
346                     if (DBG) Rlog.w(TAG, "wap push manager not found!");
347                 } else {
348                     synchronized (this) {
349                         mPowerWhitelistManager.whitelistAppTemporarilyForEvent(
350                                 mWapPushManagerPackage, PowerWhitelistManager.EVENT_MMS,
351                                 REASON_EVENT_MMS, "mms-mgr");
352                     }
353 
354                     Intent intent = new Intent();
355                     intent.putExtra("transactionId", result.transactionId);
356                     intent.putExtra("pduType", result.pduType);
357                     intent.putExtra("header", result.header);
358                     intent.putExtra("data", result.intentData);
359                     intent.putExtra("contentTypeParameters", result.contentTypeParameters);
360                     SubscriptionManager.putPhoneIdAndSubIdExtra(intent, result.phoneId);
361                     if (!TextUtils.isEmpty(address)) {
362                         intent.putExtra("address", address);
363                     }
364 
365                     int procRet = wapPushMan.processMessage(
366                         result.wapAppId, result.contentType, intent);
367                     if (DBG) Rlog.v(TAG, "procRet:" + procRet);
368                     if ((procRet & WapPushManagerParams.MESSAGE_HANDLED) > 0
369                             && (procRet & WapPushManagerParams.FURTHER_PROCESSING) == 0) {
370                         processFurther = false;
371                     }
372                 }
373                 if (!processFurther) {
374                     return Intents.RESULT_SMS_HANDLED;
375                 }
376             } catch (RemoteException e) {
377                 if (DBG) Rlog.w(TAG, "remote func failed...");
378             }
379         }
380         if (DBG) Rlog.v(TAG, "fall back to existing handler");
381 
382         if (result.mimeType == null) {
383             if (DBG) Rlog.w(TAG, "Header Content-Type error.");
384             return Intents.RESULT_SMS_GENERIC_ERROR;
385         }
386 
387         Intent intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION);
388         intent.setType(result.mimeType);
389         intent.putExtra("transactionId", result.transactionId);
390         intent.putExtra("pduType", result.pduType);
391         intent.putExtra("header", result.header);
392         intent.putExtra("data", result.intentData);
393         intent.putExtra("contentTypeParameters", result.contentTypeParameters);
394         if (!TextUtils.isEmpty(address)) {
395             intent.putExtra("address", address);
396         }
397         if (messageId != 0L) {
398             intent.putExtra("messageId", messageId);
399         }
400 
401         // Direct the intent to only the default MMS app. If we can't find a default MMS app
402         // then sent it to all broadcast receivers.
403         UserHandle userHandle = TelephonyUtils.getSubscriptionUserHandle(mContext, subId);
404         ComponentName componentName = SmsApplication.getDefaultMmsApplicationAsUser(mContext,
405                 true, userHandle);
406 
407         Bundle options = null;
408         if (componentName != null) {
409             // Deliver MMS message only to this receiver
410             intent.setComponent(componentName);
411             if (DBG) Rlog.v(TAG, "Delivering MMS to: " + componentName.getPackageName() +
412                     " " + componentName.getClassName());
413             long duration = mPowerWhitelistManager.whitelistAppTemporarilyForEvent(
414                     componentName.getPackageName(), PowerWhitelistManager.EVENT_MMS,
415                     REASON_EVENT_MMS, "mms-app");
416             BroadcastOptions bopts = BroadcastOptions.makeBasic();
417             bopts.setTemporaryAppAllowlist(duration,
418                     TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
419                     REASON_EVENT_MMS,
420                     "");
421             options = bopts.toBundle();
422         }
423 
424         if (userHandle == null) {
425             userHandle = UserHandle.SYSTEM;
426         }
427         handler.dispatchIntent(intent, getPermissionForType(result.mimeType),
428                 getAppOpsStringPermissionForIntent(result.mimeType), options, receiver,
429                 userHandle, subId);
430         return Activity.RESULT_OK;
431     }
432 
433     /**
434      * Check whether the pdu is a MMS WAP push pdu that should be dispatched to the SMS app.
435      */
436     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
isWapPushForMms(byte[] pdu, InboundSmsHandler handler)437     public boolean isWapPushForMms(byte[] pdu, InboundSmsHandler handler) {
438         DecodedResult result = decodeWapPdu(pdu, handler);
439         return result.statusCode == Activity.RESULT_OK
440             && WspTypeDecoder.CONTENT_TYPE_B_MMS.equals(result.mimeType);
441     }
442 
shouldParseContentDisposition(int subId)443     private static boolean shouldParseContentDisposition(int subId) {
444         return SmsManager
445                 .getSmsManagerForSubscriptionId(subId)
446                 .getCarrierConfigValues()
447                 .getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION, true);
448     }
449 
getPermissionForType(String mimeType)450     public static String getPermissionForType(String mimeType) {
451         String permission;
452         if (WspTypeDecoder.CONTENT_TYPE_B_MMS.equals(mimeType)) {
453             permission = android.Manifest.permission.RECEIVE_MMS;
454         } else {
455             permission = android.Manifest.permission.RECEIVE_WAP_PUSH;
456         }
457         return permission;
458     }
459 
460     /**
461      * Return a appOps String for the given MIME type.
462      * @param mimeType MIME type of the Intent
463      * @return The appOps String
464      */
getAppOpsStringPermissionForIntent(String mimeType)465     public static String getAppOpsStringPermissionForIntent(String mimeType) {
466         String appOp;
467         if (WspTypeDecoder.CONTENT_TYPE_B_MMS.equals(mimeType)) {
468             appOp = AppOpsManager.OPSTR_RECEIVE_MMS;
469         } else {
470             appOp = AppOpsManager.OPSTR_RECEIVE_WAP_PUSH;
471         }
472         return appOp;
473     }
474 
475     /**
476      * Place holder for decoded Wap pdu data.
477      */
478     private final class DecodedResult {
479         String mimeType;
480         String contentType;
481         int transactionId;
482         int pduType;
483         int phoneId;
484         int subId;
485         byte[] header;
486         String wapAppId;
487         byte[] intentData;
488         HashMap<String, String> contentTypeParameters;
489         GenericPdu parsedPdu;
490         int statusCode;
491     }
492 }
493