/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.telephony.gsm; import android.os.AsyncResult; import android.os.Message; import android.provider.Telephony.Sms.Intents; import android.telephony.Rlog; import android.telephony.ServiceState; import android.util.Pair; import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails; import com.android.internal.telephony.InboundSmsHandler; import com.android.internal.telephony.Phone; import com.android.internal.telephony.SmsConstants; import com.android.internal.telephony.SmsDispatchersController; import com.android.internal.telephony.SMSDispatcher; import com.android.internal.telephony.SmsHeader; import com.android.internal.telephony.SmsMessageBase; import com.android.internal.telephony.uicc.IccRecords; import com.android.internal.telephony.uicc.IccUtils; import com.android.internal.telephony.uicc.UiccCardApplication; import com.android.internal.telephony.uicc.UiccController; import com.android.internal.telephony.util.SMSDispatcherUtil; import java.util.HashMap; import java.util.concurrent.atomic.AtomicReference; public final class GsmSMSDispatcher extends SMSDispatcher { private static final String TAG = "GsmSMSDispatcher"; protected UiccController mUiccController = null; private AtomicReference mIccRecords = new AtomicReference(); private AtomicReference mUiccApplication = new AtomicReference(); private GsmInboundSmsHandler mGsmInboundSmsHandler; /** Status report received */ private static final int EVENT_NEW_SMS_STATUS_REPORT = 100; public GsmSMSDispatcher(Phone phone, SmsDispatchersController smsDispatchersController, GsmInboundSmsHandler gsmInboundSmsHandler) { super(phone, smsDispatchersController); mCi.setOnSmsStatus(this, EVENT_NEW_SMS_STATUS_REPORT, null); mGsmInboundSmsHandler = gsmInboundSmsHandler; mUiccController = UiccController.getInstance(); mUiccController.registerForIccChanged(this, EVENT_ICC_CHANGED, null); Rlog.d(TAG, "GsmSMSDispatcher created"); } @Override public void dispose() { super.dispose(); mCi.unSetOnSmsStatus(this); mUiccController.unregisterForIccChanged(this); } @Override protected String getFormat() { return SmsConstants.FORMAT_3GPP; } /** * Handles 3GPP format-specific events coming from the phone stack. * Other events are handled by {@link SMSDispatcher#handleMessage}. * * @param msg the message to handle */ @Override public void handleMessage(Message msg) { switch (msg.what) { case EVENT_NEW_SMS_STATUS_REPORT: handleStatusReport((AsyncResult) msg.obj); break; case EVENT_NEW_ICC_SMS: // pass to InboundSmsHandler to process mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_NEW_SMS, msg.obj); break; case EVENT_ICC_CHANGED: onUpdateIccAvailability(); break; default: super.handleMessage(msg); } } @Override protected boolean shouldBlockSmsForEcbm() { // There is no such thing as ECBM for GSM. This only applies to CDMA. return false; } @Override protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr, String message, boolean statusReportRequested, SmsHeader smsHeader, int priority, int validityPeriod) { return SMSDispatcherUtil.getSubmitPduGsm(scAddr, destAddr, message, statusReportRequested, validityPeriod); } @Override protected SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr, int destPort, byte[] message, boolean statusReportRequested) { return SMSDispatcherUtil.getSubmitPduGsm(scAddr, destAddr, destPort, message, statusReportRequested); } @Override protected TextEncodingDetails calculateLength(CharSequence messageBody, boolean use7bitOnly) { return SMSDispatcherUtil.calculateLengthGsm(messageBody, use7bitOnly); } /** * Called when a status report is received. This should correspond to * a previously successful SEND. * * @param ar AsyncResult passed into the message handler. ar.result should * be a String representing the status report PDU, as ASCII hex. */ private void handleStatusReport(AsyncResult ar) { byte[] pdu = (byte[]) ar.result; SmsMessage sms = SmsMessage.newFromCDS(pdu); if (sms != null) { int messageRef = sms.mMessageRef; for (int i = 0, count = deliveryPendingList.size(); i < count; i++) { SmsTracker tracker = deliveryPendingList.get(i); if (tracker.mMessageRef == messageRef) { Pair result = mSmsDispatchersController.handleSmsStatusReport( tracker, getFormat(), pdu); if (result.second) { deliveryPendingList.remove(i); } // Only expect to see one tracker matching this messageref break; } } } mCi.acknowledgeLastIncomingGsmSms(true, Intents.RESULT_SMS_HANDLED, null); } /** {@inheritDoc} */ @Override protected void sendSms(SmsTracker tracker) { HashMap map = tracker.getData(); byte pdu[] = (byte[]) map.get("pdu"); if (tracker.mRetryCount > 0) { Rlog.d(TAG, "sendSms: " + " mRetryCount=" + tracker.mRetryCount + " mMessageRef=" + tracker.mMessageRef + " SS=" + mPhone.getServiceState().getState()); // per TS 23.040 Section 9.2.3.6: If TP-MTI SMS-SUBMIT (0x01) type // TP-RD (bit 2) is 1 for retry // and TP-MR is set to previously failed sms TP-MR if (((0x01 & pdu[0]) == 0x01)) { pdu[0] |= 0x04; // TP-RD pdu[1] = (byte) tracker.mMessageRef; // TP-MR } } Rlog.d(TAG, "sendSms: " + " isIms()=" + isIms() + " mRetryCount=" + tracker.mRetryCount + " mImsRetry=" + tracker.mImsRetry + " mMessageRef=" + tracker.mMessageRef + " mUsesImsServiceForIms=" + tracker.mUsesImsServiceForIms + " SS=" + mPhone.getServiceState().getState()); int ss = mPhone.getServiceState().getState(); // if sms over IMS is not supported on data and voice is not available... if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) { tracker.onFailed(mContext, getNotInServiceError(ss), 0/*errorCode*/); return; } byte smsc[] = (byte[]) map.get("smsc"); Message reply = obtainMessage(EVENT_SEND_SMS_COMPLETE, tracker); // sms over gsm is used: // if sms over IMS is not supported AND // this is not a retry case after sms over IMS failed // indicated by mImsRetry > 0 OR // this tracker uses ImsSmsDispatcher to handle SMS over IMS. This dispatcher has received // this message because the ImsSmsDispatcher has indicated that the message needs to // fall back to sending over CS. if (0 == tracker.mImsRetry && !isIms() || tracker.mUsesImsServiceForIms) { if (tracker.mRetryCount == 0 && tracker.mExpectMore) { mCi.sendSMSExpectMore(IccUtils.bytesToHexString(smsc), IccUtils.bytesToHexString(pdu), reply); } else { mCi.sendSMS(IccUtils.bytesToHexString(smsc), IccUtils.bytesToHexString(pdu), reply); } } else { mCi.sendImsGsmSms(IccUtils.bytesToHexString(smsc), IccUtils.bytesToHexString(pdu), tracker.mImsRetry, tracker.mMessageRef, reply); // increment it here, so in case of SMS_FAIL_RETRY over IMS // next retry will be sent using IMS request again. tracker.mImsRetry++; } } protected UiccCardApplication getUiccCardApplication() { Rlog.d(TAG, "GsmSMSDispatcher: subId = " + mPhone.getSubId() + " slotId = " + mPhone.getPhoneId()); return mUiccController.getUiccCardApplication(mPhone.getPhoneId(), UiccController.APP_FAM_3GPP); } private void onUpdateIccAvailability() { if (mUiccController == null ) { return; } UiccCardApplication newUiccApplication = getUiccCardApplication(); UiccCardApplication app = mUiccApplication.get(); if (app != newUiccApplication) { if (app != null) { Rlog.d(TAG, "Removing stale icc objects."); if (mIccRecords.get() != null) { mIccRecords.get().unregisterForNewSms(this); } mIccRecords.set(null); mUiccApplication.set(null); } if (newUiccApplication != null) { Rlog.d(TAG, "New Uicc application found"); mUiccApplication.set(newUiccApplication); mIccRecords.set(newUiccApplication.getIccRecords()); if (mIccRecords.get() != null) { mIccRecords.get().registerForNewSms(this, EVENT_NEW_ICC_SMS, null); } } } } }