| /* |
| * 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; |
| |
| import static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PERIOD_NOT_SPECIFIED; |
| import static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PRIORITY_NOT_SPECIFIED; |
| import static com.android.internal.telephony.SmsResponse.NO_ERROR_CODE; |
| import static com.android.internal.telephony.cdma.sms.BearerData.ERROR_NONE; |
| import static com.android.internal.telephony.cdma.sms.BearerData.ERROR_TEMPORARY; |
| |
| import android.app.Activity; |
| import android.app.PendingIntent; |
| import android.app.PendingIntent.CanceledException; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.net.Uri; |
| import android.os.AsyncResult; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.os.UserManager; |
| import android.provider.Telephony.Sms; |
| import android.provider.Telephony.Sms.Intents; |
| import android.telephony.ServiceState; |
| import android.telephony.SmsManager; |
| import android.telephony.SmsMessage; |
| import android.util.Pair; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.telephony.cdma.CdmaInboundSmsHandler; |
| import com.android.internal.telephony.cdma.CdmaSMSDispatcher; |
| import com.android.internal.telephony.gsm.GsmInboundSmsHandler; |
| import com.android.internal.telephony.gsm.GsmSMSDispatcher; |
| import com.android.telephony.Rlog; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Map.Entry; |
| |
| /** |
| * |
| */ |
| public class SmsDispatchersController extends Handler { |
| private static final String TAG = "SmsDispatchersController"; |
| private static final boolean VDBG = false; // STOPSHIP if true |
| |
| /** Radio is ON */ |
| private static final int EVENT_RADIO_ON = 11; |
| |
| /** IMS registration/SMS format changed */ |
| private static final int EVENT_IMS_STATE_CHANGED = 12; |
| |
| /** Callback from RIL_REQUEST_IMS_REGISTRATION_STATE */ |
| private static final int EVENT_IMS_STATE_DONE = 13; |
| |
| /** Service state changed */ |
| private static final int EVENT_SERVICE_STATE_CHANGED = 14; |
| |
| /** Purge old message segments */ |
| private static final int EVENT_PARTIAL_SEGMENT_TIMER_EXPIRY = 15; |
| |
| /** User unlocked the device */ |
| private static final int EVENT_USER_UNLOCKED = 16; |
| |
| /** InboundSmsHandler exited WaitingState */ |
| protected static final int EVENT_SMS_HANDLER_EXITING_WAITING_STATE = 17; |
| |
| /** Delete any partial message segments after being IN_SERVICE for 1 day. */ |
| private static final long PARTIAL_SEGMENT_WAIT_DURATION = (long) (60 * 60 * 1000) * 24; |
| /** Constant for invalid time */ |
| private static final long INVALID_TIME = -1; |
| /** Time at which last IN_SERVICE event was received */ |
| private long mLastInServiceTime = INVALID_TIME; |
| /** Current IN_SERVICE duration */ |
| private long mCurrentWaitElapsedDuration = 0; |
| /** Time at which the current PARTIAL_SEGMENT_WAIT_DURATION timer was started */ |
| private long mCurrentWaitStartTime = INVALID_TIME; |
| |
| private SMSDispatcher mCdmaDispatcher; |
| private SMSDispatcher mGsmDispatcher; |
| private ImsSmsDispatcher mImsSmsDispatcher; |
| |
| private GsmInboundSmsHandler mGsmInboundSmsHandler; |
| private CdmaInboundSmsHandler mCdmaInboundSmsHandler; |
| |
| private Phone mPhone; |
| /** Outgoing message counter. Shared by all dispatchers. */ |
| private final SmsUsageMonitor mUsageMonitor; |
| private final CommandsInterface mCi; |
| private final Context mContext; |
| |
| /** true if IMS is registered and sms is supported, false otherwise.*/ |
| private boolean mIms = false; |
| private String mImsSmsFormat = SmsConstants.FORMAT_UNKNOWN; |
| |
| public SmsDispatchersController(Phone phone, SmsStorageMonitor storageMonitor, |
| SmsUsageMonitor usageMonitor) { |
| Rlog.d(TAG, "SmsDispatchersController created"); |
| |
| mContext = phone.getContext(); |
| mUsageMonitor = usageMonitor; |
| mCi = phone.mCi; |
| mPhone = phone; |
| |
| // Create dispatchers, inbound SMS handlers and |
| // broadcast undelivered messages in raw table. |
| mImsSmsDispatcher = new ImsSmsDispatcher(phone, this); |
| mCdmaDispatcher = new CdmaSMSDispatcher(phone, this); |
| mGsmInboundSmsHandler = GsmInboundSmsHandler.makeInboundSmsHandler(phone.getContext(), |
| storageMonitor, phone); |
| mCdmaInboundSmsHandler = CdmaInboundSmsHandler.makeInboundSmsHandler(phone.getContext(), |
| storageMonitor, phone, (CdmaSMSDispatcher) mCdmaDispatcher); |
| mGsmDispatcher = new GsmSMSDispatcher(phone, this, mGsmInboundSmsHandler); |
| SmsBroadcastUndelivered.initialize(phone.getContext(), |
| mGsmInboundSmsHandler, mCdmaInboundSmsHandler); |
| InboundSmsHandler.registerNewMessageNotificationActionHandler(phone.getContext()); |
| |
| mCi.registerForOn(this, EVENT_RADIO_ON, null); |
| mCi.registerForImsNetworkStateChanged(this, EVENT_IMS_STATE_CHANGED, null); |
| |
| UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); |
| if (userManager.isUserUnlocked()) { |
| if (VDBG) { |
| logd("SmsDispatchersController: user unlocked; registering for service" |
| + "state changed"); |
| } |
| mPhone.registerForServiceStateChanged(this, EVENT_SERVICE_STATE_CHANGED, null); |
| resetPartialSegmentWaitTimer(); |
| } else { |
| if (VDBG) { |
| logd("SmsDispatchersController: user locked; waiting for USER_UNLOCKED"); |
| } |
| IntentFilter userFilter = new IntentFilter(); |
| userFilter.addAction(Intent.ACTION_USER_UNLOCKED); |
| mContext.registerReceiver(mBroadcastReceiver, userFilter); |
| } |
| } |
| |
| private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(final Context context, Intent intent) { |
| Rlog.d(TAG, "Received broadcast " + intent.getAction()); |
| if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { |
| sendMessage(obtainMessage(EVENT_USER_UNLOCKED)); |
| } |
| } |
| }; |
| |
| public void dispose() { |
| mCi.unregisterForOn(this); |
| mCi.unregisterForImsNetworkStateChanged(this); |
| mPhone.unregisterForServiceStateChanged(this); |
| mGsmDispatcher.dispose(); |
| mCdmaDispatcher.dispose(); |
| mGsmInboundSmsHandler.dispose(); |
| mCdmaInboundSmsHandler.dispose(); |
| } |
| |
| /** |
| * Handles events coming from the phone stack. Overridden from handler. |
| * |
| * @param msg the message to handle |
| */ |
| @Override |
| public void handleMessage(Message msg) { |
| AsyncResult ar; |
| |
| switch (msg.what) { |
| case EVENT_RADIO_ON: |
| case EVENT_IMS_STATE_CHANGED: // received unsol |
| mCi.getImsRegistrationState(this.obtainMessage(EVENT_IMS_STATE_DONE)); |
| break; |
| |
| case EVENT_IMS_STATE_DONE: |
| ar = (AsyncResult) msg.obj; |
| |
| if (ar.exception == null) { |
| updateImsInfo(ar); |
| } else { |
| Rlog.e(TAG, "IMS State query failed with exp " |
| + ar.exception); |
| } |
| break; |
| |
| case EVENT_SERVICE_STATE_CHANGED: |
| case EVENT_SMS_HANDLER_EXITING_WAITING_STATE: |
| reevaluateTimerStatus(); |
| break; |
| |
| case EVENT_PARTIAL_SEGMENT_TIMER_EXPIRY: |
| handlePartialSegmentTimerExpiry((Long) msg.obj); |
| break; |
| |
| case EVENT_USER_UNLOCKED: |
| if (VDBG) { |
| logd("handleMessage: EVENT_USER_UNLOCKED"); |
| } |
| mPhone.registerForServiceStateChanged(this, EVENT_SERVICE_STATE_CHANGED, null); |
| resetPartialSegmentWaitTimer(); |
| break; |
| |
| default: |
| if (isCdmaMo()) { |
| mCdmaDispatcher.handleMessage(msg); |
| } else { |
| mGsmDispatcher.handleMessage(msg); |
| } |
| } |
| } |
| |
| private void reevaluateTimerStatus() { |
| long currentTime = System.currentTimeMillis(); |
| |
| // Remove unhandled timer expiry message. A new message will be posted if needed. |
| removeMessages(EVENT_PARTIAL_SEGMENT_TIMER_EXPIRY); |
| // Update timer duration elapsed time (add time since last IN_SERVICE to now). |
| // This is needed for IN_SERVICE as well as OUT_OF_SERVICE because same events can be |
| // received back to back |
| if (mLastInServiceTime != INVALID_TIME) { |
| mCurrentWaitElapsedDuration += (currentTime - mLastInServiceTime); |
| } |
| |
| if (VDBG) { |
| logd("reevaluateTimerStatus: currentTime: " + currentTime |
| + " mCurrentWaitElapsedDuration: " + mCurrentWaitElapsedDuration); |
| } |
| |
| if (mCurrentWaitElapsedDuration > PARTIAL_SEGMENT_WAIT_DURATION) { |
| // handle this event as timer expiry |
| handlePartialSegmentTimerExpiry(mCurrentWaitStartTime); |
| } else { |
| if (isInService()) { |
| handleInService(currentTime); |
| } else { |
| handleOutOfService(currentTime); |
| } |
| } |
| } |
| |
| private void handleInService(long currentTime) { |
| if (VDBG) { |
| logd("handleInService: timer expiry in " |
| + (PARTIAL_SEGMENT_WAIT_DURATION - mCurrentWaitElapsedDuration) + "ms"); |
| } |
| |
| // initialize mCurrentWaitStartTime if needed |
| if (mCurrentWaitStartTime == INVALID_TIME) mCurrentWaitStartTime = currentTime; |
| |
| // Post a message for timer expiry time. mCurrentWaitElapsedDuration is the duration already |
| // elapsed from the timer. |
| sendMessageDelayed( |
| obtainMessage(EVENT_PARTIAL_SEGMENT_TIMER_EXPIRY, mCurrentWaitStartTime), |
| PARTIAL_SEGMENT_WAIT_DURATION - mCurrentWaitElapsedDuration); |
| |
| // update mLastInServiceTime as the current time |
| mLastInServiceTime = currentTime; |
| } |
| |
| private void handleOutOfService(long currentTime) { |
| if (VDBG) { |
| logd("handleOutOfService: currentTime: " + currentTime |
| + " mCurrentWaitElapsedDuration: " + mCurrentWaitElapsedDuration); |
| } |
| |
| // mLastInServiceTime is not relevant now since state is OUT_OF_SERVICE; set it to INVALID |
| mLastInServiceTime = INVALID_TIME; |
| } |
| |
| private void handlePartialSegmentTimerExpiry(long waitTimerStart) { |
| if (mGsmInboundSmsHandler.getCurrentState().getName().equals("WaitingState") |
| || mCdmaInboundSmsHandler.getCurrentState().getName().equals("WaitingState")) { |
| logd("handlePartialSegmentTimerExpiry: ignoring timer expiry as InboundSmsHandler is" |
| + " in WaitingState"); |
| return; |
| } |
| |
| if (VDBG) { |
| logd("handlePartialSegmentTimerExpiry: calling scanRawTable()"); |
| } |
| // Timer expired. This indicates that device has been in service for |
| // PARTIAL_SEGMENT_WAIT_DURATION since waitTimerStart. Delete orphaned message segments |
| // older than waitTimerStart. |
| SmsBroadcastUndelivered.scanRawTable(mContext, mCdmaInboundSmsHandler, |
| mGsmInboundSmsHandler, waitTimerStart); |
| if (VDBG) { |
| logd("handlePartialSegmentTimerExpiry: scanRawTable() done"); |
| } |
| |
| resetPartialSegmentWaitTimer(); |
| } |
| |
| private void resetPartialSegmentWaitTimer() { |
| long currentTime = System.currentTimeMillis(); |
| |
| removeMessages(EVENT_PARTIAL_SEGMENT_TIMER_EXPIRY); |
| if (isInService()) { |
| if (VDBG) { |
| logd("resetPartialSegmentWaitTimer: currentTime: " + currentTime |
| + " IN_SERVICE"); |
| } |
| mCurrentWaitStartTime = currentTime; |
| mLastInServiceTime = currentTime; |
| sendMessageDelayed( |
| obtainMessage(EVENT_PARTIAL_SEGMENT_TIMER_EXPIRY, mCurrentWaitStartTime), |
| PARTIAL_SEGMENT_WAIT_DURATION); |
| } else { |
| if (VDBG) { |
| logd("resetPartialSegmentWaitTimer: currentTime: " + currentTime |
| + " not IN_SERVICE"); |
| } |
| mCurrentWaitStartTime = INVALID_TIME; |
| mLastInServiceTime = INVALID_TIME; |
| } |
| |
| mCurrentWaitElapsedDuration = 0; |
| } |
| |
| private boolean isInService() { |
| ServiceState serviceState = mPhone.getServiceState(); |
| return serviceState != null && serviceState.getState() == ServiceState.STATE_IN_SERVICE; |
| } |
| |
| private void setImsSmsFormat(int format) { |
| switch (format) { |
| case PhoneConstants.PHONE_TYPE_GSM: |
| mImsSmsFormat = SmsConstants.FORMAT_3GPP; |
| break; |
| case PhoneConstants.PHONE_TYPE_CDMA: |
| mImsSmsFormat = SmsConstants.FORMAT_3GPP2; |
| break; |
| default: |
| mImsSmsFormat = SmsConstants.FORMAT_UNKNOWN; |
| break; |
| } |
| } |
| |
| private void updateImsInfo(AsyncResult ar) { |
| int[] responseArray = (int[]) ar.result; |
| setImsSmsFormat(responseArray[1]); |
| mIms = responseArray[0] == 1 && !SmsConstants.FORMAT_UNKNOWN.equals(mImsSmsFormat); |
| Rlog.d(TAG, "IMS registration state: " + mIms + " format: " + mImsSmsFormat); |
| } |
| |
| /** |
| * Inject an SMS PDU into the android platform only if it is class 1. |
| * |
| * @param pdu is the byte array of pdu to be injected into android telephony layer |
| * @param format is the format of SMS pdu (3gpp or 3gpp2) |
| * @param callback if not NULL this callback is triggered when the message is successfully |
| * received by the android telephony layer. This callback is triggered at |
| * the same time an SMS received from radio is responded back. |
| */ |
| @VisibleForTesting |
| public void injectSmsPdu(byte[] pdu, String format, SmsInjectionCallback callback) { |
| // TODO We need to decide whether we should allow injecting GSM(3gpp) |
| // SMS pdus when the phone is camping on CDMA(3gpp2) network and vice versa. |
| android.telephony.SmsMessage msg = |
| android.telephony.SmsMessage.createFromPdu(pdu, format); |
| injectSmsPdu(msg, format, callback, false /* ignoreClass */); |
| } |
| |
| /** |
| * Inject an SMS PDU into the android platform. |
| * |
| * @param msg is the {@link SmsMessage} to be injected into android telephony layer |
| * @param format is the format of SMS pdu (3gpp or 3gpp2) |
| * @param callback if not NULL this callback is triggered when the message is successfully |
| * received by the android telephony layer. This callback is triggered at |
| * the same time an SMS received from radio is responded back. |
| * @param ignoreClass if set to false, this method will inject class 1 sms only. |
| */ |
| @VisibleForTesting |
| public void injectSmsPdu(SmsMessage msg, String format, SmsInjectionCallback callback, |
| boolean ignoreClass) { |
| Rlog.d(TAG, "SmsDispatchersController:injectSmsPdu"); |
| try { |
| if (msg == null) { |
| Rlog.e(TAG, "injectSmsPdu: createFromPdu returned null"); |
| callback.onSmsInjectedResult(Intents.RESULT_SMS_GENERIC_ERROR); |
| return; |
| } |
| |
| if (!ignoreClass |
| && msg.getMessageClass() != android.telephony.SmsMessage.MessageClass.CLASS_1) { |
| Rlog.e(TAG, "injectSmsPdu: not class 1"); |
| callback.onSmsInjectedResult(Intents.RESULT_SMS_GENERIC_ERROR); |
| return; |
| } |
| |
| AsyncResult ar = new AsyncResult(callback, msg, null); |
| |
| if (format.equals(SmsConstants.FORMAT_3GPP)) { |
| Rlog.i(TAG, "SmsDispatchersController:injectSmsText Sending msg=" + msg |
| + ", format=" + format + "to mGsmInboundSmsHandler"); |
| mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_INJECT_SMS, ar); |
| } else if (format.equals(SmsConstants.FORMAT_3GPP2)) { |
| Rlog.i(TAG, "SmsDispatchersController:injectSmsText Sending msg=" + msg |
| + ", format=" + format + "to mCdmaInboundSmsHandler"); |
| mCdmaInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_INJECT_SMS, ar); |
| } else { |
| // Invalid pdu format. |
| Rlog.e(TAG, "Invalid pdu format: " + format); |
| callback.onSmsInjectedResult(Intents.RESULT_SMS_GENERIC_ERROR); |
| } |
| } catch (Exception e) { |
| Rlog.e(TAG, "injectSmsPdu failed: ", e); |
| callback.onSmsInjectedResult(Intents.RESULT_SMS_GENERIC_ERROR); |
| } |
| } |
| |
| /** |
| * Retry the message along to the radio. |
| * |
| * @param tracker holds the SMS message to send |
| */ |
| public void sendRetrySms(SMSDispatcher.SmsTracker tracker) { |
| String oldFormat = tracker.mFormat; |
| boolean retryUsingImsService = false; |
| |
| if (!tracker.mUsesImsServiceForIms && mImsSmsDispatcher.isAvailable()) { |
| // If this tracker has not been handled by ImsSmsDispatcher yet and IMS Service is |
| // available now, retry this failed tracker using IMS Service. |
| retryUsingImsService = true; |
| } |
| |
| // If retryUsingImsService is true, newFormat will be IMS SMS format. Otherwise, newFormat |
| // will be based on voice technology. |
| String newFormat = |
| retryUsingImsService |
| ? mImsSmsDispatcher.getFormat() |
| : (PhoneConstants.PHONE_TYPE_CDMA == mPhone.getPhoneType()) |
| ? mCdmaDispatcher.getFormat() |
| : mGsmDispatcher.getFormat(); |
| |
| Rlog.d(TAG, "old format(" + oldFormat + ") ==> new format (" + newFormat + ")"); |
| if (!oldFormat.equals(newFormat)) { |
| // format didn't match, need to re-encode. |
| HashMap map = tracker.getData(); |
| |
| // to re-encode, fields needed are: scAddr, destAddr and text if originally sent as |
| // sendText or data and destPort if originally sent as sendData. |
| if (!(map.containsKey("scAddr") && map.containsKey("destAddr") |
| && (map.containsKey("text") |
| || (map.containsKey("data") && map.containsKey("destPort"))))) { |
| // should never come here... |
| Rlog.e(TAG, "sendRetrySms failed to re-encode per missing fields!"); |
| tracker.onFailed(mContext, SmsManager.RESULT_SMS_SEND_RETRY_FAILED, NO_ERROR_CODE); |
| return; |
| } |
| String scAddr = (String) map.get("scAddr"); |
| String destAddr = (String) map.get("destAddr"); |
| |
| SmsMessageBase.SubmitPduBase pdu = null; |
| // figure out from tracker if this was sendText/Data |
| if (map.containsKey("text")) { |
| Rlog.d(TAG, "sms failed was text"); |
| String text = (String) map.get("text"); |
| |
| if (isCdmaFormat(newFormat)) { |
| pdu = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu( |
| scAddr, destAddr, text, (tracker.mDeliveryIntent != null), null); |
| } else { |
| pdu = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu( |
| scAddr, destAddr, text, (tracker.mDeliveryIntent != null), null); |
| } |
| } else if (map.containsKey("data")) { |
| Rlog.d(TAG, "sms failed was data"); |
| byte[] data = (byte[]) map.get("data"); |
| Integer destPort = (Integer) map.get("destPort"); |
| |
| if (isCdmaFormat(newFormat)) { |
| pdu = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu( |
| scAddr, destAddr, destPort.intValue(), data, |
| (tracker.mDeliveryIntent != null)); |
| } else { |
| pdu = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu( |
| scAddr, destAddr, destPort.intValue(), data, |
| (tracker.mDeliveryIntent != null)); |
| } |
| } |
| |
| // replace old smsc and pdu with newly encoded ones |
| map.put("smsc", pdu.encodedScAddress); |
| map.put("pdu", pdu.encodedMessage); |
| tracker.mFormat = newFormat; |
| } |
| |
| SMSDispatcher dispatcher = |
| retryUsingImsService |
| ? mImsSmsDispatcher |
| : (isCdmaFormat(newFormat)) ? mCdmaDispatcher : mGsmDispatcher; |
| |
| dispatcher.sendSms(tracker); |
| } |
| |
| /** |
| * SMS over IMS is supported if IMS is registered and SMS is supported on IMS. |
| * |
| * @return true if SMS over IMS is supported via an IMS Service or mIms is true for the older |
| * implementation. Otherwise, false. |
| */ |
| public boolean isIms() { |
| return mImsSmsDispatcher.isAvailable() ? true : mIms; |
| } |
| |
| /** |
| * Gets SMS format supported on IMS. |
| * |
| * @return the SMS format from an IMS Service if available. Otherwise, mImsSmsFormat for the |
| * older implementation. |
| */ |
| public String getImsSmsFormat() { |
| return mImsSmsDispatcher.isAvailable() ? mImsSmsDispatcher.getFormat() : mImsSmsFormat; |
| } |
| |
| /** |
| * Determines whether or not to use CDMA format for MO SMS. |
| * If SMS over IMS is supported, then format is based on IMS SMS format, |
| * otherwise format is based on current phone type. |
| * |
| * @return true if Cdma format should be used for MO SMS, false otherwise. |
| */ |
| protected boolean isCdmaMo() { |
| if (!isIms()) { |
| // IMS is not registered, use Voice technology to determine SMS format. |
| return (PhoneConstants.PHONE_TYPE_CDMA == mPhone.getPhoneType()); |
| } |
| // IMS is registered with SMS support |
| return isCdmaFormat(getImsSmsFormat()); |
| } |
| |
| /** |
| * Determines whether or not format given is CDMA format. |
| * |
| * @param format |
| * @return true if format given is CDMA format, false otherwise. |
| */ |
| public boolean isCdmaFormat(String format) { |
| return (mCdmaDispatcher.getFormat().equals(format)); |
| } |
| |
| /** |
| * Send a data based SMS to a specific application port. |
| * |
| * @param callingPackage the package name of the calling app |
| * @param destAddr the address to send the message to |
| * @param scAddr is the service center address or null to use |
| * the current default SMSC |
| * @param destPort the port to deliver the message to |
| * @param data the body of the message to send |
| * @param sentIntent if not NULL this <code>PendingIntent</code> is |
| * broadcast when the message is successfully sent, or failed. |
| * The result code will be <code>Activity.RESULT_OK<code> for success, |
| * or one of these errors:<br> |
| * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> |
| * <code>RESULT_ERROR_RADIO_OFF</code><br> |
| * <code>RESULT_ERROR_NULL_PDU</code><br> |
| * <code>RESULT_ERROR_NO_SERVICE</code><br>. |
| * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include |
| * the extra "errorCode" containing a radio technology specific value, |
| * generally only useful for troubleshooting.<br> |
| * The per-application based SMS control checks sentIntent. If sentIntent |
| * is NULL the caller will be checked against all unknown applications, |
| * which cause smaller number of SMS to be sent in checking period. |
| * @param deliveryIntent if not NULL this <code>PendingIntent</code> is |
| * broadcast when the message is delivered to the recipient. The |
| * raw pdu of the status report is in the extended data ("pdu"). |
| */ |
| protected void sendData(String callingPackage, String destAddr, String scAddr, int destPort, |
| byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent, boolean isForVvm) { |
| if (mImsSmsDispatcher.isAvailable()) { |
| mImsSmsDispatcher.sendData(callingPackage, destAddr, scAddr, destPort, data, sentIntent, |
| deliveryIntent, isForVvm); |
| } else if (isCdmaMo()) { |
| mCdmaDispatcher.sendData(callingPackage, destAddr, scAddr, destPort, data, sentIntent, |
| deliveryIntent, isForVvm); |
| } else { |
| mGsmDispatcher.sendData(callingPackage, destAddr, scAddr, destPort, data, sentIntent, |
| deliveryIntent, isForVvm); |
| } |
| } |
| |
| /** |
| * Send a text based SMS. |
| * @param destAddr the address to send the message to |
| * @param scAddr is the service center address or null to use |
| * the current default SMSC |
| * @param text the body of the message to send |
| * @param sentIntent if not NULL this <code>PendingIntent</code> is |
| * broadcast when the message is successfully sent, or failed. |
| * The result code will be <code>Activity.RESULT_OK<code> for success, |
| * or one of these errors:<br> |
| * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> |
| * <code>RESULT_ERROR_RADIO_OFF</code><br> |
| * <code>RESULT_ERROR_NULL_PDU</code><br> |
| * <code>RESULT_ERROR_NO_SERVICE</code><br>. |
| * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include |
| * the extra "errorCode" containing a radio technology specific value, |
| * generally only useful for troubleshooting.<br> |
| * The per-application based SMS control checks sentIntent. If sentIntent |
| * is NULL the caller will be checked against all unknown applications, |
| * which cause smaller number of SMS to be sent in checking period. |
| * @param deliveryIntent if not NULL this <code>PendingIntent</code> is |
| * broadcast when the message is delivered to the recipient. The |
| * @param messageUri optional URI of the message if it is already stored in the system |
| * @param callingPkg the calling package name |
| * @param persistMessage whether to save the sent message into SMS DB for a |
| * non-default SMS app. |
| * @param priority Priority level of the message |
| * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1 |
| * --------------------------------- |
| * PRIORITY | Level of Priority |
| * --------------------------------- |
| * '00' | Normal |
| * '01' | Interactive |
| * '10' | Urgent |
| * '11' | Emergency |
| * ---------------------------------- |
| * Any Other values included Negative considered as Invalid Priority Indicator of the message. |
| * @param expectMore is a boolean to indicate the sending messages through same link or not. |
| * @param validityPeriod Validity Period of the message in mins. |
| * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1. |
| * Validity Period(Minimum) -> 5 mins |
| * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks). |
| * Any Other values included Negative considered as Invalid Validity Period of the message. |
| */ |
| public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent, |
| PendingIntent deliveryIntent, Uri messageUri, String callingPkg, boolean persistMessage, |
| int priority, boolean expectMore, int validityPeriod, boolean isForVvm, |
| long messageId) { |
| if (mImsSmsDispatcher.isAvailable() || mImsSmsDispatcher.isEmergencySmsSupport(destAddr)) { |
| mImsSmsDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent, |
| messageUri, callingPkg, persistMessage, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, |
| false /*expectMore*/, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, isForVvm, |
| messageId); |
| } else { |
| if (isCdmaMo()) { |
| mCdmaDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent, |
| messageUri, callingPkg, persistMessage, priority, expectMore, |
| validityPeriod, isForVvm, messageId); |
| } else { |
| mGsmDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent, |
| messageUri, callingPkg, persistMessage, priority, expectMore, |
| validityPeriod, isForVvm, messageId); |
| } |
| } |
| } |
| |
| /** |
| * Send a multi-part text based SMS. |
| * @param destAddr the address to send the message to |
| * @param scAddr is the service center address or null to use |
| * the current default SMSC |
| * @param parts an <code>ArrayList</code> of strings that, in order, |
| * comprise the original message |
| * @param sentIntents if not null, an <code>ArrayList</code> of |
| * <code>PendingIntent</code>s (one for each message part) that is |
| * broadcast when the corresponding message part has been sent. |
| * The result code will be <code>Activity.RESULT_OK<code> for success, |
| * or one of these errors: |
| * <code>RESULT_ERROR_GENERIC_FAILURE</code> |
| * <code>RESULT_ERROR_RADIO_OFF</code> |
| * <code>RESULT_ERROR_NULL_PDU</code> |
| * <code>RESULT_ERROR_NO_SERVICE</code>. |
| * The per-application based SMS control checks sentIntent. If sentIntent |
| * is NULL the caller will be checked against all unknown applications, |
| * which cause smaller number of SMS to be sent in checking period. |
| * @param deliveryIntents if not null, an <code>ArrayList</code> of |
| * <code>PendingIntent</code>s (one for each message part) that is |
| * broadcast when the corresponding message part has been delivered |
| * to the recipient. The raw pdu of the status report is in the |
| * @param messageUri optional URI of the message if it is already stored in the system |
| * @param callingPkg the calling package name |
| * @param persistMessage whether to save the sent message into SMS DB for a |
| * non-default SMS app. |
| * @param priority Priority level of the message |
| * Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1 |
| * --------------------------------- |
| * PRIORITY | Level of Priority |
| * --------------------------------- |
| * '00' | Normal |
| * '01' | Interactive |
| * '10' | Urgent |
| * '11' | Emergency |
| * ---------------------------------- |
| * Any Other values included Negative considered as Invalid Priority Indicator of the message. |
| * @param expectMore is a boolean to indicate the sending messages through same link or not. |
| * @param validityPeriod Validity Period of the message in mins. |
| * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1. |
| * Validity Period(Minimum) -> 5 mins |
| * Validity Period(Maximum) -> 635040 mins(i.e.63 weeks). |
| * Any Other values included Negative considered as Invalid Validity Period of the message. |
| * @param messageId An id that uniquely identifies the message requested to be sent. |
| * Used for logging and diagnostics purposes. The id may be 0. |
| * |
| */ |
| protected void sendMultipartText(String destAddr, String scAddr, |
| ArrayList<String> parts, ArrayList<PendingIntent> sentIntents, |
| ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg, |
| boolean persistMessage, int priority, boolean expectMore, int validityPeriod, |
| long messageId) { |
| if (mImsSmsDispatcher.isAvailable()) { |
| mImsSmsDispatcher.sendMultipartText(destAddr, scAddr, parts, sentIntents, |
| deliveryIntents, messageUri, callingPkg, persistMessage, |
| SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, |
| false /*expectMore*/, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, |
| messageId); |
| } else { |
| if (isCdmaMo()) { |
| mCdmaDispatcher.sendMultipartText(destAddr, scAddr, parts, sentIntents, |
| deliveryIntents, messageUri, callingPkg, persistMessage, priority, |
| expectMore, validityPeriod, messageId); |
| } else { |
| mGsmDispatcher.sendMultipartText(destAddr, scAddr, parts, sentIntents, |
| deliveryIntents, messageUri, callingPkg, persistMessage, priority, |
| expectMore, validityPeriod, messageId); |
| } |
| } |
| } |
| |
| /** |
| * Returns the premium SMS permission for the specified package. If the package has never |
| * been seen before, the default {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_UNKNOWN} |
| * will be returned. |
| * @param packageName the name of the package to query permission |
| * @return one of {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_UNKNOWN}, |
| * {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ASK_USER}, |
| * {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_NEVER_ALLOW}, or |
| * {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW} |
| */ |
| public int getPremiumSmsPermission(String packageName) { |
| return mUsageMonitor.getPremiumSmsPermission(packageName); |
| } |
| |
| /** |
| * Sets the premium SMS permission for the specified package and save the value asynchronously |
| * to persistent storage. |
| * @param packageName the name of the package to set permission |
| * @param permission one of {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ASK_USER}, |
| * {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_NEVER_ALLOW}, or |
| * {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW} |
| */ |
| public void setPremiumSmsPermission(String packageName, int permission) { |
| mUsageMonitor.setPremiumSmsPermission(packageName, permission); |
| } |
| |
| public SmsUsageMonitor getUsageMonitor() { |
| return mUsageMonitor; |
| } |
| |
| /** |
| * Handles the sms status report for the sent sms through ImsSmsDispatcher. Carriers can send |
| * the report over CS even if the previously submitted SMS-SUBMIT was sent over IMS. For this |
| * case, finds a corresponding tracker from the tracker map in ImsSmsDispatcher and handles it. |
| * |
| * @param messageRef the TP-MR of the previously submitted SMS-SUBMIT in the report. |
| * @param format the format. |
| * @param pdu the pdu of the report. |
| */ |
| public void handleSentOverImsStatusReport(int messageRef, String format, byte[] pdu) { |
| for (Entry<Integer, SMSDispatcher.SmsTracker> entry : |
| mImsSmsDispatcher.mTrackers.entrySet()) { |
| int token = entry.getKey(); |
| SMSDispatcher.SmsTracker tracker = entry.getValue(); |
| if (tracker.mMessageRef == messageRef) { |
| Pair<Boolean, Boolean> result = handleSmsStatusReport(tracker, format, pdu); |
| if (result.second) { |
| mImsSmsDispatcher.mTrackers.remove(token); |
| } |
| return; |
| } |
| } |
| } |
| |
| /** |
| * Triggers the correct method for handling the sms status report based on the format. |
| * |
| * @param tracker the sms tracker. |
| * @param format the format. |
| * @param pdu the pdu of the report. |
| * @return a Pair in which the first boolean is whether the report was handled successfully |
| * or not and the second boolean is whether processing the sms is complete and the |
| * tracker no longer need to be kept track of, false if we should expect more callbacks |
| * and the tracker should be kept. |
| */ |
| public Pair<Boolean, Boolean> handleSmsStatusReport(SMSDispatcher.SmsTracker tracker, |
| String format, byte[] pdu) { |
| if (isCdmaFormat(format)) { |
| return handleCdmaStatusReport(tracker, format, pdu); |
| } else { |
| return handleGsmStatusReport(tracker, format, pdu); |
| } |
| } |
| |
| private Pair<Boolean, Boolean> handleCdmaStatusReport(SMSDispatcher.SmsTracker tracker, |
| String format, byte[] pdu) { |
| com.android.internal.telephony.cdma.SmsMessage sms = |
| com.android.internal.telephony.cdma.SmsMessage.createFromPdu(pdu); |
| boolean complete = false; |
| boolean success = false; |
| if (sms != null) { |
| // The status is composed of an error class (bits 25-24) and a status code (bits 23-16). |
| int errorClass = (sms.getStatus() >> 24) & 0x03; |
| if (errorClass != ERROR_TEMPORARY) { |
| // Update the message status (COMPLETE or FAILED) |
| tracker.updateSentMessageStatus( |
| mContext, |
| (errorClass == ERROR_NONE) ? Sms.STATUS_COMPLETE : Sms.STATUS_FAILED); |
| complete = true; |
| } |
| success = triggerDeliveryIntent(tracker, format, pdu); |
| } |
| return new Pair(success, complete); |
| } |
| |
| private Pair<Boolean, Boolean> handleGsmStatusReport(SMSDispatcher.SmsTracker tracker, |
| String format, byte[] pdu) { |
| com.android.internal.telephony.gsm.SmsMessage sms = |
| com.android.internal.telephony.gsm.SmsMessage.newFromCDS(pdu); |
| boolean complete = false; |
| boolean success = false; |
| if (sms != null) { |
| int tpStatus = sms.getStatus(); |
| if(tpStatus >= Sms.STATUS_FAILED || tpStatus < Sms.STATUS_PENDING ) { |
| // Update the message status (COMPLETE or FAILED) |
| tracker.updateSentMessageStatus(mContext, tpStatus); |
| complete = true; |
| } |
| success = triggerDeliveryIntent(tracker, format, pdu); |
| } |
| return new Pair(success, complete); |
| } |
| |
| private boolean triggerDeliveryIntent(SMSDispatcher.SmsTracker tracker, String format, |
| byte[] pdu) { |
| PendingIntent intent = tracker.mDeliveryIntent; |
| Intent fillIn = new Intent(); |
| fillIn.putExtra("pdu", pdu); |
| fillIn.putExtra("format", format); |
| try { |
| intent.send(mContext, Activity.RESULT_OK, fillIn); |
| return true; |
| } catch (CanceledException ex) { |
| return false; |
| } |
| } |
| |
| |
| public interface SmsInjectionCallback { |
| void onSmsInjectedResult(int result); |
| } |
| |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| mGsmInboundSmsHandler.dump(fd, pw, args); |
| mCdmaInboundSmsHandler.dump(fd, pw, args); |
| } |
| |
| private void logd(String msg) { |
| Rlog.d(TAG, msg); |
| } |
| } |