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