| /* |
| * 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.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.annotation.NonNull; |
| import android.annotation.Nullable; |
| 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.Binder; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.UserManager; |
| import android.provider.Telephony.Sms; |
| import android.provider.Telephony.Sms.Intents; |
| import android.telephony.Annotation.DisconnectCauses; |
| import android.telephony.DomainSelectionService; |
| import android.telephony.NetworkRegistrationInfo; |
| import android.telephony.ServiceState; |
| import android.telephony.SmsManager; |
| import android.telephony.SmsMessage; |
| import android.telephony.TelephonyManager; |
| import android.telephony.emergency.EmergencyNumber; |
| import android.text.TextUtils; |
| |
| import com.android.ims.ImsManager; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.os.SomeArgs; |
| import com.android.internal.telephony.cdma.CdmaInboundSmsHandler; |
| import com.android.internal.telephony.cdma.CdmaSMSDispatcher; |
| import com.android.internal.telephony.domainselection.DomainSelectionConnection; |
| import com.android.internal.telephony.domainselection.DomainSelectionResolver; |
| import com.android.internal.telephony.domainselection.EmergencySmsDomainSelectionConnection; |
| import com.android.internal.telephony.domainselection.SmsDomainSelectionConnection; |
| import com.android.internal.telephony.emergency.EmergencyStateTracker; |
| import com.android.internal.telephony.flags.FeatureFlags; |
| 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.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.CompletableFuture; |
| |
| /** |
| * |
| */ |
| 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; |
| |
| /** Called when SMS should be sent using AP domain selection. */ |
| private static final int EVENT_SEND_SMS_USING_DOMAIN_SELECTION = 18; |
| |
| /** Called when SMS is completely sent using AP domain selection regardless of the result. */ |
| private static final int EVENT_SMS_SENT_COMPLETED_USING_DOMAIN_SELECTION = 19; |
| |
| /** Called when AP domain selection is abnormally terminated. */ |
| private static final int EVENT_DOMAIN_SELECTION_TERMINATED_ABNORMALLY = 20; |
| |
| /** Called when MT SMS is received via IMS. */ |
| private static final int EVENT_SMS_RECEIVED_VIA_IMS = 21; |
| |
| /** Called when the domain selection should be performed. */ |
| private static final int EVENT_REQUEST_DOMAIN_SELECTION = 22; |
| |
| /** 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; |
| private final @NonNull FeatureFlags mFeatureFlags; |
| |
| /** true if IMS is registered and sms is supported, false otherwise.*/ |
| private boolean mIms = false; |
| private String mImsSmsFormat = SmsConstants.FORMAT_UNKNOWN; |
| |
| /** 3GPP format sent messages awaiting a delivery status report. */ |
| private HashMap<Integer, SMSDispatcher.SmsTracker> mDeliveryPendingMapFor3GPP = new HashMap<>(); |
| |
| /** 3GPP2 format sent messages awaiting a delivery status report. */ |
| private HashMap<Integer, SMSDispatcher.SmsTracker> mDeliveryPendingMapFor3GPP2 = |
| new HashMap<>(); |
| |
| /** |
| * Testing interface for injecting mock DomainSelectionConnection and a flag to indicate |
| * whether the domain selection is supported. |
| */ |
| @VisibleForTesting |
| public interface DomainSelectionResolverProxy { |
| /** |
| * Returns a {@link DomainSelectionConnection} created using the specified |
| * context and callback. |
| * |
| * @param phone The {@link Phone} instance. |
| * @param selectorType The domain selector type to identify the domain selection connection. |
| * A {@link DomainSelectionService#SELECTOR_TYPE_SMS} is used for SMS. |
| * @param isEmergency A flag to indicate whether this connection is |
| * for an emergency SMS or not. |
| */ |
| @Nullable DomainSelectionConnection getDomainSelectionConnection(Phone phone, |
| @DomainSelectionService.SelectorType int selectorType, boolean isEmergency); |
| |
| /** |
| * Checks if the device supports the domain selection service to route the call / SMS / |
| * supplementary services to the appropriate domain. |
| * |
| * @return {@code true} if the domain selection is supported on the device, |
| * {@code false} otherwise. |
| */ |
| boolean isDomainSelectionSupported(); |
| } |
| |
| private DomainSelectionResolverProxy mDomainSelectionResolverProxy = |
| new DomainSelectionResolverProxy() { |
| @Override |
| @Nullable |
| public DomainSelectionConnection getDomainSelectionConnection(Phone phone, |
| @DomainSelectionService.SelectorType int selectorType, |
| boolean isEmergency) { |
| try { |
| return DomainSelectionResolver.getInstance().getDomainSelectionConnection( |
| phone, selectorType, isEmergency); |
| } catch (IllegalStateException e) { |
| // In general, DomainSelectionResolver is always initialized by TeleService, |
| // but if it's not initialized (like in unit tests), |
| // it returns null to perform the legacy behavior in this case. |
| return null; |
| } |
| } |
| |
| @Override |
| public boolean isDomainSelectionSupported() { |
| return DomainSelectionResolver.getInstance().isDomainSelectionSupported(); |
| } |
| }; |
| |
| /** Stores the sending SMS information for a pending request. */ |
| private static class PendingRequest { |
| public static final int TYPE_DATA = 1; |
| public static final int TYPE_TEXT = 2; |
| public static final int TYPE_MULTIPART_TEXT = 3; |
| public static final int TYPE_RETRY_SMS = 4; |
| |
| public final int type; |
| public final SMSDispatcher.SmsTracker tracker; |
| public final String callingPackage; |
| public final String destAddr; |
| public final String scAddr; |
| public final ArrayList<PendingIntent> sentIntents; |
| public final ArrayList<PendingIntent> deliveryIntents; |
| public final boolean isForVvm; |
| // sendData specific |
| public final byte[] data; |
| public final int destPort; |
| // sendText / sendMultipartText specific |
| public final ArrayList<String> texts; |
| public final Uri messageUri; |
| public final boolean persistMessage; |
| public final int priority; |
| public final boolean expectMore; |
| public final int validityPeriod; |
| public final long messageId; |
| public final boolean skipShortCodeCheck; |
| |
| PendingRequest(int type, SMSDispatcher.SmsTracker tracker, String callingPackage, |
| String destAddr, String scAddr, ArrayList<PendingIntent> sentIntents, |
| ArrayList<PendingIntent> deliveryIntents, boolean isForVvm, byte[] data, |
| int destPort, ArrayList<String> texts, Uri messageUri, boolean persistMessage, |
| int priority, boolean expectMore, int validityPeriod, long messageId, |
| boolean skipShortCodeCheck) { |
| this.type = type; |
| this.tracker = tracker; |
| this.callingPackage = callingPackage; |
| this.destAddr = destAddr; |
| this.scAddr = scAddr; |
| this.sentIntents = sentIntents; |
| this.deliveryIntents = deliveryIntents; |
| this.isForVvm = isForVvm; |
| |
| this.data = data; |
| this.destPort = destPort; |
| |
| this.texts = texts; |
| this.messageUri = messageUri; |
| this.persistMessage = persistMessage; |
| this.priority = priority; |
| this.expectMore = expectMore; |
| this.validityPeriod = validityPeriod; |
| this.messageId = messageId; |
| this.skipShortCodeCheck = skipShortCodeCheck; |
| } |
| } |
| |
| /** |
| * Manages the {@link DomainSelectionConnection} instance and its related information. |
| */ |
| @VisibleForTesting |
| protected class DomainSelectionConnectionHolder |
| implements DomainSelectionConnection.DomainSelectionConnectionCallback { |
| private final boolean mEmergency; |
| // Manages the pending request while selecting a proper domain. |
| private final List<PendingRequest> mPendingRequests = new ArrayList<>(); |
| // Manages the domain selection connections: MO SMS or emergency SMS. |
| private DomainSelectionConnection mConnection; |
| |
| DomainSelectionConnectionHolder(boolean emergency) { |
| mEmergency = emergency; |
| } |
| |
| /** |
| * Returns a {@link DomainSelectionConnection} instance. |
| */ |
| public DomainSelectionConnection getConnection() { |
| return mConnection; |
| } |
| |
| /** |
| * Returns a list of {@link PendingRequest} that was added |
| * while the domain selection is performed. |
| */ |
| public List<PendingRequest> getPendingRequests() { |
| return mPendingRequests; |
| } |
| |
| /** |
| * Checks whether or not the domain selection is requested. |
| * If there is no pending request, the domain selection request is needed to |
| * select a proper domain for MO SMS. |
| */ |
| public boolean isDomainSelectionRequested() { |
| return !mPendingRequests.isEmpty(); |
| } |
| |
| /** |
| * Checks whether or not this holder is for an emergency SMS. |
| */ |
| public boolean isEmergency() { |
| return mEmergency; |
| } |
| |
| /** |
| * Clears all pending requests. |
| */ |
| public void clearAllRequests() { |
| mPendingRequests.clear(); |
| } |
| |
| /** |
| * Add a new pending request. |
| */ |
| public void addRequest(@NonNull PendingRequest request) { |
| mPendingRequests.add(request); |
| } |
| |
| /** |
| * Sets a {@link DomainSelectionConnection} instance. |
| */ |
| public void setConnection(DomainSelectionConnection connection) { |
| mConnection = connection; |
| } |
| |
| |
| @Override |
| public void onSelectionTerminated(@DisconnectCauses int cause) { |
| logd("onSelectionTerminated: emergency=" + mEmergency + ", cause=" + cause); |
| // This callback is invoked by another thread, so this operation is posted and handled |
| // through the execution flow of SmsDispatchersController. |
| SmsDispatchersController.this.sendMessage( |
| obtainMessage(EVENT_DOMAIN_SELECTION_TERMINATED_ABNORMALLY, this)); |
| } |
| } |
| |
| /** Manages the domain selection connections: MO SMS or emergency SMS. */ |
| private DomainSelectionConnectionHolder mDscHolder; |
| private DomainSelectionConnectionHolder mEmergencyDscHolder; |
| private EmergencyStateTracker mEmergencyStateTracker; |
| |
| /** |
| * Puts a delivery pending tracker to the map based on the format. |
| * |
| * @param tracker the tracker awaiting a delivery status report. |
| */ |
| public void putDeliveryPendingTracker(SMSDispatcher.SmsTracker tracker) { |
| if (isCdmaFormat(tracker.mFormat)) { |
| mDeliveryPendingMapFor3GPP2.put(tracker.mMessageRef, tracker); |
| } else { |
| mDeliveryPendingMapFor3GPP.put(tracker.mMessageRef, tracker); |
| } |
| } |
| |
| public SmsDispatchersController(Phone phone, SmsStorageMonitor storageMonitor, |
| SmsUsageMonitor usageMonitor, @NonNull FeatureFlags featureFlags) { |
| this(phone, storageMonitor, usageMonitor, phone.getLooper(), featureFlags); |
| } |
| |
| @VisibleForTesting |
| public SmsDispatchersController(Phone phone, SmsStorageMonitor storageMonitor, |
| SmsUsageMonitor usageMonitor, Looper looper, @NonNull FeatureFlags featureFlags) { |
| super(looper); |
| |
| Rlog.d(TAG, "SmsDispatchersController created"); |
| |
| mContext = phone.getContext(); |
| mUsageMonitor = usageMonitor; |
| mCi = phone.mCi; |
| mFeatureFlags = featureFlags; |
| mPhone = phone; |
| |
| // Create dispatchers, inbound SMS handlers and |
| // broadcast undelivered messages in raw table. |
| mImsSmsDispatcher = new ImsSmsDispatcher(phone, this, ImsManager::getConnector); |
| mCdmaDispatcher = new CdmaSMSDispatcher(phone, this); |
| mGsmInboundSmsHandler = GsmInboundSmsHandler.makeInboundSmsHandler(phone.getContext(), |
| storageMonitor, phone, looper); |
| mCdmaInboundSmsHandler = CdmaInboundSmsHandler.makeInboundSmsHandler(phone.getContext(), |
| storageMonitor, phone, (CdmaSMSDispatcher) mCdmaDispatcher, looper); |
| 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(); |
| // Cancels the domain selection request if it's still in progress. |
| finishDomainSelection(mDscHolder); |
| finishDomainSelection(mEmergencyDscHolder); |
| } |
| |
| /** |
| * 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; |
| case EVENT_SEND_SMS_USING_DOMAIN_SELECTION: { |
| SomeArgs args = (SomeArgs) msg.obj; |
| DomainSelectionConnectionHolder holder = |
| (DomainSelectionConnectionHolder) args.arg1; |
| PendingRequest request = (PendingRequest) args.arg2; |
| String logTag = (String) args.arg3; |
| try { |
| handleSendSmsUsingDomainSelection(holder, request, logTag); |
| } finally { |
| args.recycle(); |
| } |
| break; |
| } |
| case EVENT_SMS_SENT_COMPLETED_USING_DOMAIN_SELECTION: { |
| SomeArgs args = (SomeArgs) msg.obj; |
| String destAddr = (String) args.arg1; |
| Long messageId = (Long) args.arg2; |
| Boolean success = (Boolean) args.arg3; |
| Boolean isOverIms = (Boolean) args.arg4; |
| Boolean isLastSmsPart = (Boolean) args.arg5; |
| try { |
| handleSmsSentCompletedUsingDomainSelection( |
| destAddr, messageId, success, isOverIms, isLastSmsPart); |
| } finally { |
| args.recycle(); |
| } |
| break; |
| } |
| case EVENT_DOMAIN_SELECTION_TERMINATED_ABNORMALLY: { |
| handleDomainSelectionTerminatedAbnormally( |
| (DomainSelectionConnectionHolder) msg.obj); |
| break; |
| } |
| case EVENT_SMS_RECEIVED_VIA_IMS: { |
| handleSmsReceivedViaIms((String) msg.obj); |
| break; |
| } |
| case EVENT_REQUEST_DOMAIN_SELECTION: { |
| SomeArgs args = (SomeArgs) msg.obj; |
| DomainSelectionConnectionHolder holder = |
| (DomainSelectionConnectionHolder) args.arg1; |
| PendingRequest request = (PendingRequest) args.arg2; |
| String logTag = (String) args.arg3; |
| try { |
| requestDomainSelection(holder, request, logTag); |
| } finally { |
| args.recycle(); |
| } |
| break; |
| } |
| default: |
| if (isCdmaMo()) { |
| mCdmaDispatcher.handleMessage(msg); |
| } else { |
| mGsmDispatcher.handleMessage(msg); |
| } |
| } |
| } |
| |
| private String getSmscAddressFromUSIMWithPhoneIdentity(String callingPkg) { |
| final long identity = Binder.clearCallingIdentity(); |
| try { |
| IccSmsInterfaceManager iccSmsIntMgr = mPhone.getIccSmsInterfaceManager(); |
| if (iccSmsIntMgr != null) { |
| return iccSmsIntMgr.getSmscAddressFromIccEf(callingPkg); |
| } else { |
| Rlog.d(TAG, "getSmscAddressFromIccEf iccSmsIntMgr is null"); |
| } |
| } finally { |
| Binder.restoreCallingIdentity(identity); |
| } |
| return null; |
| } |
| |
| 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, 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, boolean isOverIms, |
| 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 */, isOverIms, 0 /* unused */); |
| } |
| |
| @VisibleForTesting |
| public void setImsSmsDispatcher(ImsSmsDispatcher imsSmsDispatcher) { |
| mImsSmsDispatcher = imsSmsDispatcher; |
| } |
| |
| /** |
| * 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, boolean isOverIms, int token) { |
| 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, isOverIms ? 1 : 0, token, 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, isOverIms ? 1 : 0, 0, 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); |
| } |
| } |
| |
| /** |
| * sets ImsManager object. |
| * |
| * @param imsManager holds a valid object or a null for setting |
| */ |
| public boolean setImsManager(ImsManager imsManager) { |
| if (mGsmInboundSmsHandler != null) { |
| mGsmInboundSmsHandler.setImsManager(imsManager); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Retry the message along to the radio. |
| * |
| * @param tracker holds the SMS message to send |
| */ |
| public void sendRetrySms(SMSDispatcher.SmsTracker tracker) { |
| boolean retryUsingImsService = false; |
| |
| if (!tracker.mUsesImsServiceForIms) { |
| if (isSmsDomainSelectionEnabled()) { |
| TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); |
| boolean isEmergency = tm.isEmergencyNumber(tracker.mDestAddress); |
| DomainSelectionConnectionHolder holder = getDomainSelectionConnection(isEmergency); |
| |
| // If the DomainSelectionConnection is not available, |
| // fallback to the legacy implementation. |
| if (holder != null && holder.getConnection() != null) { |
| // This may be invoked by another thread, so this operation is posted and |
| // handled through the execution flow of SmsDispatchersController. |
| SomeArgs args = SomeArgs.obtain(); |
| args.arg1 = holder; |
| args.arg2 = new PendingRequest(PendingRequest.TYPE_RETRY_SMS, tracker, |
| null, null, null, null, null, false, null, 0, null, null, false, |
| 0, false, 0, 0L, false); |
| args.arg3 = "sendRetrySms"; |
| sendMessage(obtainMessage(EVENT_REQUEST_DOMAIN_SELECTION, args)); |
| return; |
| } |
| } |
| |
| if (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; |
| } |
| } |
| |
| sendRetrySms(tracker, retryUsingImsService); |
| } |
| |
| /** |
| * Retry the message along to the radio. |
| * |
| * @param tracker holds the SMS message to send |
| * @param retryUsingImsService a flag to indicate whether the retry SMS can use the ImsService |
| */ |
| public void sendRetrySms(SMSDispatcher.SmsTracker tracker, boolean retryUsingImsService) { |
| String oldFormat = tracker.mFormat; |
| // 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); |
| notifySmsSentFailedToEmergencyStateTracker( |
| tracker.mDestAddress, tracker.mMessageId, !retryUsingImsService); |
| return; |
| } |
| String scAddr = (String) map.get("scAddr"); |
| String destAddr = (String) map.get("destAddr"); |
| if (destAddr == null) { |
| Rlog.e(TAG, "sendRetrySms failed due to null destAddr"); |
| tracker.onFailed(mContext, SmsManager.RESULT_SMS_SEND_RETRY_FAILED, NO_ERROR_CODE); |
| notifySmsSentFailedToEmergencyStateTracker( |
| tracker.mDestAddress, tracker.mMessageId, !retryUsingImsService); |
| return; |
| } |
| |
| SmsMessageBase.SubmitPduBase pdu = null; |
| // figure out from tracker if this was sendText/Data |
| if (map.containsKey("text")) { |
| String text = (String) map.get("text"); |
| Rlog.d(TAG, "sms failed was text with length: " |
| + (text == null ? null : text.length())); |
| |
| 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, |
| 0, 0, 0, -1, tracker.mMessageRef); |
| } |
| } else if (map.containsKey("data")) { |
| byte[] data = (byte[]) map.get("data"); |
| Integer destPort = (Integer) map.get("destPort"); |
| Rlog.d(TAG, "sms failed was data with length: " |
| + (data == null ? null : data.length)); |
| |
| 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), tracker.mMessageRef); |
| } |
| } |
| |
| if (pdu == null) { |
| Rlog.e(TAG, String.format("sendRetrySms failed to encode message." |
| + "scAddr: %s, " |
| + "destPort: %s", scAddr, map.get("destPort"))); |
| tracker.onFailed(mContext, SmsManager.RESULT_SMS_SEND_RETRY_FAILED, NO_ERROR_CODE); |
| notifySmsSentFailedToEmergencyStateTracker( |
| tracker.mDestAddress, tracker.mMessageId, !retryUsingImsService); |
| return; |
| } |
| // 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); |
| } |
| |
| /** |
| * Memory Available Event |
| * @param result callback message |
| */ |
| public void reportSmsMemoryStatus(Message result) { |
| Rlog.d(TAG, "reportSmsMemoryStatus: "); |
| try { |
| mImsSmsDispatcher.onMemoryAvailable(); |
| AsyncResult.forMessage(result, null, null); |
| result.sendToTarget(); |
| } catch (Exception e) { |
| Rlog.e(TAG, "reportSmsMemoryStatus Failed ", e); |
| AsyncResult.forMessage(result, null, e); |
| result.sendToTarget(); |
| } |
| } |
| |
| /** |
| * 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)); |
| } |
| |
| /** Sets a proxy interface for accessing the methods of {@link DomainSelectionResolver}. */ |
| @VisibleForTesting |
| public void setDomainSelectionResolverProxy(@NonNull DomainSelectionResolverProxy proxy) { |
| mDomainSelectionResolverProxy = proxy; |
| } |
| |
| /** |
| * Checks whether the SMS domain selection is enabled or not. |
| * |
| * @return {@code true} if the SMS domain selection is enabled, {@code false} otherwise. |
| */ |
| private boolean isSmsDomainSelectionEnabled() { |
| return mFeatureFlags.smsDomainSelectionEnabled() |
| && mDomainSelectionResolverProxy.isDomainSelectionSupported(); |
| } |
| |
| /** |
| * Determines whether or not to use CDMA format for MO SMS when the domain selection uses. |
| * If the domain is {@link NetworkRegistrationInfo#DOMAIN_PS}, then format is based on |
| * IMS SMS format, otherwise format is based on current phone type. |
| * |
| * @return {@code true} if CDMA format should be used for MO SMS, {@code false} otherwise. |
| */ |
| private boolean isCdmaMo(@NetworkRegistrationInfo.Domain int domain) { |
| if (domain != NetworkRegistrationInfo.DOMAIN_PS) { |
| // 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(mImsSmsDispatcher.getFormat()); |
| } |
| |
| /** |
| * Returns a {@link DomainSelectionConnectionHolder} according to the flag specified. |
| * |
| * @param emergency The flag to indicate that the domain selection is for an emergency SMS. |
| * @return A {@link DomainSelectionConnectionHolder} instance or null. |
| */ |
| @VisibleForTesting |
| @Nullable |
| protected DomainSelectionConnectionHolder getDomainSelectionConnectionHolder( |
| boolean emergency) { |
| return emergency ? mEmergencyDscHolder : mDscHolder; |
| } |
| |
| /** |
| * Returns a {@link DomainSelectionConnectionHolder} if the domain selection supports, |
| * return null otherwise. |
| * |
| * @param emergency The flag to indicate that the domain selection is for an emergency SMS. |
| * @return A {@link DomainSelectionConnectionHolder} that grabs the |
| * {@link DomainSelectionConnection} and its related information to use the domain |
| * selection architecture. |
| */ |
| private DomainSelectionConnectionHolder getDomainSelectionConnection(boolean emergency) { |
| DomainSelectionConnectionHolder holder = getDomainSelectionConnectionHolder(emergency); |
| DomainSelectionConnection connection = (holder != null) ? holder.getConnection() : null; |
| |
| if (connection == null) { |
| connection = mDomainSelectionResolverProxy.getDomainSelectionConnection( |
| mPhone, DomainSelectionService.SELECTOR_TYPE_SMS, emergency); |
| |
| if (connection == null) { |
| // Domain selection architecture is not supported. |
| // Use the legacy architecture. |
| return null; |
| } |
| } |
| |
| if (holder == null) { |
| holder = new DomainSelectionConnectionHolder(emergency); |
| |
| if (emergency) { |
| mEmergencyDscHolder = holder; |
| } else { |
| mDscHolder = holder; |
| } |
| } |
| |
| holder.setConnection(connection); |
| |
| return holder; |
| } |
| |
| /** |
| * Requests the domain selection for MO SMS. |
| * |
| * @param holder The {@link DomainSelectionConnectionHolder} that contains the |
| * {@link DomainSelectionConnection} and its related information. |
| */ |
| @SuppressWarnings("FutureReturnValueIgnored") |
| private void requestDomainSelection(@NonNull DomainSelectionConnectionHolder holder) { |
| DomainSelectionService.SelectionAttributes attr = |
| new DomainSelectionService.SelectionAttributes.Builder(mPhone.getPhoneId(), |
| mPhone.getSubId(), DomainSelectionService.SELECTOR_TYPE_SMS) |
| .setEmergency(holder.isEmergency()) |
| .build(); |
| |
| if (holder.isEmergency()) { |
| EmergencySmsDomainSelectionConnection emergencyConnection = |
| (EmergencySmsDomainSelectionConnection) holder.getConnection(); |
| CompletableFuture<Integer> future = |
| emergencyConnection.requestDomainSelection(attr, holder); |
| future.thenAcceptAsync((domain) -> { |
| if (VDBG) { |
| logd("requestDomainSelection(emergency): domain=" |
| + DomainSelectionService.getDomainName(domain)); |
| } |
| sendAllPendingRequests(holder, domain); |
| finishDomainSelection(holder); |
| }, this::post); |
| } else { |
| SmsDomainSelectionConnection connection = |
| (SmsDomainSelectionConnection) holder.getConnection(); |
| CompletableFuture<Integer> future = connection.requestDomainSelection(attr, holder); |
| future.thenAcceptAsync((domain) -> { |
| if (VDBG) { |
| logd("requestDomainSelection: domain=" |
| + DomainSelectionService.getDomainName(domain)); |
| } |
| sendAllPendingRequests(holder, domain); |
| finishDomainSelection(holder); |
| }, this::post); |
| } |
| } |
| |
| /** |
| * Requests the domain selection for MO SMS. |
| * |
| * @param holder The {@link DomainSelectionConnectionHolder} that contains the |
| * {@link DomainSelectionConnection} and its related information. |
| * @param logTag The log string. |
| */ |
| private void requestDomainSelection(@NonNull DomainSelectionConnectionHolder holder, |
| @NonNull PendingRequest request, String logTag) { |
| boolean isDomainSelectionRequested = holder.isDomainSelectionRequested(); |
| // The domain selection is in progress so waits for the result of |
| // the domain selection by adding this request to the pending list. |
| holder.addRequest(request); |
| |
| if (!isDomainSelectionRequested) { |
| if (VDBG) { |
| logd("requestDomainSelection: " + logTag); |
| } |
| requestDomainSelection(holder); |
| } |
| } |
| |
| /** |
| * Handles an event for sending a SMS after selecting the domain via the domain selection |
| * service. |
| * |
| * @param holder The {@link DomainSelectionConnectionHolder} that contains the |
| * {@link DomainSelectionConnection} and its related information. |
| * @param request The {@link PendingRequest} that stores the SMS request |
| * (data, text, multipart text) to be sent. |
| * @param logTag The log tag to display which method called this method. |
| */ |
| @SuppressWarnings("FutureReturnValueIgnored") |
| private void handleSendSmsUsingDomainSelection(@NonNull DomainSelectionConnectionHolder holder, |
| @NonNull PendingRequest request, @NonNull String logTag) { |
| if (holder.isEmergency()) { |
| if (mEmergencyStateTracker == null) { |
| mEmergencyStateTracker = EmergencyStateTracker.getInstance(); |
| } |
| |
| CompletableFuture<Integer> future = mEmergencyStateTracker.startEmergencySms(mPhone, |
| String.valueOf(request.messageId), |
| isTestEmergencyNumber(request.destAddr)); |
| future.thenAccept((result) -> { |
| logi("startEmergencySms(" + logTag + "): messageId=" + request.messageId |
| + ", result=" + result); |
| // An emergency SMS should be proceeded regardless of the result of the |
| // EmergencyStateTracker. |
| // So the domain selection request should be invoked without checking the result. |
| requestDomainSelection(holder, request, logTag); |
| }); |
| } else { |
| requestDomainSelection(holder, request, logTag); |
| } |
| } |
| |
| /** |
| * Sends a SMS after selecting the domain via the domain selection service. |
| * |
| * @param holder The {@link DomainSelectionConnectionHolder} that contains the |
| * {@link DomainSelectionConnection} and its related information. |
| * @param request The {@link PendingRequest} that stores the SMS request |
| * (data, text, multipart text) to be sent. |
| * @param logTag The log tag to display which method called this method. |
| */ |
| private void sendSmsUsingDomainSelection(@NonNull DomainSelectionConnectionHolder holder, |
| @NonNull PendingRequest request, @NonNull String logTag) { |
| // Run on main thread for interworking with EmergencyStateTracker |
| // and adding the pending request. |
| SomeArgs args = SomeArgs.obtain(); |
| args.arg1 = holder; |
| args.arg2 = request; |
| args.arg3 = logTag; |
| sendMessage(obtainMessage(EVENT_SEND_SMS_USING_DOMAIN_SELECTION, args)); |
| } |
| |
| /** |
| * Called when sending MO SMS is complete regardless of the sent result. |
| * |
| * @param destAddr The destination address for SMS. |
| * @param messageId The message id for SMS. |
| * @param success A flag specifying whether MO SMS is successfully sent or not. |
| * @param isOverIms A flag specifying whether MO SMS is sent over IMS or not. |
| * @param isLastSmsPart A flag specifying whether this result is for the last SMS part or not. |
| */ |
| private void handleSmsSentCompletedUsingDomainSelection(@NonNull String destAddr, |
| long messageId, boolean success, boolean isOverIms, boolean isLastSmsPart) { |
| if (mEmergencyStateTracker != null) { |
| TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); |
| if (tm.isEmergencyNumber(destAddr)) { |
| mEmergencyStateTracker.endSms(String.valueOf(messageId), success, |
| isOverIms ? NetworkRegistrationInfo.DOMAIN_PS |
| : NetworkRegistrationInfo.DOMAIN_CS, |
| isLastSmsPart); |
| } |
| } |
| } |
| |
| /** |
| * Called when MO SMS is successfully sent. |
| */ |
| protected void notifySmsSentToEmergencyStateTracker(@NonNull String destAddr, long messageId, |
| boolean isOverIms, boolean isLastSmsPart) { |
| if (isSmsDomainSelectionEnabled()) { |
| // Run on main thread for interworking with EmergencyStateTracker. |
| SomeArgs args = SomeArgs.obtain(); |
| args.arg1 = destAddr; |
| args.arg2 = Long.valueOf(messageId); |
| args.arg3 = Boolean.TRUE; |
| args.arg4 = Boolean.valueOf(isOverIms); |
| args.arg5 = Boolean.valueOf(isLastSmsPart); |
| sendMessage(obtainMessage(EVENT_SMS_SENT_COMPLETED_USING_DOMAIN_SELECTION, args)); |
| } |
| } |
| |
| /** |
| * Called when sending MO SMS is failed. |
| */ |
| protected void notifySmsSentFailedToEmergencyStateTracker(@NonNull String destAddr, |
| long messageId, boolean isOverIms) { |
| if (isSmsDomainSelectionEnabled()) { |
| // Run on main thread for interworking with EmergencyStateTracker. |
| SomeArgs args = SomeArgs.obtain(); |
| args.arg1 = destAddr; |
| args.arg2 = Long.valueOf(messageId); |
| args.arg3 = Boolean.FALSE; |
| args.arg4 = Boolean.valueOf(isOverIms); |
| args.arg5 = Boolean.TRUE; // Ignored when sending SMS is failed. |
| sendMessage(obtainMessage(EVENT_SMS_SENT_COMPLETED_USING_DOMAIN_SELECTION, args)); |
| } |
| } |
| |
| /** |
| * Called when MT SMS is received via IMS. |
| * |
| * @param origAddr The originating address of MT SMS. |
| */ |
| private void handleSmsReceivedViaIms(@Nullable String origAddr) { |
| if (mEmergencyStateTracker != null) { |
| TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); |
| if (origAddr != null && tm.isEmergencyNumber(origAddr)) { |
| mEmergencyStateTracker.onEmergencySmsReceived(); |
| } |
| } |
| } |
| |
| /** |
| * Called when MT SMS is received via IMS. |
| */ |
| protected void notifySmsReceivedViaImsToEmergencyStateTracker(@Nullable String origAddr) { |
| if (isSmsDomainSelectionEnabled()) { |
| // Run on main thread for interworking with EmergencyStateTracker. |
| sendMessage(obtainMessage(EVENT_SMS_RECEIVED_VIA_IMS, origAddr)); |
| } |
| } |
| |
| private boolean isTestEmergencyNumber(String number) { |
| try { |
| TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); |
| Map<Integer, List<EmergencyNumber>> eMap = tm.getEmergencyNumberList(); |
| return eMap.values().stream().flatMap(Collection::stream).anyMatch(eNumber -> |
| eNumber.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST) |
| && number.equals(eNumber.getNumber())); |
| } catch (IllegalStateException ise) { |
| return false; |
| } catch (RuntimeException r) { |
| return false; |
| } |
| } |
| |
| /** |
| * Finishes the domain selection for MO SMS. |
| * |
| * @param holder The {@link DomainSelectionConnectionHolder} object that is being finished. |
| */ |
| private void finishDomainSelection(DomainSelectionConnectionHolder holder) { |
| DomainSelectionConnection connection = (holder != null) ? holder.getConnection() : null; |
| |
| if (connection != null) { |
| // After this method is called, the domain selection service will clean up |
| // its resources and finish the procedure that are related to the current domain |
| // selection request. |
| connection.finishSelection(); |
| } |
| |
| if (holder != null) { |
| final List<PendingRequest> pendingRequests = holder.getPendingRequests(); |
| |
| logd("finishDomainSelection: pendingRequests=" + pendingRequests.size()); |
| |
| for (PendingRequest r : pendingRequests) { |
| triggerSentIntentForFailure(r.sentIntents); |
| } |
| |
| holder.clearAllRequests(); |
| holder.setConnection(null); |
| } |
| } |
| |
| /** |
| * Called when MO SMS is not sent by the error of domain selection. |
| * |
| * @param holder The {@link DomainSelectionConnectionHolder} object that is being terminated. |
| */ |
| private void handleDomainSelectionTerminatedAbnormally( |
| @NonNull DomainSelectionConnectionHolder holder) { |
| logd("handleDomainSelectionTerminatedAbnormally: pendingRequests=" |
| + holder.getPendingRequests().size()); |
| sendAllPendingRequests(holder, NetworkRegistrationInfo.DOMAIN_UNKNOWN); |
| holder.setConnection(null); |
| } |
| |
| /** |
| * Sends all pending requests for MO SMS. |
| * |
| * @param holder The {@link DomainSelectionConnectionHolder} object that all the pending |
| * requests are handled. |
| * @param domain The domain where the SMS is being sent, which can be one of the following: |
| * - {@link NetworkRegistrationInfo#DOMAIN_PS} |
| * - {@link NetworkRegistrationInfo#DOMAIN_CS} |
| */ |
| private void sendAllPendingRequests(@NonNull DomainSelectionConnectionHolder holder, |
| @NetworkRegistrationInfo.Domain int domain) { |
| final List<PendingRequest> pendingRequests = holder.getPendingRequests(); |
| |
| if (VDBG) { |
| logd("sendAllPendingRequests: domain=" + DomainSelectionService.getDomainName(domain) |
| + ", size=" + pendingRequests.size()); |
| } |
| |
| // When the domain selection request is failed, SMS should be fallback |
| // to the legacy implementation. |
| boolean wasDomainUnknown = false; |
| |
| if (domain == NetworkRegistrationInfo.DOMAIN_UNKNOWN) { |
| logd("sendAllPendingRequests: fallback - imsAvailable=" |
| + mImsSmsDispatcher.isAvailable()); |
| |
| wasDomainUnknown = true; |
| |
| if (mImsSmsDispatcher.isAvailable()) { |
| domain = NetworkRegistrationInfo.DOMAIN_PS; |
| } else { |
| domain = NetworkRegistrationInfo.DOMAIN_CS; |
| } |
| } |
| |
| for (PendingRequest r : pendingRequests) { |
| switch (r.type) { |
| case PendingRequest.TYPE_DATA: |
| sendData(domain, r); |
| break; |
| case PendingRequest.TYPE_TEXT: |
| // When the domain selection request is failed, emergency SMS should be fallback |
| // to the legacy implementation. |
| if (wasDomainUnknown |
| && domain != NetworkRegistrationInfo.DOMAIN_PS |
| && mImsSmsDispatcher.isEmergencySmsSupport(r.destAddr)) { |
| domain = NetworkRegistrationInfo.DOMAIN_PS; |
| } |
| sendText(domain, r); |
| break; |
| case PendingRequest.TYPE_MULTIPART_TEXT: |
| sendMultipartText(domain, r); |
| break; |
| case PendingRequest.TYPE_RETRY_SMS: |
| sendRetrySms(r.tracker, (domain == NetworkRegistrationInfo.DOMAIN_PS)); |
| break; |
| default: |
| // Not reachable. |
| break; |
| } |
| } |
| |
| holder.clearAllRequests(); |
| } |
| |
| /** |
| * Sends a data based SMS to a specific application port. |
| * |
| * @param domain The domain where the SMS is being sent, which can be one of the following: |
| * - {@link NetworkRegistrationInfo#DOMAIN_PS} |
| * - {@link NetworkRegistrationInfo#DOMAIN_CS} |
| * @param request The pending request for MO SMS. |
| */ |
| private void sendData(@NetworkRegistrationInfo.Domain int domain, |
| @NonNull PendingRequest request) { |
| if (domain == NetworkRegistrationInfo.DOMAIN_PS) { |
| mImsSmsDispatcher.sendData(request.callingPackage, request.destAddr, request.scAddr, |
| request.destPort, request.data, request.sentIntents.get(0), |
| request.deliveryIntents.get(0), request.isForVvm); |
| } else if (isCdmaMo(domain)) { |
| mCdmaDispatcher.sendData(request.callingPackage, request.destAddr, request.scAddr, |
| request.destPort, request.data, request.sentIntents.get(0), |
| request.deliveryIntents.get(0), request.isForVvm); |
| } else { |
| mGsmDispatcher.sendData(request.callingPackage, request.destAddr, request.scAddr, |
| request.destPort, request.data, request.sentIntents.get(0), |
| request.deliveryIntents.get(0), request.isForVvm); |
| } |
| } |
| |
| /** |
| * Sends a text based SMS. |
| * |
| * @param domain The domain where the SMS is being sent, which can be one of the following: |
| * - {@link NetworkRegistrationInfo#DOMAIN_PS} |
| * - {@link NetworkRegistrationInfo#DOMAIN_CS} |
| * @param request The pending request for MO SMS. |
| */ |
| private void sendText(@NetworkRegistrationInfo.Domain int domain, |
| @NonNull PendingRequest request) { |
| if (domain == NetworkRegistrationInfo.DOMAIN_PS) { |
| mImsSmsDispatcher.sendText(request.destAddr, request.scAddr, request.texts.get(0), |
| request.sentIntents.get(0), request.deliveryIntents.get(0), |
| request.messageUri, request.callingPackage, request.persistMessage, |
| request.priority, false /*request.expectMore*/, request.validityPeriod, |
| request.isForVvm, request.messageId, request.skipShortCodeCheck); |
| } else { |
| if (isCdmaMo(domain)) { |
| mCdmaDispatcher.sendText(request.destAddr, request.scAddr, request.texts.get(0), |
| request.sentIntents.get(0), request.deliveryIntents.get(0), |
| request.messageUri, request.callingPackage, request.persistMessage, |
| request.priority, request.expectMore, request.validityPeriod, |
| request.isForVvm, request.messageId, request.skipShortCodeCheck); |
| } else { |
| mGsmDispatcher.sendText(request.destAddr, request.scAddr, request.texts.get(0), |
| request.sentIntents.get(0), request.deliveryIntents.get(0), |
| request.messageUri, request.callingPackage, request.persistMessage, |
| request.priority, request.expectMore, request.validityPeriod, |
| request.isForVvm, request.messageId, request.skipShortCodeCheck); |
| } |
| } |
| } |
| |
| /** |
| * Sends a multi-part text based SMS. |
| * |
| * @param domain The domain where the SMS is being sent, which can be one of the following: |
| * - {@link NetworkRegistrationInfo#DOMAIN_PS} |
| * - {@link NetworkRegistrationInfo#DOMAIN_CS} |
| * @param request The pending request for MO SMS. |
| */ |
| private void sendMultipartText(@NetworkRegistrationInfo.Domain int domain, |
| @NonNull PendingRequest request) { |
| if (domain == NetworkRegistrationInfo.DOMAIN_PS) { |
| mImsSmsDispatcher.sendMultipartText(request.destAddr, request.scAddr, request.texts, |
| request.sentIntents, request.deliveryIntents, request.messageUri, |
| request.callingPackage, request.persistMessage, request.priority, |
| false /*request.expectMore*/, request.validityPeriod, request.messageId); |
| } else { |
| if (isCdmaMo(domain)) { |
| mCdmaDispatcher.sendMultipartText(request.destAddr, request.scAddr, request.texts, |
| request.sentIntents, request.deliveryIntents, request.messageUri, |
| request.callingPackage, request.persistMessage, request.priority, |
| request.expectMore, request.validityPeriod, request.messageId); |
| } else { |
| mGsmDispatcher.sendMultipartText(request.destAddr, request.scAddr, request.texts, |
| request.sentIntents, request.deliveryIntents, request.messageUri, |
| request.callingPackage, request.persistMessage, request.priority, |
| request.expectMore, request.validityPeriod, request.messageId); |
| } |
| } |
| } |
| |
| private void triggerSentIntentForFailure(@NonNull PendingIntent sentIntent) { |
| try { |
| sentIntent.send(SmsManager.RESULT_ERROR_GENERIC_FAILURE); |
| } catch (CanceledException e) { |
| logd("Intent has been canceled!"); |
| } |
| } |
| |
| private void triggerSentIntentForFailure(@NonNull List<PendingIntent> sentIntents) { |
| for (PendingIntent sentIntent : sentIntents) { |
| triggerSentIntentForFailure(sentIntent); |
| } |
| } |
| |
| /** |
| * Creates an ArrayList object from any object. |
| */ |
| private static <T> ArrayList<T> asArrayList(T object) { |
| ArrayList<T> list = new ArrayList<>(); |
| list.add(object); |
| return list; |
| } |
| |
| /** |
| * 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>SmsManager.RESULT_ERROR_GENERIC_FAILURE</code><br> |
| * <code>SmsManager.RESULT_ERROR_RADIO_OFF</code><br> |
| * <code>SmsManager.RESULT_ERROR_NULL_PDU</code><br> |
| * <code>SmsManager.RESULT_ERROR_NO_SERVICE</code><br> |
| * <code>SmsManager.RESULT_ERROR_LIMIT_EXCEEDED</code><br> |
| * <code>SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE</code><br> |
| * <code>SmsManager.RESULT_ERROR_SHORT_CODE_NOT_ALLOWED</code><br> |
| * <code>SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED</code><br> |
| * <code>SmsManager.RESULT_RADIO_NOT_AVAILABLE</code><br> |
| * <code>SmsManager.RESULT_NETWORK_REJECT</code><br> |
| * <code>SmsManager.RESULT_INVALID_ARGUMENTS</code><br> |
| * <code>SmsManager.RESULT_INVALID_STATE</code><br> |
| * <code>SmsManager.RESULT_NO_MEMORY</code><br> |
| * <code>SmsManager.RESULT_INVALID_SMS_FORMAT</code><br> |
| * <code>SmsManager.RESULT_SYSTEM_ERROR</code><br> |
| * <code>SmsManager.RESULT_MODEM_ERROR</code><br> |
| * <code>SmsManager.RESULT_NETWORK_ERROR</code><br> |
| * <code>SmsManager.RESULT_ENCODING_ERROR</code><br> |
| * <code>SmsManager.RESULT_INVALID_SMSC_ADDRESS</code><br> |
| * <code>SmsManager.RESULT_OPERATION_NOT_ALLOWED</code><br> |
| * <code>SmsManager.RESULT_INTERNAL_ERROR</code><br> |
| * <code>SmsManager.RESULT_NO_RESOURCES</code><br> |
| * <code>SmsManager.RESULT_CANCELLED</code><br> |
| * <code>SmsManager.RESULT_REQUEST_NOT_SUPPORTED</code><br> |
| * <code>SmsManager.RESULT_NO_BLUETOOTH_SERVICE</code><br> |
| * <code>SmsManager.RESULT_INVALID_BLUETOOTH_ADDRESS</code><br> |
| * <code>SmsManager.RESULT_BLUETOOTH_DISCONNECTED</code><br> |
| * <code>SmsManager.RESULT_UNEXPECTED_EVENT_STOP_SENDING</code><br> |
| * <code>SmsManager.RESULT_SMS_BLOCKED_DURING_EMERGENCY</code><br> |
| * <code>SmsManager.RESULT_SMS_SEND_RETRY_FAILED</code><br> |
| * <code>SmsManager.RESULT_REMOTE_EXCEPTION</code><br> |
| * <code>SmsManager.RESULT_NO_DEFAULT_SMS_APP</code><br> |
| * <code>SmsManager.RESULT_RIL_RADIO_NOT_AVAILABLE</code><br> |
| * <code>SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY</code><br> |
| * <code>SmsManager.RESULT_RIL_NETWORK_REJECT</code><br> |
| * <code>SmsManager.RESULT_RIL_INVALID_STATE</code><br> |
| * <code>SmsManager.RESULT_RIL_INVALID_ARGUMENTS</code><br> |
| * <code>SmsManager.RESULT_RIL_NO_MEMORY</code><br> |
| * <code>SmsManager.RESULT_RIL_REQUEST_RATE_LIMITED</code><br> |
| * <code>SmsManager.RESULT_RIL_INVALID_SMS_FORMAT</code><br> |
| * <code>SmsManager.RESULT_RIL_SYSTEM_ERR</code><br> |
| * <code>SmsManager.RESULT_RIL_ENCODING_ERR</code><br> |
| * <code>SmsManager.RESULT_RIL_INVALID_SMSC_ADDRESS</code><br> |
| * <code>SmsManager.RESULT_RIL_MODEM_ERR</code><br> |
| * <code>SmsManager.RESULT_RIL_NETWORK_ERR</code><br> |
| * <code>SmsManager.RESULT_RIL_INTERNAL_ERR</code><br> |
| * <code>SmsManager.RESULT_RIL_REQUEST_NOT_SUPPORTED</code><br> |
| * <code>SmsManager.RESULT_RIL_INVALID_MODEM_STATE</code><br> |
| * <code>SmsManager.RESULT_RIL_NETWORK_NOT_READY</code><br> |
| * <code>SmsManager.RESULT_RIL_OPERATION_NOT_ALLOWED</code><br> |
| * <code>SmsManager.RESULT_RIL_NO_RESOURCES</code><br> |
| * <code>SmsManager.RESULT_RIL_CANCELLED</code><br> |
| * <code>SmsManager.RESULT_RIL_SIM_ABSENT</code><br> |
| * <code>SmsManager.RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED</code><br> |
| * <code>SmsManager.RESULT_RIL_ACCESS_BARRED</code><br> |
| * <code>SmsManager.RESULT_RIL_BLOCKED_DUE_TO_CALL</code><br> |
| * For <code>SmsManager.RESULT_ERROR_GENERIC_FAILURE</code> or any of the RESULT_RIL errors, |
| * 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 (TextUtils.isEmpty(scAddr)) { |
| scAddr = getSmscAddressFromUSIMWithPhoneIdentity(callingPackage); |
| } |
| |
| if (isSmsDomainSelectionEnabled()) { |
| DomainSelectionConnectionHolder holder = getDomainSelectionConnection(false); |
| |
| // If the DomainSelectionConnection is not available, |
| // fallback to the legacy implementation. |
| if (holder != null && holder.getConnection() != null) { |
| sendSmsUsingDomainSelection(holder, |
| new PendingRequest(PendingRequest.TYPE_DATA, null, callingPackage, |
| destAddr, scAddr, asArrayList(sentIntent), |
| asArrayList(deliveryIntent), isForVvm, data, destPort, null, null, |
| false, 0, false, 0, 0L, false), |
| "sendData"); |
| return; |
| } |
| } |
| |
| 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>SmsManager.RESULT_ERROR_GENERIC_FAILURE</code><br> |
| * <code>SmsManager.RESULT_ERROR_RADIO_OFF</code><br> |
| * <code>SmsManager.RESULT_ERROR_NULL_PDU</code><br> |
| * <code>SmsManager.RESULT_ERROR_NO_SERVICE</code><br> |
| * <code>SmsManager.RESULT_ERROR_LIMIT_EXCEEDED</code><br> |
| * <code>SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE</code><br> |
| * <code>SmsManager.RESULT_ERROR_SHORT_CODE_NOT_ALLOWED</code><br> |
| * <code>SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED</code><br> |
| * <code>SmsManager.RESULT_RADIO_NOT_AVAILABLE</code><br> |
| * <code>SmsManager.RESULT_NETWORK_REJECT</code><br> |
| * <code>SmsManager.RESULT_INVALID_ARGUMENTS</code><br> |
| * <code>SmsManager.RESULT_INVALID_STATE</code><br> |
| * <code>SmsManager.RESULT_NO_MEMORY</code><br> |
| * <code>SmsManager.RESULT_INVALID_SMS_FORMAT</code><br> |
| * <code>SmsManager.RESULT_SYSTEM_ERROR</code><br> |
| * <code>SmsManager.RESULT_MODEM_ERROR</code><br> |
| * <code>SmsManager.RESULT_NETWORK_ERROR</code><br> |
| * <code>SmsManager.RESULT_ENCODING_ERROR</code><br> |
| * <code>SmsManager.RESULT_INVALID_SMSC_ADDRESS</code><br> |
| * <code>SmsManager.RESULT_OPERATION_NOT_ALLOWED</code><br> |
| * <code>SmsManager.RESULT_INTERNAL_ERROR</code><br> |
| * <code>SmsManager.RESULT_NO_RESOURCES</code><br> |
| * <code>SmsManager.RESULT_CANCELLED</code><br> |
| * <code>SmsManager.RESULT_REQUEST_NOT_SUPPORTED</code><br> |
| * <code>SmsManager.RESULT_NO_BLUETOOTH_SERVICE</code><br> |
| * <code>SmsManager.RESULT_INVALID_BLUETOOTH_ADDRESS</code><br> |
| * <code>SmsManager.RESULT_BLUETOOTH_DISCONNECTED</code><br> |
| * <code>SmsManager.RESULT_UNEXPECTED_EVENT_STOP_SENDING</code><br> |
| * <code>SmsManager.RESULT_SMS_BLOCKED_DURING_EMERGENCY</code><br> |
| * <code>SmsManager.RESULT_SMS_SEND_RETRY_FAILED</code><br> |
| * <code>SmsManager.RESULT_REMOTE_EXCEPTION</code><br> |
| * <code>SmsManager.RESULT_NO_DEFAULT_SMS_APP</code><br> |
| * <code>SmsManager.RESULT_RIL_RADIO_NOT_AVAILABLE</code><br> |
| * <code>SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY</code><br> |
| * <code>SmsManager.RESULT_RIL_NETWORK_REJECT</code><br> |
| * <code>SmsManager.RESULT_RIL_INVALID_STATE</code><br> |
| * <code>SmsManager.RESULT_RIL_INVALID_ARGUMENTS</code><br> |
| * <code>SmsManager.RESULT_RIL_NO_MEMORY</code><br> |
| * <code>SmsManager.RESULT_RIL_REQUEST_RATE_LIMITED</code><br> |
| * <code>SmsManager.RESULT_RIL_INVALID_SMS_FORMAT</code><br> |
| * <code>SmsManager.RESULT_RIL_SYSTEM_ERR</code><br> |
| * <code>SmsManager.RESULT_RIL_ENCODING_ERR</code><br> |
| * <code>SmsManager.RESULT_RIL_INVALID_SMSC_ADDRESS</code><br> |
| * <code>SmsManager.RESULT_RIL_MODEM_ERR</code><br> |
| * <code>SmsManager.RESULT_RIL_NETWORK_ERR</code><br> |
| * <code>SmsManager.RESULT_RIL_INTERNAL_ERR</code><br> |
| * <code>SmsManager.RESULT_RIL_REQUEST_NOT_SUPPORTED</code><br> |
| * <code>SmsManager.RESULT_RIL_INVALID_MODEM_STATE</code><br> |
| * <code>SmsManager.RESULT_RIL_NETWORK_NOT_READY</code><br> |
| * <code>SmsManager.RESULT_RIL_OPERATION_NOT_ALLOWED</code><br> |
| * <code>SmsManager.RESULT_RIL_NO_RESOURCES</code><br> |
| * <code>SmsManager.RESULT_RIL_CANCELLED</code><br> |
| * <code>SmsManager.RESULT_RIL_SIM_ABSENT</code><br> |
| * <code>SmsManager.RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED</code><br> |
| * <code>SmsManager.RESULT_RIL_ACCESS_BARRED</code><br> |
| * <code>SmsManager.RESULT_RIL_BLOCKED_DUE_TO_CALL</code><br> |
| * For <code>SmsManager.RESULT_ERROR_GENERIC_FAILURE</code> or any of the RESULT_RIL errors, |
| * 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) { |
| sendText(destAddr, scAddr, text, sentIntent, deliveryIntent, messageUri, callingPkg, |
| persistMessage, priority, expectMore, validityPeriod, isForVvm, messageId, false); |
| } |
| |
| /** |
| * 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>SmsManager.RESULT_ERROR_GENERIC_FAILURE</code><br> |
| * <code>SmsManager.RESULT_ERROR_RADIO_OFF</code><br> |
| * <code>SmsManager.RESULT_ERROR_NULL_PDU</code><br> |
| * <code>SmsManager.RESULT_ERROR_NO_SERVICE</code><br> |
| * <code>SmsManager.RESULT_ERROR_LIMIT_EXCEEDED</code><br> |
| * <code>SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE</code><br> |
| * <code>SmsManager.RESULT_ERROR_SHORT_CODE_NOT_ALLOWED</code><br> |
| * <code>SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED</code><br> |
| * <code>SmsManager.RESULT_RADIO_NOT_AVAILABLE</code><br> |
| * <code>SmsManager.RESULT_NETWORK_REJECT</code><br> |
| * <code>SmsManager.RESULT_INVALID_ARGUMENTS</code><br> |
| * <code>SmsManager.RESULT_INVALID_STATE</code><br> |
| * <code>SmsManager.RESULT_NO_MEMORY</code><br> |
| * <code>SmsManager.RESULT_INVALID_SMS_FORMAT</code><br> |
| * <code>SmsManager.RESULT_SYSTEM_ERROR</code><br> |
| * <code>SmsManager.RESULT_MODEM_ERROR</code><br> |
| * <code>SmsManager.RESULT_NETWORK_ERROR</code><br> |
| * <code>SmsManager.RESULT_ENCODING_ERROR</code><br> |
| * <code>SmsManager.RESULT_INVALID_SMSC_ADDRESS</code><br> |
| * <code>SmsManager.RESULT_OPERATION_NOT_ALLOWED</code><br> |
| * <code>SmsManager.RESULT_INTERNAL_ERROR</code><br> |
| * <code>SmsManager.RESULT_NO_RESOURCES</code><br> |
| * <code>SmsManager.RESULT_CANCELLED</code><br> |
| * <code>SmsManager.RESULT_REQUEST_NOT_SUPPORTED</code><br> |
| * <code>SmsManager.RESULT_NO_BLUETOOTH_SERVICE</code><br> |
| * <code>SmsManager.RESULT_INVALID_BLUETOOTH_ADDRESS</code><br> |
| * <code>SmsManager.RESULT_BLUETOOTH_DISCONNECTED</code><br> |
| * <code>SmsManager.RESULT_UNEXPECTED_EVENT_STOP_SENDING</code><br> |
| * <code>SmsManager.RESULT_SMS_BLOCKED_DURING_EMERGENCY</code><br> |
| * <code>SmsManager.RESULT_SMS_SEND_RETRY_FAILED</code><br> |
| * <code>SmsManager.RESULT_REMOTE_EXCEPTION</code><br> |
| * <code>SmsManager.RESULT_NO_DEFAULT_SMS_APP</code><br> |
| * <code>SmsManager.RESULT_RIL_RADIO_NOT_AVAILABLE</code><br> |
| * <code>SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY</code><br> |
| * <code>SmsManager.RESULT_RIL_NETWORK_REJECT</code><br> |
| * <code>SmsManager.RESULT_RIL_INVALID_STATE</code><br> |
| * <code>SmsManager.RESULT_RIL_INVALID_ARGUMENTS</code><br> |
| * <code>SmsManager.RESULT_RIL_NO_MEMORY</code><br> |
| * <code>SmsManager.RESULT_RIL_REQUEST_RATE_LIMITED</code><br> |
| * <code>SmsManager.RESULT_RIL_INVALID_SMS_FORMAT</code><br> |
| * <code>SmsManager.RESULT_RIL_SYSTEM_ERR</code><br> |
| * <code>SmsManager.RESULT_RIL_ENCODING_ERR</code><br> |
| * <code>SmsManager.RESULT_RIL_INVALID_SMSC_ADDRESS</code><br> |
| * <code>SmsManager.RESULT_RIL_MODEM_ERR</code><br> |
| * <code>SmsManager.RESULT_RIL_NETWORK_ERR</code><br> |
| * <code>SmsManager.RESULT_RIL_INTERNAL_ERR</code><br> |
| * <code>SmsManager.RESULT_RIL_REQUEST_NOT_SUPPORTED</code><br> |
| * <code>SmsManager.RESULT_RIL_INVALID_MODEM_STATE</code><br> |
| * <code>SmsManager.RESULT_RIL_NETWORK_NOT_READY</code><br> |
| * <code>SmsManager.RESULT_RIL_OPERATION_NOT_ALLOWED</code><br> |
| * <code>SmsManager.RESULT_RIL_NO_RESOURCES</code><br> |
| * <code>SmsManager.RESULT_RIL_CANCELLED</code><br> |
| * <code>SmsManager.RESULT_RIL_SIM_ABSENT</code><br> |
| * <code>SmsManager.RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED</code><br> |
| * <code>SmsManager.RESULT_RIL_ACCESS_BARRED</code><br> |
| * <code>SmsManager.RESULT_RIL_BLOCKED_DUE_TO_CALL</code><br> |
| * For <code>SmsManager.RESULT_ERROR_GENERIC_FAILURE</code> or any of the RESULT_RIL errors, |
| * 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. |
| * @param skipShortCodeCheck Skip check for short code type destination address. |
| */ |
| 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, boolean skipShortCodeCheck) { |
| if (TextUtils.isEmpty(scAddr)) { |
| scAddr = getSmscAddressFromUSIMWithPhoneIdentity(callingPkg); |
| } |
| |
| if (isSmsDomainSelectionEnabled()) { |
| TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); |
| boolean isEmergency = tm.isEmergencyNumber(destAddr); |
| DomainSelectionConnectionHolder holder = getDomainSelectionConnection(isEmergency); |
| |
| // If the DomainSelectionConnection is not available, |
| // fallback to the legacy implementation. |
| if (holder != null && holder.getConnection() != null) { |
| sendSmsUsingDomainSelection(holder, |
| new PendingRequest(PendingRequest.TYPE_TEXT, null, callingPkg, |
| destAddr, scAddr, asArrayList(sentIntent), |
| asArrayList(deliveryIntent), isForVvm, null, 0, asArrayList(text), |
| messageUri, persistMessage, priority, expectMore, validityPeriod, |
| messageId, skipShortCodeCheck), |
| "sendText"); |
| return; |
| } |
| } |
| |
| if (mImsSmsDispatcher.isAvailable() || mImsSmsDispatcher.isEmergencySmsSupport(destAddr)) { |
| mImsSmsDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent, |
| messageUri, callingPkg, persistMessage, priority, false /*expectMore*/, |
| validityPeriod, isForVvm, messageId, skipShortCodeCheck); |
| } else { |
| if (isCdmaMo()) { |
| mCdmaDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent, |
| messageUri, callingPkg, persistMessage, priority, expectMore, |
| validityPeriod, isForVvm, messageId, skipShortCodeCheck); |
| } else { |
| mGsmDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent, |
| messageUri, callingPkg, persistMessage, priority, expectMore, |
| validityPeriod, isForVvm, messageId, skipShortCodeCheck); |
| } |
| } |
| } |
| |
| /** |
| * 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:<br> |
| * <code>SmsManager.RESULT_ERROR_GENERIC_FAILURE</code><br> |
| * <code>SmsManager.RESULT_ERROR_RADIO_OFF</code><br> |
| * <code>SmsManager.RESULT_ERROR_NULL_PDU</code><br> |
| * <code>SmsManager.RESULT_ERROR_NO_SERVICE</code><br> |
| * <code>SmsManager.RESULT_ERROR_LIMIT_EXCEEDED</code><br> |
| * <code>SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE</code><br> |
| * <code>SmsManager.RESULT_ERROR_SHORT_CODE_NOT_ALLOWED</code><br> |
| * <code>SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED</code><br> |
| * <code>SmsManager.RESULT_RADIO_NOT_AVAILABLE</code><br> |
| * <code>SmsManager.RESULT_NETWORK_REJECT</code><br> |
| * <code>SmsManager.RESULT_INVALID_ARGUMENTS</code><br> |
| * <code>SmsManager.RESULT_INVALID_STATE</code><br> |
| * <code>SmsManager.RESULT_NO_MEMORY</code><br> |
| * <code>SmsManager.RESULT_INVALID_SMS_FORMAT</code><br> |
| * <code>SmsManager.RESULT_SYSTEM_ERROR</code><br> |
| * <code>SmsManager.RESULT_MODEM_ERROR</code><br> |
| * <code>SmsManager.RESULT_NETWORK_ERROR</code><br> |
| * <code>SmsManager.RESULT_ENCODING_ERROR</code><br> |
| * <code>SmsManager.RESULT_INVALID_SMSC_ADDRESS</code><br> |
| * <code>SmsManager.RESULT_OPERATION_NOT_ALLOWED</code><br> |
| * <code>SmsManager.RESULT_INTERNAL_ERROR</code><br> |
| * <code>SmsManager.RESULT_NO_RESOURCES</code><br> |
| * <code>SmsManager.RESULT_CANCELLED</code><br> |
| * <code>SmsManager.RESULT_REQUEST_NOT_SUPPORTED</code><br> |
| * <code>SmsManager.RESULT_NO_BLUETOOTH_SERVICE</code><br> |
| * <code>SmsManager.RESULT_INVALID_BLUETOOTH_ADDRESS</code><br> |
| * <code>SmsManager.RESULT_BLUETOOTH_DISCONNECTED</code><br> |
| * <code>SmsManager.RESULT_UNEXPECTED_EVENT_STOP_SENDING</code><br> |
| * <code>SmsManager.RESULT_SMS_BLOCKED_DURING_EMERGENCY</code><br> |
| * <code>SmsManager.RESULT_SMS_SEND_RETRY_FAILED</code><br> |
| * <code>SmsManager.RESULT_REMOTE_EXCEPTION</code><br> |
| * <code>SmsManager.RESULT_NO_DEFAULT_SMS_APP</code><br> |
| * <code>SmsManager.RESULT_RIL_RADIO_NOT_AVAILABLE</code><br> |
| * <code>SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY</code><br> |
| * <code>SmsManager.RESULT_RIL_NETWORK_REJECT</code><br> |
| * <code>SmsManager.RESULT_RIL_INVALID_STATE</code><br> |
| * <code>SmsManager.RESULT_RIL_INVALID_ARGUMENTS</code><br> |
| * <code>SmsManager.RESULT_RIL_NO_MEMORY</code><br> |
| * <code>SmsManager.RESULT_RIL_REQUEST_RATE_LIMITED</code><br> |
| * <code>SmsManager.RESULT_RIL_INVALID_SMS_FORMAT</code><br> |
| * <code>SmsManager.RESULT_RIL_SYSTEM_ERR</code><br> |
| * <code>SmsManager.RESULT_RIL_ENCODING_ERR</code><br> |
| * <code>SmsManager.RESULT_RIL_INVALID_SMSC_ADDRESS</code><br> |
| * <code>SmsManager.RESULT_RIL_MODEM_ERR</code><br> |
| * <code>SmsManager.RESULT_RIL_NETWORK_ERR</code><br> |
| * <code>SmsManager.RESULT_RIL_INTERNAL_ERR</code><br> |
| * <code>SmsManager.RESULT_RIL_REQUEST_NOT_SUPPORTED</code><br> |
| * <code>SmsManager.RESULT_RIL_INVALID_MODEM_STATE</code><br> |
| * <code>SmsManager.RESULT_RIL_NETWORK_NOT_READY</code><br> |
| * <code>SmsManager.RESULT_RIL_OPERATION_NOT_ALLOWED</code><br> |
| * <code>SmsManager.RESULT_RIL_NO_RESOURCES</code><br> |
| * <code>SmsManager.RESULT_RIL_CANCELLED</code><br> |
| * <code>SmsManager.RESULT_RIL_SIM_ABSENT</code><br> |
| * <code>SmsManager.RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED</code><br> |
| * <code>SmsManager.RESULT_RIL_ACCESS_BARRED</code><br> |
| * <code>SmsManager.RESULT_RIL_BLOCKED_DUE_TO_CALL</code><br> |
| * For <code>SmsManager.RESULT_ERROR_GENERIC_FAILURE</code> or any of the RESULT_RIL errors, |
| * 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 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 (TextUtils.isEmpty(scAddr)) { |
| scAddr = getSmscAddressFromUSIMWithPhoneIdentity(callingPkg); |
| } |
| |
| if (isSmsDomainSelectionEnabled()) { |
| TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); |
| boolean isEmergency = tm.isEmergencyNumber(destAddr); |
| DomainSelectionConnectionHolder holder = getDomainSelectionConnection(isEmergency); |
| |
| // If the DomainSelectionConnection is not available, |
| // fallback to the legacy implementation. |
| if (holder != null && holder.getConnection() != null) { |
| sendSmsUsingDomainSelection(holder, |
| new PendingRequest(PendingRequest.TYPE_MULTIPART_TEXT, null, |
| callingPkg, destAddr, scAddr, sentIntents, deliveryIntents, false, |
| null, 0, parts, messageUri, persistMessage, priority, expectMore, |
| validityPeriod, messageId, false), |
| "sendMultipartText"); |
| return; |
| } |
| } |
| |
| if (mImsSmsDispatcher.isAvailable()) { |
| mImsSmsDispatcher.sendMultipartText(destAddr, scAddr, parts, sentIntents, |
| deliveryIntents, messageUri, callingPkg, persistMessage, priority, |
| false /*expectMore*/, validityPeriod, 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 based on the format. |
| * |
| * @param format the format. |
| * @param pdu the pdu of the report. |
| */ |
| public void handleSmsStatusReport(String format, byte[] pdu) { |
| int messageRef; |
| SMSDispatcher.SmsTracker tracker; |
| boolean handled = false; |
| if (isCdmaFormat(format)) { |
| com.android.internal.telephony.cdma.SmsMessage sms = |
| com.android.internal.telephony.cdma.SmsMessage.createFromPdu(pdu); |
| if (sms != null) { |
| boolean foundIn3GPPMap = false; |
| messageRef = sms.mMessageRef; |
| tracker = mDeliveryPendingMapFor3GPP2.get(messageRef); |
| if (tracker == null) { |
| // A tracker for this 3GPP2 report may be in the 3GPP map instead if the |
| // previously submitted SMS was 3GPP format. |
| // (i.e. Some carriers require that devices receive 3GPP2 SMS also even if IMS |
| // SMS format is 3GGP.) |
| tracker = mDeliveryPendingMapFor3GPP.get(messageRef); |
| if (tracker != null) { |
| foundIn3GPPMap = true; |
| } |
| } |
| if (tracker != 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); |
| // No longer need to be kept. |
| if (foundIn3GPPMap) { |
| mDeliveryPendingMapFor3GPP.remove(messageRef); |
| } else { |
| mDeliveryPendingMapFor3GPP2.remove(messageRef); |
| } |
| } |
| handled = triggerDeliveryIntent(tracker, format, pdu); |
| } |
| } |
| } else { |
| com.android.internal.telephony.gsm.SmsMessage sms = |
| com.android.internal.telephony.gsm.SmsMessage.createFromPdu(pdu); |
| if (sms != null) { |
| messageRef = sms.mMessageRef; |
| tracker = mDeliveryPendingMapFor3GPP.get(messageRef); |
| if (tracker != 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); |
| // No longer need to be kept. |
| mDeliveryPendingMapFor3GPP.remove(messageRef); |
| } |
| handled = triggerDeliveryIntent(tracker, format, pdu); |
| } |
| } |
| } |
| |
| if (!handled) { |
| Rlog.e(TAG, "handleSmsStatusReport: can not handle the status report!"); |
| } |
| } |
| |
| 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; |
| } |
| } |
| |
| /** |
| * Get InboundSmsHandler for the phone. |
| */ |
| public InboundSmsHandler getInboundSmsHandler(boolean is3gpp2) { |
| if (is3gpp2) return mCdmaInboundSmsHandler; |
| else return mGsmInboundSmsHandler; |
| } |
| |
| 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); |
| mGsmDispatcher.dump(fd, pw, args); |
| mCdmaDispatcher.dump(fd, pw, args); |
| mImsSmsDispatcher.dump(fd, pw, args); |
| } |
| |
| private void logd(String msg) { |
| Rlog.d(TAG, msg); |
| } |
| |
| private void logi(String s) { |
| Rlog.i(TAG + " [" + mPhone.getPhoneId() + "]", s); |
| } |
| } |