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