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 android.app.Activity; 20 import android.app.AppOpsManager; 21 import android.content.BroadcastReceiver; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.ServiceConnection; 26 import android.os.IBinder; 27 import android.os.RemoteException; 28 import android.provider.Telephony.Sms.Intents; 29 import android.telephony.Rlog; 30 31 import com.android.internal.telephony.uicc.IccUtils; 32 33 /** 34 * WAP push handler class. 35 * 36 * @hide 37 */ 38 public class WapPushOverSms implements ServiceConnection { 39 private static final String TAG = "WAP PUSH"; 40 private static final boolean DBG = true; 41 42 private final Context mContext; 43 44 /** Assigned from ServiceConnection callback on main threaad. */ 45 private volatile IWapPushManager mWapPushManager; 46 47 @Override 48 public void onServiceConnected(ComponentName name, IBinder service) { 49 mWapPushManager = IWapPushManager.Stub.asInterface(service); 50 if (DBG) Rlog.v(TAG, "wappush manager connected to " + hashCode()); 51 } 52 53 @Override 54 public void onServiceDisconnected(ComponentName name) { 55 mWapPushManager = null; 56 if (DBG) Rlog.v(TAG, "wappush manager disconnected."); 57 } 58 59 public WapPushOverSms(Context context) { 60 mContext = context; 61 Intent intent = new Intent(IWapPushManager.class.getName()); 62 ComponentName comp = intent.resolveSystemService(context.getPackageManager(), 0); 63 intent.setComponent(comp); 64 if (comp == null || !context.bindService(intent, this, Context.BIND_AUTO_CREATE)) { 65 Rlog.e(TAG, "bindService() for wappush manager failed"); 66 } else { 67 if (DBG) Rlog.v(TAG, "bindService() for wappush manager succeeded"); 68 } 69 } 70 71 void dispose() { 72 if (mWapPushManager != null) { 73 if (DBG) Rlog.v(TAG, "dispose: unbind wappush manager"); 74 mContext.unbindService(this); 75 } else { 76 Rlog.e(TAG, "dispose: not bound to a wappush manager"); 77 } 78 } 79 80 /** 81 * Dispatches inbound messages that are in the WAP PDU format. See 82 * wap-230-wsp-20010705-a section 8 for details on the WAP PDU format. 83 * 84 * @param pdu The WAP PDU, made up of one or more SMS PDUs 85 * @return a result code from {@link android.provider.Telephony.Sms.Intents}, or 86 * {@link Activity#RESULT_OK} if the message has been broadcast 87 * to applications 88 */ 89 public int dispatchWapPdu(byte[] pdu, BroadcastReceiver receiver, InboundSmsHandler handler) { 90 91 if (DBG) Rlog.d(TAG, "Rx: " + IccUtils.bytesToHexString(pdu)); 92 93 try { 94 int index = 0; 95 int transactionId = pdu[index++] & 0xFF; 96 int pduType = pdu[index++] & 0xFF; 97 98 if ((pduType != WspTypeDecoder.PDU_TYPE_PUSH) && 99 (pduType != WspTypeDecoder.PDU_TYPE_CONFIRMED_PUSH)) { 100 index = mContext.getResources().getInteger( 101 com.android.internal.R.integer.config_valid_wappush_index); 102 if (index != -1) { 103 transactionId = pdu[index++] & 0xff; 104 pduType = pdu[index++] & 0xff; 105 if (DBG) 106 Rlog.d(TAG, "index = " + index + " PDU Type = " + pduType + 107 " transactionID = " + transactionId); 108 109 // recheck wap push pduType 110 if ((pduType != WspTypeDecoder.PDU_TYPE_PUSH) 111 && (pduType != WspTypeDecoder.PDU_TYPE_CONFIRMED_PUSH)) { 112 if (DBG) Rlog.w(TAG, "Received non-PUSH WAP PDU. Type = " + pduType); 113 return Intents.RESULT_SMS_HANDLED; 114 } 115 } else { 116 if (DBG) Rlog.w(TAG, "Received non-PUSH WAP PDU. Type = " + pduType); 117 return Intents.RESULT_SMS_HANDLED; 118 } 119 } 120 121 WspTypeDecoder pduDecoder = new WspTypeDecoder(pdu); 122 123 /** 124 * Parse HeaderLen(unsigned integer). 125 * From wap-230-wsp-20010705-a section 8.1.2 126 * The maximum size of a uintvar is 32 bits. 127 * So it will be encoded in no more than 5 octets. 128 */ 129 if (pduDecoder.decodeUintvarInteger(index) == false) { 130 if (DBG) Rlog.w(TAG, "Received PDU. Header Length error."); 131 return Intents.RESULT_SMS_GENERIC_ERROR; 132 } 133 int headerLength = (int) pduDecoder.getValue32(); 134 index += pduDecoder.getDecodedDataLength(); 135 136 int headerStartIndex = index; 137 138 /** 139 * Parse Content-Type. 140 * From wap-230-wsp-20010705-a section 8.4.2.24 141 * 142 * Content-type-value = Constrained-media | Content-general-form 143 * Content-general-form = Value-length Media-type 144 * Media-type = (Well-known-media | Extension-Media) *(Parameter) 145 * Value-length = Short-length | (Length-quote Length) 146 * Short-length = <Any octet 0-30> (octet <= WAP_PDU_SHORT_LENGTH_MAX) 147 * Length-quote = <Octet 31> (WAP_PDU_LENGTH_QUOTE) 148 * Length = Uintvar-integer 149 */ 150 if (pduDecoder.decodeContentType(index) == false) { 151 if (DBG) Rlog.w(TAG, "Received PDU. Header Content-Type error."); 152 return Intents.RESULT_SMS_GENERIC_ERROR; 153 } 154 155 String mimeType = pduDecoder.getValueString(); 156 long binaryContentType = pduDecoder.getValue32(); 157 index += pduDecoder.getDecodedDataLength(); 158 159 byte[] header = new byte[headerLength]; 160 System.arraycopy(pdu, headerStartIndex, header, 0, header.length); 161 162 byte[] intentData; 163 164 if (mimeType != null && mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_PUSH_CO)) { 165 intentData = pdu; 166 } else { 167 int dataIndex = headerStartIndex + headerLength; 168 intentData = new byte[pdu.length - dataIndex]; 169 System.arraycopy(pdu, dataIndex, intentData, 0, intentData.length); 170 } 171 172 /** 173 * Seek for application ID field in WSP header. 174 * If application ID is found, WapPushManager substitute the message 175 * processing. Since WapPushManager is optional module, if WapPushManager 176 * is not found, legacy message processing will be continued. 177 */ 178 if (pduDecoder.seekXWapApplicationId(index, index + headerLength - 1)) { 179 index = (int) pduDecoder.getValue32(); 180 pduDecoder.decodeXWapApplicationId(index); 181 String wapAppId = pduDecoder.getValueString(); 182 if (wapAppId == null) { 183 wapAppId = Integer.toString((int) pduDecoder.getValue32()); 184 } 185 186 String contentType = ((mimeType == null) ? 187 Long.toString(binaryContentType) : mimeType); 188 if (DBG) Rlog.v(TAG, "appid found: " + wapAppId + ":" + contentType); 189 190 try { 191 boolean processFurther = true; 192 IWapPushManager wapPushMan = mWapPushManager; 193 194 if (wapPushMan == null) { 195 if (DBG) Rlog.w(TAG, "wap push manager not found!"); 196 } else { 197 Intent intent = new Intent(); 198 intent.putExtra("transactionId", transactionId); 199 intent.putExtra("pduType", pduType); 200 intent.putExtra("header", header); 201 intent.putExtra("data", intentData); 202 intent.putExtra("contentTypeParameters", 203 pduDecoder.getContentParameters()); 204 205 int procRet = wapPushMan.processMessage(wapAppId, contentType, intent); 206 if (DBG) Rlog.v(TAG, "procRet:" + procRet); 207 if ((procRet & WapPushManagerParams.MESSAGE_HANDLED) > 0 208 && (procRet & WapPushManagerParams.FURTHER_PROCESSING) == 0) { 209 processFurther = false; 210 } 211 } 212 if (!processFurther) { 213 return Intents.RESULT_SMS_HANDLED; 214 } 215 } catch (RemoteException e) { 216 if (DBG) Rlog.w(TAG, "remote func failed..."); 217 } 218 } 219 if (DBG) Rlog.v(TAG, "fall back to existing handler"); 220 221 if (mimeType == null) { 222 if (DBG) Rlog.w(TAG, "Header Content-Type error."); 223 return Intents.RESULT_SMS_GENERIC_ERROR; 224 } 225 226 String permission; 227 int appOp; 228 229 if (mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_MMS)) { 230 permission = android.Manifest.permission.RECEIVE_MMS; 231 appOp = AppOpsManager.OP_RECEIVE_MMS; 232 } else { 233 permission = android.Manifest.permission.RECEIVE_WAP_PUSH; 234 appOp = AppOpsManager.OP_RECEIVE_WAP_PUSH; 235 } 236 237 Intent intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION); 238 intent.setType(mimeType); 239 intent.putExtra("transactionId", transactionId); 240 intent.putExtra("pduType", pduType); 241 intent.putExtra("header", header); 242 intent.putExtra("data", intentData); 243 intent.putExtra("contentTypeParameters", pduDecoder.getContentParameters()); 244 245 // Direct the intent to only the default MMS app. If we can't find a default MMS app 246 // then sent it to all broadcast receivers. 247 ComponentName componentName = SmsApplication.getDefaultMmsApplication(mContext, true); 248 if (componentName != null) { 249 // Deliver MMS message only to this receiver 250 intent.setComponent(componentName); 251 if (DBG) Rlog.v(TAG, "Delivering MMS to: " + componentName.getPackageName() + 252 " " + componentName.getClassName()); 253 } 254 255 handler.dispatchIntent(intent, permission, appOp, receiver); 256 return Activity.RESULT_OK; 257 } catch (ArrayIndexOutOfBoundsException aie) { 258 // 0-byte WAP PDU or other unexpected WAP PDU contents can easily throw this; 259 // log exception string without stack trace and return false. 260 Rlog.e(TAG, "ignoring dispatchWapPdu() array index exception: " + aie); 261 return Intents.RESULT_SMS_GENERIC_ERROR; 262 } 263 } 264 } 265