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