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