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