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 static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_DELIVERY_IND;
20import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
21import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_READ_ORIG_IND;
22import android.app.Activity;
23import android.app.AppOpsManager;
24import android.app.BroadcastOptions;
25import android.content.BroadcastReceiver;
26import android.content.ComponentName;
27import android.content.ContentValues;
28import android.content.Context;
29import android.content.Intent;
30import android.content.IntentFilter;
31import android.content.ServiceConnection;
32import android.database.Cursor;
33import android.database.DatabaseUtils;
34import android.database.sqlite.SQLiteException;
35import android.database.sqlite.SqliteWrapper;
36import android.net.Uri;
37import android.os.Bundle;
38import android.os.IBinder;
39import android.os.IDeviceIdleController;
40import android.os.RemoteException;
41import android.os.ServiceManager;
42import android.os.UserHandle;
43import android.os.UserManager;
44import android.provider.Telephony;
45import android.provider.Telephony.Sms.Intents;
46import android.telephony.Rlog;
47import android.telephony.SmsManager;
48import android.telephony.SubscriptionManager;
49import android.util.Log;
50
51import com.android.internal.telephony.uicc.IccUtils;
52
53import java.util.HashMap;
54
55import com.google.android.mms.MmsException;
56import com.google.android.mms.pdu.DeliveryInd;
57import com.google.android.mms.pdu.GenericPdu;
58import com.google.android.mms.pdu.NotificationInd;
59import com.google.android.mms.pdu.PduHeaders;
60import com.google.android.mms.pdu.PduParser;
61import com.google.android.mms.pdu.PduPersister;
62import com.google.android.mms.pdu.ReadOrigInd;
63
64/**
65 * WAP push handler class.
66 *
67 * @hide
68 */
69public class WapPushOverSms implements ServiceConnection {
70    private static final String TAG = "WAP PUSH";
71    private static final boolean DBG = false;
72
73    private final Context mContext;
74    private IDeviceIdleController mDeviceIdleController;
75
76    private String mWapPushManagerPackage;
77
78    /** Assigned from ServiceConnection callback on main threaad. */
79    private volatile IWapPushManager mWapPushManager;
80
81    /** Broadcast receiver that binds to WapPushManager when the user unlocks the phone for the
82     *  first time after reboot and the credential-encrypted storage is available.
83     */
84    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
85        @Override
86        public void onReceive(final Context context, Intent intent) {
87            Rlog.d(TAG, "Received broadcast " + intent.getAction());
88            if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
89                new BindServiceThread(mContext).start();
90            }
91        }
92    };
93
94    private class BindServiceThread extends Thread {
95        private final Context context;
96
97        private BindServiceThread(Context context) {
98            this.context = context;
99        }
100
101        @Override
102        public void run() {
103            bindWapPushManagerService(context);
104        }
105    }
106
107    private void bindWapPushManagerService(Context context) {
108        Intent intent = new Intent(IWapPushManager.class.getName());
109        ComponentName comp = intent.resolveSystemService(context.getPackageManager(), 0);
110        intent.setComponent(comp);
111        if (comp == null || !context.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
112            Rlog.e(TAG, "bindService() for wappush manager failed");
113        } else {
114            synchronized (this) {
115                mWapPushManagerPackage = comp.getPackageName();
116            }
117            if (DBG) Rlog.v(TAG, "bindService() for wappush manager succeeded");
118        }
119    }
120
121    @Override
122    public void onServiceConnected(ComponentName name, IBinder service) {
123        mWapPushManager = IWapPushManager.Stub.asInterface(service);
124        if (DBG) Rlog.v(TAG, "wappush manager connected to " + hashCode());
125    }
126
127    @Override
128    public void onServiceDisconnected(ComponentName name) {
129        mWapPushManager = null;
130        if (DBG) Rlog.v(TAG, "wappush manager disconnected.");
131    }
132
133    public WapPushOverSms(Context context) {
134        mContext = context;
135        mDeviceIdleController = TelephonyComponentFactory.getInstance().getIDeviceIdleController();
136
137        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
138
139        if (userManager.isUserUnlocked()) {
140            bindWapPushManagerService(mContext);
141        } else {
142            IntentFilter userFilter = new IntentFilter();
143            userFilter.addAction(Intent.ACTION_USER_UNLOCKED);
144            context.registerReceiver(mBroadcastReceiver, userFilter);
145        }
146    }
147
148    public void dispose() {
149        if (mWapPushManager != null) {
150            if (DBG) Rlog.v(TAG, "dispose: unbind wappush manager");
151            mContext.unbindService(this);
152        } else {
153            Rlog.e(TAG, "dispose: not bound to a wappush manager");
154        }
155    }
156
157    /**
158     * Decodes the wap push pdu. The decoded result is wrapped inside the {@link DecodedResult}
159     * object. The caller of this method should check {@link DecodedResult#statusCode} for the
160     * decoding status. It  can have the following values.
161     *
162     * Activity.RESULT_OK - the wap push pdu is successfully decoded and should be further processed
163     * Intents.RESULT_SMS_HANDLED - the wap push pdu should be ignored.
164     * Intents.RESULT_SMS_GENERIC_ERROR - the pdu is invalid.
165     */
166    private DecodedResult decodeWapPdu(byte[] pdu, InboundSmsHandler handler) {
167        DecodedResult result = new DecodedResult();
168        if (DBG) Rlog.d(TAG, "Rx: " + IccUtils.bytesToHexString(pdu));
169
170        try {
171            int index = 0;
172            int transactionId = pdu[index++] & 0xFF;
173            int pduType = pdu[index++] & 0xFF;
174
175            // Should we "abort" if no subId for now just no supplying extra param below
176            int phoneId = handler.getPhone().getPhoneId();
177
178            if ((pduType != WspTypeDecoder.PDU_TYPE_PUSH) &&
179                    (pduType != WspTypeDecoder.PDU_TYPE_CONFIRMED_PUSH)) {
180                index = mContext.getResources().getInteger(
181                        com.android.internal.R.integer.config_valid_wappush_index);
182                if (index != -1) {
183                    transactionId = pdu[index++] & 0xff;
184                    pduType = pdu[index++] & 0xff;
185                    if (DBG)
186                        Rlog.d(TAG, "index = " + index + " PDU Type = " + pduType +
187                                " transactionID = " + transactionId);
188
189                    // recheck wap push pduType
190                    if ((pduType != WspTypeDecoder.PDU_TYPE_PUSH)
191                            && (pduType != WspTypeDecoder.PDU_TYPE_CONFIRMED_PUSH)) {
192                        if (DBG) Rlog.w(TAG, "Received non-PUSH WAP PDU. Type = " + pduType);
193                        result.statusCode = Intents.RESULT_SMS_HANDLED;
194                        return result;
195                    }
196                } else {
197                    if (DBG) Rlog.w(TAG, "Received non-PUSH WAP PDU. Type = " + pduType);
198                    result.statusCode = Intents.RESULT_SMS_HANDLED;
199                    return result;
200                }
201            }
202
203            WspTypeDecoder pduDecoder =
204                    TelephonyComponentFactory.getInstance().makeWspTypeDecoder(pdu);
205
206            /**
207             * Parse HeaderLen(unsigned integer).
208             * From wap-230-wsp-20010705-a section 8.1.2
209             * The maximum size of a uintvar is 32 bits.
210             * So it will be encoded in no more than 5 octets.
211             */
212            if (pduDecoder.decodeUintvarInteger(index) == false) {
213                if (DBG) Rlog.w(TAG, "Received PDU. Header Length error.");
214                result.statusCode = Intents.RESULT_SMS_GENERIC_ERROR;
215                return result;
216            }
217            int headerLength = (int) pduDecoder.getValue32();
218            index += pduDecoder.getDecodedDataLength();
219
220            int headerStartIndex = index;
221
222            /**
223             * Parse Content-Type.
224             * From wap-230-wsp-20010705-a section 8.4.2.24
225             *
226             * Content-type-value = Constrained-media | Content-general-form
227             * Content-general-form = Value-length Media-type
228             * Media-type = (Well-known-media | Extension-Media) *(Parameter)
229             * Value-length = Short-length | (Length-quote Length)
230             * Short-length = <Any octet 0-30>   (octet <= WAP_PDU_SHORT_LENGTH_MAX)
231             * Length-quote = <Octet 31>         (WAP_PDU_LENGTH_QUOTE)
232             * Length = Uintvar-integer
233             */
234            if (pduDecoder.decodeContentType(index) == false) {
235                if (DBG) Rlog.w(TAG, "Received PDU. Header Content-Type error.");
236                result.statusCode = Intents.RESULT_SMS_GENERIC_ERROR;
237                return result;
238            }
239
240            String mimeType = pduDecoder.getValueString();
241            long binaryContentType = pduDecoder.getValue32();
242            index += pduDecoder.getDecodedDataLength();
243
244            byte[] header = new byte[headerLength];
245            System.arraycopy(pdu, headerStartIndex, header, 0, header.length);
246
247            byte[] intentData;
248
249            if (mimeType != null && mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_PUSH_CO)) {
250                intentData = pdu;
251            } else {
252                int dataIndex = headerStartIndex + headerLength;
253                intentData = new byte[pdu.length - dataIndex];
254                System.arraycopy(pdu, dataIndex, intentData, 0, intentData.length);
255            }
256
257            int[] subIds = SubscriptionManager.getSubId(phoneId);
258            int subId = (subIds != null) && (subIds.length > 0) ? subIds[0]
259                    : SmsManager.getDefaultSmsSubscriptionId();
260
261            // Continue if PDU parsing fails: the default messaging app may successfully parse the
262            // same PDU.
263            GenericPdu parsedPdu = null;
264            try {
265                parsedPdu = new PduParser(intentData, shouldParseContentDisposition(subId)).parse();
266            } catch (Exception e) {
267                Rlog.e(TAG, "Unable to parse PDU: " + e.toString());
268            }
269
270            if (parsedPdu != null && parsedPdu.getMessageType() == MESSAGE_TYPE_NOTIFICATION_IND) {
271                final NotificationInd nInd = (NotificationInd) parsedPdu;
272                if (nInd.getFrom() != null
273                        && BlockChecker.isBlocked(mContext, nInd.getFrom().getString())) {
274                    result.statusCode = Intents.RESULT_SMS_HANDLED;
275                    return result;
276                }
277            }
278
279            /**
280             * Seek for application ID field in WSP header.
281             * If application ID is found, WapPushManager substitute the message
282             * processing. Since WapPushManager is optional module, if WapPushManager
283             * is not found, legacy message processing will be continued.
284             */
285            if (pduDecoder.seekXWapApplicationId(index, index + headerLength - 1)) {
286                index = (int) pduDecoder.getValue32();
287                pduDecoder.decodeXWapApplicationId(index);
288                String wapAppId = pduDecoder.getValueString();
289                if (wapAppId == null) {
290                    wapAppId = Integer.toString((int) pduDecoder.getValue32());
291                }
292                result.wapAppId = wapAppId;
293                String contentType = ((mimeType == null) ?
294                        Long.toString(binaryContentType) : mimeType);
295                result.contentType = contentType;
296                if (DBG) Rlog.v(TAG, "appid found: " + wapAppId + ":" + contentType);
297            }
298
299            result.subId = subId;
300            result.phoneId = phoneId;
301            result.parsedPdu = parsedPdu;
302            result.mimeType = mimeType;
303            result.transactionId = transactionId;
304            result.pduType = pduType;
305            result.header = header;
306            result.intentData = intentData;
307            result.contentTypeParameters = pduDecoder.getContentParameters();
308            result.statusCode = Activity.RESULT_OK;
309        } catch (ArrayIndexOutOfBoundsException aie) {
310            // 0-byte WAP PDU or other unexpected WAP PDU contents can easily throw this;
311            // log exception string without stack trace and return false.
312            Rlog.e(TAG, "ignoring dispatchWapPdu() array index exception: " + aie);
313            result.statusCode = Intents.RESULT_SMS_GENERIC_ERROR;
314        }
315        return result;
316    }
317
318    /**
319     * Dispatches inbound messages that are in the WAP PDU format. See
320     * wap-230-wsp-20010705-a section 8 for details on the WAP PDU format.
321     *
322     * @param pdu The WAP PDU, made up of one or more SMS PDUs
323     * @return a result code from {@link android.provider.Telephony.Sms.Intents}, or
324     *         {@link Activity#RESULT_OK} if the message has been broadcast
325     *         to applications
326     */
327    public int dispatchWapPdu(byte[] pdu, BroadcastReceiver receiver, InboundSmsHandler handler) {
328        DecodedResult result = decodeWapPdu(pdu, handler);
329        if (result.statusCode != Activity.RESULT_OK) {
330            return result.statusCode;
331        }
332
333        if (SmsManager.getDefault().getAutoPersisting()) {
334            // Store the wap push data in telephony
335            writeInboxMessage(result.subId, result.parsedPdu);
336        }
337
338        /**
339         * If the pdu has application ID, WapPushManager substitute the message
340         * processing. Since WapPushManager is optional module, if WapPushManager
341         * is not found, legacy message processing will be continued.
342         */
343        if (result.wapAppId != null) {
344            try {
345                boolean processFurther = true;
346                IWapPushManager wapPushMan = mWapPushManager;
347
348                if (wapPushMan == null) {
349                    if (DBG) Rlog.w(TAG, "wap push manager not found!");
350                } else {
351                    synchronized (this) {
352                        mDeviceIdleController.addPowerSaveTempWhitelistAppForMms(
353                                mWapPushManagerPackage, 0, "mms-mgr");
354                    }
355
356                    Intent intent = new Intent();
357                    intent.putExtra("transactionId", result.transactionId);
358                    intent.putExtra("pduType", result.pduType);
359                    intent.putExtra("header", result.header);
360                    intent.putExtra("data", result.intentData);
361                    intent.putExtra("contentTypeParameters", result.contentTypeParameters);
362                    SubscriptionManager.putPhoneIdAndSubIdExtra(intent, result.phoneId);
363
364                    int procRet = wapPushMan.processMessage(
365                        result.wapAppId, result.contentType, intent);
366                    if (DBG) Rlog.v(TAG, "procRet:" + procRet);
367                    if ((procRet & WapPushManagerParams.MESSAGE_HANDLED) > 0
368                            && (procRet & WapPushManagerParams.FURTHER_PROCESSING) == 0) {
369                        processFurther = false;
370                    }
371                }
372                if (!processFurther) {
373                    return Intents.RESULT_SMS_HANDLED;
374                }
375            } catch (RemoteException e) {
376                if (DBG) Rlog.w(TAG, "remote func failed...");
377            }
378        }
379        if (DBG) Rlog.v(TAG, "fall back to existing handler");
380
381        if (result.mimeType == null) {
382            if (DBG) Rlog.w(TAG, "Header Content-Type error.");
383            return Intents.RESULT_SMS_GENERIC_ERROR;
384        }
385
386        Intent intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION);
387        intent.setType(result.mimeType);
388        intent.putExtra("transactionId", result.transactionId);
389        intent.putExtra("pduType", result.pduType);
390        intent.putExtra("header", result.header);
391        intent.putExtra("data", result.intentData);
392        intent.putExtra("contentTypeParameters", result.contentTypeParameters);
393        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, result.phoneId);
394
395        // Direct the intent to only the default MMS app. If we can't find a default MMS app
396        // then sent it to all broadcast receivers.
397        ComponentName componentName = SmsApplication.getDefaultMmsApplication(mContext, true);
398        Bundle options = null;
399        if (componentName != null) {
400            // Deliver MMS message only to this receiver
401            intent.setComponent(componentName);
402            if (DBG) Rlog.v(TAG, "Delivering MMS to: " + componentName.getPackageName() +
403                    " " + componentName.getClassName());
404            try {
405                long duration = mDeviceIdleController.addPowerSaveTempWhitelistAppForMms(
406                        componentName.getPackageName(), 0, "mms-app");
407                BroadcastOptions bopts = BroadcastOptions.makeBasic();
408                bopts.setTemporaryAppWhitelistDuration(duration);
409                options = bopts.toBundle();
410            } catch (RemoteException e) {
411            }
412        }
413
414        handler.dispatchIntent(intent, getPermissionForType(result.mimeType),
415                getAppOpsPermissionForIntent(result.mimeType), options, receiver,
416                UserHandle.SYSTEM);
417        return Activity.RESULT_OK;
418    }
419
420    /**
421     * Check whether the pdu is a MMS WAP push pdu that should be dispatched to the SMS app.
422     */
423    public boolean isWapPushForMms(byte[] pdu, InboundSmsHandler handler) {
424        DecodedResult result = decodeWapPdu(pdu, handler);
425        return result.statusCode == Activity.RESULT_OK
426            && WspTypeDecoder.CONTENT_TYPE_B_MMS.equals(result.mimeType);
427    }
428
429    private static boolean shouldParseContentDisposition(int subId) {
430        return SmsManager
431                .getSmsManagerForSubscriptionId(subId)
432                .getCarrierConfigValues()
433                .getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION, true);
434    }
435
436    private void writeInboxMessage(int subId, GenericPdu pdu) {
437        if (pdu == null) {
438            Rlog.e(TAG, "Invalid PUSH PDU");
439        }
440        final PduPersister persister = PduPersister.getPduPersister(mContext);
441        final int type = pdu.getMessageType();
442        try {
443            switch (type) {
444                case MESSAGE_TYPE_DELIVERY_IND:
445                case MESSAGE_TYPE_READ_ORIG_IND: {
446                    final long threadId = getDeliveryOrReadReportThreadId(mContext, pdu);
447                    if (threadId == -1) {
448                        // The associated SendReq isn't found, therefore skip
449                        // processing this PDU.
450                        Rlog.e(TAG, "Failed to find delivery or read report's thread id");
451                        break;
452                    }
453                    final Uri uri = persister.persist(
454                            pdu,
455                            Telephony.Mms.Inbox.CONTENT_URI,
456                            true/*createThreadId*/,
457                            true/*groupMmsEnabled*/,
458                            null/*preOpenedFiles*/);
459                    if (uri == null) {
460                        Rlog.e(TAG, "Failed to persist delivery or read report");
461                        break;
462                    }
463                    // Update thread ID for ReadOrigInd & DeliveryInd.
464                    final ContentValues values = new ContentValues(1);
465                    values.put(Telephony.Mms.THREAD_ID, threadId);
466                    if (SqliteWrapper.update(
467                            mContext,
468                            mContext.getContentResolver(),
469                            uri,
470                            values,
471                            null/*where*/,
472                            null/*selectionArgs*/) != 1) {
473                        Rlog.e(TAG, "Failed to update delivery or read report thread id");
474                    }
475                    break;
476                }
477                case MESSAGE_TYPE_NOTIFICATION_IND: {
478                    final NotificationInd nInd = (NotificationInd) pdu;
479
480                    Bundle configs = SmsManager.getSmsManagerForSubscriptionId(subId)
481                            .getCarrierConfigValues();
482                    if (configs != null && configs.getBoolean(
483                        SmsManager.MMS_CONFIG_APPEND_TRANSACTION_ID, false)) {
484                        final byte [] contentLocation = nInd.getContentLocation();
485                        if ('=' == contentLocation[contentLocation.length - 1]) {
486                            byte [] transactionId = nInd.getTransactionId();
487                            byte [] contentLocationWithId = new byte [contentLocation.length
488                                    + transactionId.length];
489                            System.arraycopy(contentLocation, 0, contentLocationWithId,
490                                    0, contentLocation.length);
491                            System.arraycopy(transactionId, 0, contentLocationWithId,
492                                    contentLocation.length, transactionId.length);
493                            nInd.setContentLocation(contentLocationWithId);
494                        }
495                    }
496                    if (!isDuplicateNotification(mContext, nInd)) {
497                        final Uri uri = persister.persist(
498                                pdu,
499                                Telephony.Mms.Inbox.CONTENT_URI,
500                                true/*createThreadId*/,
501                                true/*groupMmsEnabled*/,
502                                null/*preOpenedFiles*/);
503                        if (uri == null) {
504                            Rlog.e(TAG, "Failed to save MMS WAP push notification ind");
505                        }
506                    } else {
507                        Rlog.d(TAG, "Skip storing duplicate MMS WAP push notification ind: "
508                                + new String(nInd.getContentLocation()));
509                    }
510                    break;
511                }
512                default:
513                    Log.e(TAG, "Received unrecognized WAP Push PDU.");
514            }
515        } catch (MmsException e) {
516            Log.e(TAG, "Failed to save MMS WAP push data: type=" + type, e);
517        } catch (RuntimeException e) {
518            Log.e(TAG, "Unexpected RuntimeException in persisting MMS WAP push data", e);
519        }
520
521    }
522
523    private static final String THREAD_ID_SELECTION =
524            Telephony.Mms.MESSAGE_ID + "=? AND " + Telephony.Mms.MESSAGE_TYPE + "=?";
525
526    private static long getDeliveryOrReadReportThreadId(Context context, GenericPdu pdu) {
527        String messageId;
528        if (pdu instanceof DeliveryInd) {
529            messageId = new String(((DeliveryInd) pdu).getMessageId());
530        } else if (pdu instanceof ReadOrigInd) {
531            messageId = new String(((ReadOrigInd) pdu).getMessageId());
532        } else {
533            Rlog.e(TAG, "WAP Push data is neither delivery or read report type: "
534                    + pdu.getClass().getCanonicalName());
535            return -1L;
536        }
537        Cursor cursor = null;
538        try {
539            cursor = SqliteWrapper.query(
540                    context,
541                    context.getContentResolver(),
542                    Telephony.Mms.CONTENT_URI,
543                    new String[]{ Telephony.Mms.THREAD_ID },
544                    THREAD_ID_SELECTION,
545                    new String[]{
546                            DatabaseUtils.sqlEscapeString(messageId),
547                            Integer.toString(PduHeaders.MESSAGE_TYPE_SEND_REQ)
548                    },
549                    null/*sortOrder*/);
550            if (cursor != null && cursor.moveToFirst()) {
551                return cursor.getLong(0);
552            }
553        } catch (SQLiteException e) {
554            Rlog.e(TAG, "Failed to query delivery or read report thread id", e);
555        } finally {
556            if (cursor != null) {
557                cursor.close();
558            }
559        }
560        return -1L;
561    }
562
563    private static final String LOCATION_SELECTION =
564            Telephony.Mms.MESSAGE_TYPE + "=? AND " + Telephony.Mms.CONTENT_LOCATION + " =?";
565
566    private static boolean isDuplicateNotification(Context context, NotificationInd nInd) {
567        final byte[] rawLocation = nInd.getContentLocation();
568        if (rawLocation != null) {
569            String location = new String(rawLocation);
570            String[] selectionArgs = new String[] { location };
571            Cursor cursor = null;
572            try {
573                cursor = SqliteWrapper.query(
574                        context,
575                        context.getContentResolver(),
576                        Telephony.Mms.CONTENT_URI,
577                        new String[]{Telephony.Mms._ID},
578                        LOCATION_SELECTION,
579                        new String[]{
580                                Integer.toString(PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND),
581                                new String(rawLocation)
582                        },
583                        null/*sortOrder*/);
584                if (cursor != null && cursor.getCount() > 0) {
585                    // We already received the same notification before.
586                    return true;
587                }
588            } catch (SQLiteException e) {
589                Rlog.e(TAG, "failed to query existing notification ind", e);
590            } finally {
591                if (cursor != null) {
592                    cursor.close();
593                }
594            }
595        }
596        return false;
597    }
598
599    public static String getPermissionForType(String mimeType) {
600        String permission;
601        if (WspTypeDecoder.CONTENT_TYPE_B_MMS.equals(mimeType)) {
602            permission = android.Manifest.permission.RECEIVE_MMS;
603        } else {
604            permission = android.Manifest.permission.RECEIVE_WAP_PUSH;
605        }
606        return permission;
607    }
608
609    public static int getAppOpsPermissionForIntent(String mimeType) {
610        int appOp;
611        if (WspTypeDecoder.CONTENT_TYPE_B_MMS.equals(mimeType)) {
612            appOp = AppOpsManager.OP_RECEIVE_MMS;
613        } else {
614            appOp = AppOpsManager.OP_RECEIVE_WAP_PUSH;
615        }
616        return appOp;
617    }
618
619    /**
620     * Place holder for decoded Wap pdu data.
621     */
622    private final class DecodedResult {
623        String mimeType;
624        String contentType;
625        int transactionId;
626        int pduType;
627        int phoneId;
628        int subId;
629        byte[] header;
630        String wapAppId;
631        byte[] intentData;
632        HashMap<String, String> contentTypeParameters;
633        GenericPdu parsedPdu;
634        int statusCode;
635    }
636}
637