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