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.util.Log;
28import android.os.IBinder;
29import android.os.RemoteException;
30
31/**
32 * WAP push handler class.
33 *
34 * @hide
35 */
36public 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