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.os.AsyncResult;
20import android.os.Message;
21import android.provider.Telephony.Sms.Intents;
22import android.telephony.Rlog;
23import android.telephony.ServiceState;
24import android.util.Pair;
25
26import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
27import com.android.internal.telephony.InboundSmsHandler;
28import com.android.internal.telephony.Phone;
29import com.android.internal.telephony.SmsConstants;
30import com.android.internal.telephony.SmsDispatchersController;
31import com.android.internal.telephony.SMSDispatcher;
32import com.android.internal.telephony.SmsHeader;
33import com.android.internal.telephony.SmsMessageBase;
34import com.android.internal.telephony.uicc.IccRecords;
35import com.android.internal.telephony.uicc.IccUtils;
36import com.android.internal.telephony.uicc.UiccCardApplication;
37import com.android.internal.telephony.uicc.UiccController;
38import com.android.internal.telephony.util.SMSDispatcherUtil;
39
40import java.util.HashMap;
41import java.util.concurrent.atomic.AtomicReference;
42
43public final class GsmSMSDispatcher extends SMSDispatcher {
44    private static final String TAG = "GsmSMSDispatcher";
45    protected UiccController mUiccController = null;
46    private AtomicReference<IccRecords> mIccRecords = new AtomicReference<IccRecords>();
47    private AtomicReference<UiccCardApplication> mUiccApplication =
48            new AtomicReference<UiccCardApplication>();
49    private GsmInboundSmsHandler mGsmInboundSmsHandler;
50
51    /** Status report received */
52    private static final int EVENT_NEW_SMS_STATUS_REPORT = 100;
53
54    public GsmSMSDispatcher(Phone phone, SmsDispatchersController smsDispatchersController,
55            GsmInboundSmsHandler gsmInboundSmsHandler) {
56        super(phone, smsDispatchersController);
57        mCi.setOnSmsStatus(this, EVENT_NEW_SMS_STATUS_REPORT, null);
58        mGsmInboundSmsHandler = gsmInboundSmsHandler;
59        mUiccController = UiccController.getInstance();
60        mUiccController.registerForIccChanged(this, EVENT_ICC_CHANGED, null);
61        Rlog.d(TAG, "GsmSMSDispatcher created");
62    }
63
64    @Override
65    public void dispose() {
66        super.dispose();
67        mCi.unSetOnSmsStatus(this);
68        mUiccController.unregisterForIccChanged(this);
69    }
70
71    @Override
72    protected String getFormat() {
73        return SmsConstants.FORMAT_3GPP;
74    }
75
76    /**
77     * Handles 3GPP format-specific events coming from the phone stack.
78     * Other events are handled by {@link SMSDispatcher#handleMessage}.
79     *
80     * @param msg the message to handle
81     */
82    @Override
83    public void handleMessage(Message msg) {
84        switch (msg.what) {
85        case EVENT_NEW_SMS_STATUS_REPORT:
86            handleStatusReport((AsyncResult) msg.obj);
87            break;
88
89        case EVENT_NEW_ICC_SMS:
90        // pass to InboundSmsHandler to process
91        mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, msg.obj);
92        break;
93
94        case EVENT_ICC_CHANGED:
95            onUpdateIccAvailability();
96            break;
97
98        default:
99            super.handleMessage(msg);
100        }
101    }
102
103    @Override
104    protected boolean shouldBlockSmsForEcbm() {
105        // There is no such thing as ECBM for GSM. This only applies to CDMA.
106        return false;
107    }
108
109    @Override
110    protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr,
111            String message, boolean statusReportRequested, SmsHeader smsHeader, int priority,
112            int validityPeriod) {
113        return SMSDispatcherUtil.getSubmitPduGsm(scAddr, destAddr, message, statusReportRequested,
114                validityPeriod);
115    }
116
117    @Override
118    protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr,
119            int destPort, byte[] message, boolean statusReportRequested) {
120        return SMSDispatcherUtil.getSubmitPduGsm(scAddr, destAddr, destPort, message,
121                statusReportRequested);
122    }
123
124    @Override
125    protected TextEncodingDetails calculateLength(CharSequence messageBody, boolean use7bitOnly) {
126        return SMSDispatcherUtil.calculateLengthGsm(messageBody, use7bitOnly);
127    }
128
129    /**
130     * Called when a status report is received.  This should correspond to
131     * a previously successful SEND.
132     *
133     * @param ar AsyncResult passed into the message handler.  ar.result should
134     *           be a String representing the status report PDU, as ASCII hex.
135     */
136    private void handleStatusReport(AsyncResult ar) {
137        byte[] pdu = (byte[]) ar.result;
138        SmsMessage sms = SmsMessage.newFromCDS(pdu);
139
140        if (sms != null) {
141            int messageRef = sms.mMessageRef;
142            for (int i = 0, count = deliveryPendingList.size(); i < count; i++) {
143                SmsTracker tracker = deliveryPendingList.get(i);
144                if (tracker.mMessageRef == messageRef) {
145                    Pair<Boolean, Boolean> result = mSmsDispatchersController.handleSmsStatusReport(
146                            tracker,
147                            getFormat(),
148                            pdu);
149                    if (result.second) {
150                        deliveryPendingList.remove(i);
151                    }
152                    // Only expect to see one tracker matching this messageref
153                    break;
154                }
155            }
156        }
157        mCi.acknowledgeLastIncomingGsmSms(true, Intents.RESULT_SMS_HANDLED, null);
158    }
159
160    /** {@inheritDoc} */
161    @Override
162    protected void sendSms(SmsTracker tracker) {
163        HashMap<String, Object> map = tracker.getData();
164
165        byte pdu[] = (byte[]) map.get("pdu");
166
167        if (tracker.mRetryCount > 0) {
168            Rlog.d(TAG, "sendSms: "
169                    + " mRetryCount=" + tracker.mRetryCount
170                    + " mMessageRef=" + tracker.mMessageRef
171                    + " SS=" + mPhone.getServiceState().getState());
172
173            // per TS 23.040 Section 9.2.3.6:  If TP-MTI SMS-SUBMIT (0x01) type
174            //   TP-RD (bit 2) is 1 for retry
175            //   and TP-MR is set to previously failed sms TP-MR
176            if (((0x01 & pdu[0]) == 0x01)) {
177                pdu[0] |= 0x04; // TP-RD
178                pdu[1] = (byte) tracker.mMessageRef; // TP-MR
179            }
180        }
181        Rlog.d(TAG, "sendSms: "
182                + " isIms()=" + isIms()
183                + " mRetryCount=" + tracker.mRetryCount
184                + " mImsRetry=" + tracker.mImsRetry
185                + " mMessageRef=" + tracker.mMessageRef
186                + " mUsesImsServiceForIms=" + tracker.mUsesImsServiceForIms
187                + " SS=" + mPhone.getServiceState().getState());
188
189        int ss = mPhone.getServiceState().getState();
190        // if sms over IMS is not supported on data and voice is not available...
191        if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
192            tracker.onFailed(mContext, getNotInServiceError(ss), 0/*errorCode*/);
193            return;
194        }
195
196        byte smsc[] = (byte[]) map.get("smsc");
197        Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker);
198
199        // sms over gsm is used:
200        //   if sms over IMS is not supported AND
201        //   this is not a retry case after sms over IMS failed
202        //     indicated by mImsRetry > 0 OR
203        //   this tracker uses ImsSmsDispatcher to handle SMS over IMS. This dispatcher has received
204        //     this message because the ImsSmsDispatcher has indicated that the message needs to
205        //     fall back to sending over CS.
206        if (0 == tracker.mImsRetry && !isIms() || tracker.mUsesImsServiceForIms) {
207            if (tracker.mRetryCount == 0 && tracker.mExpectMore) {
208                mCi.sendSMSExpectMore(IccUtils.bytesToHexString(smsc),
209                        IccUtils.bytesToHexString(pdu), reply);
210            } else {
211                mCi.sendSMS(IccUtils.bytesToHexString(smsc),
212                        IccUtils.bytesToHexString(pdu), reply);
213            }
214        } else {
215            mCi.sendImsGsmSms(IccUtils.bytesToHexString(smsc),
216                    IccUtils.bytesToHexString(pdu), tracker.mImsRetry,
217                    tracker.mMessageRef, reply);
218            // increment it here, so in case of SMS_FAIL_RETRY over IMS
219            // next retry will be sent using IMS request again.
220            tracker.mImsRetry++;
221        }
222    }
223
224    protected UiccCardApplication getUiccCardApplication() {
225            Rlog.d(TAG, "GsmSMSDispatcher: subId = " + mPhone.getSubId()
226                    + " slotId = " + mPhone.getPhoneId());
227                return mUiccController.getUiccCardApplication(mPhone.getPhoneId(),
228                        UiccController.APP_FAM_3GPP);
229    }
230
231    private void onUpdateIccAvailability() {
232        if (mUiccController == null ) {
233            return;
234        }
235
236        UiccCardApplication newUiccApplication = getUiccCardApplication();
237
238        UiccCardApplication app = mUiccApplication.get();
239        if (app != newUiccApplication) {
240            if (app != null) {
241                Rlog.d(TAG, "Removing stale icc objects.");
242                if (mIccRecords.get() != null) {
243                    mIccRecords.get().unregisterForNewSms(this);
244                }
245                mIccRecords.set(null);
246                mUiccApplication.set(null);
247            }
248            if (newUiccApplication != null) {
249                Rlog.d(TAG, "New Uicc application found");
250                mUiccApplication.set(newUiccApplication);
251                mIccRecords.set(newUiccApplication.getIccRecords());
252                if (mIccRecords.get() != null) {
253                    mIccRecords.get().registerForNewSms(this, EVENT_NEW_ICC_SMS, null);
254                }
255            }
256        }
257    }
258}
259