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