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