| /* |
| * 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 android.Manifest.permission.SEND_SMS_NO_CONFIRMATION; |
| import static android.telephony.SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE; |
| import static android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE; |
| import static android.telephony.SmsManager.RESULT_ERROR_LIMIT_EXCEEDED; |
| import static android.telephony.SmsManager.RESULT_ERROR_NONE; |
| import static android.telephony.SmsManager.RESULT_ERROR_NO_SERVICE; |
| import static android.telephony.SmsManager.RESULT_ERROR_NULL_PDU; |
| import static android.telephony.SmsManager.RESULT_ERROR_RADIO_OFF; |
| import static android.telephony.SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED; |
| import static android.telephony.SmsManager.RESULT_ERROR_SHORT_CODE_NOT_ALLOWED; |
| |
| 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 android.annotation.Nullable; |
| import android.annotation.UnsupportedAppUsage; |
| import android.annotation.UserIdInt; |
| import android.app.Activity; |
| import android.app.AlertDialog; |
| import android.app.PendingIntent; |
| import android.app.PendingIntent.CanceledException; |
| import android.content.ContentResolver; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageItemInfo; |
| import android.content.pm.PackageManager; |
| import android.content.res.Resources; |
| import android.database.ContentObserver; |
| import android.net.Uri; |
| import android.os.AsyncResult; |
| import android.os.Binder; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.os.PersistableBundle; |
| import android.os.Process; |
| import android.os.UserHandle; |
| import android.provider.Settings; |
| import android.provider.Telephony; |
| import android.provider.Telephony.Sms; |
| import android.service.carrier.CarrierMessagingService; |
| import android.service.carrier.CarrierMessagingServiceWrapper; |
| import android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallbackWrapper; |
| import android.telephony.CarrierConfigManager; |
| import android.telephony.PhoneNumberUtils; |
| import android.telephony.Rlog; |
| import android.telephony.ServiceState; |
| import android.telephony.SmsManager; |
| import android.telephony.TelephonyManager; |
| import android.text.Html; |
| import android.text.Spanned; |
| import android.text.TextUtils; |
| import android.util.EventLog; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.WindowManager; |
| import android.widget.Button; |
| import android.widget.CheckBox; |
| import android.widget.CompoundButton; |
| import android.widget.TextView; |
| |
| import com.android.internal.R; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails; |
| import com.android.internal.telephony.cdma.sms.UserData; |
| import com.android.internal.telephony.uicc.UiccCard; |
| import com.android.internal.telephony.uicc.UiccController; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Random; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| public abstract class SMSDispatcher extends Handler { |
| static final String TAG = "SMSDispatcher"; // accessed from inner class |
| static final boolean DBG = false; |
| private static final String SEND_NEXT_MSG_EXTRA = "SendNextMsg"; |
| protected static final String MAP_KEY_PDU = "pdu"; |
| protected static final String MAP_KEY_SMSC = "smsc"; |
| protected static final String MAP_KEY_DEST_ADDR = "destAddr"; |
| protected static final String MAP_KEY_SC_ADDR = "scAddr"; |
| protected static final String MAP_KEY_DEST_PORT = "destPort"; |
| protected static final String MAP_KEY_DATA = "data"; |
| protected static final String MAP_KEY_TEXT = "text"; |
| |
| private static final int PREMIUM_RULE_USE_SIM = 1; |
| private static final int PREMIUM_RULE_USE_NETWORK = 2; |
| private static final int PREMIUM_RULE_USE_BOTH = 3; |
| private final AtomicInteger mPremiumSmsRule = new AtomicInteger(PREMIUM_RULE_USE_SIM); |
| private final SettingsObserver mSettingsObserver; |
| |
| /** SMS send complete. */ |
| protected static final int EVENT_SEND_SMS_COMPLETE = 2; |
| |
| /** Retry sending a previously failed SMS message */ |
| private static final int EVENT_SEND_RETRY = 3; |
| |
| /** Confirmation required for sending a large number of messages. */ |
| private static final int EVENT_SEND_LIMIT_REACHED_CONFIRMATION = 4; |
| |
| /** Send the user confirmed SMS */ |
| static final int EVENT_SEND_CONFIRMED_SMS = 5; // accessed from inner class |
| |
| /** Don't send SMS (user did not confirm). */ |
| static final int EVENT_STOP_SENDING = 6; // accessed from inner class |
| |
| /** Don't send SMS for this app (User had already denied eariler.) */ |
| static final int EVENT_SENDING_NOT_ALLOWED = 7; |
| |
| /** Confirmation required for third-party apps sending to an SMS short code. */ |
| private static final int EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE = 8; |
| |
| /** Confirmation required for third-party apps sending to an SMS short code. */ |
| private static final int EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE = 9; |
| |
| /** Handle status report from {@code CdmaInboundSmsHandler}. */ |
| protected static final int EVENT_HANDLE_STATUS_REPORT = 10; |
| |
| // other |
| protected static final int EVENT_NEW_ICC_SMS = 14; |
| protected static final int EVENT_ICC_CHANGED = 15; |
| protected static final int EVENT_GET_IMS_SERVICE = 16; |
| |
| |
| @UnsupportedAppUsage |
| protected Phone mPhone; |
| @UnsupportedAppUsage |
| protected final Context mContext; |
| @UnsupportedAppUsage |
| protected final ContentResolver mResolver; |
| @UnsupportedAppUsage |
| protected final CommandsInterface mCi; |
| @UnsupportedAppUsage |
| protected final TelephonyManager mTelephonyManager; |
| |
| /** Maximum number of times to retry sending a failed SMS. */ |
| private static final int MAX_SEND_RETRIES = 3; |
| /** Delay before next send attempt on a failed SMS, in milliseconds. */ |
| private static final int SEND_RETRY_DELAY = 2000; |
| /** Message sending queue limit */ |
| private static final int MO_MSG_QUEUE_LIMIT = 5; |
| |
| /** |
| * Message reference for a CONCATENATED_8_BIT_REFERENCE or |
| * CONCATENATED_16_BIT_REFERENCE message set. Should be |
| * incremented for each set of concatenated messages. |
| * Static field shared by all dispatcher objects. |
| */ |
| private static int sConcatenatedRef = new Random().nextInt(256); |
| |
| protected SmsDispatchersController mSmsDispatchersController; |
| |
| /** Number of outgoing SmsTrackers waiting for user confirmation. */ |
| private int mPendingTrackerCount; |
| |
| /* Flags indicating whether the current device allows sms service */ |
| protected boolean mSmsCapable = true; |
| protected boolean mSmsSendDisabled; |
| |
| @UnsupportedAppUsage |
| protected static int getNextConcatenatedRef() { |
| sConcatenatedRef += 1; |
| return sConcatenatedRef; |
| } |
| |
| /** |
| * Create a new SMS dispatcher. |
| * @param phone the Phone to use |
| */ |
| protected SMSDispatcher(Phone phone, SmsDispatchersController smsDispatchersController) { |
| mPhone = phone; |
| mSmsDispatchersController = smsDispatchersController; |
| mContext = phone.getContext(); |
| mResolver = mContext.getContentResolver(); |
| mCi = phone.mCi; |
| mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); |
| mSettingsObserver = new SettingsObserver(this, mPremiumSmsRule, mContext); |
| mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor( |
| Settings.Global.SMS_SHORT_CODE_RULE), false, mSettingsObserver); |
| |
| mSmsCapable = mContext.getResources().getBoolean( |
| com.android.internal.R.bool.config_sms_capable); |
| mSmsSendDisabled = !mTelephonyManager.getSmsSendCapableForPhone( |
| mPhone.getPhoneId(), mSmsCapable); |
| Rlog.d(TAG, "SMSDispatcher: ctor mSmsCapable=" + mSmsCapable + " format=" + getFormat() |
| + " mSmsSendDisabled=" + mSmsSendDisabled); |
| } |
| |
| /** |
| * Observe the secure setting for updated premium sms determination rules |
| */ |
| private static class SettingsObserver extends ContentObserver { |
| private final AtomicInteger mPremiumSmsRule; |
| private final Context mContext; |
| SettingsObserver(Handler handler, AtomicInteger premiumSmsRule, Context context) { |
| super(handler); |
| mPremiumSmsRule = premiumSmsRule; |
| mContext = context; |
| onChange(false); // load initial value; |
| } |
| |
| @Override |
| public void onChange(boolean selfChange) { |
| mPremiumSmsRule.set(Settings.Global.getInt(mContext.getContentResolver(), |
| Settings.Global.SMS_SHORT_CODE_RULE, PREMIUM_RULE_USE_SIM)); |
| } |
| } |
| |
| /** Unregister for incoming SMS events. */ |
| @UnsupportedAppUsage |
| public void dispose() { |
| mContext.getContentResolver().unregisterContentObserver(mSettingsObserver); |
| } |
| |
| /** |
| * The format of the message PDU in the associated broadcast intent. |
| * This will be either "3gpp" for GSM/UMTS/LTE messages in 3GPP format |
| * or "3gpp2" for CDMA/LTE messages in 3GPP2 format. |
| * |
| * Note: All applications which handle incoming SMS messages by processing the |
| * SMS_RECEIVED_ACTION broadcast intent MUST pass the "format" extra from the intent |
| * into the new methods in {@link android.telephony.SmsMessage} which take an |
| * extra format parameter. This is required in order to correctly decode the PDU on |
| * devices which require support for both 3GPP and 3GPP2 formats at the same time, |
| * such as CDMA/LTE devices and GSM/CDMA world phones. |
| * |
| * @return the format of the message PDU |
| */ |
| protected abstract String getFormat(); |
| |
| /** |
| * Pass the Message object to subclass to handle. Currently used to pass CDMA status reports |
| * from {@link com.android.internal.telephony.cdma.CdmaInboundSmsHandler}. |
| * @param o the SmsMessage containing the status report |
| */ |
| protected void handleStatusReport(Object o) { |
| Rlog.d(TAG, "handleStatusReport() called with no subclass."); |
| } |
| |
| /* TODO: Need to figure out how to keep track of status report routing in a |
| * persistent manner. If the phone process restarts (reboot or crash), |
| * we will lose this list and any status reports that come in after |
| * will be dropped. |
| */ |
| /** Sent messages awaiting a delivery status report. */ |
| @UnsupportedAppUsage |
| protected final ArrayList<SmsTracker> deliveryPendingList = new ArrayList<SmsTracker>(); |
| |
| /** |
| * Handles events coming from the phone stack. Overridden from handler. |
| * |
| * @param msg the message to handle |
| */ |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case EVENT_SEND_SMS_COMPLETE: |
| // An outbound SMS has been successfully transferred, or failed. |
| handleSendComplete((AsyncResult) msg.obj); |
| break; |
| |
| case EVENT_SEND_RETRY: |
| Rlog.d(TAG, "SMS retry.."); |
| sendRetrySms((SmsTracker) msg.obj); |
| break; |
| |
| case EVENT_SEND_LIMIT_REACHED_CONFIRMATION: |
| handleReachSentLimit((SmsTracker[]) (msg.obj)); |
| break; |
| |
| case EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE: |
| handleConfirmShortCode(false, (SmsTracker[]) (msg.obj)); |
| break; |
| |
| case EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE: |
| handleConfirmShortCode(true, (SmsTracker[]) (msg.obj)); |
| break; |
| |
| case EVENT_SEND_CONFIRMED_SMS: |
| { |
| SmsTracker[] trackers = (SmsTracker[]) msg.obj; |
| for (SmsTracker tracker : trackers) { |
| sendSms(tracker); |
| } |
| mPendingTrackerCount--; |
| break; |
| } |
| |
| case EVENT_SENDING_NOT_ALLOWED: |
| { |
| SmsTracker[] trackers = (SmsTracker[]) msg.obj; |
| Rlog.d(TAG, "SMSDispatcher: EVENT_SENDING_NOT_ALLOWED - " |
| + "sending SHORT_CODE_NEVER_ALLOWED error code."); |
| handleSmsTrackersFailure( |
| trackers, RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED, NO_ERROR_CODE); |
| break; |
| } |
| |
| case EVENT_STOP_SENDING: |
| { |
| SmsTracker[] trackers = (SmsTracker[]) msg.obj; |
| int error; |
| if (msg.arg1 == ConfirmDialogListener.SHORT_CODE_MSG) { |
| if (msg.arg2 == ConfirmDialogListener.NEVER_ALLOW) { |
| error = RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED; |
| Rlog.d(TAG, "SMSDispatcher: EVENT_STOP_SENDING - " |
| + "sending SHORT_CODE_NEVER_ALLOWED error code."); |
| } else { |
| error = RESULT_ERROR_SHORT_CODE_NOT_ALLOWED; |
| Rlog.d(TAG, "SMSDispatcher: EVENT_STOP_SENDING - " |
| + "sending SHORT_CODE_NOT_ALLOWED error code."); |
| } |
| } else if (msg.arg1 == ConfirmDialogListener.RATE_LIMIT) { |
| error = RESULT_ERROR_LIMIT_EXCEEDED; |
| Rlog.d(TAG, "SMSDispatcher: EVENT_STOP_SENDING - " |
| + "sending LIMIT_EXCEEDED error code."); |
| } else { |
| error = RESULT_ERROR_GENERIC_FAILURE; |
| Rlog.e(TAG, "SMSDispatcher: EVENT_STOP_SENDING - unexpected cases."); |
| } |
| |
| handleSmsTrackersFailure(trackers, error, NO_ERROR_CODE); |
| mPendingTrackerCount--; |
| break; |
| } |
| |
| case EVENT_HANDLE_STATUS_REPORT: |
| handleStatusReport(msg.obj); |
| break; |
| |
| default: |
| Rlog.e(TAG, "handleMessage() ignoring message of unexpected type " + msg.what); |
| } |
| } |
| |
| /** |
| * Use the carrier messaging service to send a data or text SMS. |
| */ |
| protected abstract class SmsSender extends CarrierMessagingServiceWrapper { |
| protected final SmsTracker mTracker; |
| // Initialized in sendSmsByCarrierApp |
| protected volatile SmsSenderCallback mSenderCallback; |
| |
| protected SmsSender(SmsTracker tracker) { |
| mTracker = tracker; |
| } |
| |
| public void sendSmsByCarrierApp(String carrierPackageName, |
| SmsSenderCallback senderCallback) { |
| mSenderCallback = senderCallback; |
| if (!bindToCarrierMessagingService(mContext, carrierPackageName)) { |
| Rlog.e(TAG, "bindService() for carrier messaging service failed"); |
| mSenderCallback.onSendSmsComplete( |
| CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK, |
| 0 /* messageRef */); |
| } else { |
| Rlog.d(TAG, "bindService() for carrier messaging service succeeded"); |
| } |
| } |
| } |
| |
| private static int getSendSmsFlag(@Nullable PendingIntent deliveryIntent) { |
| if (deliveryIntent == null) { |
| return 0; |
| } |
| return CarrierMessagingService.SEND_FLAG_REQUEST_DELIVERY_STATUS; |
| } |
| |
| /** |
| * Use the carrier messaging service to send a text SMS. |
| */ |
| protected final class TextSmsSender extends SmsSender { |
| public TextSmsSender(SmsTracker tracker) { |
| super(tracker); |
| } |
| |
| @Override |
| public void onServiceReady() { |
| HashMap<String, Object> map = mTracker.getData(); |
| String text = (String) map.get(MAP_KEY_TEXT); |
| |
| if (text != null) { |
| try { |
| sendTextSms(text, getSubId(), |
| mTracker.mDestAddress, getSendSmsFlag(mTracker.mDeliveryIntent), |
| mSenderCallback); |
| } catch (RuntimeException e) { |
| Rlog.e(TAG, "Exception sending the SMS: " + e); |
| mSenderCallback.onSendSmsComplete( |
| CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK, |
| 0 /* messageRef */); |
| } |
| } else { |
| mSenderCallback.onSendSmsComplete( |
| CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK, |
| 0 /* messageRef */); |
| } |
| } |
| } |
| |
| /** |
| * Use the carrier messaging service to send a data SMS. |
| */ |
| protected final class DataSmsSender extends SmsSender { |
| public DataSmsSender(SmsTracker tracker) { |
| super(tracker); |
| } |
| |
| @Override |
| public void onServiceReady() { |
| HashMap<String, Object> map = mTracker.getData(); |
| byte[] data = (byte[]) map.get(MAP_KEY_DATA); |
| int destPort = (int) map.get(MAP_KEY_DEST_PORT); |
| |
| if (data != null) { |
| try { |
| sendDataSms(data, getSubId(), |
| mTracker.mDestAddress, destPort, |
| getSendSmsFlag(mTracker.mDeliveryIntent), mSenderCallback); |
| } catch (RuntimeException e) { |
| Rlog.e(TAG, "Exception sending the SMS: " + e); |
| mSenderCallback.onSendSmsComplete( |
| CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK, |
| 0 /* messageRef */); |
| } |
| } else { |
| mSenderCallback.onSendSmsComplete( |
| CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK, |
| 0 /* messageRef */); |
| } |
| } |
| } |
| |
| /** |
| * Callback for TextSmsSender and DataSmsSender from the carrier messaging service. |
| * Once the result is ready, the carrier messaging service connection is disposed. |
| */ |
| protected final class SmsSenderCallback extends |
| CarrierMessagingServiceWrapper.CarrierMessagingCallbackWrapper { |
| private final SmsSender mSmsSender; |
| |
| public SmsSenderCallback(SmsSender smsSender) { |
| mSmsSender = smsSender; |
| } |
| |
| /** |
| * This method should be called only once. |
| */ |
| @Override |
| public void onSendSmsComplete(int result, int messageRef) { |
| checkCallerIsPhoneOrCarrierApp(); |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| mSmsSender.disposeConnection(mContext); |
| processSendSmsResponse(mSmsSender.mTracker, result, messageRef); |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| |
| @Override |
| public void onSendMultipartSmsComplete(int result, int[] messageRefs) { |
| Rlog.e(TAG, "Unexpected onSendMultipartSmsComplete call with result: " + result); |
| } |
| |
| @Override |
| public void onFilterComplete(int result) { |
| Rlog.e(TAG, "Unexpected onFilterComplete call with result: " + result); |
| } |
| |
| @Override |
| public void onSendMmsComplete(int result, byte[] sendConfPdu) { |
| Rlog.e(TAG, "Unexpected onSendMmsComplete call with result: " + result); |
| } |
| |
| @Override |
| public void onDownloadMmsComplete(int result) { |
| Rlog.e(TAG, "Unexpected onDownloadMmsComplete call with result: " + result); |
| } |
| } |
| |
| @UnsupportedAppUsage |
| private void processSendSmsResponse(SmsTracker tracker, int result, int messageRef) { |
| if (tracker == null) { |
| Rlog.e(TAG, "processSendSmsResponse: null tracker"); |
| return; |
| } |
| |
| SmsResponse smsResponse = new SmsResponse(messageRef, null /* ackPdu */, NO_ERROR_CODE); |
| |
| switch (result) { |
| case CarrierMessagingService.SEND_STATUS_OK: |
| Rlog.d(TAG, "Sending SMS by IP succeeded."); |
| sendMessage(obtainMessage(EVENT_SEND_SMS_COMPLETE, |
| new AsyncResult(tracker, |
| smsResponse, |
| null /* exception*/ ))); |
| break; |
| case CarrierMessagingService.SEND_STATUS_ERROR: |
| Rlog.d(TAG, "Sending SMS by IP failed."); |
| sendMessage(obtainMessage(EVENT_SEND_SMS_COMPLETE, |
| new AsyncResult(tracker, smsResponse, |
| new CommandException(CommandException.Error.GENERIC_FAILURE)))); |
| break; |
| case CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK: |
| Rlog.d(TAG, "Sending SMS by IP failed. Retry on carrier network."); |
| sendSubmitPdu(tracker); |
| break; |
| default: |
| Rlog.d(TAG, "Unknown result " + result + " Retry on carrier network."); |
| sendSubmitPdu(tracker); |
| } |
| } |
| |
| /** |
| * Use the carrier messaging service to send a multipart text SMS. |
| */ |
| private final class MultipartSmsSender extends CarrierMessagingServiceWrapper { |
| private final List<String> mParts; |
| public final SmsTracker[] mTrackers; |
| // Initialized in sendSmsByCarrierApp |
| private volatile MultipartSmsSenderCallback mSenderCallback; |
| |
| MultipartSmsSender(ArrayList<String> parts, SmsTracker[] trackers) { |
| mParts = parts; |
| mTrackers = trackers; |
| } |
| |
| @UnsupportedAppUsage |
| void sendSmsByCarrierApp(String carrierPackageName, |
| MultipartSmsSenderCallback senderCallback) { |
| mSenderCallback = senderCallback; |
| if (!bindToCarrierMessagingService(mContext, carrierPackageName)) { |
| Rlog.e(TAG, "bindService() for carrier messaging service failed"); |
| mSenderCallback.onSendMultipartSmsComplete( |
| CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK, |
| null /* smsResponse */); |
| } else { |
| Rlog.d(TAG, "bindService() for carrier messaging service succeeded"); |
| } |
| } |
| |
| @Override |
| public void onServiceReady() { |
| try { |
| sendMultipartTextSms( |
| mParts, getSubId(), mTrackers[0].mDestAddress, |
| getSendSmsFlag(mTrackers[0].mDeliveryIntent), mSenderCallback); |
| } catch (RuntimeException e) { |
| Rlog.e(TAG, "Exception sending the SMS: " + e); |
| mSenderCallback.onSendMultipartSmsComplete( |
| CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK, |
| null /* smsResponse */); |
| } |
| } |
| } |
| |
| /** |
| * Callback for MultipartSmsSender from the carrier messaging service. |
| * Once the result is ready, the carrier messaging service connection is disposed. |
| */ |
| private final class MultipartSmsSenderCallback extends CarrierMessagingCallbackWrapper { |
| private final MultipartSmsSender mSmsSender; |
| |
| MultipartSmsSenderCallback(MultipartSmsSender smsSender) { |
| mSmsSender = smsSender; |
| } |
| |
| @Override |
| public void onSendSmsComplete(int result, int messageRef) { |
| Rlog.e(TAG, "Unexpected onSendSmsComplete call with result: " + result); |
| } |
| |
| /** |
| * This method should be called only once. |
| */ |
| @Override |
| public void onSendMultipartSmsComplete(int result, int[] messageRefs) { |
| mSmsSender.disposeConnection(mContext); |
| |
| if (mSmsSender.mTrackers == null) { |
| Rlog.e(TAG, "Unexpected onSendMultipartSmsComplete call with null trackers."); |
| return; |
| } |
| |
| checkCallerIsPhoneOrCarrierApp(); |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| for (int i = 0; i < mSmsSender.mTrackers.length; i++) { |
| int messageRef = 0; |
| if (messageRefs != null && messageRefs.length > i) { |
| messageRef = messageRefs[i]; |
| } |
| processSendSmsResponse(mSmsSender.mTrackers[i], result, messageRef); |
| } |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| |
| @Override |
| public void onFilterComplete(int result) { |
| Rlog.e(TAG, "Unexpected onFilterComplete call with result: " + result); |
| } |
| |
| @Override |
| public void onSendMmsComplete(int result, byte[] sendConfPdu) { |
| Rlog.e(TAG, "Unexpected onSendMmsComplete call with result: " + result); |
| } |
| |
| @Override |
| public void onDownloadMmsComplete(int result) { |
| Rlog.e(TAG, "Unexpected onDownloadMmsComplete call with result: " + result); |
| } |
| } |
| |
| /** Send a single SMS PDU. */ |
| @UnsupportedAppUsage |
| private void sendSubmitPdu(SmsTracker tracker) { |
| sendSubmitPdu(new SmsTracker[] {tracker}); |
| } |
| |
| /** Send a multi-part SMS PDU. Usually just calls {@link sendRawPdu}. */ |
| private void sendSubmitPdu(SmsTracker[] trackers) { |
| if (shouldBlockSmsForEcbm()) { |
| Rlog.d(TAG, "Block SMS in Emergency Callback mode"); |
| handleSmsTrackersFailure(trackers, RESULT_ERROR_NO_SERVICE, NO_ERROR_CODE); |
| } else { |
| sendRawPdu(trackers); |
| } |
| } |
| |
| /** |
| * @return true if MO SMS should be blocked for Emergency Callback Mode. |
| */ |
| protected abstract boolean shouldBlockSmsForEcbm(); |
| |
| /** |
| * Called when SMS send completes. Broadcasts a sentIntent on success. |
| * On failure, either sets up retries or broadcasts a sentIntent with |
| * the failure in the result code. |
| * |
| * @param ar AsyncResult passed into the message handler. ar.result should |
| * an SmsResponse instance if send was successful. ar.userObj |
| * should be an SmsTracker instance. |
| */ |
| protected void handleSendComplete(AsyncResult ar) { |
| SmsTracker tracker = (SmsTracker) ar.userObj; |
| PendingIntent sentIntent = tracker.mSentIntent; |
| |
| if (ar.result != null) { |
| tracker.mMessageRef = ((SmsResponse)ar.result).mMessageRef; |
| } else { |
| Rlog.d(TAG, "SmsResponse was null"); |
| } |
| |
| if (ar.exception == null) { |
| if (DBG) Rlog.d(TAG, "SMS send complete. Broadcasting intent: " + sentIntent); |
| |
| if (tracker.mDeliveryIntent != null) { |
| // Expecting a status report. Add it to the list. |
| deliveryPendingList.add(tracker); |
| } |
| tracker.onSent(mContext); |
| mPhone.notifySmsSent(tracker.mDestAddress); |
| } else { |
| if (DBG) Rlog.d(TAG, "SMS send failed"); |
| |
| int ss = mPhone.getServiceState().getState(); |
| |
| if ( tracker.mImsRetry > 0 && ss != ServiceState.STATE_IN_SERVICE) { |
| // This is retry after failure over IMS but voice is not available. |
| // Set retry to max allowed, so no retry is sent and |
| // cause RESULT_ERROR_GENERIC_FAILURE to be returned to app. |
| tracker.mRetryCount = MAX_SEND_RETRIES; |
| |
| Rlog.d(TAG, "handleSendComplete: Skipping retry: " |
| +" isIms()="+isIms() |
| +" mRetryCount="+tracker.mRetryCount |
| +" mImsRetry="+tracker.mImsRetry |
| +" mMessageRef="+tracker.mMessageRef |
| +" 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), NO_ERROR_CODE); |
| } else if ((((CommandException)(ar.exception)).getCommandError() |
| == CommandException.Error.SMS_FAIL_RETRY) && |
| tracker.mRetryCount < MAX_SEND_RETRIES) { |
| // Retry after a delay if needed. |
| // TODO: According to TS 23.040, 9.2.3.6, we should resend |
| // with the same TP-MR as the failed message, and |
| // TP-RD set to 1. However, we don't have a means of |
| // knowing the MR for the failed message (EF_SMSstatus |
| // may or may not have the MR corresponding to this |
| // message, depending on the failure). Also, in some |
| // implementations this retry is handled by the baseband. |
| tracker.mRetryCount++; |
| Message retryMsg = obtainMessage(EVENT_SEND_RETRY, tracker); |
| sendMessageDelayed(retryMsg, SEND_RETRY_DELAY); |
| } else { |
| int errorCode = NO_ERROR_CODE; |
| if (ar.result != null) { |
| errorCode = ((SmsResponse)ar.result).mErrorCode; |
| } |
| int error = RESULT_ERROR_GENERIC_FAILURE; |
| if (((CommandException)(ar.exception)).getCommandError() |
| == CommandException.Error.FDN_CHECK_FAILURE) { |
| error = RESULT_ERROR_FDN_CHECK_FAILURE; |
| } |
| tracker.onFailed(mContext, error, errorCode); |
| } |
| } |
| } |
| |
| /** |
| * Handles outbound message when the phone is not in service. |
| * |
| * @param ss Current service state. Valid values are: |
| * OUT_OF_SERVICE |
| * EMERGENCY_ONLY |
| * POWER_OFF |
| * @param sentIntent the PendingIntent to send the error to |
| */ |
| protected static void handleNotInService(int ss, PendingIntent sentIntent) { |
| if (sentIntent != null) { |
| try { |
| if (ss == ServiceState.STATE_POWER_OFF) { |
| sentIntent.send(RESULT_ERROR_RADIO_OFF); |
| } else { |
| sentIntent.send(RESULT_ERROR_NO_SERVICE); |
| } |
| } catch (CanceledException ex) { |
| Rlog.e(TAG, "Failed to send result"); |
| } |
| } |
| } |
| |
| /** |
| * @param ss service state |
| * @return The result error based on input service state for not in service error |
| */ |
| protected static int getNotInServiceError(int ss) { |
| if (ss == ServiceState.STATE_POWER_OFF) { |
| return RESULT_ERROR_RADIO_OFF; |
| } |
| return RESULT_ERROR_NO_SERVICE; |
| } |
| |
| /** |
| * 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"). |
| */ |
| @UnsupportedAppUsage |
| protected void sendData(String callingPackage, String destAddr, String scAddr, int destPort, |
| byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent, boolean isForVvm) { |
| SmsMessageBase.SubmitPduBase pdu = getSubmitPdu( |
| scAddr, destAddr, destPort, data, (deliveryIntent != null)); |
| if (pdu != null) { |
| HashMap map = getSmsTrackerMap(destAddr, scAddr, destPort, data, pdu); |
| SmsTracker tracker = getSmsTracker(callingPackage, map, sentIntent, deliveryIntent, |
| getFormat(), null /*messageUri*/, false /*expectMore*/, |
| null /*fullMessageText*/, false /*isText*/, |
| true /*persistMessage*/, isForVvm); |
| |
| if (!sendSmsByCarrierApp(true /* isDataSms */, tracker)) { |
| sendSubmitPdu(tracker); |
| } |
| } else { |
| Rlog.e(TAG, "SMSDispatcher.sendData(): getSubmitPdu() returned null"); |
| triggerSentIntentForFailure(sentIntent); |
| } |
| } |
| |
| /** |
| * 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) { |
| Rlog.d(TAG, "sendText"); |
| SmsMessageBase.SubmitPduBase pdu = getSubmitPdu( |
| scAddr, destAddr, text, (deliveryIntent != null), null, priority, validityPeriod); |
| if (pdu != null) { |
| HashMap map = getSmsTrackerMap(destAddr, scAddr, text, pdu); |
| SmsTracker tracker = getSmsTracker(callingPkg, map, sentIntent, deliveryIntent, |
| getFormat(), messageUri, expectMore, text, true /*isText*/, |
| persistMessage, priority, validityPeriod, isForVvm); |
| |
| if (!sendSmsByCarrierApp(false /* isDataSms */, tracker)) { |
| sendSubmitPdu(tracker); |
| } |
| } else { |
| Rlog.e(TAG, "SmsDispatcher.sendText(): getSubmitPdu() returned null"); |
| triggerSentIntentForFailure(sentIntent); |
| } |
| } |
| |
| private void triggerSentIntentForFailure(PendingIntent sentIntent) { |
| if (sentIntent != null) { |
| try { |
| sentIntent.send(RESULT_ERROR_GENERIC_FAILURE); |
| } catch (CanceledException ex) { |
| Rlog.e(TAG, "Intent has been canceled!"); |
| } |
| } |
| } |
| |
| private void triggerSentIntentForFailure(List<PendingIntent> sentIntents) { |
| if (sentIntents == null) { |
| return; |
| } |
| |
| for (PendingIntent sentIntent : sentIntents) { |
| triggerSentIntentForFailure(sentIntent); |
| } |
| } |
| |
| private boolean sendSmsByCarrierApp(boolean isDataSms, SmsTracker tracker ) { |
| String carrierPackage = getCarrierAppPackageName(); |
| if (carrierPackage != null) { |
| Rlog.d(TAG, "Found carrier package."); |
| SmsSender smsSender; |
| if (isDataSms) { |
| smsSender = new DataSmsSender(tracker); |
| } else { |
| smsSender = new TextSmsSender(tracker); |
| } |
| smsSender.sendSmsByCarrierApp(carrierPackage, new SmsSenderCallback(smsSender)); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| protected abstract SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr, |
| String message, boolean statusReportRequested, SmsHeader smsHeader, |
| int priority, int validityPeriod); |
| |
| protected abstract SmsMessageBase.SubmitPduBase getSubmitPdu(String scAddr, String destAddr, |
| int destPort, byte[] message, boolean statusReportRequested); |
| |
| /** |
| * Calculate the number of septets needed to encode the message. This function should only be |
| * called for individual segments of multipart message. |
| * |
| * @param messageBody the message to encode |
| * @param use7bitOnly ignore (but still count) illegal characters if true |
| * @return TextEncodingDetails |
| */ |
| @UnsupportedAppUsage |
| protected abstract TextEncodingDetails calculateLength(CharSequence messageBody, |
| boolean use7bitOnly); |
| |
| /** |
| * 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. |
| */ |
| @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) |
| public 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) { |
| final String fullMessageText = getMultipartMessageText(parts); |
| int refNumber = getNextConcatenatedRef() & 0x00FF; |
| int encoding = SmsConstants.ENCODING_UNKNOWN; |
| int msgCount = parts.size(); |
| if (msgCount < 1) { |
| triggerSentIntentForFailure(sentIntents); |
| return; |
| } |
| |
| TextEncodingDetails[] encodingForParts = new TextEncodingDetails[msgCount]; |
| for (int i = 0; i < msgCount; i++) { |
| TextEncodingDetails details = calculateLength(parts.get(i), false); |
| if (encoding != details.codeUnitSize |
| && (encoding == SmsConstants.ENCODING_UNKNOWN |
| || encoding == SmsConstants.ENCODING_7BIT)) { |
| encoding = details.codeUnitSize; |
| } |
| encodingForParts[i] = details; |
| } |
| |
| SmsTracker[] trackers = new SmsTracker[msgCount]; |
| |
| // States to track at the message level (for all parts) |
| final AtomicInteger unsentPartCount = new AtomicInteger(msgCount); |
| final AtomicBoolean anyPartFailed = new AtomicBoolean(false); |
| |
| for (int i = 0; i < msgCount; i++) { |
| SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef(); |
| concatRef.refNumber = refNumber; |
| concatRef.seqNumber = i + 1; // 1-based sequence |
| concatRef.msgCount = msgCount; |
| // TODO: We currently set this to true since our messaging app will never |
| // send more than 255 parts (it converts the message to MMS well before that). |
| // However, we should support 3rd party messaging apps that might need 16-bit |
| // references |
| // Note: It's not sufficient to just flip this bit to true; it will have |
| // ripple effects (several calculations assume 8-bit ref). |
| concatRef.isEightBits = true; |
| SmsHeader smsHeader = new SmsHeader(); |
| smsHeader.concatRef = concatRef; |
| |
| // Set the national language tables for 3GPP 7-bit encoding, if enabled. |
| if (encoding == SmsConstants.ENCODING_7BIT) { |
| smsHeader.languageTable = encodingForParts[i].languageTable; |
| smsHeader.languageShiftTable = encodingForParts[i].languageShiftTable; |
| } |
| |
| PendingIntent sentIntent = null; |
| if (sentIntents != null && sentIntents.size() > i) { |
| sentIntent = sentIntents.get(i); |
| } |
| |
| PendingIntent deliveryIntent = null; |
| if (deliveryIntents != null && deliveryIntents.size() > i) { |
| deliveryIntent = deliveryIntents.get(i); |
| } |
| |
| trackers[i] = |
| getNewSubmitPduTracker(callingPkg, destAddr, scAddr, parts.get(i), smsHeader, |
| encoding, sentIntent, deliveryIntent, (i == (msgCount - 1)), |
| unsentPartCount, anyPartFailed, messageUri, |
| fullMessageText, priority, expectMore, validityPeriod); |
| if (trackers[i] == null) { |
| triggerSentIntentForFailure(sentIntents); |
| return; |
| } |
| trackers[i].mPersistMessage = persistMessage; |
| } |
| |
| String carrierPackage = getCarrierAppPackageName(); |
| if (carrierPackage != null) { |
| Rlog.d(TAG, "Found carrier package."); |
| MultipartSmsSender smsSender = new MultipartSmsSender(parts, trackers); |
| smsSender.sendSmsByCarrierApp(carrierPackage, |
| new MultipartSmsSenderCallback(smsSender)); |
| } else { |
| Rlog.v(TAG, "No carrier package."); |
| sendSubmitPdu(trackers); |
| } |
| } |
| |
| /** |
| * Create a new SubmitPdu and return the SMS tracker. |
| */ |
| private SmsTracker getNewSubmitPduTracker(String callingPackage, String destinationAddress, |
| String scAddress, String message, SmsHeader smsHeader, int encoding, |
| PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart, |
| AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri, |
| String fullMessageText, int priority, boolean expectMore, int validityPeriod) { |
| if (isCdmaMo()) { |
| UserData uData = new UserData(); |
| uData.payloadStr = message; |
| uData.userDataHeader = smsHeader; |
| if (encoding == SmsConstants.ENCODING_7BIT) { |
| uData.msgEncoding = isAscii7bitSupportedForLongMessage() |
| ? UserData.ENCODING_7BIT_ASCII : UserData.ENCODING_GSM_7BIT_ALPHABET; |
| Rlog.d(TAG, "Message encoding for proper 7 bit: " + uData.msgEncoding); |
| } else { // assume UTF-16 |
| uData.msgEncoding = UserData.ENCODING_UNICODE_16; |
| } |
| uData.msgEncodingSet = true; |
| |
| /* By setting the statusReportRequested bit only for the |
| * last message fragment, this will result in only one |
| * callback to the sender when that last fragment delivery |
| * has been acknowledged. */ |
| //TODO FIX |
| SmsMessageBase.SubmitPduBase submitPdu = |
| com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(destinationAddress, |
| uData, (deliveryIntent != null) && lastPart, priority); |
| |
| if (submitPdu != null) { |
| HashMap map = getSmsTrackerMap(destinationAddress, scAddress, |
| message, submitPdu); |
| return getSmsTracker(callingPackage, map, sentIntent, deliveryIntent, |
| getFormat(), unsentPartCount, anyPartFailed, messageUri, smsHeader, |
| (!lastPart || expectMore), fullMessageText, true /*isText*/, |
| true /*persistMessage*/, priority, validityPeriod, false /* isForVvm */); |
| } else { |
| Rlog.e(TAG, "CdmaSMSDispatcher.getNewSubmitPduTracker(): getSubmitPdu() returned " |
| + "null"); |
| return null; |
| } |
| } else { |
| SmsMessageBase.SubmitPduBase pdu = |
| com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress, |
| destinationAddress, message, deliveryIntent != null, |
| SmsHeader.toByteArray(smsHeader), encoding, smsHeader.languageTable, |
| smsHeader.languageShiftTable, validityPeriod); |
| if (pdu != null) { |
| HashMap map = getSmsTrackerMap(destinationAddress, scAddress, |
| message, pdu); |
| return getSmsTracker(callingPackage, map, sentIntent, |
| deliveryIntent, getFormat(), unsentPartCount, anyPartFailed, messageUri, |
| smsHeader, (!lastPart || expectMore), fullMessageText, true /*isText*/, |
| false /*persistMessage*/, priority, validityPeriod, false /* isForVvm */); |
| } else { |
| Rlog.e(TAG, "GsmSMSDispatcher.getNewSubmitPduTracker(): getSubmitPdu() returned " |
| + "null"); |
| return null; |
| } |
| } |
| } |
| |
| /** |
| * Send a single or a multi-part SMS |
| * @param trackers each tracker will contain: |
| * -smsc the SMSC to send the message through, or NULL for the |
| * default SMSC |
| * -pdu the raw PDU to send |
| * -sentIntent if not NULL this <code>Intent</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: |
| * <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. |
| * -deliveryIntent if not NULL this <code>Intent</code> is |
| * broadcast when the message is delivered to the recipient. The |
| * raw pdu of the status report is in the extended data ("pdu"). |
| * -param destAddr the destination phone number (for short code confirmation) |
| */ |
| @VisibleForTesting |
| public void sendRawPdu(SmsTracker[] trackers) { |
| int error = RESULT_ERROR_NONE; |
| PackageInfo appInfo = null; |
| if (mSmsSendDisabled) { |
| Rlog.e(TAG, "Device does not support sending sms."); |
| error = RESULT_ERROR_NO_SERVICE; |
| } else { |
| for (SmsTracker tracker : trackers) { |
| if (tracker.getData().get(MAP_KEY_PDU) == null) { |
| Rlog.e(TAG, "Empty PDU"); |
| error = RESULT_ERROR_NULL_PDU; |
| break; |
| } |
| } |
| |
| if (error == RESULT_ERROR_NONE) { |
| PackageManager pm = mContext.getPackageManager(); |
| |
| try { |
| // Get package info via packagemanager |
| appInfo = |
| pm.getPackageInfoAsUser( |
| trackers[0].getAppPackageName(), |
| PackageManager.GET_SIGNATURES, |
| trackers[0].mUserId); |
| } catch (PackageManager.NameNotFoundException e) { |
| Rlog.e(TAG, "Can't get calling app package info: refusing to send SMS"); |
| error = RESULT_ERROR_GENERIC_FAILURE; |
| } |
| } |
| } |
| |
| if (error != RESULT_ERROR_NONE) { |
| handleSmsTrackersFailure(trackers, error, NO_ERROR_CODE); |
| return; |
| } |
| |
| // checkDestination() returns true if the destination is not a premium short code or the |
| // sending app is approved to send to short codes. Otherwise, a message is sent to our |
| // handler with the SmsTracker to request user confirmation before sending. |
| if (checkDestination(trackers)) { |
| // check for excessive outgoing SMS usage by this app |
| if (!mSmsDispatchersController |
| .getUsageMonitor() |
| .check(appInfo.packageName, trackers.length)) { |
| sendMessage(obtainMessage(EVENT_SEND_LIMIT_REACHED_CONFIRMATION, trackers)); |
| return; |
| } |
| |
| for (SmsTracker tracker : trackers) { |
| sendSms(tracker); |
| } |
| } |
| |
| if (PhoneNumberUtils.isLocalEmergencyNumber(mContext, trackers[0].mDestAddress)) { |
| new AsyncEmergencyContactNotifier(mContext).execute(); |
| } |
| } |
| |
| /** |
| * Check if destination is a potential premium short code and sender is not pre-approved to send |
| * to short codes. |
| * |
| * @param trackers the trackers for a single or a multi-part SMS to send |
| * @return true if the destination is approved; false if user confirmation event was sent |
| */ |
| boolean checkDestination(SmsTracker[] trackers) { |
| if (mContext.checkCallingOrSelfPermission(SEND_SMS_NO_CONFIRMATION) |
| == PackageManager.PERMISSION_GRANTED || trackers[0].mIsForVvm) { |
| return true; // app is pre-approved to send to short codes |
| } else { |
| int rule = mPremiumSmsRule.get(); |
| int smsCategory = SmsManager.SMS_CATEGORY_NOT_SHORT_CODE; |
| if (rule == PREMIUM_RULE_USE_SIM || rule == PREMIUM_RULE_USE_BOTH) { |
| String simCountryIso = |
| mTelephonyManager.getSimCountryIsoForPhone(mPhone.getPhoneId()); |
| if (simCountryIso == null || simCountryIso.length() != 2) { |
| Rlog.e(TAG, "Can't get SIM country Iso: trying network country Iso"); |
| simCountryIso = |
| mTelephonyManager.getNetworkCountryIso(mPhone.getPhoneId()); |
| } |
| |
| smsCategory = |
| mSmsDispatchersController |
| .getUsageMonitor() |
| .checkDestination(trackers[0].mDestAddress, simCountryIso); |
| } |
| if (rule == PREMIUM_RULE_USE_NETWORK || rule == PREMIUM_RULE_USE_BOTH) { |
| String networkCountryIso = |
| mTelephonyManager.getNetworkCountryIso(mPhone.getPhoneId()); |
| if (networkCountryIso == null || networkCountryIso.length() != 2) { |
| Rlog.e(TAG, "Can't get Network country Iso: trying SIM country Iso"); |
| networkCountryIso = |
| mTelephonyManager.getSimCountryIsoForPhone(mPhone.getPhoneId()); |
| } |
| |
| smsCategory = |
| SmsUsageMonitor.mergeShortCodeCategories( |
| smsCategory, |
| mSmsDispatchersController |
| .getUsageMonitor() |
| .checkDestination( |
| trackers[0].mDestAddress, networkCountryIso)); |
| } |
| |
| if (smsCategory == SmsManager.SMS_CATEGORY_NOT_SHORT_CODE |
| || smsCategory == SmsManager.SMS_CATEGORY_FREE_SHORT_CODE |
| || smsCategory == SmsManager.SMS_CATEGORY_STANDARD_SHORT_CODE) { |
| return true; // not a premium short code |
| } |
| |
| // Do not allow any premium sms during SuW |
| if (Settings.Global.getInt(mResolver, Settings.Global.DEVICE_PROVISIONED, 0) == 0) { |
| Rlog.e(TAG, "Can't send premium sms during Setup Wizard"); |
| return false; |
| } |
| |
| // Wait for user confirmation unless the user has set permission to always allow/deny |
| int premiumSmsPermission = |
| mSmsDispatchersController |
| .getUsageMonitor() |
| .getPremiumSmsPermission(trackers[0].getAppPackageName()); |
| if (premiumSmsPermission == SmsUsageMonitor.PREMIUM_SMS_PERMISSION_UNKNOWN) { |
| // First time trying to send to premium SMS. |
| premiumSmsPermission = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER; |
| } |
| |
| switch (premiumSmsPermission) { |
| case SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW: |
| Rlog.d(TAG, "User approved this app to send to premium SMS"); |
| return true; |
| |
| case SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW: |
| Rlog.w(TAG, "User denied this app from sending to premium SMS"); |
| Message msg = obtainMessage(EVENT_SENDING_NOT_ALLOWED, trackers); |
| sendMessage(msg); |
| return false; // reject this message |
| |
| case SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER: |
| default: |
| int event; |
| if (smsCategory == SmsManager.SMS_CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE) { |
| event = EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE; |
| } else { |
| event = EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE; |
| } |
| sendMessage(obtainMessage(event, trackers)); |
| return false; // wait for user confirmation |
| } |
| } |
| } |
| |
| /** |
| * Deny sending a single or a multi-part SMS if the outgoing queue limit is reached. Used when |
| * the message must be confirmed by the user due to excessive usage or potential premium SMS |
| * detected. |
| * |
| * @param trackers the SmsTracker array for the message to send |
| * @return true if the message was denied; false to continue with send confirmation |
| */ |
| private boolean denyIfQueueLimitReached(SmsTracker[] trackers) { |
| // one SmsTracker array is treated as one message for checking queue limit. |
| if (mPendingTrackerCount >= MO_MSG_QUEUE_LIMIT) { |
| // Deny sending message when the queue limit is reached. |
| Rlog.e(TAG, "Denied because queue limit reached"); |
| handleSmsTrackersFailure(trackers, RESULT_ERROR_LIMIT_EXCEEDED, NO_ERROR_CODE); |
| return true; |
| } |
| mPendingTrackerCount++; |
| return false; |
| } |
| |
| /** |
| * Returns the label for the specified app package name. |
| * @param appPackage the package name of the app requesting to send an SMS |
| * @return the label for the specified app, or the package name if getApplicationInfo() fails |
| */ |
| private CharSequence getAppLabel(String appPackage, @UserIdInt int userId) { |
| PackageManager pm = mContext.getPackageManager(); |
| try { |
| ApplicationInfo appInfo = pm.getApplicationInfoAsUser(appPackage, 0, |
| UserHandle.getUserHandleForUid(userId)); |
| return appInfo.loadSafeLabel(pm, PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX, |
| PackageItemInfo.SAFE_LABEL_FLAG_TRIM |
| | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE); |
| } catch (PackageManager.NameNotFoundException e) { |
| Rlog.e(TAG, "PackageManager Name Not Found for package " + appPackage); |
| return appPackage; // fall back to package name if we can't get app label |
| } |
| } |
| |
| /** |
| * Post an alert when SMS needs confirmation due to excessive usage. |
| * |
| * @param trackers the SmsTracker array for the current message. |
| */ |
| protected void handleReachSentLimit(SmsTracker[] trackers) { |
| if (denyIfQueueLimitReached(trackers)) { |
| return; // queue limit reached; error was returned to caller |
| } |
| |
| CharSequence appLabel = getAppLabel(trackers[0].getAppPackageName(), trackers[0].mUserId); |
| Resources r = Resources.getSystem(); |
| Spanned messageText = Html.fromHtml(r.getString(R.string.sms_control_message, appLabel)); |
| |
| // Construct ConfirmDialogListenter for Rate Limit handling |
| ConfirmDialogListener listener = |
| new ConfirmDialogListener(trackers, null, ConfirmDialogListener.RATE_LIMIT); |
| |
| AlertDialog d = new AlertDialog.Builder(mContext) |
| .setTitle(R.string.sms_control_title) |
| .setIcon(R.drawable.stat_sys_warning) |
| .setMessage(messageText) |
| .setPositiveButton(r.getString(R.string.sms_control_yes), listener) |
| .setNegativeButton(r.getString(R.string.sms_control_no), listener) |
| .setOnCancelListener(listener) |
| .create(); |
| |
| d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); |
| d.show(); |
| } |
| |
| /** |
| * Post an alert for user confirmation when sending to a potential short code. |
| * |
| * @param isPremium true if the destination is known to be a premium short code |
| * @param trackers the SmsTracker array for the current message. |
| */ |
| @UnsupportedAppUsage |
| protected void handleConfirmShortCode(boolean isPremium, SmsTracker[] trackers) { |
| if (denyIfQueueLimitReached(trackers)) { |
| return; // queue limit reached; error was returned to caller |
| } |
| |
| int detailsId; |
| if (isPremium) { |
| detailsId = R.string.sms_premium_short_code_details; |
| } else { |
| detailsId = R.string.sms_short_code_details; |
| } |
| |
| CharSequence appLabel = getAppLabel(trackers[0].getAppPackageName(), trackers[0].mUserId); |
| Resources r = Resources.getSystem(); |
| Spanned messageText = |
| Html.fromHtml( |
| r.getString( |
| R.string.sms_short_code_confirm_message, |
| appLabel, |
| trackers[0].mDestAddress)); |
| |
| LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( |
| Context.LAYOUT_INFLATER_SERVICE); |
| View layout = inflater.inflate(R.layout.sms_short_code_confirmation_dialog, null); |
| |
| // Construct ConfirmDialogListenter for short code message sending |
| ConfirmDialogListener listener = |
| new ConfirmDialogListener( |
| trackers, |
| (TextView) |
| layout.findViewById(R.id.sms_short_code_remember_undo_instruction), |
| ConfirmDialogListener.SHORT_CODE_MSG); |
| |
| TextView messageView = (TextView) layout.findViewById(R.id.sms_short_code_confirm_message); |
| messageView.setText(messageText); |
| |
| ViewGroup detailsLayout = (ViewGroup) layout.findViewById( |
| R.id.sms_short_code_detail_layout); |
| TextView detailsView = (TextView) detailsLayout.findViewById( |
| R.id.sms_short_code_detail_message); |
| detailsView.setText(detailsId); |
| |
| CheckBox rememberChoice = (CheckBox) layout.findViewById( |
| R.id.sms_short_code_remember_choice_checkbox); |
| rememberChoice.setOnCheckedChangeListener(listener); |
| |
| AlertDialog d = new AlertDialog.Builder(mContext) |
| .setView(layout) |
| .setPositiveButton(r.getString(R.string.sms_short_code_confirm_allow), listener) |
| .setNegativeButton(r.getString(R.string.sms_short_code_confirm_deny), listener) |
| .setOnCancelListener(listener) |
| .create(); |
| |
| d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); |
| d.show(); |
| |
| listener.setPositiveButton(d.getButton(DialogInterface.BUTTON_POSITIVE)); |
| listener.setNegativeButton(d.getButton(DialogInterface.BUTTON_NEGATIVE)); |
| } |
| |
| /** |
| * Send the message along to the radio. |
| * |
| * @param tracker holds the SMS message to send |
| */ |
| @UnsupportedAppUsage |
| protected abstract void sendSms(SmsTracker tracker); |
| |
| /** |
| * Retry the message along to the radio. |
| * |
| * @param tracker holds the SMS message to send |
| */ |
| public void sendRetrySms(SmsTracker tracker) { |
| // re-routing to SmsDispatchersController |
| if (mSmsDispatchersController != null) { |
| mSmsDispatchersController.sendRetrySms(tracker); |
| } else { |
| Rlog.e(TAG, mSmsDispatchersController + " is null. Retry failed"); |
| } |
| } |
| |
| private void handleSmsTrackersFailure(SmsTracker[] trackers, int error, int errorCode) { |
| for (SmsTracker tracker : trackers) { |
| tracker.onFailed(mContext, error, errorCode); |
| } |
| } |
| |
| /** |
| * Keeps track of an SMS that has been sent to the RIL, until it has |
| * successfully been sent, or we're done trying. |
| */ |
| public static class SmsTracker { |
| // fields need to be public for derived SmsDispatchers |
| @UnsupportedAppUsage |
| private final HashMap<String, Object> mData; |
| public int mRetryCount; |
| // IMS retry counter. Nonzero indicates initial message was sent over IMS channel in RIL and |
| // counts how many retries have been made on the IMS channel. |
| // Used in older implementations where the message is sent over IMS using the RIL. |
| public int mImsRetry; |
| // Tag indicating that this SMS is being handled by the ImsSmsDispatcher. This tracker |
| // should not try to use SMS over IMS over the RIL interface in this case when falling back. |
| public boolean mUsesImsServiceForIms; |
| @UnsupportedAppUsage |
| public int mMessageRef; |
| public boolean mExpectMore; |
| public int mValidityPeriod; |
| public int mPriority; |
| String mFormat; |
| |
| @UnsupportedAppUsage |
| public final PendingIntent mSentIntent; |
| @UnsupportedAppUsage |
| public final PendingIntent mDeliveryIntent; |
| |
| @UnsupportedAppUsage |
| public final PackageInfo mAppInfo; |
| @UnsupportedAppUsage |
| public final String mDestAddress; |
| |
| public final SmsHeader mSmsHeader; |
| |
| @UnsupportedAppUsage |
| private long mTimestamp = System.currentTimeMillis(); |
| @UnsupportedAppUsage |
| public Uri mMessageUri; // Uri of persisted message if we wrote one |
| |
| // Reference to states of a multipart message that this part belongs to |
| private AtomicInteger mUnsentPartCount; |
| private AtomicBoolean mAnyPartFailed; |
| // The full message content of a single part message |
| // or a multipart message that this part belongs to |
| private String mFullMessageText; |
| |
| private int mSubId; |
| |
| // If this is a text message (instead of data message) |
| private boolean mIsText; |
| |
| @UnsupportedAppUsage |
| private boolean mPersistMessage; |
| |
| // User who sends the SMS. |
| private final @UserIdInt int mUserId; |
| |
| private final boolean mIsForVvm; |
| |
| private SmsTracker(HashMap<String, Object> data, PendingIntent sentIntent, |
| PendingIntent deliveryIntent, PackageInfo appInfo, String destAddr, String format, |
| AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri, |
| SmsHeader smsHeader, boolean expectMore, String fullMessageText, int subId, |
| boolean isText, boolean persistMessage, int userId, int priority, |
| int validityPeriod, boolean isForVvm) { |
| mData = data; |
| mSentIntent = sentIntent; |
| mDeliveryIntent = deliveryIntent; |
| mRetryCount = 0; |
| mAppInfo = appInfo; |
| mDestAddress = destAddr; |
| mFormat = format; |
| mExpectMore = expectMore; |
| mImsRetry = 0; |
| mUsesImsServiceForIms = false; |
| mMessageRef = 0; |
| mUnsentPartCount = unsentPartCount; |
| mAnyPartFailed = anyPartFailed; |
| mMessageUri = messageUri; |
| mSmsHeader = smsHeader; |
| mFullMessageText = fullMessageText; |
| mSubId = subId; |
| mIsText = isText; |
| mPersistMessage = persistMessage; |
| mUserId = userId; |
| mPriority = priority; |
| mValidityPeriod = validityPeriod; |
| mIsForVvm = isForVvm; |
| } |
| |
| public HashMap<String, Object> getData() { |
| return mData; |
| } |
| |
| /** |
| * Get the App package name |
| * @return App package name info |
| */ |
| public String getAppPackageName() { |
| return mAppInfo != null ? mAppInfo.packageName : null; |
| } |
| |
| /** |
| * Update the status of this message if we persisted it |
| */ |
| @UnsupportedAppUsage |
| public void updateSentMessageStatus(Context context, int status) { |
| if (mMessageUri != null) { |
| // If we wrote this message in writeSentMessage, update it now |
| ContentValues values = new ContentValues(1); |
| values.put(Sms.STATUS, status); |
| context.getContentResolver().update(mMessageUri, values, null, null); |
| } |
| } |
| |
| /** |
| * Set the final state of a message: FAILED or SENT |
| * |
| * @param context The Context |
| * @param messageType The final message type |
| * @param errorCode The error code |
| */ |
| private void updateMessageState(Context context, int messageType, int errorCode) { |
| if (mMessageUri == null) { |
| return; |
| } |
| final ContentValues values = new ContentValues(2); |
| values.put(Sms.TYPE, messageType); |
| values.put(Sms.ERROR_CODE, errorCode); |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| if (context.getContentResolver().update(mMessageUri, values, |
| null/*where*/, null/*selectionArgs*/) != 1) { |
| Rlog.e(TAG, "Failed to move message to " + messageType); |
| } |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| |
| /** |
| * Persist a sent SMS if required: |
| * 1. It is a text message |
| * 2. SmsApplication tells us to persist: sent from apps that are not default-SMS app or |
| * bluetooth |
| * |
| * @param context |
| * @param messageType The folder to store (FAILED or SENT) |
| * @param errorCode The current error code for this SMS or SMS part |
| * @return The telephony provider URI if stored |
| */ |
| private Uri persistSentMessageIfRequired(Context context, int messageType, int errorCode) { |
| if (!mIsText || !mPersistMessage || |
| !SmsApplication.shouldWriteMessageForPackage(mAppInfo.packageName, context)) { |
| return null; |
| } |
| Rlog.d(TAG, "Persist SMS into " |
| + (messageType == Sms.MESSAGE_TYPE_FAILED ? "FAILED" : "SENT")); |
| final ContentValues values = new ContentValues(); |
| values.put(Sms.SUBSCRIPTION_ID, mSubId); |
| values.put(Sms.ADDRESS, mDestAddress); |
| values.put(Sms.BODY, mFullMessageText); |
| values.put(Sms.DATE, System.currentTimeMillis()); // milliseconds |
| values.put(Sms.SEEN, 1); |
| values.put(Sms.READ, 1); |
| final String creator = mAppInfo != null ? mAppInfo.packageName : null; |
| if (!TextUtils.isEmpty(creator)) { |
| values.put(Sms.CREATOR, creator); |
| } |
| if (mDeliveryIntent != null) { |
| values.put(Sms.STATUS, Telephony.Sms.STATUS_PENDING); |
| } |
| if (errorCode != NO_ERROR_CODE) { |
| values.put(Sms.ERROR_CODE, errorCode); |
| } |
| final long identity = Binder.clearCallingIdentity(); |
| final ContentResolver resolver = context.getContentResolver(); |
| try { |
| final Uri uri = resolver.insert(Telephony.Sms.Sent.CONTENT_URI, values); |
| if (uri != null && messageType == Sms.MESSAGE_TYPE_FAILED) { |
| // Since we can't persist a message directly into FAILED box, |
| // we have to update the column after we persist it into SENT box. |
| // The gap between the state change is tiny so I would not expect |
| // it to cause any serious problem |
| // TODO: we should add a "failed" URI for this in SmsProvider? |
| final ContentValues updateValues = new ContentValues(1); |
| updateValues.put(Sms.TYPE, Sms.MESSAGE_TYPE_FAILED); |
| resolver.update(uri, updateValues, null/*where*/, null/*selectionArgs*/); |
| } |
| return uri; |
| } catch (Exception e) { |
| Rlog.e(TAG, "writeOutboxMessage: Failed to persist outbox message", e); |
| return null; |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| } |
| |
| /** |
| * Persist or update an SMS depending on if we send a new message or a stored message |
| * |
| * @param context |
| * @param messageType The message folder for this SMS, FAILED or SENT |
| * @param errorCode The current error code for this SMS or SMS part |
| */ |
| private void persistOrUpdateMessage(Context context, int messageType, int errorCode) { |
| if (mMessageUri != null) { |
| updateMessageState(context, messageType, errorCode); |
| } else { |
| mMessageUri = persistSentMessageIfRequired(context, messageType, errorCode); |
| } |
| } |
| |
| /** |
| * Handle a failure of a single part message or a part of a multipart message |
| * |
| * @param context The Context |
| * @param error The error to send back with |
| * @param errorCode |
| */ |
| @UnsupportedAppUsage |
| public void onFailed(Context context, int error, int errorCode) { |
| if (mAnyPartFailed != null) { |
| mAnyPartFailed.set(true); |
| } |
| // is single part or last part of multipart message |
| boolean isSinglePartOrLastPart = true; |
| if (mUnsentPartCount != null) { |
| isSinglePartOrLastPart = mUnsentPartCount.decrementAndGet() == 0; |
| } |
| if (isSinglePartOrLastPart) { |
| persistOrUpdateMessage(context, Sms.MESSAGE_TYPE_FAILED, errorCode); |
| } |
| if (mSentIntent != null) { |
| try { |
| // Extra information to send with the sent intent |
| Intent fillIn = new Intent(); |
| if (mMessageUri != null) { |
| // Pass this to SMS apps so that they know where it is stored |
| fillIn.putExtra("uri", mMessageUri.toString()); |
| } |
| if (errorCode != NO_ERROR_CODE) { |
| fillIn.putExtra("errorCode", errorCode); |
| } |
| if (mUnsentPartCount != null && isSinglePartOrLastPart) { |
| // Is multipart and last part |
| fillIn.putExtra(SEND_NEXT_MSG_EXTRA, true); |
| } |
| mSentIntent.send(context, error, fillIn); |
| } catch (CanceledException ex) { |
| Rlog.e(TAG, "Failed to send result"); |
| } |
| } |
| } |
| |
| /** |
| * Handle the sent of a single part message or a part of a multipart message |
| * |
| * @param context The Context |
| */ |
| @UnsupportedAppUsage |
| public void onSent(Context context) { |
| // is single part or last part of multipart message |
| boolean isSinglePartOrLastPart = true; |
| if (mUnsentPartCount != null) { |
| isSinglePartOrLastPart = mUnsentPartCount.decrementAndGet() == 0; |
| } |
| if (isSinglePartOrLastPart) { |
| int messageType = Sms.MESSAGE_TYPE_SENT; |
| if (mAnyPartFailed != null && mAnyPartFailed.get()) { |
| messageType = Sms.MESSAGE_TYPE_FAILED; |
| } |
| persistOrUpdateMessage(context, messageType, NO_ERROR_CODE); |
| } |
| if (mSentIntent != null) { |
| try { |
| // Extra information to send with the sent intent |
| Intent fillIn = new Intent(); |
| if (mMessageUri != null) { |
| // Pass this to SMS apps so that they know where it is stored |
| fillIn.putExtra("uri", mMessageUri.toString()); |
| } |
| if (mUnsentPartCount != null && isSinglePartOrLastPart) { |
| // Is multipart and last part |
| fillIn.putExtra(SEND_NEXT_MSG_EXTRA, true); |
| } |
| mSentIntent.send(context, Activity.RESULT_OK, fillIn); |
| } catch (CanceledException ex) { |
| Rlog.e(TAG, "Failed to send result"); |
| } |
| } |
| } |
| } |
| |
| protected SmsTracker getSmsTracker(String callingPackage, HashMap<String, Object> data, |
| PendingIntent sentIntent, PendingIntent deliveryIntent, String format, |
| AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri, |
| SmsHeader smsHeader, boolean expectMore, String fullMessageText, boolean isText, |
| boolean persistMessage, int priority, int validityPeriod, boolean isForVvm) { |
| // Get calling app package name via UID from Binder call |
| PackageManager pm = mContext.getPackageManager(); |
| |
| // Get package info via packagemanager |
| final int userId = UserHandle.getUserHandleForUid(Binder.getCallingUid()).getIdentifier(); |
| PackageInfo appInfo = null; |
| try { |
| appInfo = pm.getPackageInfoAsUser( |
| callingPackage, PackageManager.GET_SIGNATURES, userId); |
| } catch (PackageManager.NameNotFoundException e) { |
| // error will be logged in sendRawPdu |
| } |
| // Strip non-digits from destination phone number before checking for short codes |
| // and before displaying the number to the user if confirmation is required. |
| String destAddr = PhoneNumberUtils.extractNetworkPortion((String) data.get("destAddr")); |
| return new SmsTracker(data, sentIntent, deliveryIntent, appInfo, destAddr, format, |
| unsentPartCount, anyPartFailed, messageUri, smsHeader, expectMore, |
| fullMessageText, getSubId(), isText, persistMessage, userId, priority, |
| validityPeriod, isForVvm); |
| } |
| |
| protected SmsTracker getSmsTracker(String callingPackage, HashMap<String, Object> data, |
| PendingIntent sentIntent, PendingIntent deliveryIntent, String format, Uri messageUri, |
| boolean expectMore, String fullMessageText, boolean isText, boolean persistMessage, |
| boolean isForVvm) { |
| return getSmsTracker(callingPackage, data, sentIntent, deliveryIntent, format, |
| null/*unsentPartCount*/, null/*anyPartFailed*/, messageUri, null/*smsHeader*/, |
| expectMore, fullMessageText, isText, persistMessage, |
| SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, isForVvm); |
| } |
| |
| protected SmsTracker getSmsTracker(String callingPackage, HashMap<String, Object> data, |
| PendingIntent sentIntent, PendingIntent deliveryIntent, String format, Uri messageUri, |
| boolean expectMore, String fullMessageText, boolean isText, boolean persistMessage, |
| int priority, int validityPeriod, boolean isForVvm) { |
| return getSmsTracker(callingPackage, data, sentIntent, deliveryIntent, format, |
| null/*unsentPartCount*/, null/*anyPartFailed*/, messageUri, null/*smsHeader*/, |
| expectMore, fullMessageText, isText, persistMessage, priority, validityPeriod, |
| isForVvm); |
| } |
| |
| protected HashMap<String, Object> getSmsTrackerMap(String destAddr, String scAddr, |
| String text, SmsMessageBase.SubmitPduBase pdu) { |
| HashMap<String, Object> map = new HashMap<String, Object>(); |
| map.put(MAP_KEY_DEST_ADDR, destAddr); |
| map.put(MAP_KEY_SC_ADDR, scAddr); |
| map.put(MAP_KEY_TEXT, text); |
| map.put(MAP_KEY_SMSC, pdu.encodedScAddress); |
| map.put(MAP_KEY_PDU, pdu.encodedMessage); |
| return map; |
| } |
| |
| protected HashMap<String, Object> getSmsTrackerMap(String destAddr, String scAddr, |
| int destPort, byte[] data, SmsMessageBase.SubmitPduBase pdu) { |
| HashMap<String, Object> map = new HashMap<String, Object>(); |
| map.put(MAP_KEY_DEST_ADDR, destAddr); |
| map.put(MAP_KEY_SC_ADDR, scAddr); |
| map.put(MAP_KEY_DEST_PORT, destPort); |
| map.put(MAP_KEY_DATA, data); |
| map.put(MAP_KEY_SMSC, pdu.encodedScAddress); |
| map.put(MAP_KEY_PDU, pdu.encodedMessage); |
| return map; |
| } |
| |
| /** |
| * Dialog listener for SMS confirmation dialog. |
| */ |
| private final class ConfirmDialogListener |
| implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener, |
| CompoundButton.OnCheckedChangeListener { |
| |
| private final SmsTracker[] mTrackers; |
| @UnsupportedAppUsage |
| private Button mPositiveButton; |
| @UnsupportedAppUsage |
| private Button mNegativeButton; |
| private boolean mRememberChoice; // default is unchecked |
| @UnsupportedAppUsage |
| private final TextView mRememberUndoInstruction; |
| private int mConfirmationType; // 0 - Short Code Msg Sending; 1 - Rate Limit Exceeded |
| private static final int SHORT_CODE_MSG = 0; // Short Code Msg |
| private static final int RATE_LIMIT = 1; // Rate Limit Exceeded |
| private static final int NEVER_ALLOW = 1; // Never Allow |
| |
| ConfirmDialogListener(SmsTracker[] trackers, TextView textView, int confirmationType) { |
| mTrackers = trackers; |
| mRememberUndoInstruction = textView; |
| mConfirmationType = confirmationType; |
| } |
| |
| void setPositiveButton(Button button) { |
| mPositiveButton = button; |
| } |
| |
| void setNegativeButton(Button button) { |
| mNegativeButton = button; |
| } |
| |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| // Always set the SMS permission so that Settings will show a permission setting |
| // for the app (it won't be shown until after the app tries to send to a short code). |
| int newSmsPermission = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER; |
| |
| if (which == DialogInterface.BUTTON_POSITIVE) { |
| Rlog.d(TAG, "CONFIRM sending SMS"); |
| // XXX this is lossy- apps can have more than one signature |
| EventLog.writeEvent( |
| EventLogTags.EXP_DET_SMS_SENT_BY_USER, |
| mTrackers[0].mAppInfo.applicationInfo == null |
| ? -1 |
| : mTrackers[0].mAppInfo.applicationInfo.uid); |
| sendMessage(obtainMessage(EVENT_SEND_CONFIRMED_SMS, mTrackers)); |
| if (mRememberChoice) { |
| newSmsPermission = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW; |
| } |
| } else if (which == DialogInterface.BUTTON_NEGATIVE) { |
| Rlog.d(TAG, "DENY sending SMS"); |
| // XXX this is lossy- apps can have more than one signature |
| EventLog.writeEvent( |
| EventLogTags.EXP_DET_SMS_DENIED_BY_USER, |
| mTrackers[0].mAppInfo.applicationInfo == null |
| ? -1 |
| : mTrackers[0].mAppInfo.applicationInfo.uid); |
| Message msg = obtainMessage(EVENT_STOP_SENDING, mTrackers); |
| msg.arg1 = mConfirmationType; |
| if (mRememberChoice) { |
| newSmsPermission = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW; |
| msg.arg2 = ConfirmDialogListener.NEVER_ALLOW; |
| } |
| sendMessage(msg); |
| } |
| mSmsDispatchersController.setPremiumSmsPermission( |
| mTrackers[0].getAppPackageName(), newSmsPermission); |
| } |
| |
| @Override |
| public void onCancel(DialogInterface dialog) { |
| Rlog.d(TAG, "dialog dismissed: don't send SMS"); |
| Message msg = obtainMessage(EVENT_STOP_SENDING, mTrackers); |
| msg.arg1 = mConfirmationType; |
| sendMessage(msg); |
| } |
| |
| @Override |
| public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { |
| Rlog.d(TAG, "remember this choice: " + isChecked); |
| mRememberChoice = isChecked; |
| if (isChecked) { |
| mPositiveButton.setText(R.string.sms_short_code_confirm_always_allow); |
| mNegativeButton.setText(R.string.sms_short_code_confirm_never_allow); |
| if (mRememberUndoInstruction != null) { |
| mRememberUndoInstruction. |
| setText(R.string.sms_short_code_remember_undo_instruction); |
| mRememberUndoInstruction.setPadding(0,0,0,32); |
| } |
| } else { |
| mPositiveButton.setText(R.string.sms_short_code_confirm_allow); |
| mNegativeButton.setText(R.string.sms_short_code_confirm_deny); |
| if (mRememberUndoInstruction != null) { |
| mRememberUndoInstruction.setText(""); |
| mRememberUndoInstruction.setPadding(0,0,0,0); |
| } |
| } |
| } |
| } |
| |
| public boolean isIms() { |
| if (mSmsDispatchersController != null) { |
| return mSmsDispatchersController.isIms(); |
| } else { |
| Rlog.e(TAG, "mSmsDispatchersController is null"); |
| return false; |
| } |
| } |
| |
| @UnsupportedAppUsage |
| private String getMultipartMessageText(ArrayList<String> parts) { |
| final StringBuilder sb = new StringBuilder(); |
| for (String part : parts) { |
| if (part != null) { |
| sb.append(part); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| @UnsupportedAppUsage |
| protected String getCarrierAppPackageName() { |
| UiccCard card = UiccController.getInstance().getUiccCard(mPhone.getPhoneId()); |
| if (card == null) { |
| return null; |
| } |
| |
| List<String> carrierPackages = card.getCarrierPackageNamesForIntent( |
| mContext.getPackageManager(), new Intent(CarrierMessagingService.SERVICE_INTERFACE)); |
| if (carrierPackages != null && carrierPackages.size() == 1) { |
| return carrierPackages.get(0); |
| } |
| // If there is no carrier package which implements CarrierMessagingService, then lookup if |
| // for a carrierImsPackage that implements CarrierMessagingService. |
| return CarrierSmsUtils.getCarrierImsPackageForIntent(mContext, mPhone, |
| new Intent(CarrierMessagingService.SERVICE_INTERFACE)); |
| } |
| |
| @UnsupportedAppUsage |
| protected int getSubId() { |
| return SubscriptionController.getInstance().getSubIdUsingPhoneId(mPhone.getPhoneId()); |
| } |
| |
| @UnsupportedAppUsage |
| private void checkCallerIsPhoneOrCarrierApp() { |
| int uid = Binder.getCallingUid(); |
| int appId = UserHandle.getAppId(uid); |
| if (appId == Process.PHONE_UID || uid == 0) { |
| return; |
| } |
| try { |
| PackageManager pm = mContext.getPackageManager(); |
| ApplicationInfo ai = pm.getApplicationInfo(getCarrierAppPackageName(), 0); |
| if (UserHandle.getAppId(ai.uid) != UserHandle.getAppId(Binder.getCallingUid())) { |
| throw new SecurityException("Caller is not phone or carrier app!"); |
| } |
| } catch (PackageManager.NameNotFoundException re) { |
| throw new SecurityException("Caller is not phone or carrier app!"); |
| } |
| } |
| |
| protected boolean isCdmaMo() { |
| return mSmsDispatchersController.isCdmaMo(); |
| } |
| |
| private boolean isAscii7bitSupportedForLongMessage() { |
| //TODO: Do not rely on calling identity here, we should store UID & clear identity earlier. |
| long token = Binder.clearCallingIdentity(); |
| try { |
| CarrierConfigManager configManager = (CarrierConfigManager) mContext.getSystemService( |
| Context.CARRIER_CONFIG_SERVICE); |
| PersistableBundle pb = null; |
| pb = configManager.getConfigForSubId(mPhone.getSubId()); |
| if (pb != null) { |
| return pb.getBoolean(CarrierConfigManager |
| .KEY_ASCII_7_BIT_SUPPORT_FOR_LONG_MESSAGE_BOOL); |
| } |
| return false; |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| } |