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