SMSDispatcher.java revision ec125af4306f1be2c829c3a1d7adbf9b8cd7e659
1/*
2 * Copyright (C) 2006 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.AlertDialog;
21import android.app.PendingIntent;
22import android.app.PendingIntent.CanceledException;
23import android.content.ContentResolver;
24import android.content.Context;
25import android.content.DialogInterface;
26import android.content.Intent;
27import android.content.pm.ApplicationInfo;
28import android.content.pm.PackageInfo;
29import android.content.pm.PackageManager;
30import android.content.res.Resources;
31import android.database.ContentObserver;
32import android.os.AsyncResult;
33import android.os.Binder;
34import android.os.Handler;
35import android.os.Message;
36import android.os.SystemProperties;
37import android.provider.Settings;
38import android.telephony.PhoneNumberUtils;
39import android.telephony.Rlog;
40import android.telephony.ServiceState;
41import android.telephony.TelephonyManager;
42import android.text.Html;
43import android.text.Spanned;
44import android.util.EventLog;
45import android.view.LayoutInflater;
46import android.view.View;
47import android.view.ViewGroup;
48import android.view.WindowManager;
49import android.widget.Button;
50import android.widget.CheckBox;
51import android.widget.CompoundButton;
52import android.widget.TextView;
53
54import com.android.internal.R;
55import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
56
57import java.util.ArrayList;
58import java.util.HashMap;
59import java.util.Random;
60import java.util.concurrent.atomic.AtomicInteger;
61
62import static android.telephony.SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE;
63import static android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE;
64import static android.telephony.SmsManager.RESULT_ERROR_LIMIT_EXCEEDED;
65import static android.telephony.SmsManager.RESULT_ERROR_NO_SERVICE;
66import static android.telephony.SmsManager.RESULT_ERROR_NULL_PDU;
67import static android.telephony.SmsManager.RESULT_ERROR_RADIO_OFF;
68
69public abstract class SMSDispatcher extends Handler {
70    static final String TAG = "SMSDispatcher";    // accessed from inner class
71    static final boolean DBG = false;
72    private static final String SEND_NEXT_MSG_EXTRA = "SendNextMsg";
73
74    /** Permission required to send SMS to short codes without user confirmation. */
75    private static final String SEND_SMS_NO_CONFIRMATION_PERMISSION =
76            "android.permission.SEND_SMS_NO_CONFIRMATION";
77
78    private static final int PREMIUM_RULE_USE_SIM = 1;
79    private static final int PREMIUM_RULE_USE_NETWORK = 2;
80    private static final int PREMIUM_RULE_USE_BOTH = 3;
81    private final AtomicInteger mPremiumSmsRule = new AtomicInteger(PREMIUM_RULE_USE_SIM);
82    private final SettingsObserver mSettingsObserver;
83
84    /** SMS send complete. */
85    protected static final int EVENT_SEND_SMS_COMPLETE = 2;
86
87    /** Retry sending a previously failed SMS message */
88    private static final int EVENT_SEND_RETRY = 3;
89
90    /** Confirmation required for sending a large number of messages. */
91    private static final int EVENT_SEND_LIMIT_REACHED_CONFIRMATION = 4;
92
93    /** Send the user confirmed SMS */
94    static final int EVENT_SEND_CONFIRMED_SMS = 5;  // accessed from inner class
95
96    /** Don't send SMS (user did not confirm). */
97    static final int EVENT_STOP_SENDING = 7;        // accessed from inner class
98
99    /** Confirmation required for third-party apps sending to an SMS short code. */
100    private static final int EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE = 8;
101
102    /** Confirmation required for third-party apps sending to an SMS short code. */
103    private static final int EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE = 9;
104
105    /** Handle status report from {@code CdmaInboundSmsHandler}. */
106    protected static final int EVENT_HANDLE_STATUS_REPORT = 10;
107
108    protected final Phone mPhone;
109    protected final Context mContext;
110    protected final ContentResolver mResolver;
111    protected final CommandsInterface mCi;
112    protected final TelephonyManager mTelephonyManager;
113
114    /** Maximum number of times to retry sending a failed SMS. */
115    private static final int MAX_SEND_RETRIES = 3;
116    /** Delay before next send attempt on a failed SMS, in milliseconds. */
117    private static final int SEND_RETRY_DELAY = 2000;
118    /** single part SMS */
119    private static final int SINGLE_PART_SMS = 1;
120    /** Message sending queue limit */
121    private static final int MO_MSG_QUEUE_LIMIT = 5;
122
123    /**
124     * Message reference for a CONCATENATED_8_BIT_REFERENCE or
125     * CONCATENATED_16_BIT_REFERENCE message set.  Should be
126     * incremented for each set of concatenated messages.
127     * Static field shared by all dispatcher objects.
128     */
129    private static int sConcatenatedRef = new Random().nextInt(256);
130
131    /** Outgoing message counter. Shared by all dispatchers. */
132    private final SmsUsageMonitor mUsageMonitor;
133
134    /** Number of outgoing SmsTrackers waiting for user confirmation. */
135    private int mPendingTrackerCount;
136
137    /* Flags indicating whether the current device allows sms service */
138    protected boolean mSmsCapable = true;
139    protected boolean mSmsSendDisabled;
140
141    protected int mRemainingMessages = -1;
142
143    protected static int getNextConcatenatedRef() {
144        sConcatenatedRef += 1;
145        return sConcatenatedRef;
146    }
147
148    /**
149     * Create a new SMS dispatcher.
150     * @param phone the Phone to use
151     * @param usageMonitor the SmsUsageMonitor to use
152     */
153    protected SMSDispatcher(PhoneBase phone, SmsUsageMonitor usageMonitor) {
154        mPhone = phone;
155        mContext = phone.getContext();
156        mResolver = mContext.getContentResolver();
157        mCi = phone.mCi;
158        mUsageMonitor = usageMonitor;
159        mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
160        mSettingsObserver = new SettingsObserver(this, mPremiumSmsRule, mContext);
161        mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
162                Settings.Global.SMS_SHORT_CODE_RULE), false, mSettingsObserver);
163
164        mSmsCapable = mContext.getResources().getBoolean(
165                com.android.internal.R.bool.config_sms_capable);
166        mSmsSendDisabled = !SystemProperties.getBoolean(
167                                TelephonyProperties.PROPERTY_SMS_SEND, mSmsCapable);
168        Rlog.d(TAG, "SMSDispatcher: ctor mSmsCapable=" + mSmsCapable + " format=" + getFormat()
169                + " mSmsSendDisabled=" + mSmsSendDisabled);
170    }
171
172    /**
173     * Observe the secure setting for updated premium sms determination rules
174     */
175    private static class SettingsObserver extends ContentObserver {
176        private final AtomicInteger mPremiumSmsRule;
177        private final Context mContext;
178        SettingsObserver(Handler handler, AtomicInteger premiumSmsRule, Context context) {
179            super(handler);
180            mPremiumSmsRule = premiumSmsRule;
181            mContext = context;
182            onChange(false); // load initial value;
183        }
184
185        @Override
186        public void onChange(boolean selfChange) {
187            mPremiumSmsRule.set(Settings.Global.getInt(mContext.getContentResolver(),
188                    Settings.Global.SMS_SHORT_CODE_RULE, PREMIUM_RULE_USE_SIM));
189        }
190    }
191
192    /** Unregister for incoming SMS events. */
193    public void dispose() {
194        mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
195    }
196
197    /**
198     * The format of the message PDU in the associated broadcast intent.
199     * This will be either "3gpp" for GSM/UMTS/LTE messages in 3GPP format
200     * or "3gpp2" for CDMA/LTE messages in 3GPP2 format.
201     *
202     * Note: All applications which handle incoming SMS messages by processing the
203     * SMS_RECEIVED_ACTION broadcast intent MUST pass the "format" extra from the intent
204     * into the new methods in {@link android.telephony.SmsMessage} which take an
205     * extra format parameter. This is required in order to correctly decode the PDU on
206     * devices which require support for both 3GPP and 3GPP2 formats at the same time,
207     * such as CDMA/LTE devices and GSM/CDMA world phones.
208     *
209     * @return the format of the message PDU
210     */
211    protected abstract String getFormat();
212
213    /**
214     * Pass the Message object to subclass to handle. Currently used to pass CDMA status reports
215     * from {@link com.android.internal.telephony.cdma.CdmaInboundSmsHandler}.
216     * @param o the SmsMessage containing the status report
217     */
218    protected void handleStatusReport(Object o) {
219        Rlog.d(TAG, "handleStatusReport() called with no subclass.");
220    }
221
222    /* TODO: Need to figure out how to keep track of status report routing in a
223     *       persistent manner. If the phone process restarts (reboot or crash),
224     *       we will lose this list and any status reports that come in after
225     *       will be dropped.
226     */
227    /** Sent messages awaiting a delivery status report. */
228    protected final ArrayList<SmsTracker> deliveryPendingList = new ArrayList<SmsTracker>();
229
230    /**
231     * Handles events coming from the phone stack. Overridden from handler.
232     *
233     * @param msg the message to handle
234     */
235    @Override
236    public void handleMessage(Message msg) {
237        switch (msg.what) {
238        case EVENT_SEND_SMS_COMPLETE:
239            // An outbound SMS has been successfully transferred, or failed.
240            handleSendComplete((AsyncResult) msg.obj);
241            break;
242
243        case EVENT_SEND_RETRY:
244            sendSms((SmsTracker) msg.obj);
245            break;
246
247        case EVENT_SEND_LIMIT_REACHED_CONFIRMATION:
248            handleReachSentLimit((SmsTracker)(msg.obj));
249            break;
250
251        case EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE:
252            handleConfirmShortCode(false, (SmsTracker)(msg.obj));
253            break;
254
255        case EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE:
256            handleConfirmShortCode(true, (SmsTracker)(msg.obj));
257            break;
258
259        case EVENT_SEND_CONFIRMED_SMS:
260        {
261            SmsTracker tracker = (SmsTracker) msg.obj;
262            if (tracker.isMultipart()) {
263                sendMultipartSms(tracker);
264            } else {
265                sendSms(tracker);
266            }
267            mPendingTrackerCount--;
268            break;
269        }
270
271        case EVENT_STOP_SENDING:
272        {
273            SmsTracker tracker = (SmsTracker) msg.obj;
274            if (tracker.mSentIntent != null) {
275                try {
276                    tracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
277                } catch (CanceledException ex) {
278                    Rlog.e(TAG, "failed to send RESULT_ERROR_LIMIT_EXCEEDED");
279                }
280            }
281            mPendingTrackerCount--;
282            break;
283        }
284
285        case EVENT_HANDLE_STATUS_REPORT:
286            handleStatusReport(msg.obj);
287            break;
288
289        default:
290            Rlog.e(TAG, "handleMessage() ignoring message of unexpected type " + msg.what);
291        }
292    }
293
294    /**
295     * Called when SMS send completes. Broadcasts a sentIntent on success.
296     * On failure, either sets up retries or broadcasts a sentIntent with
297     * the failure in the result code.
298     *
299     * @param ar AsyncResult passed into the message handler.  ar.result should
300     *           an SmsResponse instance if send was successful.  ar.userObj
301     *           should be an SmsTracker instance.
302     */
303    protected void handleSendComplete(AsyncResult ar) {
304        SmsTracker tracker = (SmsTracker) ar.userObj;
305        PendingIntent sentIntent = tracker.mSentIntent;
306
307        if (ar.result != null) {
308            tracker.mMessageRef = ((SmsResponse)ar.result).mMessageRef;
309        } else {
310            Rlog.d(TAG, "SmsResponse was null");
311        }
312
313        if (ar.exception == null) {
314            if (DBG) Rlog.d(TAG, "SMS send complete. Broadcasting intent: " + sentIntent);
315
316            if (tracker.mDeliveryIntent != null) {
317                // Expecting a status report.  Add it to the list.
318                deliveryPendingList.add(tracker);
319            }
320
321            if (sentIntent != null) {
322                try {
323                    if (mRemainingMessages > -1) {
324                        mRemainingMessages--;
325                    }
326
327                    if (mRemainingMessages == 0) {
328                        Intent sendNext = new Intent();
329                        sendNext.putExtra(SEND_NEXT_MSG_EXTRA, true);
330                        sentIntent.send(mContext, Activity.RESULT_OK, sendNext);
331                    } else {
332                        sentIntent.send(Activity.RESULT_OK);
333                    }
334                } catch (CanceledException ex) {}
335            }
336        } else {
337            if (DBG) Rlog.d(TAG, "SMS send failed");
338
339            int ss = mPhone.getServiceState().getState();
340
341            if (ss != ServiceState.STATE_IN_SERVICE) {
342                handleNotInService(ss, tracker.mSentIntent);
343            } else if ((((CommandException)(ar.exception)).getCommandError()
344                    == CommandException.Error.SMS_FAIL_RETRY) &&
345                   tracker.mRetryCount < MAX_SEND_RETRIES) {
346                // Retry after a delay if needed.
347                // TODO: According to TS 23.040, 9.2.3.6, we should resend
348                //       with the same TP-MR as the failed message, and
349                //       TP-RD set to 1.  However, we don't have a means of
350                //       knowing the MR for the failed message (EF_SMSstatus
351                //       may or may not have the MR corresponding to this
352                //       message, depending on the failure).  Also, in some
353                //       implementations this retry is handled by the baseband.
354                tracker.mRetryCount++;
355                Message retryMsg = obtainMessage(EVENT_SEND_RETRY, tracker);
356                sendMessageDelayed(retryMsg, SEND_RETRY_DELAY);
357            } else if (tracker.mSentIntent != null) {
358                int error = RESULT_ERROR_GENERIC_FAILURE;
359
360                if (((CommandException)(ar.exception)).getCommandError()
361                        == CommandException.Error.FDN_CHECK_FAILURE) {
362                    error = RESULT_ERROR_FDN_CHECK_FAILURE;
363                }
364                // Done retrying; return an error to the app.
365                try {
366                    Intent fillIn = new Intent();
367                    if (ar.result != null) {
368                        fillIn.putExtra("errorCode", ((SmsResponse)ar.result).mErrorCode);
369                    }
370                    if (mRemainingMessages > -1) {
371                        mRemainingMessages--;
372                    }
373
374                    if (mRemainingMessages == 0) {
375                        fillIn.putExtra(SEND_NEXT_MSG_EXTRA, true);
376                    }
377
378                    tracker.mSentIntent.send(mContext, error, fillIn);
379                } catch (CanceledException ex) {}
380            }
381        }
382    }
383
384    /**
385     * Handles outbound message when the phone is not in service.
386     *
387     * @param ss     Current service state.  Valid values are:
388     *                  OUT_OF_SERVICE
389     *                  EMERGENCY_ONLY
390     *                  POWER_OFF
391     * @param sentIntent the PendingIntent to send the error to
392     */
393    protected static void handleNotInService(int ss, PendingIntent sentIntent) {
394        if (sentIntent != null) {
395            try {
396                if (ss == ServiceState.STATE_POWER_OFF) {
397                    sentIntent.send(RESULT_ERROR_RADIO_OFF);
398                } else {
399                    sentIntent.send(RESULT_ERROR_NO_SERVICE);
400                }
401            } catch (CanceledException ex) {}
402        }
403    }
404
405    /**
406     * Send a data based SMS to a specific application port.
407     *
408     * @param destAddr the address to send the message to
409     * @param scAddr is the service center address or null to use
410     *  the current default SMSC
411     * @param destPort the port to deliver the message to
412     * @param data the body of the message to send
413     * @param sentIntent if not NULL this <code>PendingIntent</code> is
414     *  broadcast when the message is successfully sent, or failed.
415     *  The result code will be <code>Activity.RESULT_OK<code> for success,
416     *  or one of these errors:<br>
417     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
418     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
419     *  <code>RESULT_ERROR_NULL_PDU</code><br>
420     *  <code>RESULT_ERROR_NO_SERVICE</code><br>.
421     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
422     *  the extra "errorCode" containing a radio technology specific value,
423     *  generally only useful for troubleshooting.<br>
424     *  The per-application based SMS control checks sentIntent. If sentIntent
425     *  is NULL the caller will be checked against all unknown applications,
426     *  which cause smaller number of SMS to be sent in checking period.
427     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
428     *  broadcast when the message is delivered to the recipient.  The
429     *  raw pdu of the status report is in the extended data ("pdu").
430     */
431    protected abstract void sendData(String destAddr, String scAddr, int destPort,
432            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent);
433
434    /**
435     * Send a text based SMS.
436     *
437     * @param destAddr the address to send the message to
438     * @param scAddr is the service center address or null to use
439     *  the current default SMSC
440     * @param text the body of the message to send
441     * @param sentIntent if not NULL this <code>PendingIntent</code> is
442     *  broadcast when the message is successfully sent, or failed.
443     *  The result code will be <code>Activity.RESULT_OK<code> for success,
444     *  or one of these errors:<br>
445     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
446     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
447     *  <code>RESULT_ERROR_NULL_PDU</code><br>
448     *  <code>RESULT_ERROR_NO_SERVICE</code><br>.
449     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
450     *  the extra "errorCode" containing a radio technology specific value,
451     *  generally only useful for troubleshooting.<br>
452     *  The per-application based SMS control checks sentIntent. If sentIntent
453     *  is NULL the caller will be checked against all unknown applications,
454     *  which cause smaller number of SMS to be sent in checking period.
455     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
456     *  broadcast when the message is delivered to the recipient.  The
457     *  raw pdu of the status report is in the extended data ("pdu").
458     */
459    protected abstract void sendText(String destAddr, String scAddr,
460            String text, PendingIntent sentIntent, PendingIntent deliveryIntent);
461
462    /**
463     * Calculate the number of septets needed to encode the message.
464     *
465     * @param messageBody the message to encode
466     * @param use7bitOnly ignore (but still count) illegal characters if true
467     * @return TextEncodingDetails
468     */
469    protected abstract TextEncodingDetails calculateLength(CharSequence messageBody,
470            boolean use7bitOnly);
471
472    /**
473     * Send a multi-part text based SMS.
474     *
475     * @param destAddr the address to send the message to
476     * @param scAddr is the service center address or null to use
477     *   the current default SMSC
478     * @param parts an <code>ArrayList</code> of strings that, in order,
479     *   comprise the original message
480     * @param sentIntents if not null, an <code>ArrayList</code> of
481     *   <code>PendingIntent</code>s (one for each message part) that is
482     *   broadcast when the corresponding message part has been sent.
483     *   The result code will be <code>Activity.RESULT_OK<code> for success,
484     *   or one of these errors:
485     *   <code>RESULT_ERROR_GENERIC_FAILURE</code>
486     *   <code>RESULT_ERROR_RADIO_OFF</code>
487     *   <code>RESULT_ERROR_NULL_PDU</code>
488     *   <code>RESULT_ERROR_NO_SERVICE</code>.
489     *  The per-application based SMS control checks sentIntent. If sentIntent
490     *  is NULL the caller will be checked against all unknown applications,
491     *  which cause smaller number of SMS to be sent in checking period.
492     * @param deliveryIntents if not null, an <code>ArrayList</code> of
493     *   <code>PendingIntent</code>s (one for each message part) that is
494     *   broadcast when the corresponding message part has been delivered
495     *   to the recipient.  The raw pdu of the status report is in the
496     *   extended data ("pdu").
497     */
498    protected void sendMultipartText(String destAddr, String scAddr,
499            ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
500            ArrayList<PendingIntent> deliveryIntents) {
501
502        int refNumber = getNextConcatenatedRef() & 0x00FF;
503        int msgCount = parts.size();
504        int encoding = SmsConstants.ENCODING_UNKNOWN;
505
506        mRemainingMessages = msgCount;
507
508        TextEncodingDetails[] encodingForParts = new TextEncodingDetails[msgCount];
509        for (int i = 0; i < msgCount; i++) {
510            TextEncodingDetails details = calculateLength(parts.get(i), false);
511            if (encoding != details.codeUnitSize
512                    && (encoding == SmsConstants.ENCODING_UNKNOWN
513                            || encoding == SmsConstants.ENCODING_7BIT)) {
514                encoding = details.codeUnitSize;
515            }
516            encodingForParts[i] = details;
517        }
518
519        for (int i = 0; i < msgCount; i++) {
520            SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
521            concatRef.refNumber = refNumber;
522            concatRef.seqNumber = i + 1;  // 1-based sequence
523            concatRef.msgCount = msgCount;
524            // TODO: We currently set this to true since our messaging app will never
525            // send more than 255 parts (it converts the message to MMS well before that).
526            // However, we should support 3rd party messaging apps that might need 16-bit
527            // references
528            // Note:  It's not sufficient to just flip this bit to true; it will have
529            // ripple effects (several calculations assume 8-bit ref).
530            concatRef.isEightBits = true;
531            SmsHeader smsHeader = new SmsHeader();
532            smsHeader.concatRef = concatRef;
533
534            // Set the national language tables for 3GPP 7-bit encoding, if enabled.
535            if (encoding == SmsConstants.ENCODING_7BIT) {
536                smsHeader.languageTable = encodingForParts[i].languageTable;
537                smsHeader.languageShiftTable = encodingForParts[i].languageShiftTable;
538            }
539
540            PendingIntent sentIntent = null;
541            if (sentIntents != null && sentIntents.size() > i) {
542                sentIntent = sentIntents.get(i);
543            }
544
545            PendingIntent deliveryIntent = null;
546            if (deliveryIntents != null && deliveryIntents.size() > i) {
547                deliveryIntent = deliveryIntents.get(i);
548            }
549
550            sendNewSubmitPdu(destAddr, scAddr, parts.get(i), smsHeader, encoding,
551                    sentIntent, deliveryIntent, (i == (msgCount - 1)));
552        }
553
554    }
555
556    /**
557     * Create a new SubmitPdu and send it.
558     */
559    protected abstract void sendNewSubmitPdu(String destinationAddress, String scAddress,
560            String message, SmsHeader smsHeader, int encoding,
561            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart);
562
563    /**
564     * Send a SMS
565     *
566     * @param smsc the SMSC to send the message through, or NULL for the
567     *  default SMSC
568     * @param pdu the raw PDU to send
569     * @param sentIntent if not NULL this <code>Intent</code> is
570     *  broadcast when the message is successfully sent, or failed.
571     *  The result code will be <code>Activity.RESULT_OK<code> for success,
572     *  or one of these errors:
573     *  <code>RESULT_ERROR_GENERIC_FAILURE</code>
574     *  <code>RESULT_ERROR_RADIO_OFF</code>
575     *  <code>RESULT_ERROR_NULL_PDU</code>
576     *  <code>RESULT_ERROR_NO_SERVICE</code>.
577     *  The per-application based SMS control checks sentIntent. If sentIntent
578     *  is NULL the caller will be checked against all unknown applications,
579     *  which cause smaller number of SMS to be sent in checking period.
580     * @param deliveryIntent if not NULL this <code>Intent</code> is
581     *  broadcast when the message is delivered to the recipient.  The
582     *  raw pdu of the status report is in the extended data ("pdu").
583     * @param destAddr the destination phone number (for short code confirmation)
584     */
585    protected void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent,
586            PendingIntent deliveryIntent, String destAddr) {
587        if (mSmsSendDisabled) {
588            if (sentIntent != null) {
589                try {
590                    sentIntent.send(RESULT_ERROR_NO_SERVICE);
591                } catch (CanceledException ex) {}
592            }
593            Rlog.d(TAG, "Device does not support sending sms.");
594            return;
595        }
596
597        if (pdu == null) {
598            if (sentIntent != null) {
599                try {
600                    sentIntent.send(RESULT_ERROR_NULL_PDU);
601                } catch (CanceledException ex) {}
602            }
603            return;
604        }
605
606        HashMap<String, Object> map = new HashMap<String, Object>();
607        map.put("smsc", smsc);
608        map.put("pdu", pdu);
609
610        // Get calling app package name via UID from Binder call
611        PackageManager pm = mContext.getPackageManager();
612        String[] packageNames = pm.getPackagesForUid(Binder.getCallingUid());
613
614        if (packageNames == null || packageNames.length == 0) {
615            // Refuse to send SMS if we can't get the calling package name.
616            Rlog.e(TAG, "Can't get calling app package name: refusing to send SMS");
617            if (sentIntent != null) {
618                try {
619                    sentIntent.send(RESULT_ERROR_GENERIC_FAILURE);
620                } catch (CanceledException ex) {
621                    Rlog.e(TAG, "failed to send error result");
622                }
623            }
624            return;
625        }
626
627        // Get package info via packagemanager
628        PackageInfo appInfo;
629        try {
630            // XXX this is lossy- apps can share a UID
631            appInfo = pm.getPackageInfo(packageNames[0], PackageManager.GET_SIGNATURES);
632        } catch (PackageManager.NameNotFoundException e) {
633            Rlog.e(TAG, "Can't get calling app package info: refusing to send SMS");
634            if (sentIntent != null) {
635                try {
636                    sentIntent.send(RESULT_ERROR_GENERIC_FAILURE);
637                } catch (CanceledException ex) {
638                    Rlog.e(TAG, "failed to send error result");
639                }
640            }
641            return;
642        }
643
644        // Strip non-digits from destination phone number before checking for short codes
645        // and before displaying the number to the user if confirmation is required.
646        SmsTracker tracker = new SmsTracker(map, sentIntent, deliveryIntent, appInfo,
647                PhoneNumberUtils.extractNetworkPortion(destAddr));
648
649        // checkDestination() returns true if the destination is not a premium short code or the
650        // sending app is approved to send to short codes. Otherwise, a message is sent to our
651        // handler with the SmsTracker to request user confirmation before sending.
652        if (checkDestination(tracker)) {
653            // check for excessive outgoing SMS usage by this app
654            if (!mUsageMonitor.check(appInfo.packageName, SINGLE_PART_SMS)) {
655                sendMessage(obtainMessage(EVENT_SEND_LIMIT_REACHED_CONFIRMATION, tracker));
656                return;
657            }
658
659            int ss = mPhone.getServiceState().getState();
660
661            if (ss != ServiceState.STATE_IN_SERVICE) {
662                handleNotInService(ss, tracker.mSentIntent);
663            } else {
664                sendSms(tracker);
665            }
666        }
667    }
668
669    /**
670     * Check if destination is a potential premium short code and sender is not pre-approved to
671     * send to short codes.
672     *
673     * @param tracker the tracker for the SMS to send
674     * @return true if the destination is approved; false if user confirmation event was sent
675     */
676    boolean checkDestination(SmsTracker tracker) {
677        if (mContext.checkCallingOrSelfPermission(SEND_SMS_NO_CONFIRMATION_PERMISSION)
678                == PackageManager.PERMISSION_GRANTED) {
679            return true;            // app is pre-approved to send to short codes
680        } else {
681            int rule = mPremiumSmsRule.get();
682            int smsCategory = SmsUsageMonitor.CATEGORY_NOT_SHORT_CODE;
683            if (rule == PREMIUM_RULE_USE_SIM || rule == PREMIUM_RULE_USE_BOTH) {
684                String simCountryIso = mTelephonyManager.getSimCountryIso();
685                if (simCountryIso == null || simCountryIso.length() != 2) {
686                    Rlog.e(TAG, "Can't get SIM country Iso: trying network country Iso");
687                    simCountryIso = mTelephonyManager.getNetworkCountryIso();
688                }
689
690                smsCategory = mUsageMonitor.checkDestination(tracker.mDestAddress, simCountryIso);
691            }
692            if (rule == PREMIUM_RULE_USE_NETWORK || rule == PREMIUM_RULE_USE_BOTH) {
693                String networkCountryIso = mTelephonyManager.getNetworkCountryIso();
694                if (networkCountryIso == null || networkCountryIso.length() != 2) {
695                    Rlog.e(TAG, "Can't get Network country Iso: trying SIM country Iso");
696                    networkCountryIso = mTelephonyManager.getSimCountryIso();
697                }
698
699                smsCategory = SmsUsageMonitor.mergeShortCodeCategories(smsCategory,
700                        mUsageMonitor.checkDestination(tracker.mDestAddress, networkCountryIso));
701            }
702
703            if (smsCategory == SmsUsageMonitor.CATEGORY_NOT_SHORT_CODE
704                    || smsCategory == SmsUsageMonitor.CATEGORY_FREE_SHORT_CODE
705                    || smsCategory == SmsUsageMonitor.CATEGORY_STANDARD_SHORT_CODE) {
706                return true;    // not a premium short code
707            }
708
709            // Wait for user confirmation unless the user has set permission to always allow/deny
710            int premiumSmsPermission = mUsageMonitor.getPremiumSmsPermission(
711                    tracker.mAppInfo.packageName);
712            if (premiumSmsPermission == SmsUsageMonitor.PREMIUM_SMS_PERMISSION_UNKNOWN) {
713                // First time trying to send to premium SMS.
714                premiumSmsPermission = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER;
715            }
716
717            switch (premiumSmsPermission) {
718                case SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW:
719                    Rlog.d(TAG, "User approved this app to send to premium SMS");
720                    return true;
721
722                case SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW:
723                    Rlog.w(TAG, "User denied this app from sending to premium SMS");
724                    sendMessage(obtainMessage(EVENT_STOP_SENDING, tracker));
725                    return false;   // reject this message
726
727                case SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER:
728                default:
729                    int event;
730                    if (smsCategory == SmsUsageMonitor.CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE) {
731                        event = EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE;
732                    } else {
733                        event = EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE;
734                    }
735                    sendMessage(obtainMessage(event, tracker));
736                    return false;   // wait for user confirmation
737            }
738        }
739    }
740
741    /**
742     * Deny sending an SMS if the outgoing queue limit is reached. Used when the message
743     * must be confirmed by the user due to excessive usage or potential premium SMS detected.
744     * @param tracker the SmsTracker for the message to send
745     * @return true if the message was denied; false to continue with send confirmation
746     */
747    private boolean denyIfQueueLimitReached(SmsTracker tracker) {
748        if (mPendingTrackerCount >= MO_MSG_QUEUE_LIMIT) {
749            // Deny sending message when the queue limit is reached.
750            try {
751                if (tracker.mSentIntent != null) {
752                    tracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
753                }
754            } catch (CanceledException ex) {
755                Rlog.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
756            }
757            return true;
758        }
759        mPendingTrackerCount++;
760        return false;
761    }
762
763    /**
764     * Returns the label for the specified app package name.
765     * @param appPackage the package name of the app requesting to send an SMS
766     * @return the label for the specified app, or the package name if getApplicationInfo() fails
767     */
768    private CharSequence getAppLabel(String appPackage) {
769        PackageManager pm = mContext.getPackageManager();
770        try {
771            ApplicationInfo appInfo = pm.getApplicationInfo(appPackage, 0);
772            return appInfo.loadLabel(pm);
773        } catch (PackageManager.NameNotFoundException e) {
774            Rlog.e(TAG, "PackageManager Name Not Found for package " + appPackage);
775            return appPackage;  // fall back to package name if we can't get app label
776        }
777    }
778
779    /**
780     * Post an alert when SMS needs confirmation due to excessive usage.
781     * @param tracker an SmsTracker for the current message.
782     */
783    protected void handleReachSentLimit(SmsTracker tracker) {
784        if (denyIfQueueLimitReached(tracker)) {
785            return;     // queue limit reached; error was returned to caller
786        }
787
788        CharSequence appLabel = getAppLabel(tracker.mAppInfo.packageName);
789        Resources r = Resources.getSystem();
790        Spanned messageText = Html.fromHtml(r.getString(R.string.sms_control_message, appLabel));
791
792        ConfirmDialogListener listener = new ConfirmDialogListener(tracker, null);
793
794        AlertDialog d = new AlertDialog.Builder(mContext)
795                .setTitle(R.string.sms_control_title)
796                .setIcon(R.drawable.stat_sys_warning)
797                .setMessage(messageText)
798                .setPositiveButton(r.getString(R.string.sms_control_yes), listener)
799                .setNegativeButton(r.getString(R.string.sms_control_no), listener)
800                .setOnCancelListener(listener)
801                .create();
802
803        d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
804        d.show();
805    }
806
807    /**
808     * Post an alert for user confirmation when sending to a potential short code.
809     * @param isPremium true if the destination is known to be a premium short code
810     * @param tracker the SmsTracker for the current message.
811     */
812    protected void handleConfirmShortCode(boolean isPremium, SmsTracker tracker) {
813        if (denyIfQueueLimitReached(tracker)) {
814            return;     // queue limit reached; error was returned to caller
815        }
816
817        int detailsId;
818        if (isPremium) {
819            detailsId = R.string.sms_premium_short_code_details;
820        } else {
821            detailsId = R.string.sms_short_code_details;
822        }
823
824        CharSequence appLabel = getAppLabel(tracker.mAppInfo.packageName);
825        Resources r = Resources.getSystem();
826        Spanned messageText = Html.fromHtml(r.getString(R.string.sms_short_code_confirm_message,
827                appLabel, tracker.mDestAddress));
828
829        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
830                Context.LAYOUT_INFLATER_SERVICE);
831        View layout = inflater.inflate(R.layout.sms_short_code_confirmation_dialog, null);
832
833        ConfirmDialogListener listener = new ConfirmDialogListener(tracker,
834                (TextView)layout.findViewById(R.id.sms_short_code_remember_undo_instruction));
835
836
837        TextView messageView = (TextView) layout.findViewById(R.id.sms_short_code_confirm_message);
838        messageView.setText(messageText);
839
840        ViewGroup detailsLayout = (ViewGroup) layout.findViewById(
841                R.id.sms_short_code_detail_layout);
842        TextView detailsView = (TextView) detailsLayout.findViewById(
843                R.id.sms_short_code_detail_message);
844        detailsView.setText(detailsId);
845
846        CheckBox rememberChoice = (CheckBox) layout.findViewById(
847                R.id.sms_short_code_remember_choice_checkbox);
848        rememberChoice.setOnCheckedChangeListener(listener);
849
850        AlertDialog d = new AlertDialog.Builder(mContext)
851                .setView(layout)
852                .setPositiveButton(r.getString(R.string.sms_short_code_confirm_allow), listener)
853                .setNegativeButton(r.getString(R.string.sms_short_code_confirm_deny), listener)
854                .setOnCancelListener(listener)
855                .create();
856
857        d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
858        d.show();
859
860        listener.setPositiveButton(d.getButton(DialogInterface.BUTTON_POSITIVE));
861        listener.setNegativeButton(d.getButton(DialogInterface.BUTTON_NEGATIVE));
862    }
863
864    /**
865     * Returns the premium SMS permission for the specified package. If the package has never
866     * been seen before, the default {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ASK_USER}
867     * will be returned.
868     * @param packageName the name of the package to query permission
869     * @return one of {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_UNKNOWN},
870     *  {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ASK_USER},
871     *  {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_NEVER_ALLOW}, or
872     *  {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW}
873     */
874    public int getPremiumSmsPermission(String packageName) {
875        return mUsageMonitor.getPremiumSmsPermission(packageName);
876    }
877
878    /**
879     * Sets the premium SMS permission for the specified package and save the value asynchronously
880     * to persistent storage.
881     * @param packageName the name of the package to set permission
882     * @param permission one of {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ASK_USER},
883     *  {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_NEVER_ALLOW}, or
884     *  {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW}
885     */
886    public void setPremiumSmsPermission(String packageName, int permission) {
887        mUsageMonitor.setPremiumSmsPermission(packageName, permission);
888    }
889
890    /**
891     * Send the message along to the radio.
892     *
893     * @param tracker holds the SMS message to send
894     */
895    protected abstract void sendSms(SmsTracker tracker);
896
897    /**
898     * Send the multi-part SMS based on multipart Sms tracker
899     *
900     * @param tracker holds the multipart Sms tracker ready to be sent
901     */
902    private void sendMultipartSms(SmsTracker tracker) {
903        ArrayList<String> parts;
904        ArrayList<PendingIntent> sentIntents;
905        ArrayList<PendingIntent> deliveryIntents;
906
907        HashMap<String, Object> map = tracker.mData;
908
909        String destinationAddress = (String) map.get("destination");
910        String scAddress = (String) map.get("scaddress");
911
912        parts = (ArrayList<String>) map.get("parts");
913        sentIntents = (ArrayList<PendingIntent>) map.get("sentIntents");
914        deliveryIntents = (ArrayList<PendingIntent>) map.get("deliveryIntents");
915
916        // check if in service
917        int ss = mPhone.getServiceState().getState();
918        if (ss != ServiceState.STATE_IN_SERVICE) {
919            for (int i = 0, count = parts.size(); i < count; i++) {
920                PendingIntent sentIntent = null;
921                if (sentIntents != null && sentIntents.size() > i) {
922                    sentIntent = sentIntents.get(i);
923                }
924                handleNotInService(ss, sentIntent);
925            }
926            return;
927        }
928
929        sendMultipartText(destinationAddress, scAddress, parts, sentIntents, deliveryIntents);
930    }
931
932    /**
933     * Keeps track of an SMS that has been sent to the RIL, until it has
934     * successfully been sent, or we're done trying.
935     */
936    protected static final class SmsTracker {
937        // fields need to be public for derived SmsDispatchers
938        public final HashMap<String, Object> mData;
939        public int mRetryCount;
940        public int mMessageRef;
941
942        public final PendingIntent mSentIntent;
943        public final PendingIntent mDeliveryIntent;
944
945        public final PackageInfo mAppInfo;
946        public final String mDestAddress;
947
948        public SmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
949                PendingIntent deliveryIntent, PackageInfo appInfo, String destAddr) {
950            mData = data;
951            mSentIntent = sentIntent;
952            mDeliveryIntent = deliveryIntent;
953            mRetryCount = 0;
954            mAppInfo = appInfo;
955            mDestAddress = destAddr;
956        }
957
958        /**
959         * Returns whether this tracker holds a multi-part SMS.
960         * @return true if the tracker holds a multi-part SMS; false otherwise
961         */
962        boolean isMultipart() {
963            HashMap<String, Object> map = mData;
964            return map.containsKey("parts");
965        }
966    }
967
968    /**
969     * Dialog listener for SMS confirmation dialog.
970     */
971    private final class ConfirmDialogListener
972            implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener,
973            CompoundButton.OnCheckedChangeListener {
974
975        private final SmsTracker mTracker;
976        private Button mPositiveButton;
977        private Button mNegativeButton;
978        private boolean mRememberChoice;    // default is unchecked
979        private final TextView mRememberUndoInstruction;
980
981        ConfirmDialogListener(SmsTracker tracker, TextView textView) {
982            mTracker = tracker;
983            mRememberUndoInstruction = textView;
984        }
985
986        void setPositiveButton(Button button) {
987            mPositiveButton = button;
988        }
989
990        void setNegativeButton(Button button) {
991            mNegativeButton = button;
992        }
993
994        @Override
995        public void onClick(DialogInterface dialog, int which) {
996            // Always set the SMS permission so that Settings will show a permission setting
997            // for the app (it won't be shown until after the app tries to send to a short code).
998            int newSmsPermission = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER;
999
1000            if (which == DialogInterface.BUTTON_POSITIVE) {
1001                Rlog.d(TAG, "CONFIRM sending SMS");
1002                // XXX this is lossy- apps can have more than one signature
1003                EventLog.writeEvent(EventLogTags.EXP_DET_SMS_SENT_BY_USER,
1004                                    mTracker.mAppInfo.applicationInfo == null ?
1005                                    -1 : mTracker.mAppInfo.applicationInfo.uid);
1006                sendMessage(obtainMessage(EVENT_SEND_CONFIRMED_SMS, mTracker));
1007                if (mRememberChoice) {
1008                    newSmsPermission = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW;
1009                }
1010            } else if (which == DialogInterface.BUTTON_NEGATIVE) {
1011                Rlog.d(TAG, "DENY sending SMS");
1012                // XXX this is lossy- apps can have more than one signature
1013                EventLog.writeEvent(EventLogTags.EXP_DET_SMS_DENIED_BY_USER,
1014                                    mTracker.mAppInfo.applicationInfo == null ?
1015                                    -1 :  mTracker.mAppInfo.applicationInfo.uid);
1016                sendMessage(obtainMessage(EVENT_STOP_SENDING, mTracker));
1017                if (mRememberChoice) {
1018                    newSmsPermission = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW;
1019                }
1020            }
1021            setPremiumSmsPermission(mTracker.mAppInfo.packageName, newSmsPermission);
1022        }
1023
1024        @Override
1025        public void onCancel(DialogInterface dialog) {
1026            Rlog.d(TAG, "dialog dismissed: don't send SMS");
1027            sendMessage(obtainMessage(EVENT_STOP_SENDING, mTracker));
1028        }
1029
1030        @Override
1031        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
1032            Rlog.d(TAG, "remember this choice: " + isChecked);
1033            mRememberChoice = isChecked;
1034            if (isChecked) {
1035                mPositiveButton.setText(R.string.sms_short_code_confirm_always_allow);
1036                mNegativeButton.setText(R.string.sms_short_code_confirm_never_allow);
1037                if (mRememberUndoInstruction != null) {
1038                    mRememberUndoInstruction.
1039                            setText(R.string.sms_short_code_remember_undo_instruction);
1040                    mRememberUndoInstruction.setPadding(0,0,0,32);
1041                }
1042            } else {
1043                mPositiveButton.setText(R.string.sms_short_code_confirm_allow);
1044                mNegativeButton.setText(R.string.sms_short_code_confirm_deny);
1045                if (mRememberUndoInstruction != null) {
1046                    mRememberUndoInstruction.setText("");
1047                    mRememberUndoInstruction.setPadding(0,0,0,0);
1048                }
1049            }
1050        }
1051    }
1052}
1053