1d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd/*
2d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Copyright (C) 2015 The Android Open Source Project
3d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd *
4d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Licensed under the Apache License, Version 2.0 (the "License");
5d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * you may not use this file except in compliance with the License.
6d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * You may obtain a copy of the License at
7d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd *
8d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd *      http://www.apache.org/licenses/LICENSE-2.0
9d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd *
10d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Unless required by applicable law or agreed to in writing, software
11d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * distributed under the License is distributed on an "AS IS" BASIS,
12d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * See the License for the specific language governing permissions and
14d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * limitations under the License.
15d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */
16d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
17d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddpackage com.android.messaging.sms;
18d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
19d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.app.Activity;
20d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.app.PendingIntent;
21d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.content.Context;
22d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.content.Intent;
23d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.net.Uri;
24d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.os.SystemClock;
25d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.telephony.PhoneNumberUtils;
26d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.telephony.SmsManager;
27d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.text.TextUtils;
28d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
29d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.Factory;
30d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.R;
31d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.receiver.SendStatusReceiver;
32d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.Assert;
33d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.BugleGservices;
34d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.BugleGservicesKeys;
35d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.LogUtil;
36d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.PhoneUtils;
37d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.UiUtils;
38d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
39d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport java.util.ArrayList;
40d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport java.util.Random;
41d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport java.util.concurrent.ConcurrentHashMap;
42d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
43d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd/**
44d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Class that sends chat message via SMS.
45d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd *
46d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * The interface emulates a blocking sending similar to making an HTTP request.
47d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * It calls the SmsManager to send a (potentially multipart) message and waits
48d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * on the sent status on each part. The waiting has a timeout so it won't wait
49d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * forever. Once the sent status of all parts received, the call returns.
50d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * A successful sending requires success status for all parts. Otherwise, we
51d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * pick the highest level of failure as the error for the whole message, which
52d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * is used to determine if we need to retry the sending.
53d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */
54d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddpublic class SmsSender {
55d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static final String TAG = LogUtil.BUGLE_TAG;
56d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
57d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static final String EXTRA_PART_ID = "part_id";
58d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
59d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /*
60d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * A map for pending sms messages. The key is the random request UUID.
61d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
62d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static ConcurrentHashMap<Uri, SendResult> sPendingMessageMap =
63d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            new ConcurrentHashMap<Uri, SendResult>();
64d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
65d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static final Random RANDOM = new Random();
66d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
67d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    // Whether we should send multipart SMS as separate messages
68d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static Boolean sSendMultipartSmsAsSeparateMessages = null;
69d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
70d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
71d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Class that holds the sent status for all parts of a multipart message sending
72d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
73d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static class SendResult {
74d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Failure levels, used by the caller of the sender.
75d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // For temporary failures, possibly we could retry the sending
76d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // For permanent failures, we probably won't retry
77d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        public static final int FAILURE_LEVEL_NONE = 0;
78d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        public static final int FAILURE_LEVEL_TEMPORARY = 1;
79d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        public static final int FAILURE_LEVEL_PERMANENT = 2;
80d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
81d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Tracking the remaining pending parts in sending
82d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        private int mPendingParts;
83d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Tracking the highest level of failure among all parts
84d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        private int mHighestFailureLevel;
85d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
86d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        public SendResult(final int numOfParts) {
87d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            Assert.isTrue(numOfParts > 0);
88d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            mPendingParts = numOfParts;
89d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            mHighestFailureLevel = FAILURE_LEVEL_NONE;
90d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
91d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
92d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Update the sent status of one part
93d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        public void setPartResult(final int resultCode) {
94d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            mPendingParts--;
95d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            setHighestFailureLevel(resultCode);
96d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
97d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
98d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        public boolean hasPending() {
99d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return mPendingParts > 0;
100d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
101d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
102d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        public int getHighestFailureLevel() {
103d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return mHighestFailureLevel;
104d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
105d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
106d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        private int getFailureLevel(final int resultCode) {
107d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            switch (resultCode) {
108d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                case Activity.RESULT_OK:
109d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    return FAILURE_LEVEL_NONE;
110d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                case SmsManager.RESULT_ERROR_NO_SERVICE:
111d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    return FAILURE_LEVEL_TEMPORARY;
112d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                case SmsManager.RESULT_ERROR_RADIO_OFF:
113d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    return FAILURE_LEVEL_PERMANENT;
114d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
115d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    return FAILURE_LEVEL_PERMANENT;
116d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                default: {
117d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    LogUtil.e(TAG, "SmsSender: Unexpected sent intent resultCode = " + resultCode);
118d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    return FAILURE_LEVEL_PERMANENT;
119d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
120d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
121d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
122d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
123d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        private void setHighestFailureLevel(final int resultCode) {
124d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final int level = getFailureLevel(resultCode);
125d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (level > mHighestFailureLevel) {
126d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                mHighestFailureLevel = level;
127d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
128d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
129d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
130d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        @Override
131d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        public String toString() {
132d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final StringBuilder sb = new StringBuilder();
133d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            sb.append("SendResult:");
134d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            sb.append("Pending=").append(mPendingParts).append(",");
135d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            sb.append("HighestFailureLevel=").append(mHighestFailureLevel);
136d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return sb.toString();
137d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
138d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
139d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
140d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static void setResult(final Uri requestId, final int resultCode,
141d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final int errorCode, final int partId, int subId) {
142d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (resultCode != Activity.RESULT_OK) {
143d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            LogUtil.e(TAG, "SmsSender: failure in sending message part. "
144d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    + " requestId=" + requestId + " partId=" + partId
145d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    + " resultCode=" + resultCode + " errorCode=" + errorCode);
146d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (errorCode != SendStatusReceiver.NO_ERROR_CODE) {
147d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                final Context context = Factory.get().getApplicationContext();
148d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                UiUtils.showToastAtBottom(getSendErrorToastMessage(context, subId, errorCode));
149d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
150d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } else {
151d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
152d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                LogUtil.v(TAG, "SmsSender: received sent result. " + " requestId=" + requestId
153d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        + " partId=" + partId + " resultCode=" + resultCode);
154d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
155d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
156d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (requestId != null) {
157d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final SendResult result = sPendingMessageMap.get(requestId);
158d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (result != null) {
159d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                synchronized (result) {
160d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    result.setPartResult(resultCode);
161d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    if (!result.hasPending()) {
162d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        result.notifyAll();
163d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    }
164d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
165d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            } else {
166d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                LogUtil.e(TAG, "SmsSender: ignoring sent result. " + " requestId=" + requestId
167d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        + " partId=" + partId + " resultCode=" + resultCode);
168d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
169d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
170d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
171d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
172d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static String getSendErrorToastMessage(final Context context, final int subId,
173d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final int errorCode) {
174d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final String carrierName = PhoneUtils.get(subId).getCarrierName();
175d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (TextUtils.isEmpty(carrierName)) {
176d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return context.getString(R.string.carrier_send_error_unknown_carrier, errorCode);
177d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } else {
178d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return context.getString(R.string.carrier_send_error, carrierName, errorCode);
179d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
180d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
181d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
182d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    // This should be called from a RequestWriter queue thread
183d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static SendResult sendMessage(final Context context,  final int subId, String dest,
184d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            String message, final String serviceCenter, final boolean requireDeliveryReport,
185d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final Uri messageUri) throws SmsException {
186d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
187d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            LogUtil.v(TAG, "SmsSender: sending message. " +
188d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    "dest=" + dest + " message=" + message +
189d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    " serviceCenter=" + serviceCenter +
190d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    " requireDeliveryReport=" + requireDeliveryReport +
191d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    " requestId=" + messageUri);
192d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
193d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (TextUtils.isEmpty(message)) {
194d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            throw new SmsException("SmsSender: empty text message");
195d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
196d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Get the real dest and message for email or alias if dest is email or alias
197d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Or sanitize the dest if dest is a number
198d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (!TextUtils.isEmpty(MmsConfig.get(subId).getEmailGateway()) &&
199d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                (MmsSmsUtils.isEmailAddress(dest) || MmsSmsUtils.isAlias(dest, subId))) {
200d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // The original destination (email address) goes with the message
201d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            message = dest + " " + message;
202d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // the new address is the email gateway #
203d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            dest = MmsConfig.get(subId).getEmailGateway();
204d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } else {
205d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // remove spaces and dashes from destination number
206d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // (e.g. "801 555 1212" -> "8015551212")
207d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // (e.g. "+8211-123-4567" -> "+82111234567")
208d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            dest = PhoneNumberUtils.stripSeparators(dest);
209d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
210d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (TextUtils.isEmpty(dest)) {
211d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            throw new SmsException("SmsSender: empty destination address");
212d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
213d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Divide the input message by SMS length limit
214d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final SmsManager smsManager = PhoneUtils.get(subId).getSmsManager();
215d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ArrayList<String> messages = smsManager.divideMessage(message);
216d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (messages == null || messages.size() < 1) {
217d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            throw new SmsException("SmsSender: fails to divide message");
218d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
219d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Prepare the send result, which collects the send status for each part
220d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final SendResult pendingResult = new SendResult(messages.size());
221d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        sPendingMessageMap.put(messageUri, pendingResult);
222d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Actually send the sms
223d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        sendInternal(
224d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                context, subId, dest, messages, serviceCenter, requireDeliveryReport, messageUri);
225d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Wait for pending intent to come back
226d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        synchronized (pendingResult) {
227d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final long smsSendTimeoutInMillis = BugleGservices.get().getLong(
228d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    BugleGservicesKeys.SMS_SEND_TIMEOUT_IN_MILLIS,
229d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    BugleGservicesKeys.SMS_SEND_TIMEOUT_IN_MILLIS_DEFAULT);
230d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final long beginTime = SystemClock.elapsedRealtime();
231d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            long waitTime = smsSendTimeoutInMillis;
232d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // We could possibly be woken up while still pending
233d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // so make sure we wait the full timeout period unless
234d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // we have the send results of all parts.
235d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            while (pendingResult.hasPending() && waitTime > 0) {
236d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                try {
237d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    pendingResult.wait(waitTime);
238d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                } catch (final InterruptedException e) {
239d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    LogUtil.e(TAG, "SmsSender: sending wait interrupted");
240d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
241d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                waitTime = smsSendTimeoutInMillis - (SystemClock.elapsedRealtime() - beginTime);
242d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
243d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
244d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Either we timed out or have all the results (success or failure)
245d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        sPendingMessageMap.remove(messageUri);
246d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
247d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            LogUtil.v(TAG, "SmsSender: sending completed. " +
248d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    "dest=" + dest + " message=" + message + " result=" + pendingResult);
249d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
250d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return pendingResult;
251d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
252d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
253d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    // Actually sending the message using SmsManager
254d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static void sendInternal(final Context context, final int subId, String dest,
255d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final ArrayList<String> messages, final String serviceCenter,
256d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final boolean requireDeliveryReport, final Uri messageUri) throws SmsException {
257d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        Assert.notNull(context);
258d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final SmsManager smsManager = PhoneUtils.get(subId).getSmsManager();
259d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int messageCount = messages.size();
260d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ArrayList<PendingIntent> deliveryIntents = new ArrayList<PendingIntent>(messageCount);
261d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(messageCount);
262d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        for (int i = 0; i < messageCount; i++) {
263d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Make pending intents different for each message part
264d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final int partId = (messageCount <= 1 ? 0 : i + 1);
265d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (requireDeliveryReport && (i == (messageCount - 1))) {
266d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // TODO we only care about the delivery status of the last part
267d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // Shall we have better tracking of delivery status of all parts?
268d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                deliveryIntents.add(PendingIntent.getBroadcast(
269d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        context,
270d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        partId,
271d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        getSendStatusIntent(context, SendStatusReceiver.MESSAGE_DELIVERED_ACTION,
272d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                                messageUri, partId, subId),
273d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        0/*flag*/));
274d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            } else {
275d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                deliveryIntents.add(null);
276d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
277d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            sentIntents.add(PendingIntent.getBroadcast(
278d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    context,
279d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    partId,
280d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    getSendStatusIntent(context, SendStatusReceiver.MESSAGE_SENT_ACTION,
281d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            messageUri, partId, subId),
282d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    0/*flag*/));
283d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
284d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (sSendMultipartSmsAsSeparateMessages == null) {
285d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            sSendMultipartSmsAsSeparateMessages = MmsConfig.get(subId)
286d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    .getSendMultipartSmsAsSeparateMessages();
287d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
288d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        try {
289d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (sSendMultipartSmsAsSeparateMessages) {
290d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                // If multipart sms is not supported, send them as separate messages
291d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                for (int i = 0; i < messageCount; i++) {
292d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    smsManager.sendTextMessage(dest,
293d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            serviceCenter,
294d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            messages.get(i),
295d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            sentIntents.get(i),
296d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                            deliveryIntents.get(i));
297d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
298d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            } else {
299d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                smsManager.sendMultipartTextMessage(
300d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                        dest, serviceCenter, messages, sentIntents, deliveryIntents);
301d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
302d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } catch (final Exception e) {
303d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            throw new SmsException("SmsSender: caught exception in sending " + e);
304d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
305d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
306d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
307d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static Intent getSendStatusIntent(final Context context, final String action,
308d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final Uri requestUri, final int partId, final int subId) {
309d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Encode requestId in intent data
310d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final Intent intent = new Intent(action, requestUri, context, SendStatusReceiver.class);
311d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        intent.putExtra(SendStatusReceiver.EXTRA_PART_ID, partId);
312d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        intent.putExtra(SendStatusReceiver.EXTRA_SUB_ID, subId);
313d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return intent;
314d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
315d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd}
316