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