1/*
2 * Copyright (C) 2008 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.cdma;
18
19import android.app.Activity;
20import android.app.PendingIntent;
21import android.app.PendingIntent.CanceledException;
22import android.content.Intent;
23import android.net.Uri;
24import android.os.Message;
25import android.os.SystemProperties;
26import android.provider.Telephony.Sms;
27import android.telephony.Rlog;
28import android.telephony.ServiceState;
29import android.telephony.SmsManager;
30import android.telephony.TelephonyManager;
31
32import com.android.internal.telephony.GsmAlphabet;
33import com.android.internal.telephony.ImsSMSDispatcher;
34import com.android.internal.telephony.PhoneBase;
35import com.android.internal.telephony.PhoneConstants;
36import com.android.internal.telephony.SMSDispatcher;
37import com.android.internal.telephony.SmsConstants;
38import com.android.internal.telephony.SmsHeader;
39import com.android.internal.telephony.SmsUsageMonitor;
40import com.android.internal.telephony.TelephonyProperties;
41import com.android.internal.telephony.cdma.sms.UserData;
42
43import java.util.HashMap;
44import java.util.concurrent.atomic.AtomicBoolean;
45import java.util.concurrent.atomic.AtomicInteger;
46
47public class CdmaSMSDispatcher extends SMSDispatcher {
48    private static final String TAG = "CdmaSMSDispatcher";
49    private static final boolean VDBG = false;
50
51    public CdmaSMSDispatcher(PhoneBase phone, SmsUsageMonitor usageMonitor,
52            ImsSMSDispatcher imsSMSDispatcher) {
53        super(phone, usageMonitor, imsSMSDispatcher);
54        Rlog.d(TAG, "CdmaSMSDispatcher created");
55    }
56
57    @Override
58    protected String getFormat() {
59        return SmsConstants.FORMAT_3GPP2;
60    }
61
62    /**
63     * Send the SMS status report to the dispatcher thread to process.
64     * @param sms the CDMA SMS message containing the status report
65     */
66    void sendStatusReportMessage(SmsMessage sms) {
67        if (VDBG) Rlog.d(TAG, "sending EVENT_HANDLE_STATUS_REPORT message");
68        sendMessage(obtainMessage(EVENT_HANDLE_STATUS_REPORT, sms));
69    }
70
71    @Override
72    protected void handleStatusReport(Object o) {
73        if (o instanceof SmsMessage) {
74            if (VDBG) Rlog.d(TAG, "calling handleCdmaStatusReport()");
75            handleCdmaStatusReport((SmsMessage) o);
76        } else {
77            Rlog.e(TAG, "handleStatusReport() called for object type " + o.getClass().getName());
78        }
79    }
80
81    /**
82     * Called from parent class to handle status report from {@code CdmaInboundSmsHandler}.
83     * @param sms the CDMA SMS message to process
84     */
85    void handleCdmaStatusReport(SmsMessage sms) {
86        for (int i = 0, count = deliveryPendingList.size(); i < count; i++) {
87            SmsTracker tracker = deliveryPendingList.get(i);
88            if (tracker.mMessageRef == sms.mMessageRef) {
89                // Found it.  Remove from list and broadcast.
90                deliveryPendingList.remove(i);
91                // Update the message status (COMPLETE)
92                tracker.updateSentMessageStatus(mContext, Sms.STATUS_COMPLETE);
93
94                PendingIntent intent = tracker.mDeliveryIntent;
95                Intent fillIn = new Intent();
96                fillIn.putExtra("pdu", sms.getPdu());
97                fillIn.putExtra("format", getFormat());
98                try {
99                    intent.send(mContext, Activity.RESULT_OK, fillIn);
100                } catch (CanceledException ex) {}
101                break;  // Only expect to see one tracker matching this message.
102            }
103        }
104    }
105
106    /** {@inheritDoc} */
107    @Override
108    protected void sendData(String destAddr, String scAddr, int destPort,
109            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
110        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
111                scAddr, destAddr, destPort, data, (deliveryIntent != null));
112        HashMap map = getSmsTrackerMap(destAddr, scAddr, destPort, data, pdu);
113        SmsTracker tracker = getSmsTracker(map, sentIntent, deliveryIntent, getFormat(),
114                null /*messageUri*/, false /*isExpectMore*/, null /*fullMessageText*/,
115                false /*isText*/);
116
117        String carrierPackage = getCarrierAppPackageName();
118        if (carrierPackage != null) {
119            Rlog.d(TAG, "Found carrier package.");
120            DataSmsSender smsSender = new DataSmsSender(tracker);
121            smsSender.sendSmsByCarrierApp(carrierPackage, new SmsSenderCallback(smsSender));
122        } else {
123            Rlog.v(TAG, "No carrier package.");
124            sendSubmitPdu(tracker);
125        }
126    }
127
128    /** {@inheritDoc} */
129    @Override
130    protected void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent,
131            PendingIntent deliveryIntent, Uri messageUri, String callingPkg) {
132        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
133                scAddr, destAddr, text, (deliveryIntent != null), null);
134        if (pdu != null) {
135            HashMap map = getSmsTrackerMap(destAddr, scAddr, text, pdu);
136            SmsTracker tracker = getSmsTracker(map, sentIntent, deliveryIntent, getFormat(),
137                    messageUri, false /*isExpectMore*/, text, true /*isText*/);
138
139            String carrierPackage = getCarrierAppPackageName();
140            if (carrierPackage != null) {
141                Rlog.d(TAG, "Found carrier package.");
142                TextSmsSender smsSender = new TextSmsSender(tracker);
143                smsSender.sendSmsByCarrierApp(carrierPackage, new SmsSenderCallback(smsSender));
144            } else {
145                Rlog.v(TAG, "No carrier package.");
146                sendSubmitPdu(tracker);
147            }
148        } else {
149            Rlog.e(TAG, "CdmaSMSDispatcher.sendText(): getSubmitPdu() returned null");
150        }
151    }
152
153    /** {@inheritDoc} */
154    @Override
155    protected void injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent) {
156        throw new IllegalStateException("This method must be called only on ImsSMSDispatcher");
157    }
158
159    /** {@inheritDoc} */
160    @Override
161    protected GsmAlphabet.TextEncodingDetails calculateLength(CharSequence messageBody,
162            boolean use7bitOnly) {
163        return SmsMessage.calculateLength(messageBody, use7bitOnly, false);
164    }
165
166    /** {@inheritDoc} */
167    @Override
168    protected SmsTracker getNewSubmitPduTracker(String destinationAddress, String scAddress,
169            String message, SmsHeader smsHeader, int encoding,
170            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart,
171            AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
172            String fullMessageText) {
173        UserData uData = new UserData();
174        uData.payloadStr = message;
175        uData.userDataHeader = smsHeader;
176        if (encoding == SmsConstants.ENCODING_7BIT) {
177            uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
178        } else { // assume UTF-16
179            uData.msgEncoding = UserData.ENCODING_UNICODE_16;
180        }
181        uData.msgEncodingSet = true;
182
183        /* By setting the statusReportRequested bit only for the
184         * last message fragment, this will result in only one
185         * callback to the sender when that last fragment delivery
186         * has been acknowledged. */
187        SmsMessage.SubmitPdu submitPdu = SmsMessage.getSubmitPdu(destinationAddress,
188                uData, (deliveryIntent != null) && lastPart);
189
190        HashMap map = getSmsTrackerMap(destinationAddress, scAddress,
191                message, submitPdu);
192        return getSmsTracker(map, sentIntent, deliveryIntent,
193                getFormat(), unsentPartCount, anyPartFailed, messageUri, smsHeader,
194                false /*isExpextMore*/, fullMessageText, true /*isText*/);
195    }
196
197    @Override
198    protected void sendSubmitPdu(SmsTracker tracker) {
199        if (SystemProperties.getBoolean(TelephonyProperties.PROPERTY_INECM_MODE, false)) {
200            if (VDBG) {
201                Rlog.d(TAG, "Block SMS in Emergency Callback mode");
202            }
203            tracker.onFailed(mContext, SmsManager.RESULT_ERROR_NO_SERVICE, 0/*errorCode*/);
204            return;
205        }
206        sendRawPdu(tracker);
207    }
208
209    /** {@inheritDoc} */
210    @Override
211    protected void sendSms(SmsTracker tracker) {
212        HashMap<String, Object> map = tracker.mData;
213
214        // byte[] smsc = (byte[]) map.get("smsc");  // unused for CDMA
215        byte[] pdu = (byte[]) map.get("pdu");
216
217        Rlog.d(TAG, "sendSms: "
218                + " isIms()=" + isIms()
219                + " mRetryCount=" + tracker.mRetryCount
220                + " mImsRetry=" + tracker.mImsRetry
221                + " mMessageRef=" + tracker.mMessageRef
222                + " SS=" + mPhone.getServiceState().getState());
223
224        sendSmsByPstn(tracker);
225    }
226
227    /** {@inheritDoc} */
228    @Override
229    protected void sendSmsByPstn(SmsTracker tracker) {
230        int ss = mPhone.getServiceState().getState();
231        // if sms over IMS is not supported on data and voice is not available...
232        if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
233            tracker.onFailed(mContext, getNotInServiceError(ss), 0/*errorCode*/);
234            return;
235        }
236
237        Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
238        byte[] pdu = (byte[]) tracker.mData.get("pdu");
239
240        int currentDataNetwork = mPhone.getServiceState().getDataNetworkType();
241        boolean imsSmsDisabled = (currentDataNetwork == TelephonyManager.NETWORK_TYPE_EHRPD
242                    || (currentDataNetwork == TelephonyManager.NETWORK_TYPE_LTE
243                    && !mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()))
244                    && mPhone.getServiceState().getVoiceNetworkType()
245                    == TelephonyManager.NETWORK_TYPE_1xRTT
246                    && ((CDMAPhone) mPhone).mCT.mState != PhoneConstants.State.IDLE;
247
248        // sms over cdma is used:
249        //   if sms over IMS is not supported AND
250        //   this is not a retry case after sms over IMS failed
251        //     indicated by mImsRetry > 0
252        if (0 == tracker.mImsRetry && !isIms() || imsSmsDisabled) {
253            mCi.sendCdmaSms(pdu, reply);
254        } else {
255            mCi.sendImsCdmaSms(pdu, tracker.mImsRetry, tracker.mMessageRef, reply);
256            // increment it here, so in case of SMS_FAIL_RETRY over IMS
257            // next retry will be sent using IMS request again.
258            tracker.mImsRetry++;
259        }
260    }
261}
262