| /* |
| * 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.app.Activity; |
| import android.app.PendingIntent; |
| import android.app.PendingIntent.CanceledException; |
| import android.content.Intent; |
| import android.net.Uri; |
| import android.os.AsyncResult; |
| import android.os.Message; |
| import android.provider.Telephony.Sms; |
| import android.provider.Telephony.Sms.Intents; |
| import android.telephony.Rlog; |
| import android.telephony.ServiceState; |
| |
| import com.android.internal.telephony.GsmAlphabet; |
| import com.android.internal.telephony.ImsSMSDispatcher; |
| import com.android.internal.telephony.InboundSmsHandler; |
| import com.android.internal.telephony.PhoneBase; |
| import com.android.internal.telephony.SMSDispatcher; |
| import com.android.internal.telephony.SmsConstants; |
| import com.android.internal.telephony.SmsHeader; |
| import com.android.internal.telephony.SmsUsageMonitor; |
| 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 java.util.HashMap; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| public final class GsmSMSDispatcher extends SMSDispatcher { |
| private static final String TAG = "GsmSMSDispatcher"; |
| private static final boolean VDBG = false; |
| protected UiccController mUiccController = null; |
| private AtomicReference<IccRecords> mIccRecords = new AtomicReference<IccRecords>(); |
| private AtomicReference<UiccCardApplication> mUiccApplication = |
| new AtomicReference<UiccCardApplication>(); |
| private GsmInboundSmsHandler mGsmInboundSmsHandler; |
| |
| /** Status report received */ |
| private static final int EVENT_NEW_SMS_STATUS_REPORT = 100; |
| |
| public GsmSMSDispatcher(PhoneBase phone, SmsUsageMonitor usageMonitor, |
| ImsSMSDispatcher imsSMSDispatcher, |
| GsmInboundSmsHandler gsmInboundSmsHandler) { |
| super(phone, usageMonitor, imsSMSDispatcher); |
| 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); |
| } |
| } |
| |
| /** |
| * 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) { |
| String pduString = (String) ar.result; |
| SmsMessage sms = SmsMessage.newFromCDS(pduString); |
| |
| if (sms != null) { |
| int tpStatus = sms.getStatus(); |
| int messageRef = sms.mMessageRef; |
| for (int i = 0, count = deliveryPendingList.size(); i < count; i++) { |
| SmsTracker tracker = deliveryPendingList.get(i); |
| if (tracker.mMessageRef == messageRef) { |
| // Found it. Remove from list and broadcast. |
| if(tpStatus >= Sms.STATUS_FAILED || tpStatus < Sms.STATUS_PENDING ) { |
| deliveryPendingList.remove(i); |
| // Update the message status (COMPLETE or FAILED) |
| tracker.updateSentMessageStatus(mContext, tpStatus); |
| } |
| PendingIntent intent = tracker.mDeliveryIntent; |
| Intent fillIn = new Intent(); |
| fillIn.putExtra("pdu", IccUtils.hexStringToBytes(pduString)); |
| fillIn.putExtra("format", getFormat()); |
| try { |
| intent.send(mContext, Activity.RESULT_OK, fillIn); |
| } catch (CanceledException ex) {} |
| |
| // Only expect to see one tracker matching this messageref |
| break; |
| } |
| } |
| } |
| mCi.acknowledgeLastIncomingGsmSms(true, Intents.RESULT_SMS_HANDLED, null); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| protected void sendData(String destAddr, String scAddr, int destPort, |
| byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) { |
| SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu( |
| scAddr, destAddr, destPort, data, (deliveryIntent != null)); |
| if (pdu != null) { |
| HashMap map = getSmsTrackerMap(destAddr, scAddr, destPort, data, pdu); |
| SmsTracker tracker = getSmsTracker(map, sentIntent, deliveryIntent, getFormat(), |
| null /*messageUri*/, false /*isExpectMore*/, null /*fullMessageText*/, |
| false /*isText*/, true /*persistMessage*/); |
| |
| String carrierPackage = getCarrierAppPackageName(); |
| if (carrierPackage != null) { |
| Rlog.d(TAG, "Found carrier package."); |
| DataSmsSender smsSender = new DataSmsSender(tracker); |
| smsSender.sendSmsByCarrierApp(carrierPackage, new SmsSenderCallback(smsSender)); |
| } else { |
| Rlog.v(TAG, "No carrier package."); |
| sendRawPdu(tracker); |
| } |
| } else { |
| Rlog.e(TAG, "GsmSMSDispatcher.sendData(): getSubmitPdu() returned null"); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| protected void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent, |
| PendingIntent deliveryIntent, Uri messageUri, String callingPkg, |
| boolean persistMessage) { |
| SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu( |
| scAddr, destAddr, text, (deliveryIntent != null)); |
| if (pdu != null) { |
| HashMap map = getSmsTrackerMap(destAddr, scAddr, text, pdu); |
| SmsTracker tracker = getSmsTracker(map, sentIntent, deliveryIntent, getFormat(), |
| messageUri, false /*isExpectMore*/, text /*fullMessageText*/, true /*isText*/, |
| persistMessage); |
| |
| String carrierPackage = getCarrierAppPackageName(); |
| if (carrierPackage != null) { |
| Rlog.d(TAG, "Found carrier package."); |
| TextSmsSender smsSender = new TextSmsSender(tracker); |
| smsSender.sendSmsByCarrierApp(carrierPackage, new SmsSenderCallback(smsSender)); |
| } else { |
| Rlog.v(TAG, "No carrier package."); |
| sendRawPdu(tracker); |
| } |
| } else { |
| Rlog.e(TAG, "GsmSMSDispatcher.sendText(): getSubmitPdu() returned null"); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| protected void injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent) { |
| throw new IllegalStateException("This method must be called only on ImsSMSDispatcher"); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| protected GsmAlphabet.TextEncodingDetails calculateLength(CharSequence messageBody, |
| boolean use7bitOnly) { |
| return SmsMessage.calculateLength(messageBody, use7bitOnly); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| protected SmsTracker getNewSubmitPduTracker(String destinationAddress, String scAddress, |
| String message, SmsHeader smsHeader, int encoding, |
| PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart, |
| AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri, |
| String fullMessageText) { |
| SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(scAddress, destinationAddress, |
| message, deliveryIntent != null, SmsHeader.toByteArray(smsHeader), |
| encoding, smsHeader.languageTable, smsHeader.languageShiftTable); |
| if (pdu != null) { |
| HashMap map = getSmsTrackerMap(destinationAddress, scAddress, |
| message, pdu); |
| return getSmsTracker(map, sentIntent, |
| deliveryIntent, getFormat(), unsentPartCount, anyPartFailed, messageUri, |
| smsHeader, !lastPart, fullMessageText, true /*isText*/, |
| false /*persistMessage*/); |
| } else { |
| Rlog.e(TAG, "GsmSMSDispatcher.sendNewSubmitPdu(): getSubmitPdu() returned null"); |
| return null; |
| } |
| } |
| |
| @Override |
| protected void sendSubmitPdu(SmsTracker tracker) { |
| sendRawPdu(tracker); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| protected void sendSms(SmsTracker tracker) { |
| HashMap<String, Object> map = tracker.mData; |
| |
| 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 |
| + " SS=" + mPhone.getServiceState().getState()); |
| |
| sendSmsByPstn(tracker); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| protected void sendSmsByPstn(SmsTracker tracker) { |
| 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; |
| } |
| |
| HashMap<String, Object> map = tracker.mData; |
| |
| byte smsc[] = (byte[]) map.get("smsc"); |
| byte[] pdu = (byte[]) map.get("pdu"); |
| 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 |
| if (0 == tracker.mImsRetry && !isIms()) { |
| if (tracker.mRetryCount > 0) { |
| // 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 |
| } |
| } |
| 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); |
| } |
| } |
| } |
| } |
| } |