1583 lines
68 KiB
Java
1583 lines
68 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2008 The Android Open Source Project
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package com.android.internal.telephony;
|
||
|
|
||
|
import static android.telephony.SmsManager.STATUS_ON_ICC_FREE;
|
||
|
import static android.telephony.SmsManager.STATUS_ON_ICC_READ;
|
||
|
import static android.telephony.SmsManager.STATUS_ON_ICC_UNREAD;
|
||
|
|
||
|
import android.Manifest;
|
||
|
import android.annotation.NonNull;
|
||
|
import android.annotation.RequiresPermission;
|
||
|
import android.app.AppOpsManager;
|
||
|
import android.app.PendingIntent;
|
||
|
import android.compat.annotation.UnsupportedAppUsage;
|
||
|
import android.content.ContentResolver;
|
||
|
import android.content.Context;
|
||
|
import android.content.pm.PackageManager;
|
||
|
import android.database.Cursor;
|
||
|
import android.database.sqlite.SQLiteException;
|
||
|
import android.net.Uri;
|
||
|
import android.os.AsyncResult;
|
||
|
import android.os.Binder;
|
||
|
import android.os.Build;
|
||
|
import android.os.Handler;
|
||
|
import android.os.Looper;
|
||
|
import android.os.Message;
|
||
|
import android.provider.Telephony;
|
||
|
import android.telephony.SmsCbMessage;
|
||
|
import android.telephony.SmsManager;
|
||
|
import android.telephony.SmsMessage;
|
||
|
import android.telephony.emergency.EmergencyNumber;
|
||
|
import android.util.LocalLog;
|
||
|
import android.util.Log;
|
||
|
|
||
|
import com.android.internal.annotations.VisibleForTesting;
|
||
|
import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
|
||
|
import com.android.internal.telephony.flags.FeatureFlags;
|
||
|
import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
|
||
|
import com.android.internal.telephony.uicc.IccConstants;
|
||
|
import com.android.internal.telephony.uicc.IccFileHandler;
|
||
|
import com.android.internal.telephony.uicc.IccUtils;
|
||
|
import com.android.internal.telephony.uicc.UiccController;
|
||
|
import com.android.internal.telephony.uicc.UiccProfile;
|
||
|
import com.android.internal.util.HexDump;
|
||
|
import com.android.telephony.Rlog;
|
||
|
|
||
|
import java.io.FileDescriptor;
|
||
|
import java.io.PrintWriter;
|
||
|
import java.util.ArrayList;
|
||
|
import java.util.Arrays;
|
||
|
import java.util.List;
|
||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||
|
|
||
|
/**
|
||
|
* IccSmsInterfaceManager to provide an inter-process communication to
|
||
|
* access Sms in Icc.
|
||
|
*/
|
||
|
public class IccSmsInterfaceManager {
|
||
|
static final String LOG_TAG = "IccSmsInterfaceManager";
|
||
|
static final boolean DBG = true;
|
||
|
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
private CellBroadcastRangeManager mCellBroadcastRangeManager =
|
||
|
new CellBroadcastRangeManager();
|
||
|
private CdmaBroadcastRangeManager mCdmaBroadcastRangeManager =
|
||
|
new CdmaBroadcastRangeManager();
|
||
|
|
||
|
private static final int EVENT_LOAD_DONE = 1;
|
||
|
private static final int EVENT_UPDATE_DONE = 2;
|
||
|
protected static final int EVENT_SET_BROADCAST_ACTIVATION_DONE = 3;
|
||
|
protected static final int EVENT_SET_BROADCAST_CONFIG_DONE = 4;
|
||
|
private static final int EVENT_GET_SMSC_DONE = 5;
|
||
|
private static final int EVENT_SET_SMSC_DONE = 6;
|
||
|
private static final int SMS_CB_CODE_SCHEME_MIN = 0;
|
||
|
private static final int SMS_CB_CODE_SCHEME_MAX = 255;
|
||
|
public static final int SMS_MESSAGE_PRIORITY_NOT_SPECIFIED = -1;
|
||
|
public static final int SMS_MESSAGE_PERIOD_NOT_SPECIFIED = -1;
|
||
|
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
protected Phone mPhone;
|
||
|
@UnsupportedAppUsage
|
||
|
final protected Context mContext;
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
final protected AppOpsManager mAppOps;
|
||
|
@VisibleForTesting
|
||
|
public SmsDispatchersController mDispatchersController;
|
||
|
private SmsPermissions mSmsPermissions;
|
||
|
|
||
|
private final LocalLog mCellBroadcastLocalLog = new LocalLog(64);
|
||
|
|
||
|
private static final class Request {
|
||
|
AtomicBoolean mStatus = new AtomicBoolean(false);
|
||
|
Object mResult = null;
|
||
|
}
|
||
|
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
protected Handler mHandler = new Handler() {
|
||
|
@Override
|
||
|
public void handleMessage(Message msg) {
|
||
|
AsyncResult ar = (AsyncResult) msg.obj;
|
||
|
Request request = (Request) ar.userObj;
|
||
|
|
||
|
switch (msg.what) {
|
||
|
case EVENT_UPDATE_DONE:
|
||
|
case EVENT_SET_BROADCAST_ACTIVATION_DONE:
|
||
|
case EVENT_SET_BROADCAST_CONFIG_DONE:
|
||
|
case EVENT_SET_SMSC_DONE:
|
||
|
notifyPending(request, ar.exception == null);
|
||
|
break;
|
||
|
case EVENT_LOAD_DONE:
|
||
|
List<SmsRawData> smsRawDataList = null;
|
||
|
if (ar.exception == null) {
|
||
|
smsRawDataList = buildValidRawData((ArrayList<byte[]>) ar.result);
|
||
|
//Mark SMS as read after importing it from card.
|
||
|
markMessagesAsRead((ArrayList<byte[]>) ar.result);
|
||
|
} else {
|
||
|
if (Rlog.isLoggable("SMS", Log.DEBUG)) {
|
||
|
loge("Cannot load Sms records");
|
||
|
}
|
||
|
}
|
||
|
notifyPending(request, smsRawDataList);
|
||
|
break;
|
||
|
case EVENT_GET_SMSC_DONE:
|
||
|
String smsc = null;
|
||
|
if (ar.exception == null) {
|
||
|
smsc = (String) ar.result;
|
||
|
} else {
|
||
|
loge("Cannot read SMSC");
|
||
|
}
|
||
|
notifyPending(request, smsc);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void notifyPending(Request request, Object result) {
|
||
|
if (request != null) {
|
||
|
synchronized (request) {
|
||
|
request.mResult = result;
|
||
|
request.mStatus.set(true);
|
||
|
request.notifyAll();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
protected IccSmsInterfaceManager(Phone phone, @NonNull FeatureFlags featureFlags) {
|
||
|
this(phone, phone.getContext(),
|
||
|
(AppOpsManager) phone.getContext().getSystemService(Context.APP_OPS_SERVICE),
|
||
|
new SmsDispatchersController(
|
||
|
phone, phone.mSmsStorageMonitor, phone.mSmsUsageMonitor, featureFlags),
|
||
|
new SmsPermissions(phone, phone.getContext(),
|
||
|
(AppOpsManager) phone.getContext().getSystemService(
|
||
|
Context.APP_OPS_SERVICE)));
|
||
|
}
|
||
|
|
||
|
@VisibleForTesting
|
||
|
public IccSmsInterfaceManager(
|
||
|
Phone phone, Context context, AppOpsManager appOps,
|
||
|
SmsDispatchersController dispatchersController, SmsPermissions smsPermissions) {
|
||
|
mPhone = phone;
|
||
|
mContext = context;
|
||
|
mAppOps = appOps;
|
||
|
mDispatchersController = dispatchersController;
|
||
|
mSmsPermissions = smsPermissions;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* PhoneFactory Dependencies for testing.
|
||
|
*/
|
||
|
@VisibleForTesting
|
||
|
public interface PhoneFactoryProxy {
|
||
|
Phone getPhone(int index);
|
||
|
Phone getDefaultPhone();
|
||
|
Phone[] getPhones();
|
||
|
}
|
||
|
|
||
|
private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() {
|
||
|
@Override
|
||
|
public Phone getPhone(int index) {
|
||
|
return PhoneFactory.getPhone(index);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public Phone getDefaultPhone() {
|
||
|
return PhoneFactory.getDefaultPhone();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public Phone[] getPhones() {
|
||
|
return PhoneFactory.getPhones();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Overrides PhoneFactory dependencies for testing.
|
||
|
*/
|
||
|
@VisibleForTesting
|
||
|
public void setPhoneFactoryProxy(PhoneFactoryProxy proxy) {
|
||
|
mPhoneFactoryProxy = proxy;
|
||
|
}
|
||
|
|
||
|
private void enforceNotOnHandlerThread(String methodName) {
|
||
|
if (Looper.myLooper() == mHandler.getLooper()) {
|
||
|
throw new RuntimeException("This method " + methodName + " will deadlock if called from"
|
||
|
+ " the handler's thread.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected void markMessagesAsRead(ArrayList<byte[]> messages) {
|
||
|
if (messages == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//IccFileHandler can be null, if icc card is absent.
|
||
|
IccFileHandler fh = mPhone.getIccFileHandler();
|
||
|
if (fh == null) {
|
||
|
//shouldn't really happen, as messages are marked as read, only
|
||
|
//after importing it from icc.
|
||
|
if (Rlog.isLoggable("SMS", Log.DEBUG)) {
|
||
|
loge("markMessagesAsRead - aborting, no icc card present.");
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
int count = messages.size();
|
||
|
|
||
|
for (int i = 0; i < count; i++) {
|
||
|
byte[] ba = messages.get(i);
|
||
|
if ((ba[0] & 0x07) == STATUS_ON_ICC_UNREAD) {
|
||
|
int n = ba.length;
|
||
|
byte[] nba = new byte[n - 1];
|
||
|
System.arraycopy(ba, 1, nba, 0, n - 1);
|
||
|
byte[] record = makeSmsRecordData(STATUS_ON_ICC_READ, nba);
|
||
|
fh.updateEFLinearFixed(IccConstants.EF_SMS, i + 1, record, null, null);
|
||
|
if (Rlog.isLoggable("SMS", Log.DEBUG)) {
|
||
|
log("SMS " + (i + 1) + " marked as read");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
protected void enforceReceiveAndSend(String message) {
|
||
|
mContext.enforceCallingOrSelfPermission(
|
||
|
Manifest.permission.RECEIVE_SMS, message);
|
||
|
mContext.enforceCallingOrSelfPermission(
|
||
|
Manifest.permission.SEND_SMS, message);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Enforce the permission for access messages on ICC
|
||
|
*/
|
||
|
private void enforceAccessMessageOnICC(String message) {
|
||
|
mContext.enforceCallingOrSelfPermission(
|
||
|
Manifest.permission.ACCESS_MESSAGES_ON_ICC, message);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Update the specified message on the Icc.
|
||
|
*
|
||
|
* @param index record index of message to update
|
||
|
* @param status new message status (STATUS_ON_ICC_READ,
|
||
|
* STATUS_ON_ICC_UNREAD, STATUS_ON_ICC_SENT,
|
||
|
* STATUS_ON_ICC_UNSENT, STATUS_ON_ICC_FREE)
|
||
|
* @param pdu the raw PDU to store
|
||
|
* @return success or not
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
public boolean
|
||
|
updateMessageOnIccEf(String callingPackage, int index, int status, byte[] pdu) {
|
||
|
if (DBG) log("updateMessageOnIccEf: index=" + index +
|
||
|
" status=" + status + " ==> " +
|
||
|
"("+ Arrays.toString(pdu) + ")");
|
||
|
enforceReceiveAndSend("Updating message on Icc");
|
||
|
enforceAccessMessageOnICC("Updating message on Icc");
|
||
|
enforceNotOnHandlerThread("updateMessageOnIccEf");
|
||
|
if (mAppOps.noteOp(AppOpsManager.OPSTR_WRITE_ICC_SMS, Binder.getCallingUid(),
|
||
|
callingPackage) != AppOpsManager.MODE_ALLOWED) {
|
||
|
return false;
|
||
|
}
|
||
|
Request updateRequest = new Request();
|
||
|
synchronized (updateRequest) {
|
||
|
Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE, updateRequest);
|
||
|
|
||
|
if ((status & 0x01) == STATUS_ON_ICC_FREE) {
|
||
|
// RIL_REQUEST_DELETE_SMS_ON_SIM vs RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM
|
||
|
// Special case FREE: call deleteSmsOnSim/Ruim instead of
|
||
|
// manipulating the record
|
||
|
// Will eventually fail if icc card is not present.
|
||
|
if (PhoneConstants.PHONE_TYPE_GSM == mPhone.getPhoneType()) {
|
||
|
mPhone.mCi.deleteSmsOnSim(index, response);
|
||
|
} else {
|
||
|
mPhone.mCi.deleteSmsOnRuim(index, response);
|
||
|
}
|
||
|
} else {
|
||
|
//IccFilehandler can be null if ICC card is not present.
|
||
|
IccFileHandler fh = mPhone.getIccFileHandler();
|
||
|
if (fh == null) {
|
||
|
response.recycle();
|
||
|
return false; /* is false */
|
||
|
}
|
||
|
byte[] record = makeSmsRecordData(status, pdu);
|
||
|
fh.updateEFLinearFixed(
|
||
|
IccConstants.EF_SMS,
|
||
|
index, record, null, response);
|
||
|
}
|
||
|
waitForResult(updateRequest);
|
||
|
}
|
||
|
return (boolean) updateRequest.mResult;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Copies a raw SMS PDU to the ICC.
|
||
|
*
|
||
|
* @param callingPackage the package name of the calling app.
|
||
|
* @param status message status. One of these status:
|
||
|
* <code>STATUS_ON_ICC_READ</code>
|
||
|
* <code>STATUS_ON_ICC_UNREAD</code>
|
||
|
* <code>STATUS_ON_ICC_SENT</code>
|
||
|
* <code>STATUS_ON_ICC_UNSENT</code>
|
||
|
* @param pdu the raw PDU to store.
|
||
|
* @param smsc the SMSC for this message. Null means use default.
|
||
|
* @return true for success. Otherwise false.
|
||
|
*/
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
public boolean copyMessageToIccEf(String callingPackage, int status, byte[] pdu, byte[] smsc) {
|
||
|
//NOTE smsc not used in RUIM
|
||
|
if (DBG) log("copyMessageToIccEf: status=" + status + " ==> " +
|
||
|
"pdu=("+ Arrays.toString(pdu) +
|
||
|
"), smsc=(" + Arrays.toString(smsc) +")");
|
||
|
enforceReceiveAndSend("Copying message to Icc");
|
||
|
enforceNotOnHandlerThread("copyMessageToIccEf");
|
||
|
if (mAppOps.noteOp(AppOpsManager.OPSTR_WRITE_ICC_SMS, Binder.getCallingUid(),
|
||
|
callingPackage) != AppOpsManager.MODE_ALLOWED) {
|
||
|
return false;
|
||
|
}
|
||
|
Request copyRequest = new Request();
|
||
|
synchronized (copyRequest) {
|
||
|
Message response = mHandler.obtainMessage(EVENT_UPDATE_DONE, copyRequest);
|
||
|
|
||
|
//RIL_REQUEST_WRITE_SMS_TO_SIM vs RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM
|
||
|
if (PhoneConstants.PHONE_TYPE_GSM == mPhone.getPhoneType()) {
|
||
|
mPhone.mCi.writeSmsToSim(status, IccUtils.bytesToHexString(smsc),
|
||
|
IccUtils.bytesToHexString(pdu), response);
|
||
|
} else {
|
||
|
mPhone.mCi.writeSmsToRuim(status, pdu, response);
|
||
|
}
|
||
|
|
||
|
waitForResult(copyRequest);
|
||
|
}
|
||
|
return (boolean) copyRequest.mResult;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Retrieves all messages currently stored on Icc.
|
||
|
*
|
||
|
* @return list of SmsRawData of all sms on Icc
|
||
|
*/
|
||
|
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
public List<SmsRawData> getAllMessagesFromIccEf(String callingPackage) {
|
||
|
if (DBG) log("getAllMessagesFromEF");
|
||
|
|
||
|
mContext.enforceCallingOrSelfPermission(
|
||
|
Manifest.permission.RECEIVE_SMS,
|
||
|
"Reading messages from Icc");
|
||
|
enforceAccessMessageOnICC("Reading messages from Icc");
|
||
|
enforceNotOnHandlerThread("getAllMessagesFromIccEf");
|
||
|
if (mAppOps.noteOp(AppOpsManager.OPSTR_READ_ICC_SMS, Binder.getCallingUid(),
|
||
|
callingPackage) != AppOpsManager.MODE_ALLOWED) {
|
||
|
return new ArrayList<SmsRawData>();
|
||
|
}
|
||
|
Request getRequest = new Request();
|
||
|
synchronized (getRequest) {
|
||
|
|
||
|
IccFileHandler fh = mPhone.getIccFileHandler();
|
||
|
if (fh == null) {
|
||
|
loge("Cannot load Sms records. No icc card?");
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
Message response = mHandler.obtainMessage(EVENT_LOAD_DONE, getRequest);
|
||
|
fh.loadEFLinearFixedAll(IccConstants.EF_SMS, response);
|
||
|
|
||
|
waitForResult(getRequest);
|
||
|
}
|
||
|
return (List<SmsRawData>) getRequest.mResult;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A permissions check before passing to {@link IccSmsInterfaceManager#sendDataInternal}.
|
||
|
* This method checks if the calling package or itself has the permission to send the data sms.
|
||
|
*/
|
||
|
public void sendDataWithSelfPermissions(String callingPackage, String callingAttributionTag,
|
||
|
String destAddr, String scAddr, int destPort, byte[] data, PendingIntent sentIntent,
|
||
|
PendingIntent deliveryIntent, boolean isForVvm) {
|
||
|
if (!mSmsPermissions.checkCallingOrSelfCanSendSms(callingPackage, callingAttributionTag,
|
||
|
"Sending SMS message")) {
|
||
|
returnUnspecifiedFailure(sentIntent);
|
||
|
return;
|
||
|
}
|
||
|
sendDataInternal(callingPackage, destAddr, scAddr, destPort, data, sentIntent,
|
||
|
deliveryIntent, isForVvm);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @deprecated Use {@link #sendData(String, String, String, String, int, byte[], PendingIntent,
|
||
|
* PendingIntent)} instead.
|
||
|
*/
|
||
|
@Deprecated
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
public void sendData(String callingPackage, String destAddr, String scAddr, int destPort,
|
||
|
byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
|
||
|
sendData(callingPackage, null, destAddr, scAddr, destPort, data,
|
||
|
sentIntent, deliveryIntent);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A permissions check before passing to {@link IccSmsInterfaceManager#sendDataInternal}.
|
||
|
* This method checks only if the calling package has the permission to send the data sms.
|
||
|
*/
|
||
|
public void sendData(String callingPackage, String callingAttributionTag,
|
||
|
String destAddr, String scAddr, int destPort, byte[] data, PendingIntent sentIntent,
|
||
|
PendingIntent deliveryIntent) {
|
||
|
if (!mSmsPermissions.checkCallingCanSendSms(callingPackage, callingAttributionTag,
|
||
|
"Sending SMS message")) {
|
||
|
returnUnspecifiedFailure(sentIntent);
|
||
|
return;
|
||
|
}
|
||
|
sendDataInternal(callingPackage, destAddr, scAddr, destPort, data, sentIntent,
|
||
|
deliveryIntent, false /* isForVvm */);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Send a data based SMS to a specific application port.
|
||
|
*
|
||
|
* @param callingPackage the package name of the calling app
|
||
|
* @param destAddr the address to send the message to
|
||
|
* @param scAddr is the service center address or null to use
|
||
|
* the current default SMSC
|
||
|
* @param destPort the port to deliver the message to
|
||
|
* @param data the body of the message to send
|
||
|
* @param sentIntent if not NULL this <code>PendingIntent</code> is
|
||
|
* broadcast when the message is successfully sent, or failed.
|
||
|
* The result code will be <code>Activity.RESULT_OK<code> for success,
|
||
|
* or one of these errors:<br>
|
||
|
* <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
|
||
|
* <code>RESULT_ERROR_RADIO_OFF</code><br>
|
||
|
* <code>RESULT_ERROR_NULL_PDU</code><br>
|
||
|
* For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
|
||
|
* the extra "errorCode" containing a radio technology specific value,
|
||
|
* generally only useful for troubleshooting.<br>
|
||
|
* The per-application based SMS control checks sentIntent. If sentIntent
|
||
|
* is NULL the caller will be checked against all unknown applications,
|
||
|
* which cause smaller number of SMS to be sent in checking period.
|
||
|
* @param deliveryIntent if not NULL this <code>PendingIntent</code> is
|
||
|
* broadcast when the message is delivered to the recipient. The
|
||
|
* raw pdu of the status report is in the extended data ("pdu").
|
||
|
*/
|
||
|
|
||
|
private void sendDataInternal(String callingPackage, String destAddr, String scAddr,
|
||
|
int destPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent,
|
||
|
boolean isForVvm) {
|
||
|
if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
|
||
|
log("sendData: destAddr=" + destAddr + " scAddr=" + scAddr + " destPort="
|
||
|
+ destPort + " data='" + HexDump.toHexString(data) + "' sentIntent="
|
||
|
+ sentIntent + " deliveryIntent=" + deliveryIntent + " isForVVM=" + isForVvm);
|
||
|
}
|
||
|
destAddr = filterDestAddress(destAddr);
|
||
|
mDispatchersController.sendData(callingPackage, destAddr, scAddr, destPort, data,
|
||
|
sentIntent, deliveryIntent, isForVvm);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A permissions check before passing to {@link IccSmsInterfaceManager#sendTextInternal}.
|
||
|
* This method checks only if the calling package has the permission to send the sms.
|
||
|
* Note: SEND_SMS permission should be checked by the caller of this method
|
||
|
*/
|
||
|
public void sendText(String callingPackage, String destAddr, String scAddr,
|
||
|
String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
|
||
|
boolean persistMessageForNonDefaultSmsApp, long messageId, boolean skipShortCodeCheck) {
|
||
|
sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
|
||
|
persistMessageForNonDefaultSmsApp, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
|
||
|
false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, false /* isForVvm */,
|
||
|
messageId, skipShortCodeCheck);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A permissions check before passing to {@link IccSmsInterfaceManager#sendTextInternal}.
|
||
|
* This method checks if the calling package or itself has the permission to send the sms.
|
||
|
*/
|
||
|
public void sendTextWithSelfPermissions(String callingPackage, String callingAttributeTag,
|
||
|
String destAddr, String scAddr, String text, PendingIntent sentIntent,
|
||
|
PendingIntent deliveryIntent, boolean persistMessage, boolean isForVvm) {
|
||
|
if (!mSmsPermissions.checkCallingOrSelfCanSendSms(callingPackage, callingAttributeTag,
|
||
|
"Sending SMS message")) {
|
||
|
returnUnspecifiedFailure(sentIntent);
|
||
|
return;
|
||
|
}
|
||
|
sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
|
||
|
persistMessage, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, false /* expectMore */,
|
||
|
SMS_MESSAGE_PERIOD_NOT_SPECIFIED, isForVvm, 0L /* messageId */);
|
||
|
}
|
||
|
|
||
|
|
||
|
private void sendTextInternal(String callingPackage, String destAddr, String scAddr,
|
||
|
String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
|
||
|
boolean persistMessageForNonDefaultSmsApp, int priority, boolean expectMore,
|
||
|
int validityPeriod, boolean isForVvm, long messageId) {
|
||
|
sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
|
||
|
persistMessageForNonDefaultSmsApp, 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>RESULT_ERROR_GENERIC_FAILURE</code><br>
|
||
|
* <code>RESULT_ERROR_RADIO_OFF</code><br>
|
||
|
* <code>RESULT_ERROR_NULL_PDU</code><br>
|
||
|
* For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
|
||
|
* the extra "errorCode" containing a radio technology specific value,
|
||
|
* generally only useful for troubleshooting.<br>
|
||
|
* The per-application based SMS control checks sentIntent. If sentIntent
|
||
|
* is NULL the caller will be checked against all unknown applications,
|
||
|
* which cause smaller number of SMS to be sent in checking period.
|
||
|
* @param deliveryIntent if not NULL this <code>PendingIntent</code> is
|
||
|
* broadcast when the message is delivered to the recipient. The
|
||
|
* raw pdu of the status report is in the extended data ("pdu").
|
||
|
* @param persistMessageForNonDefaultSmsApp whether the sent message should
|
||
|
* be automatically persisted in the SMS db. It only affects messages sent
|
||
|
* by a non-default SMS app. Currently only the carrier app can set this
|
||
|
* parameter to false to skip auto message persistence.
|
||
|
* @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 including 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 including 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.
|
||
|
* @param skipShortCodeCheck Skip check for short code type destination address.
|
||
|
*/
|
||
|
|
||
|
private void sendTextInternal(String callingPackage, String destAddr, String scAddr,
|
||
|
String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
|
||
|
boolean persistMessageForNonDefaultSmsApp, int priority, boolean expectMore,
|
||
|
int validityPeriod, boolean isForVvm, long messageId, boolean skipShortCodeCheck) {
|
||
|
if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
|
||
|
log("sendText: destAddr=" + destAddr + " scAddr=" + scAddr
|
||
|
+ " text='" + text + "' sentIntent=" + sentIntent + " deliveryIntent="
|
||
|
+ deliveryIntent + " priority=" + priority + " expectMore=" + expectMore
|
||
|
+ " validityPeriod=" + validityPeriod + " isForVVM=" + isForVvm
|
||
|
+ " " + SmsController.formatCrossStackMessageId(messageId));
|
||
|
}
|
||
|
notifyIfOutgoingEmergencySms(destAddr);
|
||
|
destAddr = filterDestAddress(destAddr);
|
||
|
mDispatchersController.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
|
||
|
null/*messageUri*/, callingPackage, persistMessageForNonDefaultSmsApp,
|
||
|
priority, expectMore, validityPeriod, isForVvm, messageId, skipShortCodeCheck);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Send a text based SMS with Messaging Options.
|
||
|
*
|
||
|
* @param destAddr the address to send the message to
|
||
|
* @param scAddr is the service center address or null to use
|
||
|
* the current default SMSC
|
||
|
* @param text the body of the message to send
|
||
|
* @param sentIntent if not NULL this <code>PendingIntent</code> is
|
||
|
* broadcast when the message is successfully sent, or failed.
|
||
|
* The result code will be <code>Activity.RESULT_OK<code> for success,
|
||
|
* or one of these errors:<br>
|
||
|
* <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
|
||
|
* <code>RESULT_ERROR_RADIO_OFF</code><br>
|
||
|
* <code>RESULT_ERROR_NULL_PDU</code><br>
|
||
|
* For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
|
||
|
* the extra "errorCode" containing a radio technology specific value,
|
||
|
* generally only useful for troubleshooting.<br>
|
||
|
* The per-application based SMS control checks sentIntent. If sentIntent
|
||
|
* is NULL the caller will be checked against all unknown applications,
|
||
|
* which cause smaller number of SMS to be sent in checking period.
|
||
|
* @param deliveryIntent if not NULL this <code>PendingIntent</code> is
|
||
|
* broadcast when the message is delivered to the recipient. The
|
||
|
* raw pdu of the status report is in the extended data ("pdu").
|
||
|
* @param persistMessageForNonDefaultSmsApp whether the sent message should
|
||
|
* be automatically persisted in the SMS db. It only affects messages sent
|
||
|
* by a non-default SMS app. Currently only the carrier app can set this
|
||
|
* parameter to false to skip auto message persistence.
|
||
|
* @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 including 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 including negative considered as Invalid Validity Period of the message.
|
||
|
*/
|
||
|
|
||
|
public void sendTextWithOptions(String callingPackage, String callingAttributionTag,
|
||
|
String destAddr, String scAddr, String text, PendingIntent sentIntent,
|
||
|
PendingIntent deliveryIntent, boolean persistMessageForNonDefaultSmsApp, int priority,
|
||
|
boolean expectMore, int validityPeriod) {
|
||
|
if (!mSmsPermissions.checkCallingCanSendText(persistMessageForNonDefaultSmsApp,
|
||
|
callingPackage, callingAttributionTag, "Sending SMS message")) {
|
||
|
returnUnspecifiedFailure(sentIntent);
|
||
|
return;
|
||
|
}
|
||
|
sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
|
||
|
persistMessageForNonDefaultSmsApp, priority, expectMore, validityPeriod,
|
||
|
false /* isForVvm */, 0L /* messageId */);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Inject an SMS PDU into the android application framework.
|
||
|
*
|
||
|
* @param pdu is the byte array of pdu to be injected into android application framework
|
||
|
* @param format is the format of SMS pdu (3gpp or 3gpp2)
|
||
|
* @param receivedIntent if not NULL this <code>PendingIntent</code> is
|
||
|
* broadcast when the message is successfully received by the
|
||
|
* android application framework. This intent is broadcasted at
|
||
|
* the same time an SMS received from radio is acknowledged back.
|
||
|
*/
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
public void injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent) {
|
||
|
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
|
||
|
!= PackageManager.PERMISSION_GRANTED) {
|
||
|
mSmsPermissions.enforceCallerIsImsAppOrCarrierApp("injectSmsPdu");
|
||
|
}
|
||
|
|
||
|
if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
|
||
|
log("pdu: " + IccUtils.bytesToHexString(pdu)
|
||
|
+ "\n format=" + format
|
||
|
+ "\n receivedIntent=" + receivedIntent);
|
||
|
}
|
||
|
mDispatchersController.injectSmsPdu(pdu, format, false /* isOverIms */,
|
||
|
result -> {
|
||
|
if (receivedIntent != null) {
|
||
|
try {
|
||
|
receivedIntent.send(result);
|
||
|
} catch (PendingIntent.CanceledException e) {
|
||
|
loge("receivedIntent cancelled.");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Send a multi-part text based SMS.
|
||
|
*
|
||
|
* @param destAddr the address to send the message to
|
||
|
* @param scAddr is the service center address or null to use
|
||
|
* the current default SMSC
|
||
|
* @param parts an <code>ArrayList</code> of strings that, in order,
|
||
|
* comprise the original message
|
||
|
* @param sentIntents if not null, an <code>ArrayList</code> of
|
||
|
* <code>PendingIntent</code>s (one for each message part) that is
|
||
|
* broadcast when the corresponding message part has been sent.
|
||
|
* The result code will be <code>Activity.RESULT_OK<code> for success,
|
||
|
* or one of these errors:
|
||
|
* <code>RESULT_ERROR_GENERIC_FAILURE</code>
|
||
|
* <code>RESULT_ERROR_RADIO_OFF</code>
|
||
|
* <code>RESULT_ERROR_NULL_PDU</code>.
|
||
|
* 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
|
||
|
* extended data ("pdu").
|
||
|
* @param messageId An id that uniquely identifies the message requested to be sent.
|
||
|
* Used for logging and diagnostics purposes. The id may be 0.
|
||
|
*/
|
||
|
|
||
|
public void sendMultipartText(String callingPackage, String callingAttributionTag,
|
||
|
String destAddr, String scAddr, List<String> parts, List<PendingIntent> sentIntents,
|
||
|
List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp,
|
||
|
long messageId) {
|
||
|
sendMultipartTextWithOptions(callingPackage, callingAttributionTag, destAddr, scAddr, parts,
|
||
|
sentIntents, deliveryIntents, persistMessageForNonDefaultSmsApp,
|
||
|
SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, false /* expectMore */,
|
||
|
SMS_MESSAGE_PERIOD_NOT_SPECIFIED,
|
||
|
messageId);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Send a multi-part text based SMS with Messaging Options.
|
||
|
*
|
||
|
* @param destAddr the address to send the message to
|
||
|
* @param scAddr is the service center address or null to use
|
||
|
* the current default SMSC
|
||
|
* @param parts an <code>ArrayList</code> of strings that, in order,
|
||
|
* comprise the original message
|
||
|
* @param sentIntents if not null, an <code>ArrayList</code> of
|
||
|
* <code>PendingIntent</code>s (one for each message part) that is
|
||
|
* broadcast when the corresponding message part has been sent.
|
||
|
* The result code will be <code>Activity.RESULT_OK<code> for success,
|
||
|
* or one of these errors:
|
||
|
* <code>RESULT_ERROR_GENERIC_FAILURE</code>
|
||
|
* <code>RESULT_ERROR_RADIO_OFF</code>
|
||
|
* <code>RESULT_ERROR_NULL_PDU</code>.
|
||
|
* 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
|
||
|
* extended data ("pdu").
|
||
|
* @param persistMessageForNonDefaultSmsApp whether the sent message should
|
||
|
* be automatically persisted in the SMS db. It only affects messages sent
|
||
|
* by a non-default SMS app. Currently only the carrier app can set this
|
||
|
* parameter to false to skip auto message persistence.
|
||
|
* @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 including 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 including 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.
|
||
|
*/
|
||
|
|
||
|
public void sendMultipartTextWithOptions(String callingPackage, String callingAttributionTag,
|
||
|
String destAddr, String scAddr, List<String> parts, List<PendingIntent> sentIntents,
|
||
|
List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp,
|
||
|
int priority, boolean expectMore, int validityPeriod, long messageId) {
|
||
|
if (!mSmsPermissions.checkCallingCanSendText(persistMessageForNonDefaultSmsApp,
|
||
|
callingPackage, callingAttributionTag, "Sending SMS message")) {
|
||
|
returnUnspecifiedFailure(sentIntents);
|
||
|
return;
|
||
|
}
|
||
|
if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
|
||
|
int i = 0;
|
||
|
for (String part : parts) {
|
||
|
log("sendMultipartTextWithOptions: destAddr=" + destAddr + ", srAddr=" + scAddr
|
||
|
+ ", part[" + (i++) + "]=" + part
|
||
|
+ " " + SmsController.formatCrossStackMessageId(messageId));
|
||
|
}
|
||
|
}
|
||
|
notifyIfOutgoingEmergencySms(destAddr);
|
||
|
destAddr = filterDestAddress(destAddr);
|
||
|
|
||
|
if (parts.size() > 1 && parts.size() < 10 && !SmsMessage.hasEmsSupport()) {
|
||
|
for (int i = 0; i < parts.size(); i++) {
|
||
|
// If EMS is not supported, we have to break down EMS into single segment SMS
|
||
|
// and add page info " x/y".
|
||
|
String singlePart = parts.get(i);
|
||
|
if (SmsMessage.shouldAppendPageNumberAsPrefix()) {
|
||
|
singlePart = String.valueOf(i + 1) + '/' + parts.size() + ' ' + singlePart;
|
||
|
} else {
|
||
|
singlePart = singlePart.concat(' ' + String.valueOf(i + 1) + '/'
|
||
|
+ parts.size());
|
||
|
}
|
||
|
|
||
|
PendingIntent singleSentIntent = null;
|
||
|
if (sentIntents != null && sentIntents.size() > i) {
|
||
|
singleSentIntent = sentIntents.get(i);
|
||
|
}
|
||
|
|
||
|
PendingIntent singleDeliveryIntent = null;
|
||
|
if (deliveryIntents != null && deliveryIntents.size() > i) {
|
||
|
singleDeliveryIntent = deliveryIntents.get(i);
|
||
|
}
|
||
|
|
||
|
mDispatchersController.sendText(destAddr, scAddr, singlePart, singleSentIntent,
|
||
|
singleDeliveryIntent, null /* messageUri */, callingPackage,
|
||
|
persistMessageForNonDefaultSmsApp, priority, expectMore, validityPeriod,
|
||
|
false /* isForVvm */, messageId);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
mDispatchersController.sendMultipartText(destAddr,
|
||
|
scAddr,
|
||
|
(ArrayList<String>) parts,
|
||
|
(ArrayList<PendingIntent>) sentIntents,
|
||
|
(ArrayList<PendingIntent>) deliveryIntents,
|
||
|
null, callingPackage, persistMessageForNonDefaultSmsApp,
|
||
|
priority, expectMore, validityPeriod, messageId);
|
||
|
|
||
|
}
|
||
|
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
public int getPremiumSmsPermission(String packageName) {
|
||
|
return mDispatchersController.getPremiumSmsPermission(packageName);
|
||
|
}
|
||
|
|
||
|
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
public void setPremiumSmsPermission(String packageName, int permission) {
|
||
|
mDispatchersController.setPremiumSmsPermission(packageName, permission);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* create SmsRawData lists from all sms record byte[]
|
||
|
* Use null to indicate "free" record
|
||
|
*
|
||
|
* @param messages List of message records from EF_SMS.
|
||
|
* @return SmsRawData list of all in-used records
|
||
|
*/
|
||
|
protected ArrayList<SmsRawData> buildValidRawData(ArrayList<byte[]> messages) {
|
||
|
int count = messages.size();
|
||
|
ArrayList<SmsRawData> ret;
|
||
|
|
||
|
ret = new ArrayList<SmsRawData>(count);
|
||
|
|
||
|
for (int i = 0; i < count; i++) {
|
||
|
byte[] ba = messages.get(i);
|
||
|
if ((ba[0] & 0x01) == STATUS_ON_ICC_FREE) {
|
||
|
ret.add(null);
|
||
|
} else {
|
||
|
ret.add(new SmsRawData(messages.get(i)));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Generates an EF_SMS record from status and raw PDU.
|
||
|
*
|
||
|
* @param status Message status. See TS 51.011 10.5.3.
|
||
|
* @param pdu Raw message PDU.
|
||
|
* @return byte array for the record.
|
||
|
*/
|
||
|
protected byte[] makeSmsRecordData(int status, byte[] pdu) {
|
||
|
byte[] data;
|
||
|
if (PhoneConstants.PHONE_TYPE_GSM == mPhone.getPhoneType()) {
|
||
|
data = new byte[SmsManager.SMS_RECORD_LENGTH];
|
||
|
} else {
|
||
|
data = new byte[SmsManager.CDMA_SMS_RECORD_LENGTH];
|
||
|
}
|
||
|
|
||
|
// Status bits for this record. See TS 51.011 10.5.3
|
||
|
data[0] = (byte) (status & 0x07);
|
||
|
|
||
|
System.arraycopy(pdu, 0, data, 1, pdu.length);
|
||
|
|
||
|
// Pad out with 0xFF's.
|
||
|
for (int j = pdu.length+1; j < data.length; j++) {
|
||
|
data[j] = -1;
|
||
|
}
|
||
|
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the SMSC address from (U)SIM.
|
||
|
*
|
||
|
* @return the SMSC address string, null if failed.
|
||
|
*/
|
||
|
public String getSmscAddressFromIccEf(String callingPackage) {
|
||
|
if (!mSmsPermissions.checkCallingOrSelfCanGetSmscAddress(
|
||
|
callingPackage, "getSmscAddressFromIccEf")) {
|
||
|
loge("Caller do not have permission to call GetSmscAddress");
|
||
|
return null;
|
||
|
}
|
||
|
enforceNotOnHandlerThread("getSmscAddressFromIccEf");
|
||
|
Request getRequest = new Request();
|
||
|
synchronized (getRequest) {
|
||
|
Message response = mHandler.obtainMessage(EVENT_GET_SMSC_DONE, getRequest);
|
||
|
mPhone.mCi.getSmscAddress(response);
|
||
|
waitForResult(getRequest);
|
||
|
}
|
||
|
return (String) getRequest.mResult;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the SMSC address on (U)SIM.
|
||
|
*
|
||
|
* @param smsc the SMSC address string.
|
||
|
* @return true for success, false otherwise.
|
||
|
*/
|
||
|
public boolean setSmscAddressOnIccEf(String callingPackage, String smsc) {
|
||
|
if (!mSmsPermissions.checkCallingOrSelfCanSetSmscAddress(
|
||
|
callingPackage, "setSmscAddressOnIccEf")) {
|
||
|
loge("Caller do not have permission to call SetSmscAddress");
|
||
|
return false;
|
||
|
}
|
||
|
enforceNotOnHandlerThread("setSmscAddressOnIccEf");
|
||
|
Request setRequest = new Request();
|
||
|
synchronized (setRequest) {
|
||
|
Message response = mHandler.obtainMessage(EVENT_SET_SMSC_DONE, setRequest);
|
||
|
mPhone.mCi.setSmscAddress(smsc, response);
|
||
|
waitForResult(setRequest);
|
||
|
}
|
||
|
return (boolean) setRequest.mResult;
|
||
|
}
|
||
|
|
||
|
public boolean enableCellBroadcast(int messageIdentifier, int ranType) {
|
||
|
return enableCellBroadcastRange(messageIdentifier, messageIdentifier, ranType);
|
||
|
}
|
||
|
|
||
|
public boolean disableCellBroadcast(int messageIdentifier, int ranType) {
|
||
|
return disableCellBroadcastRange(messageIdentifier, messageIdentifier, ranType);
|
||
|
}
|
||
|
|
||
|
public boolean enableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) {
|
||
|
mContext.enforceCallingPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
|
||
|
"enabling cell broadcast range [" + startMessageId + "-" + endMessageId + "]. "
|
||
|
+ "ranType=" + ranType);
|
||
|
if (ranType == SmsCbMessage.MESSAGE_FORMAT_3GPP) {
|
||
|
return enableGsmBroadcastRange(startMessageId, endMessageId);
|
||
|
} else if (ranType == SmsCbMessage.MESSAGE_FORMAT_3GPP2) {
|
||
|
return enableCdmaBroadcastRange(startMessageId, endMessageId);
|
||
|
} else {
|
||
|
throw new IllegalArgumentException("Not a supported RAN Type");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public boolean disableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) {
|
||
|
mContext.enforceCallingPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
|
||
|
"disabling cell broadcast range [" + startMessageId + "-" + endMessageId
|
||
|
+ "]. ranType=" + ranType);
|
||
|
if (ranType == SmsCbMessage.MESSAGE_FORMAT_3GPP) {
|
||
|
return disableGsmBroadcastRange(startMessageId, endMessageId);
|
||
|
} else if (ranType == SmsCbMessage.MESSAGE_FORMAT_3GPP2) {
|
||
|
return disableCdmaBroadcastRange(startMessageId, endMessageId);
|
||
|
} else {
|
||
|
throw new IllegalArgumentException("Not a supported RAN Type");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
synchronized public boolean enableGsmBroadcastRange(int startMessageId, int endMessageId) {
|
||
|
|
||
|
mContext.enforceCallingPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
|
||
|
"Enabling cell broadcast SMS");
|
||
|
|
||
|
String client = mContext.getPackageManager().getNameForUid(
|
||
|
Binder.getCallingUid());
|
||
|
|
||
|
String msg;
|
||
|
if (!mCellBroadcastRangeManager.enableRange(startMessageId, endMessageId, client)) {
|
||
|
msg = "Failed to add GSM cell broadcast channels range " + startMessageId
|
||
|
+ " to " + endMessageId;
|
||
|
log(msg);
|
||
|
mCellBroadcastLocalLog.log(msg);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (DBG) {
|
||
|
msg = "Added GSM cell broadcast channels range " + startMessageId
|
||
|
+ " to " + endMessageId;
|
||
|
log(msg);
|
||
|
mCellBroadcastLocalLog.log(msg);
|
||
|
}
|
||
|
|
||
|
setCellBroadcastActivation(!mCellBroadcastRangeManager.isEmpty());
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
synchronized public boolean disableGsmBroadcastRange(int startMessageId, int endMessageId) {
|
||
|
|
||
|
mContext.enforceCallingPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
|
||
|
"Disabling cell broadcast SMS");
|
||
|
|
||
|
String client = mContext.getPackageManager().getNameForUid(
|
||
|
Binder.getCallingUid());
|
||
|
|
||
|
String msg;
|
||
|
if (!mCellBroadcastRangeManager.disableRange(startMessageId, endMessageId, client)) {
|
||
|
msg = "Failed to remove GSM cell broadcast channels range " + startMessageId
|
||
|
+ " to " + endMessageId;
|
||
|
log(msg);
|
||
|
mCellBroadcastLocalLog.log(msg);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (DBG) {
|
||
|
msg = "Removed GSM cell broadcast channels range " + startMessageId
|
||
|
+ " to " + endMessageId;
|
||
|
log(msg);
|
||
|
mCellBroadcastLocalLog.log(msg);
|
||
|
}
|
||
|
|
||
|
setCellBroadcastActivation(!mCellBroadcastRangeManager.isEmpty());
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
synchronized public boolean enableCdmaBroadcastRange(int startMessageId, int endMessageId) {
|
||
|
|
||
|
mContext.enforceCallingPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
|
||
|
"Enabling cdma broadcast SMS");
|
||
|
|
||
|
String client = mContext.getPackageManager().getNameForUid(
|
||
|
Binder.getCallingUid());
|
||
|
|
||
|
String msg;
|
||
|
if (!mCdmaBroadcastRangeManager.enableRange(startMessageId, endMessageId, client)) {
|
||
|
msg = "Failed to add cdma broadcast channels range " + startMessageId + " to "
|
||
|
+ endMessageId;
|
||
|
log(msg);
|
||
|
mCellBroadcastLocalLog.log(msg);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (DBG) {
|
||
|
msg = "Added cdma broadcast channels range " + startMessageId + " to " + endMessageId;
|
||
|
log(msg);
|
||
|
mCellBroadcastLocalLog.log(msg);
|
||
|
}
|
||
|
|
||
|
setCdmaBroadcastActivation(!mCdmaBroadcastRangeManager.isEmpty());
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
synchronized public boolean disableCdmaBroadcastRange(int startMessageId, int endMessageId) {
|
||
|
|
||
|
mContext.enforceCallingPermission(android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
|
||
|
"Disabling cell broadcast SMS");
|
||
|
|
||
|
String client = mContext.getPackageManager().getNameForUid(
|
||
|
Binder.getCallingUid());
|
||
|
|
||
|
String msg;
|
||
|
if (!mCdmaBroadcastRangeManager.disableRange(startMessageId, endMessageId, client)) {
|
||
|
msg = "Failed to remove cdma broadcast channels range " + startMessageId + " to "
|
||
|
+ endMessageId;
|
||
|
log(msg);
|
||
|
mCellBroadcastLocalLog.log(msg);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (DBG) {
|
||
|
msg = "Removed cdma broadcast channels range " + startMessageId + " to " + endMessageId;
|
||
|
log(msg);
|
||
|
mCellBroadcastLocalLog.log(msg);
|
||
|
}
|
||
|
|
||
|
setCdmaBroadcastActivation(!mCdmaBroadcastRangeManager.isEmpty());
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reset all cell broadcast ranges. Previously enabled ranges will become invalid after this.
|
||
|
*/
|
||
|
@RequiresPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS)
|
||
|
public void resetAllCellBroadcastRanges() {
|
||
|
mContext.enforceCallingPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS,
|
||
|
"resetAllCellBroadcastRanges");
|
||
|
mCdmaBroadcastRangeManager.clearRanges();
|
||
|
mCellBroadcastRangeManager.clearRanges();
|
||
|
log("Cell broadcast ranges reset.");
|
||
|
}
|
||
|
|
||
|
class CellBroadcastRangeManager extends IntRangeManager {
|
||
|
private ArrayList<SmsBroadcastConfigInfo> mConfigList =
|
||
|
new ArrayList<SmsBroadcastConfigInfo>();
|
||
|
|
||
|
/**
|
||
|
* Called when the list of enabled ranges has changed. This will be
|
||
|
* followed by zero or more calls to {@link #addRange} followed by
|
||
|
* a call to {@link #finishUpdate}.
|
||
|
*/
|
||
|
protected void startUpdate() {
|
||
|
mConfigList.clear();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Called after {@link #startUpdate} to indicate a range of enabled
|
||
|
* values.
|
||
|
* @param startId the first id included in the range
|
||
|
* @param endId the last id included in the range
|
||
|
*/
|
||
|
protected void addRange(int startId, int endId, boolean selected) {
|
||
|
mConfigList.add(new SmsBroadcastConfigInfo(startId, endId,
|
||
|
SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, selected));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Called to indicate the end of a range update started by the
|
||
|
* previous call to {@link #startUpdate}.
|
||
|
* @return true if successful, false otherwise
|
||
|
*/
|
||
|
protected boolean finishUpdate() {
|
||
|
if (mConfigList.isEmpty()) {
|
||
|
return true;
|
||
|
} else {
|
||
|
SmsBroadcastConfigInfo[] configs =
|
||
|
mConfigList.toArray(new SmsBroadcastConfigInfo[mConfigList.size()]);
|
||
|
return setCellBroadcastConfig(configs);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class CdmaBroadcastRangeManager extends IntRangeManager {
|
||
|
private ArrayList<CdmaSmsBroadcastConfigInfo> mConfigList =
|
||
|
new ArrayList<CdmaSmsBroadcastConfigInfo>();
|
||
|
|
||
|
/**
|
||
|
* Called when the list of enabled ranges has changed. This will be
|
||
|
* followed by zero or more calls to {@link #addRange} followed by a
|
||
|
* call to {@link #finishUpdate}.
|
||
|
*/
|
||
|
protected void startUpdate() {
|
||
|
mConfigList.clear();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Called after {@link #startUpdate} to indicate a range of enabled
|
||
|
* values.
|
||
|
* @param startId the first id included in the range
|
||
|
* @param endId the last id included in the range
|
||
|
*/
|
||
|
protected void addRange(int startId, int endId, boolean selected) {
|
||
|
mConfigList.add(new CdmaSmsBroadcastConfigInfo(startId, endId,
|
||
|
1, selected));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Called to indicate the end of a range update started by the previous
|
||
|
* call to {@link #startUpdate}.
|
||
|
* @return true if successful, false otherwise
|
||
|
*/
|
||
|
protected boolean finishUpdate() {
|
||
|
if (mConfigList.isEmpty()) {
|
||
|
return true;
|
||
|
} else {
|
||
|
CdmaSmsBroadcastConfigInfo[] configs =
|
||
|
mConfigList.toArray(new CdmaSmsBroadcastConfigInfo[mConfigList.size()]);
|
||
|
return setCdmaBroadcastConfig(configs);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
private boolean setCellBroadcastConfig(SmsBroadcastConfigInfo[] configs) {
|
||
|
if (DBG) {
|
||
|
log("Calling setGsmBroadcastConfig with " + configs.length + " configurations");
|
||
|
}
|
||
|
enforceNotOnHandlerThread("setCellBroadcastConfig");
|
||
|
Request setRequest = new Request();
|
||
|
synchronized (setRequest) {
|
||
|
Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_CONFIG_DONE, setRequest);
|
||
|
|
||
|
mPhone.mCi.setGsmBroadcastConfig(configs, response);
|
||
|
|
||
|
waitForResult(setRequest);
|
||
|
}
|
||
|
|
||
|
return (boolean) setRequest.mResult;
|
||
|
}
|
||
|
|
||
|
private boolean setCellBroadcastActivation(boolean activate) {
|
||
|
if (DBG) {
|
||
|
log("Calling setCellBroadcastActivation(" + activate + ')');
|
||
|
}
|
||
|
|
||
|
enforceNotOnHandlerThread("setCellBroadcastConfig");
|
||
|
Request setRequest = new Request();
|
||
|
synchronized (setRequest) {
|
||
|
Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_ACTIVATION_DONE,
|
||
|
setRequest);
|
||
|
|
||
|
mPhone.mCi.setGsmBroadcastActivation(activate, response);
|
||
|
waitForResult(setRequest);
|
||
|
}
|
||
|
|
||
|
return (boolean) setRequest.mResult;
|
||
|
}
|
||
|
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
private boolean setCdmaBroadcastConfig(CdmaSmsBroadcastConfigInfo[] configs) {
|
||
|
if (DBG) {
|
||
|
log("Calling setCdmaBroadcastConfig with " + configs.length + " configurations");
|
||
|
}
|
||
|
|
||
|
enforceNotOnHandlerThread("setCdmaBroadcastConfig");
|
||
|
Request setRequest = new Request();
|
||
|
synchronized (setRequest) {
|
||
|
Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_CONFIG_DONE, setRequest);
|
||
|
|
||
|
mPhone.mCi.setCdmaBroadcastConfig(configs, response);
|
||
|
|
||
|
waitForResult(setRequest);
|
||
|
}
|
||
|
|
||
|
return (boolean) setRequest.mResult;
|
||
|
}
|
||
|
|
||
|
private boolean setCdmaBroadcastActivation(boolean activate) {
|
||
|
if (DBG) {
|
||
|
log("Calling setCdmaBroadcastActivation(" + activate + ")");
|
||
|
}
|
||
|
|
||
|
enforceNotOnHandlerThread("setCdmaBroadcastActivation");
|
||
|
Request setRequest = new Request();
|
||
|
synchronized (setRequest) {
|
||
|
Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_ACTIVATION_DONE,
|
||
|
setRequest);
|
||
|
|
||
|
mPhone.mCi.setCdmaBroadcastActivation(activate, response);
|
||
|
|
||
|
waitForResult(setRequest);
|
||
|
}
|
||
|
|
||
|
return (boolean) setRequest.mResult;
|
||
|
}
|
||
|
|
||
|
@UnsupportedAppUsage
|
||
|
protected void log(String msg) {
|
||
|
Rlog.d(LOG_TAG, msg);
|
||
|
}
|
||
|
|
||
|
protected void loge(String msg) {
|
||
|
Rlog.e(LOG_TAG, msg);
|
||
|
}
|
||
|
|
||
|
protected void loge(String msg, Throwable e) {
|
||
|
Rlog.e(LOG_TAG, msg, e);
|
||
|
}
|
||
|
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
public boolean isImsSmsSupported() {
|
||
|
return mDispatchersController.isIms();
|
||
|
}
|
||
|
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
public String getImsSmsFormat() {
|
||
|
return mDispatchersController.getImsSmsFormat();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @deprecated Use {@link #sendStoredText(String, String, Uri, String, PendingIntent,
|
||
|
* PendingIntent)} instead
|
||
|
*/
|
||
|
@Deprecated
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
public void sendStoredText(String callingPkg, Uri messageUri, String scAddress,
|
||
|
PendingIntent sentIntent, PendingIntent deliveryIntent) {
|
||
|
sendStoredText(callingPkg, null, messageUri, scAddress, sentIntent, deliveryIntent);
|
||
|
}
|
||
|
|
||
|
public void sendStoredText(String callingPkg, String callingAttributionTag,
|
||
|
Uri messageUri, String scAddress, PendingIntent sentIntent,
|
||
|
PendingIntent deliveryIntent) {
|
||
|
if (!mSmsPermissions.checkCallingCanSendSms(callingPkg, callingAttributionTag,
|
||
|
"Sending SMS message")) {
|
||
|
returnUnspecifiedFailure(sentIntent);
|
||
|
return;
|
||
|
}
|
||
|
if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
|
||
|
log("sendStoredText: scAddr=" + scAddress + " messageUri=" + messageUri
|
||
|
+ " sentIntent=" + sentIntent + " deliveryIntent=" + deliveryIntent);
|
||
|
}
|
||
|
final ContentResolver resolver = mContext.getContentResolver();
|
||
|
if (!isFailedOrDraft(resolver, messageUri)) {
|
||
|
loge("sendStoredText: not FAILED or DRAFT message");
|
||
|
returnUnspecifiedFailure(sentIntent);
|
||
|
return;
|
||
|
}
|
||
|
final String[] textAndAddress = loadTextAndAddress(resolver, messageUri);
|
||
|
if (textAndAddress == null) {
|
||
|
loge("sendStoredText: can not load text");
|
||
|
returnUnspecifiedFailure(sentIntent);
|
||
|
return;
|
||
|
}
|
||
|
notifyIfOutgoingEmergencySms(textAndAddress[1]);
|
||
|
textAndAddress[1] = filterDestAddress(textAndAddress[1]);
|
||
|
mDispatchersController.sendText(textAndAddress[1], scAddress, textAndAddress[0],
|
||
|
sentIntent, deliveryIntent, messageUri, callingPkg,
|
||
|
true /* persistMessageForNonDefaultSmsApp */, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
|
||
|
false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, false /* isForVvm */,
|
||
|
0L /* messageId */);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @deprecated Use {@link #sendStoredMultipartText(String, String, Uri, String, List, List)}
|
||
|
* instead
|
||
|
*/
|
||
|
@Deprecated
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
public void sendStoredMultipartText(String callingPkg, Uri messageUri, String scAddress,
|
||
|
List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
|
||
|
sendStoredMultipartText(callingPkg, null, messageUri, scAddress, sentIntents,
|
||
|
deliveryIntents);
|
||
|
}
|
||
|
|
||
|
public void sendStoredMultipartText(String callingPkg,
|
||
|
String callingAttributionTag, Uri messageUri, String scAddress,
|
||
|
List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
|
||
|
if (!mSmsPermissions.checkCallingCanSendSms(callingPkg, callingAttributionTag,
|
||
|
"Sending SMS message")) {
|
||
|
returnUnspecifiedFailure(sentIntents);
|
||
|
return;
|
||
|
}
|
||
|
final ContentResolver resolver = mContext.getContentResolver();
|
||
|
if (!isFailedOrDraft(resolver, messageUri)) {
|
||
|
loge("sendStoredMultipartText: not FAILED or DRAFT message");
|
||
|
returnUnspecifiedFailure(sentIntents);
|
||
|
return;
|
||
|
}
|
||
|
final String[] textAndAddress = loadTextAndAddress(resolver, messageUri);
|
||
|
if (textAndAddress == null) {
|
||
|
loge("sendStoredMultipartText: can not load text");
|
||
|
returnUnspecifiedFailure(sentIntents);
|
||
|
return;
|
||
|
}
|
||
|
final ArrayList<String> parts = SmsManager.getDefault().divideMessage(textAndAddress[0]);
|
||
|
if (parts == null || parts.size() < 1) {
|
||
|
loge("sendStoredMultipartText: can not divide text");
|
||
|
returnUnspecifiedFailure(sentIntents);
|
||
|
return;
|
||
|
}
|
||
|
notifyIfOutgoingEmergencySms(textAndAddress[1]);
|
||
|
textAndAddress[1] = filterDestAddress(textAndAddress[1]);
|
||
|
|
||
|
if (parts.size() > 1 && parts.size() < 10 && !SmsMessage.hasEmsSupport()) {
|
||
|
for (int i = 0; i < parts.size(); i++) {
|
||
|
// If EMS is not supported, we have to break down EMS into single segment SMS
|
||
|
// and add page info " x/y".
|
||
|
String singlePart = parts.get(i);
|
||
|
if (SmsMessage.shouldAppendPageNumberAsPrefix()) {
|
||
|
singlePart = String.valueOf(i + 1) + '/' + parts.size() + ' ' + singlePart;
|
||
|
} else {
|
||
|
singlePart = singlePart.concat(' ' + String.valueOf(i + 1) + '/'
|
||
|
+ parts.size());
|
||
|
}
|
||
|
|
||
|
PendingIntent singleSentIntent = null;
|
||
|
if (sentIntents != null && sentIntents.size() > i) {
|
||
|
singleSentIntent = sentIntents.get(i);
|
||
|
}
|
||
|
|
||
|
PendingIntent singleDeliveryIntent = null;
|
||
|
if (deliveryIntents != null && deliveryIntents.size() > i) {
|
||
|
singleDeliveryIntent = deliveryIntents.get(i);
|
||
|
}
|
||
|
|
||
|
mDispatchersController.sendText(textAndAddress[1], scAddress, singlePart,
|
||
|
singleSentIntent, singleDeliveryIntent, messageUri, callingPkg,
|
||
|
true /* persistMessageForNonDefaultSmsApp */,
|
||
|
SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
|
||
|
false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED,
|
||
|
false /* isForVvm */, 0L /* messageId */);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
mDispatchersController.sendMultipartText(
|
||
|
textAndAddress[1], // destAddress
|
||
|
scAddress,
|
||
|
parts,
|
||
|
(ArrayList<PendingIntent>) sentIntents,
|
||
|
(ArrayList<PendingIntent>) deliveryIntents,
|
||
|
messageUri,
|
||
|
callingPkg,
|
||
|
true /* persistMessageForNonDefaultSmsApp */,
|
||
|
SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
|
||
|
false /* expectMore */,
|
||
|
SMS_MESSAGE_PERIOD_NOT_SPECIFIED,
|
||
|
0L /* messageId */);
|
||
|
}
|
||
|
|
||
|
public int getSmsCapacityOnIcc(String callingPackage, String callingFeatureId) {
|
||
|
if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
|
||
|
mContext, mPhone.getSubId(), callingPackage, callingFeatureId,
|
||
|
"getSmsCapacityOnIcc")) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int numberOnIcc = 0;
|
||
|
if (mPhone.getIccRecordsLoaded()) {
|
||
|
final UiccProfile uiccProfile = UiccController.getInstance()
|
||
|
.getUiccProfileForPhone(mPhone.getPhoneId());
|
||
|
if(uiccProfile != null) {
|
||
|
numberOnIcc = uiccProfile.getIccRecords().getSmsCapacityOnIcc();
|
||
|
} else {
|
||
|
loge("uiccProfile is null");
|
||
|
}
|
||
|
} else {
|
||
|
loge("getSmsCapacityOnIcc - aborting, no icc card present.");
|
||
|
}
|
||
|
|
||
|
log("getSmsCapacityOnIcc().numberOnIcc = " + numberOnIcc);
|
||
|
return numberOnIcc;
|
||
|
}
|
||
|
|
||
|
private boolean isFailedOrDraft(ContentResolver resolver, Uri messageUri) {
|
||
|
// Clear the calling identity and query the database using the phone user id
|
||
|
// Otherwise the AppOps check in TelephonyProvider would complain about mismatch
|
||
|
// between the calling uid and the package uid
|
||
|
final long identity = Binder.clearCallingIdentity();
|
||
|
Cursor cursor = null;
|
||
|
try {
|
||
|
cursor = resolver.query(
|
||
|
messageUri,
|
||
|
new String[]{ Telephony.Sms.TYPE },
|
||
|
null/*selection*/,
|
||
|
null/*selectionArgs*/,
|
||
|
null/*sortOrder*/);
|
||
|
if (cursor != null && cursor.moveToFirst()) {
|
||
|
final int type = cursor.getInt(0);
|
||
|
return type == Telephony.Sms.MESSAGE_TYPE_DRAFT
|
||
|
|| type == Telephony.Sms.MESSAGE_TYPE_FAILED;
|
||
|
}
|
||
|
} catch (SQLiteException e) {
|
||
|
loge("isFailedOrDraft: query message type failed", e);
|
||
|
} finally {
|
||
|
if (cursor != null) {
|
||
|
cursor.close();
|
||
|
}
|
||
|
Binder.restoreCallingIdentity(identity);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Return an array including both the SMS text (0) and address (1)
|
||
|
private String[] loadTextAndAddress(ContentResolver resolver, Uri messageUri) {
|
||
|
// Clear the calling identity and query the database using the phone user id
|
||
|
// Otherwise the AppOps check in TelephonyProvider would complain about mismatch
|
||
|
// between the calling uid and the package uid
|
||
|
final long identity = Binder.clearCallingIdentity();
|
||
|
Cursor cursor = null;
|
||
|
try {
|
||
|
cursor = resolver.query(
|
||
|
messageUri,
|
||
|
new String[]{
|
||
|
Telephony.Sms.BODY,
|
||
|
Telephony.Sms.ADDRESS
|
||
|
},
|
||
|
null/*selection*/,
|
||
|
null/*selectionArgs*/,
|
||
|
null/*sortOrder*/);
|
||
|
if (cursor != null && cursor.moveToFirst()) {
|
||
|
return new String[]{ cursor.getString(0), cursor.getString(1) };
|
||
|
}
|
||
|
} catch (SQLiteException e) {
|
||
|
loge("loadText: query message text failed", e);
|
||
|
} finally {
|
||
|
if (cursor != null) {
|
||
|
cursor.close();
|
||
|
}
|
||
|
Binder.restoreCallingIdentity(identity);
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
@VisibleForTesting
|
||
|
public void notifyIfOutgoingEmergencySms(String destAddr) {
|
||
|
Phone[] allPhones = mPhoneFactoryProxy.getPhones();
|
||
|
EmergencyNumber emergencyNumber = mPhone.getEmergencyNumberTracker().getEmergencyNumber(
|
||
|
destAddr);
|
||
|
if (emergencyNumber != null) {
|
||
|
mPhone.notifyOutgoingEmergencySms(emergencyNumber);
|
||
|
} else if (allPhones.length > 1) {
|
||
|
// If there are multiple active SIMs, check all instances:
|
||
|
for (Phone phone : allPhones) {
|
||
|
// If the current iteration was already checked, skip:
|
||
|
if (phone.getPhoneId() == mPhone.getPhoneId()) {
|
||
|
continue;
|
||
|
}
|
||
|
emergencyNumber = phone.getEmergencyNumberTracker()
|
||
|
.getEmergencyNumber(destAddr);
|
||
|
if (emergencyNumber != null) {
|
||
|
mPhone.notifyOutgoingEmergencySms(emergencyNumber);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void returnUnspecifiedFailure(PendingIntent pi) {
|
||
|
if (pi != null) {
|
||
|
try {
|
||
|
pi.send(SmsManager.RESULT_ERROR_GENERIC_FAILURE);
|
||
|
} catch (PendingIntent.CanceledException e) {
|
||
|
// ignore
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void returnUnspecifiedFailure(List<PendingIntent> pis) {
|
||
|
if (pis == null) {
|
||
|
return;
|
||
|
}
|
||
|
for (PendingIntent pi : pis) {
|
||
|
returnUnspecifiedFailure(pi);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
private String filterDestAddress(String destAddr) {
|
||
|
String result = SmsNumberUtils.filterDestAddr(mContext, mPhone.getSubId(), destAddr);
|
||
|
return result != null ? result : destAddr;
|
||
|
}
|
||
|
|
||
|
private void waitForResult(Request request) {
|
||
|
synchronized (request) {
|
||
|
while (!request.mStatus.get()) {
|
||
|
try {
|
||
|
request.wait();
|
||
|
} catch (InterruptedException e) {
|
||
|
log("Interrupted while waiting for result");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get InboundSmsHandler for the phone.
|
||
|
*/
|
||
|
public InboundSmsHandler getInboundSmsHandler(boolean is3gpp2) {
|
||
|
return mDispatchersController.getInboundSmsHandler(is3gpp2);
|
||
|
}
|
||
|
|
||
|
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||
|
pw.println("Enabled GSM channels: " + mCellBroadcastRangeManager);
|
||
|
pw.println("Enabled CDMA channels: " + mCdmaBroadcastRangeManager);
|
||
|
pw.println("CellBroadcast log:");
|
||
|
mCellBroadcastLocalLog.dump(fd, pw, args);
|
||
|
pw.println("SMS dispatcher controller log:");
|
||
|
mDispatchersController.dump(fd, pw, args);
|
||
|
pw.flush();
|
||
|
}
|
||
|
}
|