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 17package com.android.internal.telephony; 18 19import android.app.Activity; 20import android.app.AppOpsManager; 21import android.content.BroadcastReceiver; 22import android.content.ComponentName; 23import android.content.Context; 24import android.content.Intent; 25import android.content.ServiceConnection; 26import android.os.IBinder; 27import android.os.RemoteException; 28import android.provider.Telephony.Sms.Intents; 29import android.telephony.Rlog; 30 31import com.android.internal.telephony.uicc.IccUtils; 32 33/** 34 * WAP push handler class. 35 * 36 * @hide 37 */ 38public 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 int index = 0; 94 int transactionId = pdu[index++] & 0xFF; 95 int pduType = pdu[index++] & 0xFF; 96 97 if ((pduType != WspTypeDecoder.PDU_TYPE_PUSH) && 98 (pduType != WspTypeDecoder.PDU_TYPE_CONFIRMED_PUSH)) { 99 index = mContext.getResources().getInteger( 100 com.android.internal.R.integer.config_valid_wappush_index); 101 if(index != -1) { 102 transactionId = pdu[index++] & 0xff; 103 pduType = pdu[index++] & 0xff; 104 if (DBG) 105 Rlog.d(TAG, "index = " + index + " PDU Type = " + pduType + 106 " transactionID = " + transactionId); 107 108 // recheck wap push pduType 109 if ((pduType != WspTypeDecoder.PDU_TYPE_PUSH) 110 && (pduType != WspTypeDecoder.PDU_TYPE_CONFIRMED_PUSH)) { 111 if (DBG) Rlog.w(TAG, "Received non-PUSH WAP PDU. Type = " + pduType); 112 return Intents.RESULT_SMS_HANDLED; 113 } 114 } else { 115 if (DBG) Rlog.w(TAG, "Received non-PUSH WAP PDU. Type = " + pduType); 116 return Intents.RESULT_SMS_HANDLED; 117 } 118 } 119 120 WspTypeDecoder pduDecoder = new WspTypeDecoder(pdu); 121 122 /** 123 * Parse HeaderLen(unsigned integer). 124 * From wap-230-wsp-20010705-a section 8.1.2 125 * The maximum size of a uintvar is 32 bits. 126 * So it will be encoded in no more than 5 octets. 127 */ 128 if (pduDecoder.decodeUintvarInteger(index) == false) { 129 if (DBG) Rlog.w(TAG, "Received PDU. Header Length error."); 130 return Intents.RESULT_SMS_GENERIC_ERROR; 131 } 132 int headerLength = (int) pduDecoder.getValue32(); 133 index += pduDecoder.getDecodedDataLength(); 134 135 int headerStartIndex = index; 136 137 /** 138 * Parse Content-Type. 139 * From wap-230-wsp-20010705-a section 8.4.2.24 140 * 141 * Content-type-value = Constrained-media | Content-general-form 142 * Content-general-form = Value-length Media-type 143 * Media-type = (Well-known-media | Extension-Media) *(Parameter) 144 * Value-length = Short-length | (Length-quote Length) 145 * Short-length = <Any octet 0-30> (octet <= WAP_PDU_SHORT_LENGTH_MAX) 146 * Length-quote = <Octet 31> (WAP_PDU_LENGTH_QUOTE) 147 * Length = Uintvar-integer 148 */ 149 if (pduDecoder.decodeContentType(index) == false) { 150 if (DBG) Rlog.w(TAG, "Received PDU. Header Content-Type error."); 151 return Intents.RESULT_SMS_GENERIC_ERROR; 152 } 153 154 String mimeType = pduDecoder.getValueString(); 155 long binaryContentType = pduDecoder.getValue32(); 156 index += pduDecoder.getDecodedDataLength(); 157 158 byte[] header = new byte[headerLength]; 159 System.arraycopy(pdu, headerStartIndex, header, 0, header.length); 160 161 byte[] intentData; 162 163 if (mimeType != null && mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_PUSH_CO)) { 164 intentData = pdu; 165 } else { 166 int dataIndex = headerStartIndex + headerLength; 167 intentData = new byte[pdu.length - dataIndex]; 168 System.arraycopy(pdu, dataIndex, intentData, 0, intentData.length); 169 } 170 171 /** 172 * Seek for application ID field in WSP header. 173 * If application ID is found, WapPushManager substitute the message 174 * processing. Since WapPushManager is optional module, if WapPushManager 175 * is not found, legacy message processing will be continued. 176 */ 177 if (pduDecoder.seekXWapApplicationId(index, index + headerLength - 1)) { 178 index = (int) pduDecoder.getValue32(); 179 pduDecoder.decodeXWapApplicationId(index); 180 String wapAppId = pduDecoder.getValueString(); 181 if (wapAppId == null) { 182 wapAppId = Integer.toString((int) pduDecoder.getValue32()); 183 } 184 185 String contentType = ((mimeType == null) ? 186 Long.toString(binaryContentType) : mimeType); 187 if (DBG) Rlog.v(TAG, "appid found: " + wapAppId + ":" + contentType); 188 189 try { 190 boolean processFurther = true; 191 IWapPushManager wapPushMan = mWapPushManager; 192 193 if (wapPushMan == null) { 194 if (DBG) Rlog.w(TAG, "wap push manager not found!"); 195 } else { 196 Intent intent = new Intent(); 197 intent.putExtra("transactionId", transactionId); 198 intent.putExtra("pduType", pduType); 199 intent.putExtra("header", header); 200 intent.putExtra("data", intentData); 201 intent.putExtra("contentTypeParameters", 202 pduDecoder.getContentParameters()); 203 204 int procRet = wapPushMan.processMessage(wapAppId, contentType, intent); 205 if (DBG) Rlog.v(TAG, "procRet:" + procRet); 206 if ((procRet & WapPushManagerParams.MESSAGE_HANDLED) > 0 207 && (procRet & WapPushManagerParams.FURTHER_PROCESSING) == 0) { 208 processFurther = false; 209 } 210 } 211 if (!processFurther) { 212 return Intents.RESULT_SMS_HANDLED; 213 } 214 } catch (RemoteException e) { 215 if (DBG) Rlog.w(TAG, "remote func failed..."); 216 } 217 } 218 if (DBG) Rlog.v(TAG, "fall back to existing handler"); 219 220 if (mimeType == null) { 221 if (DBG) Rlog.w(TAG, "Header Content-Type error."); 222 return Intents.RESULT_SMS_GENERIC_ERROR; 223 } 224 225 String permission; 226 int appOp; 227 228 if (mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_MMS)) { 229 permission = android.Manifest.permission.RECEIVE_MMS; 230 appOp = AppOpsManager.OP_RECEIVE_MMS; 231 } else { 232 permission = android.Manifest.permission.RECEIVE_WAP_PUSH; 233 appOp = AppOpsManager.OP_RECEIVE_WAP_PUSH; 234 } 235 236 Intent intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION); 237 intent.setType(mimeType); 238 intent.putExtra("transactionId", transactionId); 239 intent.putExtra("pduType", pduType); 240 intent.putExtra("header", header); 241 intent.putExtra("data", intentData); 242 intent.putExtra("contentTypeParameters", pduDecoder.getContentParameters()); 243 244 // Direct the intent to only the default MMS app. If we can't find a default MMS app 245 // then sent it to all broadcast receivers. 246 ComponentName componentName = SmsApplication.getDefaultMmsApplication(mContext, true); 247 if (componentName != null) { 248 // Deliver MMS message only to this receiver 249 intent.setComponent(componentName); 250 if (DBG) Rlog.v(TAG, "Delivering MMS to: " + componentName.getPackageName() + 251 " " + componentName.getClassName()); 252 } 253 254 handler.dispatchIntent(intent, permission, appOp, receiver); 255 return Activity.RESULT_OK; 256 } 257} 258