WapPushOverSms.java revision d720945f2be5ea5fe0faf67e67d9ea0e184eba67
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
18package com.android.internal.telephony;
19
20import android.app.Activity;
21import android.content.Context;
22import android.content.ComponentName;
23import android.content.Intent;
24import android.content.ServiceConnection;
25import android.provider.Telephony;
26import android.provider.Telephony.Sms.Intents;
27import android.telephony.Rlog;
28import android.os.IBinder;
29import android.os.RemoteException;
30
31import com.android.internal.telephony.uicc.IccUtils;
32
33/**
34 * WAP push handler class.
35 *
36 * @hide
37 */
38public class WapPushOverSms {
39    private static final String LOG_TAG = "WAP PUSH";
40
41    private final Context mContext;
42    private WspTypeDecoder pduDecoder;
43    private SMSDispatcher mSmsDispatcher;
44
45    /**
46     * Hold the wake lock for 5 seconds, which should be enough time for
47     * any receiver(s) to grab its own wake lock.
48     */
49    private final int WAKE_LOCK_TIMEOUT = 5000;
50
51    private final int BIND_RETRY_INTERVAL = 1000;
52    /**
53     * A handle to WapPushManager interface
54     */
55    private WapPushConnection mWapConn = null;
56    private class WapPushConnection implements ServiceConnection {
57        private IWapPushManager mWapPushMan;
58        private Context mOwner;
59
60        public WapPushConnection(Context ownerContext) {
61            mOwner = ownerContext;
62        }
63
64        public void onServiceConnected(ComponentName name, IBinder service) {
65            mWapPushMan = IWapPushManager.Stub.asInterface(service);
66            if (false) Rlog.v(LOG_TAG, "wappush manager connected to " +
67                    mOwner.hashCode());
68        }
69
70        public void onServiceDisconnected(ComponentName name) {
71            mWapPushMan = null;
72            if (false) Rlog.v(LOG_TAG, "wappush manager disconnected.");
73            // WapPushManager must be always attached.
74            rebindWapPushManager();
75        }
76
77        /**
78         * bind WapPushManager
79         */
80        public void bindWapPushManager() {
81            if (mWapPushMan != null) return;
82
83            final ServiceConnection wapPushConnection = this;
84
85            mOwner.bindService(new Intent(IWapPushManager.class.getName()),
86                    wapPushConnection, Context.BIND_AUTO_CREATE);
87        }
88
89        /**
90         * rebind WapPushManager
91         * This method is called when WapPushManager is disconnected unexpectedly.
92         */
93        private void rebindWapPushManager() {
94            if (mWapPushMan != null) return;
95
96            final ServiceConnection wapPushConnection = this;
97            new Thread() {
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 (false) 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 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 (false) 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 (false) 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 (false) 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 (false) 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 (false) 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 (false) 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 (false) 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 (false) Rlog.w(LOG_TAG, "remote func failed...");
248            }
249        }
250        if (false) Rlog.v(LOG_TAG, "fall back to existing handler");
251
252        if (mimeType == null) {
253            if (false) Rlog.w(LOG_TAG, "Header Content-Type error.");
254            return Intents.RESULT_SMS_GENERIC_ERROR;
255        }
256
257        String permission;
258
259        if (mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_MMS)) {
260            permission = "android.permission.RECEIVE_MMS";
261        } else {
262            permission = "android.permission.RECEIVE_WAP_PUSH";
263        }
264
265        Intent intent = new Intent(Intents.WAP_PUSH_RECEIVED_ACTION);
266        intent.setType(mimeType);
267        intent.putExtra("transactionId", transactionId);
268        intent.putExtra("pduType", pduType);
269        intent.putExtra("header", header);
270        intent.putExtra("data", intentData);
271        intent.putExtra("contentTypeParameters", pduDecoder.getContentParameters());
272
273        mSmsDispatcher.dispatch(intent, permission);
274
275        return Activity.RESULT_OK;
276    }
277}
278