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