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