2140 lines
98 KiB
Java
2140 lines
98 KiB
Java
/*
|
|
* 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);
|
|
}
|
|
}
|