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