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.gsm;
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.AsyncResult;
25import android.os.Message;
26import android.provider.Telephony.Sms;
27import android.provider.Telephony.Sms.Intents;
28import android.telephony.Rlog;
29import android.telephony.ServiceState;
30
31import com.android.internal.annotations.VisibleForTesting;
32import com.android.internal.telephony.GsmAlphabet;
33import com.android.internal.telephony.ImsSMSDispatcher;
34import com.android.internal.telephony.InboundSmsHandler;
35import com.android.internal.telephony.Phone;
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.uicc.IccRecords;
41import com.android.internal.telephony.uicc.IccUtils;
42import com.android.internal.telephony.uicc.UiccCardApplication;
43import com.android.internal.telephony.uicc.UiccController;
44
45import java.util.HashMap;
46import java.util.concurrent.atomic.AtomicBoolean;
47import java.util.concurrent.atomic.AtomicInteger;
48import java.util.concurrent.atomic.AtomicReference;
49
50public final class GsmSMSDispatcher extends SMSDispatcher {
51    private static final String TAG = "GsmSMSDispatcher";
52    private static final boolean VDBG = false;
53    protected UiccController mUiccController = null;
54    private AtomicReference<IccRecords> mIccRecords = new AtomicReference<IccRecords>();
55    private AtomicReference<UiccCardApplication> mUiccApplication =
56            new AtomicReference<UiccCardApplication>();
57    private GsmInboundSmsHandler mGsmInboundSmsHandler;
58
59    /** Status report received */
60    private static final int EVENT_NEW_SMS_STATUS_REPORT = 100;
61
62    public GsmSMSDispatcher(Phone phone, SmsUsageMonitor usageMonitor,
63            ImsSMSDispatcher imsSMSDispatcher,
64            GsmInboundSmsHandler gsmInboundSmsHandler) {
65        super(phone, usageMonitor, imsSMSDispatcher);
66        mCi.setOnSmsStatus(this, EVENT_NEW_SMS_STATUS_REPORT, null);
67        mGsmInboundSmsHandler = gsmInboundSmsHandler;
68        mUiccController = UiccController.getInstance();
69        mUiccController.registerForIccChanged(this, EVENT_ICC_CHANGED, null);
70        Rlog.d(TAG, "GsmSMSDispatcher created");
71    }
72
73    @Override
74    public void dispose() {
75        super.dispose();
76        mCi.unSetOnSmsStatus(this);
77        mUiccController.unregisterForIccChanged(this);
78    }
79
80    @Override
81    protected String getFormat() {
82        return SmsConstants.FORMAT_3GPP;
83    }
84
85    /**
86     * Handles 3GPP format-specific events coming from the phone stack.
87     * Other events are handled by {@link SMSDispatcher#handleMessage}.
88     *
89     * @param msg the message to handle
90     */
91    @Override
92    public void handleMessage(Message msg) {
93        switch (msg.what) {
94        case EVENT_NEW_SMS_STATUS_REPORT:
95            handleStatusReport((AsyncResult) msg.obj);
96            break;
97
98        case EVENT_NEW_ICC_SMS:
99        // pass to InboundSmsHandler to process
100        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, msg.obj);
101        break;
102
103        case EVENT_ICC_CHANGED:
104            onUpdateIccAvailability();
105            break;
106
107        default:
108            super.handleMessage(msg);
109        }
110    }
111
112    /**
113     * Called when a status report is received.  This should correspond to
114     * a previously successful SEND.
115     *
116     * @param ar AsyncResult passed into the message handler.  ar.result should
117     *           be a String representing the status report PDU, as ASCII hex.
118     */
119    private void handleStatusReport(AsyncResult ar) {
120        byte[] pdu = (byte[]) ar.result;
121        SmsMessage sms = SmsMessage.newFromCDS(pdu);
122
123        if (sms != null) {
124            int tpStatus = sms.getStatus();
125            int messageRef = sms.mMessageRef;
126            for (int i = 0, count = deliveryPendingList.size(); i < count; i++) {
127                SmsTracker tracker = deliveryPendingList.get(i);
128                if (tracker.mMessageRef == messageRef) {
129                    // Found it.  Remove from list and broadcast.
130                    if(tpStatus >= Sms.STATUS_FAILED || tpStatus < Sms.STATUS_PENDING ) {
131                       deliveryPendingList.remove(i);
132                       // Update the message status (COMPLETE or FAILED)
133                       tracker.updateSentMessageStatus(mContext, tpStatus);
134                    }
135                    PendingIntent intent = tracker.mDeliveryIntent;
136                    Intent fillIn = new Intent();
137                    fillIn.putExtra("pdu", pdu);
138                    fillIn.putExtra("format", getFormat());
139                    try {
140                        intent.send(mContext, Activity.RESULT_OK, fillIn);
141                    } catch (CanceledException ex) {}
142
143                    // Only expect to see one tracker matching this messageref
144                    break;
145                }
146            }
147        }
148        mCi.acknowledgeLastIncomingGsmSms(true, Intents.RESULT_SMS_HANDLED, null);
149    }
150
151    /** {@inheritDoc} */
152    @Override
153    protected void sendData(String destAddr, String scAddr, int destPort,
154            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
155        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
156                scAddr, destAddr, destPort, data, (deliveryIntent != null));
157        if (pdu != null) {
158            HashMap map = getSmsTrackerMap(destAddr, scAddr, destPort, data, pdu);
159            SmsTracker tracker = getSmsTracker(map, sentIntent, deliveryIntent, getFormat(),
160                    null /*messageUri*/, false /*isExpectMore*/, null /*fullMessageText*/,
161                    false /*isText*/, true /*persistMessage*/);
162
163            String carrierPackage = getCarrierAppPackageName();
164            if (carrierPackage != null) {
165                Rlog.d(TAG, "Found carrier package.");
166                DataSmsSender smsSender = new DataSmsSender(tracker);
167                smsSender.sendSmsByCarrierApp(carrierPackage, new SmsSenderCallback(smsSender));
168            } else {
169                Rlog.v(TAG, "No carrier package.");
170                sendRawPdu(tracker);
171            }
172        } else {
173            Rlog.e(TAG, "GsmSMSDispatcher.sendData(): getSubmitPdu() returned null");
174        }
175    }
176
177    /** {@inheritDoc} */
178    @VisibleForTesting
179    @Override
180    public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent,
181            PendingIntent deliveryIntent, Uri messageUri, String callingPkg,
182            boolean persistMessage) {
183        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
184                scAddr, destAddr, text, (deliveryIntent != null));
185        if (pdu != null) {
186            HashMap map = getSmsTrackerMap(destAddr, scAddr, text, pdu);
187            SmsTracker tracker = getSmsTracker(map, sentIntent, deliveryIntent, getFormat(),
188                    messageUri, false /*isExpectMore*/, text /*fullMessageText*/, true /*isText*/,
189                    persistMessage);
190
191            String carrierPackage = getCarrierAppPackageName();
192            if (carrierPackage != null) {
193                Rlog.d(TAG, "Found carrier package.");
194                TextSmsSender smsSender = new TextSmsSender(tracker);
195                smsSender.sendSmsByCarrierApp(carrierPackage, new SmsSenderCallback(smsSender));
196            } else {
197                Rlog.v(TAG, "No carrier package.");
198                sendRawPdu(tracker);
199            }
200        } else {
201            Rlog.e(TAG, "GsmSMSDispatcher.sendText(): getSubmitPdu() returned null");
202        }
203    }
204
205    /** {@inheritDoc} */
206    @Override
207    protected void injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent) {
208        throw new IllegalStateException("This method must be called only on ImsSMSDispatcher");
209    }
210
211    /** {@inheritDoc} */
212    @Override
213    protected GsmAlphabet.TextEncodingDetails calculateLength(CharSequence messageBody,
214            boolean use7bitOnly) {
215        return SmsMessage.calculateLength(messageBody, use7bitOnly);
216    }
217
218    /** {@inheritDoc} */
219    @Override
220    protected SmsTracker getNewSubmitPduTracker(String destinationAddress, String scAddress,
221            String message, SmsHeader smsHeader, int encoding,
222            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart,
223            AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
224            String fullMessageText) {
225        SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
226                message, deliveryIntent != null, SmsHeader.toByteArray(smsHeader),
227                encoding, smsHeader.languageTable, smsHeader.languageShiftTable);
228        if (pdu != null) {
229            HashMap map =  getSmsTrackerMap(destinationAddress, scAddress,
230                    message, pdu);
231            return getSmsTracker(map, sentIntent,
232                    deliveryIntent, getFormat(), unsentPartCount, anyPartFailed, messageUri,
233                    smsHeader, !lastPart, fullMessageText, true /*isText*/,
234                    false /*persistMessage*/);
235        } else {
236            Rlog.e(TAG, "GsmSMSDispatcher.sendNewSubmitPdu(): getSubmitPdu() returned null");
237            return null;
238        }
239    }
240
241    @Override
242    protected void sendSubmitPdu(SmsTracker tracker) {
243        sendRawPdu(tracker);
244    }
245
246    /** {@inheritDoc} */
247    @Override
248    protected void sendSms(SmsTracker tracker) {
249        HashMap<String, Object> map = tracker.getData();
250
251        byte pdu[] = (byte[]) map.get("pdu");
252
253        if (tracker.mRetryCount > 0) {
254            Rlog.d(TAG, "sendSms: "
255                    + " mRetryCount=" + tracker.mRetryCount
256                    + " mMessageRef=" + tracker.mMessageRef
257                    + " SS=" + mPhone.getServiceState().getState());
258
259            // per TS 23.040 Section 9.2.3.6:  If TP-MTI SMS-SUBMIT (0x01) type
260            //   TP-RD (bit 2) is 1 for retry
261            //   and TP-MR is set to previously failed sms TP-MR
262            if (((0x01 & pdu[0]) == 0x01)) {
263                pdu[0] |= 0x04; // TP-RD
264                pdu[1] = (byte) tracker.mMessageRef; // TP-MR
265            }
266        }
267        Rlog.d(TAG, "sendSms: "
268                + " isIms()=" + isIms()
269                + " mRetryCount=" + tracker.mRetryCount
270                + " mImsRetry=" + tracker.mImsRetry
271                + " mMessageRef=" + tracker.mMessageRef
272                + " SS=" + mPhone.getServiceState().getState());
273
274        sendSmsByPstn(tracker);
275    }
276
277    /** {@inheritDoc} */
278    @Override
279    protected void sendSmsByPstn(SmsTracker tracker) {
280        int ss = mPhone.getServiceState().getState();
281        // if sms over IMS is not supported on data and voice is not available...
282        if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
283            tracker.onFailed(mContext, getNotInServiceError(ss), 0/*errorCode*/);
284            return;
285        }
286
287        HashMap<String, Object> map = tracker.getData();
288
289        byte smsc[] = (byte[]) map.get("smsc");
290        byte[] pdu = (byte[]) map.get("pdu");
291        Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
292
293        // sms over gsm is used:
294        //   if sms over IMS is not supported AND
295        //   this is not a retry case after sms over IMS failed
296        //     indicated by mImsRetry > 0
297        if (0 == tracker.mImsRetry && !isIms()) {
298            if (tracker.mRetryCount > 0) {
299                // per TS 23.040 Section 9.2.3.6:  If TP-MTI SMS-SUBMIT (0x01) type
300                //   TP-RD (bit 2) is 1 for retry
301                //   and TP-MR is set to previously failed sms TP-MR
302                if (((0x01 & pdu[0]) == 0x01)) {
303                    pdu[0] |= 0x04; // TP-RD
304                    pdu[1] = (byte) tracker.mMessageRef; // TP-MR
305                }
306            }
307            if (tracker.mRetryCount == 0 && tracker.mExpectMore) {
308                mCi.sendSMSExpectMore(IccUtils.bytesToHexString(smsc),
309                        IccUtils.bytesToHexString(pdu), reply);
310            } else {
311                mCi.sendSMS(IccUtils.bytesToHexString(smsc),
312                        IccUtils.bytesToHexString(pdu), reply);
313            }
314        } else {
315            mCi.sendImsGsmSms(IccUtils.bytesToHexString(smsc),
316                    IccUtils.bytesToHexString(pdu), tracker.mImsRetry,
317                    tracker.mMessageRef, reply);
318            // increment it here, so in case of SMS_FAIL_RETRY over IMS
319            // next retry will be sent using IMS request again.
320            tracker.mImsRetry++;
321        }
322    }
323
324    protected UiccCardApplication getUiccCardApplication() {
325            Rlog.d(TAG, "GsmSMSDispatcher: subId = " + mPhone.getSubId()
326                    + " slotId = " + mPhone.getPhoneId());
327                return mUiccController.getUiccCardApplication(mPhone.getPhoneId(),
328                        UiccController.APP_FAM_3GPP);
329    }
330
331    private void onUpdateIccAvailability() {
332        if (mUiccController == null ) {
333            return;
334        }
335
336        UiccCardApplication newUiccApplication = getUiccCardApplication();
337
338        UiccCardApplication app = mUiccApplication.get();
339        if (app != newUiccApplication) {
340            if (app != null) {
341                Rlog.d(TAG, "Removing stale icc objects.");
342                if (mIccRecords.get() != null) {
343                    mIccRecords.get().unregisterForNewSms(this);
344                }
345                mIccRecords.set(null);
346                mUiccApplication.set(null);
347            }
348            if (newUiccApplication != null) {
349                Rlog.d(TAG, "New Uicc application found");
350                mUiccApplication.set(newUiccApplication);
351                mIccRecords.set(newUiccApplication.getIccRecords());
352                if (mIccRecords.get() != null) {
353                    mIccRecords.get().registerForNewSms(this, EVENT_NEW_ICC_SMS, null);
354                }
355            }
356        }
357    }
358}
359