diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java new file mode 100644 index 0000000000000..dc78366127631 --- /dev/null +++ b/telephony/java/android/telephony/SmsManager.java @@ -0,0 +1,1689 @@ +/* + * 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 android.telephony; + +import android.app.ActivityThread; +import android.app.PendingIntent; +import android.content.ActivityNotFoundException; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.BaseBundle; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.internal.telephony.IMms; +import com.android.internal.telephony.ISms; +import com.android.internal.telephony.SmsRawData; +import com.android.internal.telephony.uicc.IccConstants; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/* + * TODO(code review): Curious question... Why are a lot of these + * methods not declared as static, since they do not seem to require + * any local object state? Presumably this cannot be changed without + * interfering with the API... + */ + +/** + * Manages SMS operations such as sending data, text, and pdu SMS messages. + * Get this object by calling the static method {@link #getDefault()}. + * + *
For information about how to behave as the default SMS app on Android 4.4 (API level 19)
+ * and higher, see {@link android.provider.Telephony}.
+ */
+public final class SmsManager {
+ private static final String TAG = "SmsManager";
+ /**
+ * A psuedo-subId that represents the default subId at any given time. The actual subId it
+ * represents changes as the default subId is changed.
+ */
+ private static final int DEFAULT_SUBSCRIPTION_ID = -1002;
+
+ /** Singleton object constructed during class initialization. */
+ private static final SmsManager sInstance = new SmsManager(DEFAULT_SUBSCRIPTION_ID);
+ private static final Object sLockObject = new Object();
+
+ /** @hide */
+ public static final int CELL_BROADCAST_RAN_TYPE_GSM = 0;
+ /** @hide */
+ public static final int CELL_BROADCAST_RAN_TYPE_CDMA = 1;
+
+ private static final Map Note: Using this method requires that your app has the
+ * {@link android.Manifest.permission#SEND_SMS} permission. Note: Beginning with Android 4.4 (API level 19), if
+ * and only if an app is not selected as the default SMS app, the system automatically
+ * writes messages sent using this method to the SMS Provider (the default SMS app is always
+ * responsible for writing its sent messages to the SMS Provider). For information about
+ * how to behave as the default SMS app, see {@link android.provider.Telephony}. Only the carrier app can call this method. Note: Using this method requires that your app has the
+ * {@link android.Manifest.permission#SEND_SMS} permission. Note: Beginning with Android 4.4 (API level 19), if
+ * and only if an app is not selected as the default SMS app, the system automatically
+ * writes messages sent using this method to the SMS Provider (the default SMS app is always
+ * responsible for writing its sent messages to the SMS Provider). For information about
+ * how to behave as the default SMS app, see {@link android.provider.Telephony}. Only the carrier app can call this method. Note: Using this method requires that your app has the
+ * {@link android.Manifest.permission#SEND_SMS} permission.PendingIntent is
+ * broadcast when the message is successfully sent, or failed.
+ * The result code will be Activity.RESULT_OK for success,
+ * or one of these errors:
+ * RESULT_ERROR_GENERIC_FAILURE
+ * RESULT_ERROR_RADIO_OFF
+ * RESULT_ERROR_NULL_PDU
+ * For RESULT_ERROR_GENERIC_FAILURE the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.
+ * 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 PendingIntent is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ *
+ * @throws IllegalArgumentException if destinationAddress or text are empty
+ */
+ public void sendTextMessage(
+ String destinationAddress, String scAddress, String text,
+ PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ sendTextMessageInternal(destinationAddress, scAddress, text,
+ sentIntent, deliveryIntent, true /* persistMessageForCarrierApp*/);
+ }
+
+ private void sendTextMessageInternal(String destinationAddress, String scAddress,
+ String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
+ boolean persistMessageForCarrierApp) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+
+ if (TextUtils.isEmpty(text)) {
+ throw new IllegalArgumentException("Invalid message body");
+ }
+
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ iccISms.sendTextForSubscriber(getSubscriptionId(), ActivityThread.currentPackageName(),
+ destinationAddress,
+ scAddress, text, sentIntent, deliveryIntent,
+ persistMessageForCarrierApp);
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Send a text based SMS without writing it into the SMS Provider.
+ *
+ * PendingIntent is
+ * broadcast when the message is successfully received by the
+ * android application framework, or failed. This intent is broadcasted at
+ * the same time an SMS received from radio is acknowledged back.
+ * The result code will be RESULT_SMS_HANDLED for success, or
+ * RESULT_SMS_GENERIC_ERROR for error.
+ *
+ * @throws IllegalArgumentException if format is not one of 3gpp and 3gpp2.
+ */
+ public void injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent) {
+ if (!format.equals(SmsMessage.FORMAT_3GPP) && !format.equals(SmsMessage.FORMAT_3GPP2)) {
+ // Format must be either 3gpp or 3gpp2.
+ throw new IllegalArgumentException(
+ "Invalid pdu format. format must be either 3gpp or 3gpp2");
+ }
+ try {
+ ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ if (iccISms != null) {
+ iccISms.injectSmsPduForSubscriber(
+ getSubscriptionId(), pdu, format, receivedIntent);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Divide a message text into several fragments, none bigger than
+ * the maximum SMS message size.
+ *
+ * @param text the original message. Must not be null.
+ * @return an ArrayList of strings that, in order,
+ * comprise the original message
+ *
+ * @throws IllegalArgumentException if text is null
+ */
+ public ArrayListdivideMessage.
+ *
+ * ArrayList of strings that, in order,
+ * comprise the original message
+ * @param sentIntents if not null, an ArrayList of
+ * PendingIntents (one for each message part) that is
+ * broadcast when the corresponding message part has been sent.
+ * The result code will be Activity.RESULT_OK for success,
+ * or one of these errors:
+ * RESULT_ERROR_GENERIC_FAILURE
+ * RESULT_ERROR_RADIO_OFF
+ * RESULT_ERROR_NULL_PDU
+ * For RESULT_ERROR_GENERIC_FAILURE each sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.
+ * 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 ArrayList of
+ * PendingIntents (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").
+ *
+ * @throws IllegalArgumentException if destinationAddress or data are empty
+ */
+ public void sendMultipartTextMessage(
+ String destinationAddress, String scAddress, ArrayListPendingIntent is
+ * broadcast when the message is successfully sent, or failed.
+ * The result code will be Activity.RESULT_OK for success,
+ * or one of these errors:
+ * RESULT_ERROR_GENERIC_FAILURE
+ * RESULT_ERROR_RADIO_OFF
+ * RESULT_ERROR_NULL_PDU
+ * For RESULT_ERROR_GENERIC_FAILURE the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.
+ * 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 PendingIntent is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ *
+ * @throws IllegalArgumentException if destinationAddress or data are empty
+ */
+ public void sendDataMessage(
+ String destinationAddress, String scAddress, short destinationPort,
+ byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+
+ if (data == null || data.length == 0) {
+ throw new IllegalArgumentException("Invalid message data");
+ }
+
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ iccISms.sendDataForSubscriber(getSubscriptionId(), ActivityThread.currentPackageName(),
+ destinationAddress, scAddress, destinationPort & 0xFFFF,
+ data, sentIntent, deliveryIntent);
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * A variant of {@link SmsManager#sendDataMessage} that allows self to be the caller. This is
+ * for internal use only.
+ *
+ * @hide
+ */
+ public void sendDataMessageWithSelfPermissions(
+ String destinationAddress, String scAddress, short destinationPort,
+ byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+
+ if (data == null || data.length == 0) {
+ throw new IllegalArgumentException("Invalid message data");
+ }
+
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ iccISms.sendDataForSubscriberWithSelfPermissions(getSubscriptionId(),
+ ActivityThread.currentPackageName(), destinationAddress, scAddress,
+ destinationPort & 0xFFFF, data, sentIntent, deliveryIntent);
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+
+
+ /**
+ * Get the SmsManager associated with the default subscription id. The instance will always be
+ * associated with the default subscription id, even if the default subscription id is changed.
+ *
+ * @return the SmsManager associated with the default subscription id
+ */
+ public static SmsManager getDefault() {
+ return sInstance;
+ }
+
+ /**
+ * Get the the instance of the SmsManager associated with a particular subscription id
+ *
+ * @param subId an SMS subscription id, typically accessed using
+ * {@link android.telephony.SubscriptionManager}
+ * @return the instance of the SmsManager associated with subId
+ */
+ public static SmsManager getSmsManagerForSubscriptionId(int subId) {
+ // TODO(shri): Add javadoc link once SubscriptionManager is made public api
+ synchronized(sLockObject) {
+ SmsManager smsManager = sSubInstances.get(subId);
+ if (smsManager == null) {
+ smsManager = new SmsManager(subId);
+ sSubInstances.put(subId, smsManager);
+ }
+ return smsManager;
+ }
+ }
+
+ private SmsManager(int subId) {
+ mSubId = subId;
+ }
+
+ /**
+ * Get the associated subscription id. If the instance was returned by {@link #getDefault()},
+ * then this method may return different values at different points in time (if the user
+ * changes the default subscription id). It will return < 0 if the default subscription id
+ * cannot be determined.
+ *
+ * Additionally, to support legacy applications that are not multi-SIM aware,
+ * if the following are true:
+ * - We are using a multi-SIM device
+ * - A default SMS SIM has not been selected
+ * - At least one SIM subscription is available
+ * then ask the user to set the default SMS SIM.
+ *
+ * @return associated subscription id
+ */
+ public int getSubscriptionId() {
+ final int subId = (mSubId == DEFAULT_SUBSCRIPTION_ID)
+ ? getDefaultSmsSubscriptionId() : mSubId;
+ boolean isSmsSimPickActivityNeeded = false;
+ final Context context = ActivityThread.currentApplication().getApplicationContext();
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ isSmsSimPickActivityNeeded = iccISms.isSmsSimPickActivityNeeded(subId);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Exception in getSubscriptionId");
+ }
+
+ if (isSmsSimPickActivityNeeded) {
+ Log.d(TAG, "getSubscriptionId isSmsSimPickActivityNeeded is true");
+ // ask the user for a default SMS SIM.
+ Intent intent = new Intent();
+ intent.setClassName("com.android.settings",
+ "com.android.settings.sim.SimDialogActivity");
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(DIALOG_TYPE_KEY, SMS_PICK);
+ try {
+ context.startActivity(intent);
+ } catch (ActivityNotFoundException anfe) {
+ // If Settings is not installed, only log the error as we do not want to break
+ // legacy applications.
+ Log.e(TAG, "Unable to launch Settings application.");
+ }
+ }
+
+ return subId;
+ }
+
+ /**
+ * Returns the ISms service, or throws an UnsupportedOperationException if
+ * the service does not exist.
+ */
+ private static ISms getISmsServiceOrThrow() {
+ ISms iccISms = getISmsService();
+ if (iccISms == null) {
+ throw new UnsupportedOperationException("Sms is not supported");
+ }
+ return iccISms;
+ }
+
+ private static ISms getISmsService() {
+ return ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ }
+
+ /**
+ * Copy a raw SMS PDU to the ICC.
+ * ICC (Integrated Circuit Card) is the card of the device.
+ * For example, this can be the SIM or USIM for GSM.
+ *
+ * @param smsc the SMSC for this message, or NULL for the default SMSC
+ * @param pdu the raw PDU to store
+ * @param status message status (STATUS_ON_ICC_READ, STATUS_ON_ICC_UNREAD,
+ * STATUS_ON_ICC_SENT, STATUS_ON_ICC_UNSENT)
+ * @return true for success
+ *
+ * @throws IllegalArgumentException if pdu is NULL
+ * {@hide}
+ */
+ public boolean copyMessageToIcc(byte[] smsc, byte[] pdu,int status) {
+ boolean success = false;
+
+ if (null == pdu) {
+ throw new IllegalArgumentException("pdu is NULL");
+ }
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ success = iccISms.copyMessageToIccEfForSubscriber(getSubscriptionId(),
+ ActivityThread.currentPackageName(),
+ status, pdu, smsc);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Delete the specified message from the ICC.
+ * ICC (Integrated Circuit Card) is the card of the device.
+ * For example, this can be the SIM or USIM for GSM.
+ *
+ * @param messageIndex is the record index of the message on ICC
+ * @return true for success
+ *
+ * {@hide}
+ */
+ public boolean
+ deleteMessageFromIcc(int messageIndex) {
+ boolean success = false;
+ byte[] pdu = new byte[IccConstants.SMS_RECORD_LENGTH-1];
+ Arrays.fill(pdu, (byte)0xff);
+
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ success = iccISms.updateMessageOnIccEfForSubscriber(getSubscriptionId(),
+ ActivityThread.currentPackageName(),
+ messageIndex, STATUS_ON_ICC_FREE, pdu);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Update the specified message on the ICC.
+ * ICC (Integrated Circuit Card) is the card of the device.
+ * For example, this can be the SIM or USIM for GSM.
+ *
+ * @param messageIndex record index of message to update
+ * @param newStatus 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 true for success
+ *
+ * {@hide}
+ */
+ public boolean updateMessageOnIcc(int messageIndex, int newStatus, byte[] pdu) {
+ boolean success = false;
+
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ success = iccISms.updateMessageOnIccEfForSubscriber(getSubscriptionId(),
+ ActivityThread.currentPackageName(),
+ messageIndex, newStatus, pdu);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Retrieves all messages currently stored on ICC.
+ * ICC (Integrated Circuit Card) is the card of the device.
+ * For example, this can be the SIM or USIM for GSM.
+ *
+ * @return ArrayList of SmsMessage objects
+ *
+ * {@hide}
+ */
+ public ArrayListSmsMessages from a list of RawSmsData
+ * records returned by getAllMessagesFromIcc()
+ *
+ * @param records SMS EF records, returned by
+ * getAllMessagesFromIcc
+ * @return ArrayList of SmsMessage objects.
+ */
+ private static ArrayListPendingIntent is
+ * broadcast when the message is successfully sent, or failed
+ * @throws IllegalArgumentException if contentUri is empty
+ */
+ public void sendMultimediaMessage(Context context, Uri contentUri, String locationUrl,
+ Bundle configOverrides, PendingIntent sentIntent) {
+ if (contentUri == null) {
+ throw new IllegalArgumentException("Uri contentUri null");
+ }
+ try {
+ final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms == null) {
+ return;
+ }
+
+ iMms.sendMessage(getSubscriptionId(), ActivityThread.currentPackageName(), contentUri,
+ locationUrl, configOverrides, sentIntent);
+ } catch (RemoteException e) {
+ // Ignore it
+ }
+ }
+
+ /**
+ * Download an MMS message from carrier by a given location URL
+ *
+ * @param context application context
+ * @param locationUrl the location URL of the MMS message to be downloaded, usually obtained
+ * from the MMS WAP push notification
+ * @param contentUri the content uri to which the downloaded pdu will be written
+ * @param configOverrides the carrier-specific messaging configuration values to override for
+ * downloading the message.
+ * @param downloadedIntent if not NULL this PendingIntent is
+ * broadcast when the message is downloaded, or the download is failed
+ * @throws IllegalArgumentException if locationUrl or contentUri is empty
+ */
+ public void downloadMultimediaMessage(Context context, String locationUrl, Uri contentUri,
+ Bundle configOverrides, PendingIntent downloadedIntent) {
+ if (TextUtils.isEmpty(locationUrl)) {
+ throw new IllegalArgumentException("Empty MMS location URL");
+ }
+ if (contentUri == null) {
+ throw new IllegalArgumentException("Uri contentUri null");
+ }
+ try {
+ final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms == null) {
+ return;
+ }
+ iMms.downloadMessage(
+ getSubscriptionId(), ActivityThread.currentPackageName(), locationUrl,
+ contentUri, configOverrides, downloadedIntent);
+ } catch (RemoteException e) {
+ // Ignore it
+ }
+ }
+
+ // MMS send/download failure result codes
+ public static final int MMS_ERROR_UNSPECIFIED = 1;
+ public static final int MMS_ERROR_INVALID_APN = 2;
+ public static final int MMS_ERROR_UNABLE_CONNECT_MMS = 3;
+ public static final int MMS_ERROR_HTTP_FAILURE = 4;
+ public static final int MMS_ERROR_IO_ERROR = 5;
+ public static final int MMS_ERROR_RETRY = 6;
+ public static final int MMS_ERROR_CONFIGURATION_ERROR = 7;
+ public static final int MMS_ERROR_NO_DATA_NETWORK = 8;
+
+ /** Intent extra name for MMS sending result data in byte array type */
+ public static final String EXTRA_MMS_DATA = "android.telephony.extra.MMS_DATA";
+ /** Intent extra name for HTTP status code for MMS HTTP failure in integer type */
+ public static final String EXTRA_MMS_HTTP_STATUS = "android.telephony.extra.MMS_HTTP_STATUS";
+
+ /**
+ * Import a text message into system's SMS store
+ *
+ * Only default SMS apps can import SMS
+ *
+ * @param address the destination(source) address of the sent(received) message
+ * @param type the type of the message
+ * @param text the message text
+ * @param timestampMillis the message timestamp in milliseconds
+ * @param seen if the message is seen
+ * @param read if the message is read
+ * @return the message URI, null if failed
+ * @hide
+ */
+ public Uri importTextMessage(String address, int type, String text, long timestampMillis,
+ boolean seen, boolean read) {
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.importTextMessage(ActivityThread.currentPackageName(),
+ address, type, text, timestampMillis, seen, read);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return null;
+ }
+
+ /** Represents the received SMS message for importing {@hide} */
+ public static final int SMS_TYPE_INCOMING = 0;
+ /** Represents the sent SMS message for importing {@hide} */
+ public static final int SMS_TYPE_OUTGOING = 1;
+
+ /**
+ * Import a multimedia message into system's MMS store. Only the following PDU type is
+ * supported: Retrieve.conf, Send.req, Notification.ind, Delivery.ind, Read-Orig.ind
+ *
+ * Only default SMS apps can import MMS
+ *
+ * @param contentUri the content uri from which to read the PDU of the message to import
+ * @param messageId the optional message id. Use null if not specifying
+ * @param timestampSecs the optional message timestamp. Use -1 if not specifying
+ * @param seen if the message is seen
+ * @param read if the message is read
+ * @return the message URI, null if failed
+ * @throws IllegalArgumentException if pdu is empty
+ * {@hide}
+ */
+ public Uri importMultimediaMessage(Uri contentUri, String messageId, long timestampSecs,
+ boolean seen, boolean read) {
+ if (contentUri == null) {
+ throw new IllegalArgumentException("Uri contentUri null");
+ }
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.importMultimediaMessage(ActivityThread.currentPackageName(),
+ contentUri, messageId, timestampSecs, seen, read);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return null;
+ }
+
+ /**
+ * Delete a system stored SMS or MMS message
+ *
+ * Only default SMS apps can delete system stored SMS and MMS messages
+ *
+ * @param messageUri the URI of the stored message
+ * @return true if deletion is successful, false otherwise
+ * @throws IllegalArgumentException if messageUri is empty
+ * {@hide}
+ */
+ public boolean deleteStoredMessage(Uri messageUri) {
+ if (messageUri == null) {
+ throw new IllegalArgumentException("Empty message URI");
+ }
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.deleteStoredMessage(ActivityThread.currentPackageName(), messageUri);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return false;
+ }
+
+ /**
+ * Delete a system stored SMS or MMS thread
+ *
+ * Only default SMS apps can delete system stored SMS and MMS conversations
+ *
+ * @param conversationId the ID of the message conversation
+ * @return true if deletion is successful, false otherwise
+ * {@hide}
+ */
+ public boolean deleteStoredConversation(long conversationId) {
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.deleteStoredConversation(
+ ActivityThread.currentPackageName(), conversationId);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return false;
+ }
+
+ /**
+ * Update the status properties of a system stored SMS or MMS message, e.g.
+ * the read status of a message, etc.
+ *
+ * @param messageUri the URI of the stored message
+ * @param statusValues a list of status properties in key-value pairs to update
+ * @return true if update is successful, false otherwise
+ * @throws IllegalArgumentException if messageUri is empty
+ * {@hide}
+ */
+ public boolean updateStoredMessageStatus(Uri messageUri, ContentValues statusValues) {
+ if (messageUri == null) {
+ throw new IllegalArgumentException("Empty message URI");
+ }
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.updateStoredMessageStatus(ActivityThread.currentPackageName(),
+ messageUri, statusValues);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return false;
+ }
+
+ /** Message status property: whether the message has been seen. 1 means seen, 0 not {@hide} */
+ public static final String MESSAGE_STATUS_SEEN = "seen";
+ /** Message status property: whether the message has been read. 1 means read, 0 not {@hide} */
+ public static final String MESSAGE_STATUS_READ = "read";
+
+ /**
+ * Archive or unarchive a stored conversation
+ *
+ * @param conversationId the ID of the message conversation
+ * @param archived true to archive the conversation, false to unarchive
+ * @return true if update is successful, false otherwise
+ * {@hide}
+ */
+ public boolean archiveStoredConversation(long conversationId, boolean archived) {
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.archiveStoredConversation(ActivityThread.currentPackageName(),
+ conversationId, archived);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return false;
+ }
+
+ /**
+ * Add a text message draft to system SMS store
+ *
+ * Only default SMS apps can add SMS draft
+ *
+ * @param address the destination address of message
+ * @param text the body of the message to send
+ * @return the URI of the stored draft message
+ * {@hide}
+ */
+ public Uri addTextMessageDraft(String address, String text) {
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.addTextMessageDraft(ActivityThread.currentPackageName(), address, text);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return null;
+ }
+
+ /**
+ * Add a multimedia message draft to system MMS store
+ *
+ * Only default SMS apps can add MMS draft
+ *
+ * @param contentUri the content uri from which to read the PDU data of the draft MMS
+ * @return the URI of the stored draft message
+ * @throws IllegalArgumentException if pdu is empty
+ * {@hide}
+ */
+ public Uri addMultimediaMessageDraft(Uri contentUri) {
+ if (contentUri == null) {
+ throw new IllegalArgumentException("Uri contentUri null");
+ }
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.addMultimediaMessageDraft(ActivityThread.currentPackageName(),
+ contentUri);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return null;
+ }
+
+ /**
+ * Send a system stored text message.
+ *
+ * You can only send a failed text message or a draft text message.
+ *
+ * @param messageUri the URI of the stored message
+ * @param scAddress is the service center address or null to use the current default SMSC
+ * @param sentIntent if not NULL this PendingIntent is
+ * broadcast when the message is successfully sent, or failed.
+ * The result code will be Activity.RESULT_OK for success,
+ * or one of these errors:
+ * RESULT_ERROR_GENERIC_FAILURE
+ * RESULT_ERROR_RADIO_OFF
+ * RESULT_ERROR_NULL_PDU
+ * For RESULT_ERROR_GENERIC_FAILURE the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.
+ * 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 PendingIntent is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ *
+ * @throws IllegalArgumentException if messageUri is empty
+ * {@hide}
+ */
+ public void sendStoredTextMessage(Uri messageUri, String scAddress, PendingIntent sentIntent,
+ PendingIntent deliveryIntent) {
+ if (messageUri == null) {
+ throw new IllegalArgumentException("Empty message URI");
+ }
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ iccISms.sendStoredText(
+ getSubscriptionId(), ActivityThread.currentPackageName(), messageUri,
+ scAddress, sentIntent, deliveryIntent);
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Send a system stored multi-part text message.
+ *
+ * You can only send a failed text message or a draft text message.
+ * The provided PendingIntent lists should match the part number of the
+ * divided text of the stored message by using divideMessage
+ *
+ * @param messageUri the URI of the stored message
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param sentIntents if not null, an ArrayList of
+ * PendingIntents (one for each message part) that is
+ * broadcast when the corresponding message part has been sent.
+ * The result code will be Activity.RESULT_OK for success,
+ * or one of these errors:
+ * RESULT_ERROR_GENERIC_FAILURE
+ * RESULT_ERROR_RADIO_OFF
+ * RESULT_ERROR_NULL_PDU
+ * For RESULT_ERROR_GENERIC_FAILURE each sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.
+ * 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 ArrayList of
+ * PendingIntents (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").
+ *
+ * @throws IllegalArgumentException if messageUri is empty
+ * {@hide}
+ */
+ public void sendStoredMultipartTextMessage(Uri messageUri, String scAddress,
+ ArrayListPendingIntent is
+ * broadcast when the message is successfully sent, or failed
+ * @throws IllegalArgumentException if messageUri is empty
+ * {@hide}
+ */
+ public void sendStoredMultimediaMessage(Uri messageUri, Bundle configOverrides,
+ PendingIntent sentIntent) {
+ if (messageUri == null) {
+ throw new IllegalArgumentException("Empty message URI");
+ }
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ iMms.sendStoredMessage(
+ getSubscriptionId(), ActivityThread.currentPackageName(), messageUri,
+ configOverrides, sentIntent);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Turns on/off the flag to automatically write sent/received SMS/MMS messages into system
+ *
+ * When this flag is on, all SMS/MMS sent/received are stored by system automatically
+ * When this flag is off, only SMS/MMS sent by non-default SMS apps are stored by system
+ * automatically
+ *
+ * This flag can only be changed by default SMS apps
+ *
+ * @param enabled Whether to enable message auto persisting
+ * {@hide}
+ */
+ public void setAutoPersisting(boolean enabled) {
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ iMms.setAutoPersisting(ActivityThread.currentPackageName(), enabled);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Get the value of the flag to automatically write sent/received SMS/MMS messages into system
+ *
+ * When this flag is on, all SMS/MMS sent/received are stored by system automatically
+ * When this flag is off, only SMS/MMS sent by non-default SMS apps are stored by system
+ * automatically
+ *
+ * @return the current value of the auto persist flag
+ * {@hide}
+ */
+ public boolean getAutoPersisting() {
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.getAutoPersisting();
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return false;
+ }
+
+ /**
+ * Get carrier-dependent configuration values.
+ *
+ * @return bundle key/values pairs of configuration values
+ */
+ public Bundle getCarrierConfigValues() {
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.getCarrierConfigValues(getSubscriptionId());
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return null;
+ }
+
+ /**
+ * Create a single use app specific incoming SMS request for the the calling package.
+ *
+ * This method returns a token that if included in a subsequent incoming SMS message will cause
+ * {@code intent} to be sent with the SMS data.
+ *
+ * The token is only good for one use, after an SMS has been received containing the token all
+ * subsequent SMS messages with the token will be routed as normal.
+ *
+ * An app can only have one request at a time, if the app already has a request pending it will
+ * be replaced with a new request.
+ *
+ * @return Token to include in an SMS message. The token will be 11 characters long.
+ * @see android.provider.Telephony.Sms.Intents#getMessagesFromIntent
+ */
+ public String createAppSpecificSmsToken(PendingIntent intent) {
+ try {
+ ISms iccSms = getISmsServiceOrThrow();
+ return iccSms.createAppSpecificSmsToken(getSubscriptionId(),
+ ActivityThread.currentPackageName(), intent);
+
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ return null;
+ }
+ }
+
+ /**
+ * Filters a bundle to only contain MMS config variables.
+ *
+ * This is for use with bundles returned by {@link CarrierConfigManager} which contain MMS
+ * config and unrelated config. It is assumed that all MMS_CONFIG_* keys are present in the
+ * supplied bundle.
+ *
+ * @param config a Bundle that contains MMS config variables and possibly more.
+ * @return a new Bundle that only contains the MMS_CONFIG_* keys defined above.
+ * @hide
+ */
+ public static Bundle getMmsConfig(BaseBundle config) {
+ Bundle filtered = new Bundle();
+ filtered.putBoolean(MMS_CONFIG_APPEND_TRANSACTION_ID,
+ config.getBoolean(MMS_CONFIG_APPEND_TRANSACTION_ID));
+ filtered.putBoolean(MMS_CONFIG_MMS_ENABLED, config.getBoolean(MMS_CONFIG_MMS_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_GROUP_MMS_ENABLED,
+ config.getBoolean(MMS_CONFIG_GROUP_MMS_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_NOTIFY_WAP_MMSC_ENABLED,
+ config.getBoolean(MMS_CONFIG_NOTIFY_WAP_MMSC_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_ALIAS_ENABLED, config.getBoolean(MMS_CONFIG_ALIAS_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_ALLOW_ATTACH_AUDIO,
+ config.getBoolean(MMS_CONFIG_ALLOW_ATTACH_AUDIO));
+ filtered.putBoolean(MMS_CONFIG_MULTIPART_SMS_ENABLED,
+ config.getBoolean(MMS_CONFIG_MULTIPART_SMS_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_SMS_DELIVERY_REPORT_ENABLED,
+ config.getBoolean(MMS_CONFIG_SMS_DELIVERY_REPORT_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION,
+ config.getBoolean(MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION));
+ filtered.putBoolean(MMS_CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES,
+ config.getBoolean(MMS_CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES));
+ filtered.putBoolean(MMS_CONFIG_MMS_READ_REPORT_ENABLED,
+ config.getBoolean(MMS_CONFIG_MMS_READ_REPORT_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_MMS_DELIVERY_REPORT_ENABLED,
+ config.getBoolean(MMS_CONFIG_MMS_DELIVERY_REPORT_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_CLOSE_CONNECTION,
+ config.getBoolean(MMS_CONFIG_CLOSE_CONNECTION));
+ filtered.putInt(MMS_CONFIG_MAX_MESSAGE_SIZE, config.getInt(MMS_CONFIG_MAX_MESSAGE_SIZE));
+ filtered.putInt(MMS_CONFIG_MAX_IMAGE_WIDTH, config.getInt(MMS_CONFIG_MAX_IMAGE_WIDTH));
+ filtered.putInt(MMS_CONFIG_MAX_IMAGE_HEIGHT, config.getInt(MMS_CONFIG_MAX_IMAGE_HEIGHT));
+ filtered.putInt(MMS_CONFIG_RECIPIENT_LIMIT, config.getInt(MMS_CONFIG_RECIPIENT_LIMIT));
+ filtered.putInt(MMS_CONFIG_ALIAS_MIN_CHARS, config.getInt(MMS_CONFIG_ALIAS_MIN_CHARS));
+ filtered.putInt(MMS_CONFIG_ALIAS_MAX_CHARS, config.getInt(MMS_CONFIG_ALIAS_MAX_CHARS));
+ filtered.putInt(MMS_CONFIG_SMS_TO_MMS_TEXT_THRESHOLD,
+ config.getInt(MMS_CONFIG_SMS_TO_MMS_TEXT_THRESHOLD));
+ filtered.putInt(MMS_CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD,
+ config.getInt(MMS_CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD));
+ filtered.putInt(MMS_CONFIG_MESSAGE_TEXT_MAX_SIZE,
+ config.getInt(MMS_CONFIG_MESSAGE_TEXT_MAX_SIZE));
+ filtered.putInt(MMS_CONFIG_SUBJECT_MAX_LENGTH,
+ config.getInt(MMS_CONFIG_SUBJECT_MAX_LENGTH));
+ filtered.putInt(MMS_CONFIG_HTTP_SOCKET_TIMEOUT,
+ config.getInt(MMS_CONFIG_HTTP_SOCKET_TIMEOUT));
+ filtered.putString(MMS_CONFIG_UA_PROF_TAG_NAME,
+ config.getString(MMS_CONFIG_UA_PROF_TAG_NAME));
+ filtered.putString(MMS_CONFIG_USER_AGENT, config.getString(MMS_CONFIG_USER_AGENT));
+ filtered.putString(MMS_CONFIG_UA_PROF_URL, config.getString(MMS_CONFIG_UA_PROF_URL));
+ filtered.putString(MMS_CONFIG_HTTP_PARAMS, config.getString(MMS_CONFIG_HTTP_PARAMS));
+ filtered.putString(MMS_CONFIG_EMAIL_GATEWAY_NUMBER,
+ config.getString(MMS_CONFIG_EMAIL_GATEWAY_NUMBER));
+ filtered.putString(MMS_CONFIG_NAI_SUFFIX, config.getString(MMS_CONFIG_NAI_SUFFIX));
+ filtered.putBoolean(MMS_CONFIG_SHOW_CELL_BROADCAST_APP_LINKS,
+ config.getBoolean(MMS_CONFIG_SHOW_CELL_BROADCAST_APP_LINKS));
+ filtered.putBoolean(MMS_CONFIG_SUPPORT_HTTP_CHARSET_HEADER,
+ config.getBoolean(MMS_CONFIG_SUPPORT_HTTP_CHARSET_HEADER));
+ return filtered;
+ }
+
+}
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
new file mode 100644
index 0000000000000..ba4deaea3155b
--- /dev/null
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -0,0 +1,891 @@
+/*
+ * 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 android.telephony;
+
+import android.os.Binder;
+import android.os.Parcel;
+import android.content.res.Resources;
+import android.hardware.radio.V1_0.CdmaSmsMessage;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
+import com.android.internal.telephony.Sms7BitEncodingTranslator;
+
+import java.lang.Math;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
+
+
+/**
+ * A Short Message Service message.
+ * @see android.provider.Telephony.Sms.Intents#getMessagesFromIntent
+ */
+public class SmsMessage {
+ private static final String LOG_TAG = "SmsMessage";
+
+ /**
+ * SMS Class enumeration.
+ * See TS 23.038.
+ *
+ */
+ public enum MessageClass{
+ UNKNOWN, CLASS_0, CLASS_1, CLASS_2, CLASS_3;
+ }
+
+ /** User data text encoding code unit size */
+ public static final int ENCODING_UNKNOWN = 0;
+ public static final int ENCODING_7BIT = 1;
+ public static final int ENCODING_8BIT = 2;
+ public static final int ENCODING_16BIT = 3;
+ /**
+ * @hide This value is not defined in global standard. Only in Korea, this is used.
+ */
+ public static final int ENCODING_KSC5601 = 4;
+
+ /** The maximum number of payload bytes per message */
+ public static final int MAX_USER_DATA_BYTES = 140;
+
+ /**
+ * The maximum number of payload bytes per message if a user data header
+ * is present. This assumes the header only contains the
+ * CONCATENATED_8_BIT_REFERENCE element.
+ */
+ public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134;
+
+ /** The maximum number of payload septets per message */
+ public static final int MAX_USER_DATA_SEPTETS = 160;
+
+ /**
+ * The maximum number of payload septets per message if a user data header
+ * is present. This assumes the header only contains the
+ * CONCATENATED_8_BIT_REFERENCE element.
+ */
+ public static final int MAX_USER_DATA_SEPTETS_WITH_HEADER = 153;
+
+ /**
+ * Indicates a 3GPP format SMS message.
+ * @hide pending API council approval
+ */
+ public static final String FORMAT_3GPP = "3gpp";
+
+ /**
+ * Indicates a 3GPP2 format SMS message.
+ * @hide pending API council approval
+ */
+ public static final String FORMAT_3GPP2 = "3gpp2";
+
+ /** Contains actual SmsMessage. Only public for debugging and for framework layer.
+ *
+ * @hide
+ */
+ public SmsMessageBase mWrappedSmsMessage;
+
+ /** Indicates the subId
+ *
+ * @hide
+ */
+ private int mSubId = 0;
+
+ /** set Subscription information
+ *
+ * @hide
+ */
+ public void setSubId(int subId) {
+ mSubId = subId;
+ }
+
+ /** get Subscription information
+ *
+ * @hide
+ */
+ public int getSubId() {
+ return mSubId;
+ }
+
+ public static class SubmitPdu {
+
+ public byte[] encodedScAddress; // Null if not applicable.
+ public byte[] encodedMessage;
+
+ @Override
+ public String toString() {
+ return "SubmitPdu: encodedScAddress = "
+ + Arrays.toString(encodedScAddress)
+ + ", encodedMessage = "
+ + Arrays.toString(encodedMessage);
+ }
+
+ /**
+ * @hide
+ */
+ protected SubmitPdu(SubmitPduBase spb) {
+ this.encodedMessage = spb.encodedMessage;
+ this.encodedScAddress = spb.encodedScAddress;
+ }
+
+ }
+
+ private SmsMessage(SmsMessageBase smb) {
+ mWrappedSmsMessage = smb;
+ }
+
+ /**
+ * Create an SmsMessage from a raw PDU. Guess format based on Voice
+ * technology first, if it fails use other format.
+ * All applications which handle
+ * incoming SMS messages by processing the {@code SMS_RECEIVED_ACTION} broadcast
+ * intent must now pass the new {@code format} String extra from the intent
+ * into the new method {@code createFromPdu(byte[], String)} which takes an
+ * extra format parameter. This is required in order to correctly decode the PDU on
+ * devices that require support for both 3GPP and 3GPP2 formats at the same time,
+ * such as dual-mode GSM/CDMA and CDMA/LTE phones.
+ * @deprecated Use {@link #createFromPdu(byte[], String)} instead.
+ */
+ @Deprecated
+ public static SmsMessage createFromPdu(byte[] pdu) {
+ SmsMessage message = null;
+
+ // cdma(3gpp2) vs gsm(3gpp) format info was not given,
+ // guess from active voice phone type
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+ String format = (PHONE_TYPE_CDMA == activePhone) ?
+ SmsConstants.FORMAT_3GPP2 : SmsConstants.FORMAT_3GPP;
+ message = createFromPdu(pdu, format);
+
+ if (null == message || null == message.mWrappedSmsMessage) {
+ // decoding pdu failed based on activePhone type, must be other format
+ format = (PHONE_TYPE_CDMA == activePhone) ?
+ SmsConstants.FORMAT_3GPP : SmsConstants.FORMAT_3GPP2;
+ message = createFromPdu(pdu, format);
+ }
+ return message;
+ }
+
+ /**
+ * Create an SmsMessage from a raw PDU with the specified message format. The
+ * message format is passed in the
+ * {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} as the {@code format}
+ * String extra, and will be either "3gpp" for GSM/UMTS/LTE messages in 3GPP format
+ * or "3gpp2" for CDMA/LTE messages in 3GPP2 format.
+ *
+ * @param pdu the message PDU from the
+ * {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} intent
+ * @param format the format extra from the
+ * {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} intent
+ */
+ public static SmsMessage createFromPdu(byte[] pdu, String format) {
+ SmsMessageBase wrappedMessage;
+
+ if (SmsConstants.FORMAT_3GPP2.equals(format)) {
+ wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromPdu(pdu);
+ } else if (SmsConstants.FORMAT_3GPP.equals(format)) {
+ wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromPdu(pdu);
+ } else {
+ Rlog.e(LOG_TAG, "createFromPdu(): unsupported message format " + format);
+ return null;
+ }
+
+ if (wrappedMessage != null) {
+ return new SmsMessage(wrappedMessage);
+ } else {
+ Rlog.e(LOG_TAG, "createFromPdu(): wrappedMessage is null");
+ return null;
+ }
+ }
+
+ /**
+ * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
+ * +CMT unsolicited response (PDU mode, of course)
+ * +CMT: [<alpha>],ArrayList of strings that, in order,
+ * comprise the original msg text
+ *
+ * @hide
+ */
+ public static ArrayListSubmitPdu containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message, boolean statusReportRequested) {
+ SubmitPduBase spb;
+
+ if (useCdmaFormatForMoSms()) {
+ spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, message, statusReportRequested, null);
+ } else {
+ spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, message, statusReportRequested);
+ }
+
+ return new SubmitPdu(spb);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a data message to a destination address & port.
+ * This method will not attempt to use any GSM national language 7 bit encodings.
+ *
+ * @param scAddress Service Centre address. null == use default
+ * @param destinationAddress the address of the destination for the message
+ * @param destinationPort the port to deliver the message to at the
+ * destination
+ * @param data the data for the message
+ * @return a SubmitPdu containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, short destinationPort, byte[] data,
+ boolean statusReportRequested) {
+ SubmitPduBase spb;
+
+ if (useCdmaFormatForMoSms()) {
+ spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, destinationPort, data, statusReportRequested);
+ } else {
+ spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, destinationPort, data, statusReportRequested);
+ }
+
+ return new SubmitPdu(spb);
+ }
+
+ /**
+ * Returns the address of the SMS service center that relayed this message
+ * or null if there is none.
+ */
+ public String getServiceCenterAddress() {
+ return mWrappedSmsMessage.getServiceCenterAddress();
+ }
+
+ /**
+ * Returns the originating address (sender) of this SMS message in String
+ * form or null if unavailable
+ */
+ public String getOriginatingAddress() {
+ return mWrappedSmsMessage.getOriginatingAddress();
+ }
+
+ /**
+ * Returns the originating address, or email from address if this message
+ * was from an email gateway. Returns null if originating address
+ * unavailable.
+ */
+ public String getDisplayOriginatingAddress() {
+ return mWrappedSmsMessage.getDisplayOriginatingAddress();
+ }
+
+ /**
+ * Returns the message body as a String, if it exists and is text based.
+ * @return message body is there is one, otherwise null
+ */
+ public String getMessageBody() {
+ return mWrappedSmsMessage.getMessageBody();
+ }
+
+ /**
+ * Returns the class of this message.
+ */
+ public MessageClass getMessageClass() {
+ switch(mWrappedSmsMessage.getMessageClass()) {
+ case CLASS_0: return MessageClass.CLASS_0;
+ case CLASS_1: return MessageClass.CLASS_1;
+ case CLASS_2: return MessageClass.CLASS_2;
+ case CLASS_3: return MessageClass.CLASS_3;
+ default: return MessageClass.UNKNOWN;
+
+ }
+ }
+
+ /**
+ * Returns the message body, or email message body if this message was from
+ * an email gateway. Returns null if message body unavailable.
+ */
+ public String getDisplayMessageBody() {
+ return mWrappedSmsMessage.getDisplayMessageBody();
+ }
+
+ /**
+ * Unofficial convention of a subject line enclosed in parens empty string
+ * if not present
+ */
+ public String getPseudoSubject() {
+ return mWrappedSmsMessage.getPseudoSubject();
+ }
+
+ /**
+ * Returns the service centre timestamp in currentTimeMillis() format
+ */
+ public long getTimestampMillis() {
+ return mWrappedSmsMessage.getTimestampMillis();
+ }
+
+ /**
+ * Returns true if message is an email.
+ *
+ * @return true if this message came through an email gateway and email
+ * sender / subject / parsed body are available
+ */
+ public boolean isEmail() {
+ return mWrappedSmsMessage.isEmail();
+ }
+
+ /**
+ * @return if isEmail() is true, body of the email sent through the gateway.
+ * null otherwise
+ */
+ public String getEmailBody() {
+ return mWrappedSmsMessage.getEmailBody();
+ }
+
+ /**
+ * @return if isEmail() is true, email from address of email sent through
+ * the gateway. null otherwise
+ */
+ public String getEmailFrom() {
+ return mWrappedSmsMessage.getEmailFrom();
+ }
+
+ /**
+ * Get protocol identifier.
+ */
+ public int getProtocolIdentifier() {
+ return mWrappedSmsMessage.getProtocolIdentifier();
+ }
+
+ /**
+ * See TS 23.040 9.2.3.9 returns true if this is a "replace short message"
+ * SMS
+ */
+ public boolean isReplace() {
+ return mWrappedSmsMessage.isReplace();
+ }
+
+ /**
+ * Returns true for CPHS MWI toggle message.
+ *
+ * @return true if this is a CPHS MWI toggle message See CPHS 4.2 section
+ * B.4.2
+ */
+ public boolean isCphsMwiMessage() {
+ return mWrappedSmsMessage.isCphsMwiMessage();
+ }
+
+ /**
+ * returns true if this message is a CPHS voicemail / message waiting
+ * indicator (MWI) clear message
+ */
+ public boolean isMWIClearMessage() {
+ return mWrappedSmsMessage.isMWIClearMessage();
+ }
+
+ /**
+ * returns true if this message is a CPHS voicemail / message waiting
+ * indicator (MWI) set message
+ */
+ public boolean isMWISetMessage() {
+ return mWrappedSmsMessage.isMWISetMessage();
+ }
+
+ /**
+ * returns true if this message is a "Message Waiting Indication Group:
+ * Discard Message" notification and should not be stored.
+ */
+ public boolean isMwiDontStore() {
+ return mWrappedSmsMessage.isMwiDontStore();
+ }
+
+ /**
+ * returns the user data section minus the user data header if one was
+ * present.
+ */
+ public byte[] getUserData() {
+ return mWrappedSmsMessage.getUserData();
+ }
+
+ /**
+ * Returns the raw PDU for the message.
+ *
+ * @return the raw PDU for the message.
+ */
+ public byte[] getPdu() {
+ return mWrappedSmsMessage.getPdu();
+ }
+
+ /**
+ * Returns the status of the message on the SIM (read, unread, sent, unsent).
+ *
+ * @return the status of the message on the SIM. These are:
+ * SmsManager.STATUS_ON_SIM_FREE
+ * SmsManager.STATUS_ON_SIM_READ
+ * SmsManager.STATUS_ON_SIM_UNREAD
+ * SmsManager.STATUS_ON_SIM_SEND
+ * SmsManager.STATUS_ON_SIM_UNSENT
+ * @deprecated Use getStatusOnIcc instead.
+ */
+ @Deprecated public int getStatusOnSim() {
+ return mWrappedSmsMessage.getStatusOnIcc();
+ }
+
+ /**
+ * Returns the status of the message on the ICC (read, unread, sent, unsent).
+ *
+ * @return the status of the message on the ICC. These are:
+ * SmsManager.STATUS_ON_ICC_FREE
+ * SmsManager.STATUS_ON_ICC_READ
+ * SmsManager.STATUS_ON_ICC_UNREAD
+ * SmsManager.STATUS_ON_ICC_SEND
+ * SmsManager.STATUS_ON_ICC_UNSENT
+ */
+ public int getStatusOnIcc() {
+ return mWrappedSmsMessage.getStatusOnIcc();
+ }
+
+ /**
+ * Returns the record index of the message on the SIM (1-based index).
+ * @return the record index of the message on the SIM, or -1 if this
+ * SmsMessage was not created from a SIM SMS EF record.
+ * @deprecated Use getIndexOnIcc instead.
+ */
+ @Deprecated public int getIndexOnSim() {
+ return mWrappedSmsMessage.getIndexOnIcc();
+ }
+
+ /**
+ * Returns the record index of the message on the ICC (1-based index).
+ * @return the record index of the message on the ICC, or -1 if this
+ * SmsMessage was not created from a ICC SMS EF record.
+ */
+ public int getIndexOnIcc() {
+ return mWrappedSmsMessage.getIndexOnIcc();
+ }
+
+ /**
+ * GSM:
+ * For an SMS-STATUS-REPORT message, this returns the status field from
+ * the status report. This field indicates the status of a previously
+ * submitted SMS, if requested. See TS 23.040, 9.2.3.15 TP-Status for a
+ * description of values.
+ * CDMA:
+ * For not interfering with status codes from GSM, the value is
+ * shifted to the bits 31-16.
+ * The value is composed of an error class (bits 25-24) and a status code (bits 23-16).
+ * Possible codes are described in C.S0015-B, v2.0, 4.5.21.
+ *
+ * @return 0 indicates the previously sent message was received.
+ * See TS 23.040, 9.9.2.3.15 and C.S0015-B, v2.0, 4.5.21
+ * for a description of other possible values.
+ */
+ public int getStatus() {
+ return mWrappedSmsMessage.getStatus();
+ }
+
+ /**
+ * Return true iff the message is a SMS-STATUS-REPORT message.
+ */
+ public boolean isStatusReportMessage() {
+ return mWrappedSmsMessage.isStatusReportMessage();
+ }
+
+ /**
+ * Returns true iff the TP-Reply-Path bit is set in
+ * this message.
+ */
+ public boolean isReplyPathPresent() {
+ return mWrappedSmsMessage.isReplyPathPresent();
+ }
+
+ /**
+ * 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.
+ */
+ private static boolean useCdmaFormatForMoSms() {
+ if (!SmsManager.getDefault().isImsSmsSupported()) {
+ // use Voice technology to determine SMS format.
+ return isCdmaVoice();
+ }
+ // IMS is registered with SMS support, check the SMS format supported
+ return (SmsConstants.FORMAT_3GPP2.equals(SmsManager.getDefault().getImsSmsFormat()));
+ }
+
+ /**
+ * Determines whether or not to current phone type is cdma.
+ *
+ * @return true if current phone type is cdma, false otherwise.
+ */
+ private static boolean isCdmaVoice() {
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+ return (PHONE_TYPE_CDMA == activePhone);
+ }
+
+ /**
+ * Decide if the carrier supports long SMS.
+ * {@hide}
+ */
+ public static boolean hasEmsSupport() {
+ if (!isNoEmsSupportConfigListExisted()) {
+ return true;
+ }
+
+ String simOperator;
+ String gid;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ simOperator = TelephonyManager.getDefault().getSimOperatorNumeric();
+ gid = TelephonyManager.getDefault().getGroupIdLevel1();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ if (!TextUtils.isEmpty(simOperator)) {
+ for (NoEmsSupportConfig currentConfig : mNoEmsSupportConfigList) {
+ if (simOperator.startsWith(currentConfig.mOperatorNumber) &&
+ (TextUtils.isEmpty(currentConfig.mGid1) ||
+ (!TextUtils.isEmpty(currentConfig.mGid1) &&
+ currentConfig.mGid1.equalsIgnoreCase(gid)))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Check where to add " x/y" in each SMS segment, begin or end.
+ * {@hide}
+ */
+ public static boolean shouldAppendPageNumberAsPrefix() {
+ if (!isNoEmsSupportConfigListExisted()) {
+ return false;
+ }
+
+ String simOperator;
+ String gid;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ simOperator = TelephonyManager.getDefault().getSimOperatorNumeric();
+ gid = TelephonyManager.getDefault().getGroupIdLevel1();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ for (NoEmsSupportConfig currentConfig : mNoEmsSupportConfigList) {
+ if (simOperator.startsWith(currentConfig.mOperatorNumber) &&
+ (TextUtils.isEmpty(currentConfig.mGid1) ||
+ (!TextUtils.isEmpty(currentConfig.mGid1)
+ && currentConfig.mGid1.equalsIgnoreCase(gid)))) {
+ return currentConfig.mIsPrefix;
+ }
+ }
+ return false;
+ }
+
+ private static class NoEmsSupportConfig {
+ String mOperatorNumber;
+ String mGid1;
+ boolean mIsPrefix;
+
+ public NoEmsSupportConfig(String[] config) {
+ mOperatorNumber = config[0];
+ mIsPrefix = "prefix".equals(config[1]);
+ mGid1 = config.length > 2 ? config[2] : null;
+ }
+
+ @Override
+ public String toString() {
+ return "NoEmsSupportConfig { mOperatorNumber = " + mOperatorNumber
+ + ", mIsPrefix = " + mIsPrefix + ", mGid1 = " + mGid1 + " }";
+ }
+ }
+
+ private static NoEmsSupportConfig[] mNoEmsSupportConfigList = null;
+ private static boolean mIsNoEmsSupportConfigListLoaded = false;
+
+ private static boolean isNoEmsSupportConfigListExisted() {
+ if (!mIsNoEmsSupportConfigListLoaded) {
+ Resources r = Resources.getSystem();
+ if (r != null) {
+ String[] listArray = r.getStringArray(
+ com.android.internal.R.array.no_ems_support_sim_operators);
+ if ((listArray != null) && (listArray.length > 0)) {
+ mNoEmsSupportConfigList = new NoEmsSupportConfig[listArray.length];
+ for (int i=0; i
Only the default SMS app (selected by the user in system settings) is able to write to the + * SMS Provider (the tables defined within the {@code Telephony} class) and only the default SMS + * app receives the {@link android.provider.Telephony.Sms.Intents#SMS_DELIVER_ACTION} broadcast + * when the user receives an SMS or the {@link + * android.provider.Telephony.Sms.Intents#WAP_PUSH_DELIVER_ACTION} broadcast when the user + * receives an MMS.
+ * + *Any app that wants to behave as the user's default SMS app must handle the following intents: + *
"android.provider.Telephony.SMS_DELIVER"). The broadcast receiver must also
+ * require the {@link android.Manifest.permission#BROADCAST_SMS} permission.
+ * This allows your app to directly receive incoming SMS messages.
"application/vnd.wap.mms-message".
+ * The broadcast receiver must also require the {@link
+ * android.Manifest.permission#BROADCAST_WAP_PUSH} permission.
+ * This allows your app to directly receive incoming MMS messages.
"android.intent.action.SENDTO"
+ * ) with schemas, sms:, smsto:, mms:, and
+ * mmsto:.
+ * This allows your app to receive intents from other apps that want to deliver a + * message.
"android.intent.action.RESPOND_VIA_MESSAGE") with schemas,
+ * sms:, smsto:, mms:, and mmsto:.
+ * This service must also require the {@link
+ * android.Manifest.permission#SEND_RESPOND_VIA_MESSAGE} permission.
+ * This allows users to respond to incoming phone calls with an immediate text message + * using your app.
Other apps that are not selected as the default SMS app can only read the SMS + * Provider, but may also be notified when a new SMS arrives by listening for the {@link + * Sms.Intents#SMS_RECEIVED_ACTION} + * broadcast, which is a non-abortable broadcast that may be delivered to multiple apps. This + * broadcast is intended for apps that—while not selected as the default SMS app—need to + * read special incoming messages such as to perform phone number verification.
+ * + *For more information about building SMS apps, read the blog post, Getting Your SMS Apps Ready for KitKat.
+ * + */ +public final class Telephony { + private static final String TAG = "Telephony"; + + /** + * Not instantiable. + * @hide + */ + private Telephony() { + } + + /** + * Base columns for tables that contain text-based SMSs. + */ + public interface TextBasedSmsColumns { + + /** Message type: all messages. */ + public static final int MESSAGE_TYPE_ALL = 0; + + /** Message type: inbox. */ + public static final int MESSAGE_TYPE_INBOX = 1; + + /** Message type: sent messages. */ + public static final int MESSAGE_TYPE_SENT = 2; + + /** Message type: drafts. */ + public static final int MESSAGE_TYPE_DRAFT = 3; + + /** Message type: outbox. */ + public static final int MESSAGE_TYPE_OUTBOX = 4; + + /** Message type: failed outgoing message. */ + public static final int MESSAGE_TYPE_FAILED = 5; + + /** Message type: queued to send later. */ + public static final int MESSAGE_TYPE_QUEUED = 6; + + /** + * The type of message. + *Type: INTEGER
+ */ + public static final String TYPE = "type"; + + /** + * The thread ID of the message. + *Type: INTEGER
+ */ + public static final String THREAD_ID = "thread_id"; + + /** + * The address of the other party. + *Type: TEXT
+ */ + public static final String ADDRESS = "address"; + + /** + * The date the message was received. + *Type: INTEGER (long)
+ */ + public static final String DATE = "date"; + + /** + * The date the message was sent. + *Type: INTEGER (long)
+ */ + public static final String DATE_SENT = "date_sent"; + + /** + * Has the message been read? + *Type: INTEGER (boolean)
+ */ + public static final String READ = "read"; + + /** + * Has the message been seen by the user? The "seen" flag determines + * whether we need to show a notification. + *Type: INTEGER (boolean)
+ */ + public static final String SEEN = "seen"; + + /** + * {@code TP-Status} value for the message, or -1 if no status has been received. + *Type: INTEGER
+ */ + public static final String STATUS = "status"; + + /** TP-Status: no status received. */ + public static final int STATUS_NONE = -1; + /** TP-Status: complete. */ + public static final int STATUS_COMPLETE = 0; + /** TP-Status: pending. */ + public static final int STATUS_PENDING = 32; + /** TP-Status: failed. */ + public static final int STATUS_FAILED = 64; + + /** + * The subject of the message, if present. + *Type: TEXT
+ */ + public static final String SUBJECT = "subject"; + + /** + * The body of the message. + *Type: TEXT
+ */ + public static final String BODY = "body"; + + /** + * The ID of the sender of the conversation, if present. + *Type: INTEGER (reference to item in {@code content://contacts/people})
+ */ + public static final String PERSON = "person"; + + /** + * The protocol identifier code. + *Type: INTEGER
+ */ + public static final String PROTOCOL = "protocol"; + + /** + * Is the {@code TP-Reply-Path} flag set? + *Type: BOOLEAN
+ */ + public static final String REPLY_PATH_PRESENT = "reply_path_present"; + + /** + * The service center (SC) through which to send the message, if present. + *Type: TEXT
+ */ + public static final String SERVICE_CENTER = "service_center"; + + /** + * Is the message locked? + *Type: INTEGER (boolean)
+ */ + public static final String LOCKED = "locked"; + + /** + * The subscription to which the message belongs to. Its value will be + * < 0 if the sub id cannot be determined. + *Type: INTEGER (long)
+ */ + public static final String SUBSCRIPTION_ID = "sub_id"; + + /** + * The MTU size of the mobile interface to which the APN connected + * @hide + */ + public static final String MTU = "mtu"; + + /** + * Error code associated with sending or receiving this message + *Type: INTEGER
+ */ + public static final String ERROR_CODE = "error_code"; + + /** + * The identity of the sender of a sent message. It is + * usually the package name of the app which sends the message. + *Note: + * This column is read-only. It is set by the provider and can not be changed by apps. + *
Type: TEXT
+ */ + public static final String CREATOR = "creator"; + } + + /** + * Contains all text-based SMS messages. + */ + public static final class Sms implements BaseColumns, TextBasedSmsColumns { + + /** + * Not instantiable. + * @hide + */ + private Sms() { + } + + /** + * Used to determine the currently configured default SMS package. + * @param context context of the requesting application + * @return package name for the default SMS package or null + */ + public static String getDefaultSmsPackage(Context context) { + ComponentName component = SmsApplication.getDefaultSmsApplication(context, false); + if (component != null) { + return component.getPackageName(); + } + return null; + } + + /** + * Return cursor for table query. + * @hide + */ + public static Cursor query(ContentResolver cr, String[] projection) { + return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER); + } + + /** + * Return cursor for table query. + * @hide + */ + public static Cursor query(ContentResolver cr, String[] projection, + String where, String orderBy) { + return cr.query(CONTENT_URI, projection, where, + null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy); + } + + /** + * The {@code content://} style URL for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://sms"); + + /** + * The default sort order for this table. + */ + public static final String DEFAULT_SORT_ORDER = "date DESC"; + + /** + * Add an SMS to the given URI. + * + * @param resolver the content resolver to use + * @param uri the URI to add the message to + * @param address the address of the sender + * @param body the body of the message + * @param subject the pseudo-subject of the message + * @param date the timestamp for the message + * @param read true if the message has been read, false if not + * @param deliveryReport true if a delivery report was requested, false if not + * @return the URI for the new message + * @hide + */ + public static Uri addMessageToUri(ContentResolver resolver, + Uri uri, String address, String body, String subject, + Long date, boolean read, boolean deliveryReport) { + return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(), + resolver, uri, address, body, subject, date, read, deliveryReport, -1L); + } + + /** + * Add an SMS to the given URI. + * + * @param resolver the content resolver to use + * @param uri the URI to add the message to + * @param address the address of the sender + * @param body the body of the message + * @param subject the psuedo-subject of the message + * @param date the timestamp for the message + * @param read true if the message has been read, false if not + * @param deliveryReport true if a delivery report was requested, false if not + * @param subId the subscription which the message belongs to + * @return the URI for the new message + * @hide + */ + public static Uri addMessageToUri(int subId, ContentResolver resolver, + Uri uri, String address, String body, String subject, + Long date, boolean read, boolean deliveryReport) { + return addMessageToUri(subId, resolver, uri, address, body, subject, + date, read, deliveryReport, -1L); + } + + /** + * Add an SMS to the given URI with the specified thread ID. + * + * @param resolver the content resolver to use + * @param uri the URI to add the message to + * @param address the address of the sender + * @param body the body of the message + * @param subject the pseudo-subject of the message + * @param date the timestamp for the message + * @param read true if the message has been read, false if not + * @param deliveryReport true if a delivery report was requested, false if not + * @param threadId the thread_id of the message + * @return the URI for the new message + * @hide + */ + public static Uri addMessageToUri(ContentResolver resolver, + Uri uri, String address, String body, String subject, + Long date, boolean read, boolean deliveryReport, long threadId) { + return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(), + resolver, uri, address, body, subject, + date, read, deliveryReport, threadId); + } + + /** + * Add an SMS to the given URI with thread_id specified. + * + * @param resolver the content resolver to use + * @param uri the URI to add the message to + * @param address the address of the sender + * @param body the body of the message + * @param subject the psuedo-subject of the message + * @param date the timestamp for the message + * @param read true if the message has been read, false if not + * @param deliveryReport true if a delivery report was requested, false if not + * @param threadId the thread_id of the message + * @param subId the subscription which the message belongs to + * @return the URI for the new message + * @hide + */ + public static Uri addMessageToUri(int subId, ContentResolver resolver, + Uri uri, String address, String body, String subject, + Long date, boolean read, boolean deliveryReport, long threadId) { + ContentValues values = new ContentValues(8); + Rlog.v(TAG,"Telephony addMessageToUri sub id: " + subId); + + values.put(SUBSCRIPTION_ID, subId); + values.put(ADDRESS, address); + if (date != null) { + values.put(DATE, date); + } + values.put(READ, read ? Integer.valueOf(1) : Integer.valueOf(0)); + values.put(SUBJECT, subject); + values.put(BODY, body); + if (deliveryReport) { + values.put(STATUS, STATUS_PENDING); + } + if (threadId != -1L) { + values.put(THREAD_ID, threadId); + } + return resolver.insert(uri, values); + } + + /** + * Move a message to the given folder. + * + * @param context the context to use + * @param uri the message to move + * @param folder the folder to move to + * @return true if the operation succeeded + * @hide + */ + public static boolean moveMessageToFolder(Context context, + Uri uri, int folder, int error) { + if (uri == null) { + return false; + } + + boolean markAsUnread = false; + boolean markAsRead = false; + switch(folder) { + case MESSAGE_TYPE_INBOX: + case MESSAGE_TYPE_DRAFT: + break; + case MESSAGE_TYPE_OUTBOX: + case MESSAGE_TYPE_SENT: + markAsRead = true; + break; + case MESSAGE_TYPE_FAILED: + case MESSAGE_TYPE_QUEUED: + markAsUnread = true; + break; + default: + return false; + } + + ContentValues values = new ContentValues(3); + + values.put(TYPE, folder); + if (markAsUnread) { + values.put(READ, 0); + } else if (markAsRead) { + values.put(READ, 1); + } + values.put(ERROR_CODE, error); + + return 1 == SqliteWrapper.update(context, context.getContentResolver(), + uri, values, null, null); + } + + /** + * Returns true iff the folder (message type) identifies an + * outgoing message. + * @hide + */ + public static boolean isOutgoingFolder(int messageType) { + return (messageType == MESSAGE_TYPE_FAILED) + || (messageType == MESSAGE_TYPE_OUTBOX) + || (messageType == MESSAGE_TYPE_SENT) + || (messageType == MESSAGE_TYPE_QUEUED); + } + + /** + * Contains all text-based SMS messages in the SMS app inbox. + */ + public static final class Inbox implements BaseColumns, TextBasedSmsColumns { + + /** + * Not instantiable. + * @hide + */ + private Inbox() { + } + + /** + * The {@code content://} style URL for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://sms/inbox"); + + /** + * The default sort order for this table. + */ + public static final String DEFAULT_SORT_ORDER = "date DESC"; + + /** + * Add an SMS to the Draft box. + * + * @param resolver the content resolver to use + * @param address the address of the sender + * @param body the body of the message + * @param subject the pseudo-subject of the message + * @param date the timestamp for the message + * @param read true if the message has been read, false if not + * @return the URI for the new message + * @hide + */ + public static Uri addMessage(ContentResolver resolver, + String address, String body, String subject, Long date, + boolean read) { + return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(), + resolver, CONTENT_URI, address, body, subject, date, read, false); + } + + /** + * Add an SMS to the Draft box. + * + * @param resolver the content resolver to use + * @param address the address of the sender + * @param body the body of the message + * @param subject the psuedo-subject of the message + * @param date the timestamp for the message + * @param read true if the message has been read, false if not + * @param subId the subscription which the message belongs to + * @return the URI for the new message + * @hide + */ + public static Uri addMessage(int subId, ContentResolver resolver, + String address, String body, String subject, Long date, boolean read) { + return addMessageToUri(subId, resolver, CONTENT_URI, address, body, + subject, date, read, false); + } + } + + /** + * Contains all sent text-based SMS messages in the SMS app. + */ + public static final class Sent implements BaseColumns, TextBasedSmsColumns { + + /** + * Not instantiable. + * @hide + */ + private Sent() { + } + + /** + * The {@code content://} style URL for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://sms/sent"); + + /** + * The default sort order for this table. + */ + public static final String DEFAULT_SORT_ORDER = "date DESC"; + + /** + * Add an SMS to the Draft box. + * + * @param resolver the content resolver to use + * @param address the address of the sender + * @param body the body of the message + * @param subject the pseudo-subject of the message + * @param date the timestamp for the message + * @return the URI for the new message + * @hide + */ + public static Uri addMessage(ContentResolver resolver, + String address, String body, String subject, Long date) { + return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(), + resolver, CONTENT_URI, address, body, subject, date, true, false); + } + + /** + * Add an SMS to the Draft box. + * + * @param resolver the content resolver to use + * @param address the address of the sender + * @param body the body of the message + * @param subject the psuedo-subject of the message + * @param date the timestamp for the message + * @param subId the subscription which the message belongs to + * @return the URI for the new message + * @hide + */ + public static Uri addMessage(int subId, ContentResolver resolver, + String address, String body, String subject, Long date) { + return addMessageToUri(subId, resolver, CONTENT_URI, address, body, + subject, date, true, false); + } + } + + /** + * Contains all sent text-based SMS messages in the SMS app. + */ + public static final class Draft implements BaseColumns, TextBasedSmsColumns { + + /** + * Not instantiable. + * @hide + */ + private Draft() { + } + + /** + * The {@code content://} style URL for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://sms/draft"); + + /** + * @hide + */ + public static Uri addMessage(ContentResolver resolver, + String address, String body, String subject, Long date) { + return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(), + resolver, CONTENT_URI, address, body, subject, date, true, false); + } + + /** + * Add an SMS to the Draft box. + * + * @param resolver the content resolver to use + * @param address the address of the sender + * @param body the body of the message + * @param subject the psuedo-subject of the message + * @param date the timestamp for the message + * @param subId the subscription which the message belongs to + * @return the URI for the new message + * @hide + */ + public static Uri addMessage(int subId, ContentResolver resolver, + String address, String body, String subject, Long date) { + return addMessageToUri(subId, resolver, CONTENT_URI, address, body, + subject, date, true, false); + } + + /** + * The default sort order for this table. + */ + public static final String DEFAULT_SORT_ORDER = "date DESC"; + } + + /** + * Contains all pending outgoing text-based SMS messages. + */ + public static final class Outbox implements BaseColumns, TextBasedSmsColumns { + + /** + * Not instantiable. + * @hide + */ + private Outbox() { + } + + /** + * The {@code content://} style URL for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://sms/outbox"); + + /** + * The default sort order for this table. + */ + public static final String DEFAULT_SORT_ORDER = "date DESC"; + + /** + * Add an SMS to the outbox. + * + * @param resolver the content resolver to use + * @param address the address of the sender + * @param body the body of the message + * @param subject the pseudo-subject of the message + * @param date the timestamp for the message + * @param deliveryReport whether a delivery report was requested for the message + * @return the URI for the new message + * @hide + */ + public static Uri addMessage(ContentResolver resolver, + String address, String body, String subject, Long date, + boolean deliveryReport, long threadId) { + return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(), + resolver, CONTENT_URI, address, body, subject, date, + true, deliveryReport, threadId); + } + + /** + * Add an SMS to the Out box. + * + * @param resolver the content resolver to use + * @param address the address of the sender + * @param body the body of the message + * @param subject the psuedo-subject of the message + * @param date the timestamp for the message + * @param deliveryReport whether a delivery report was requested for the message + * @param subId the subscription which the message belongs to + * @return the URI for the new message + * @hide + */ + public static Uri addMessage(int subId, ContentResolver resolver, + String address, String body, String subject, Long date, + boolean deliveryReport, long threadId) { + return addMessageToUri(subId, resolver, CONTENT_URI, address, body, + subject, date, true, deliveryReport, threadId); + } + } + + /** + * Contains all sent text-based SMS messages in the SMS app. + */ + public static final class Conversations + implements BaseColumns, TextBasedSmsColumns { + + /** + * Not instantiable. + * @hide + */ + private Conversations() { + } + + /** + * The {@code content://} style URL for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://sms/conversations"); + + /** + * The default sort order for this table. + */ + public static final String DEFAULT_SORT_ORDER = "date DESC"; + + /** + * The first 45 characters of the body of the message. + *Type: TEXT
+ */ + public static final String SNIPPET = "snippet"; + + /** + * The number of messages in the conversation. + *Type: INTEGER
+ */ + public static final String MESSAGE_COUNT = "msg_count"; + } + + /** + * Contains constants for SMS related Intents that are broadcast. + */ + public static final class Intents { + + /** + * Not instantiable. + * @hide + */ + private Intents() { + } + + /** + * Set by BroadcastReceiver to indicate that the message was handled + * successfully. + */ + public static final int RESULT_SMS_HANDLED = 1; + + /** + * Set by BroadcastReceiver to indicate a generic error while + * processing the message. + */ + public static final int RESULT_SMS_GENERIC_ERROR = 2; + + /** + * Set by BroadcastReceiver to indicate insufficient memory to store + * the message. + */ + public static final int RESULT_SMS_OUT_OF_MEMORY = 3; + + /** + * Set by BroadcastReceiver to indicate that the message, while + * possibly valid, is of a format or encoding that is not + * supported. + */ + public static final int RESULT_SMS_UNSUPPORTED = 4; + + /** + * Set by BroadcastReceiver to indicate a duplicate incoming message. + */ + public static final int RESULT_SMS_DUPLICATED = 5; + + /** + * Activity action: Ask the user to change the default + * SMS application. This will show a dialog that asks the + * user whether they want to replace the current default + * SMS application with the one specified in + * {@link #EXTRA_PACKAGE_NAME}. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CHANGE_DEFAULT = + "android.provider.Telephony.ACTION_CHANGE_DEFAULT"; + + /** + * The PackageName string passed in as an + * extra for {@link #ACTION_CHANGE_DEFAULT} + * + * @see #ACTION_CHANGE_DEFAULT + */ + public static final String EXTRA_PACKAGE_NAME = "package"; + + /** + * Broadcast Action: A new text-based SMS message has been received + * by the device. This intent will only be delivered to the default + * sms app. That app is responsible for writing the message and notifying + * the user. The intent will have the following extra values: + * + *The extra values can be extracted using + * {@link #getMessagesFromIntent(Intent)}.
+ * + *If a BroadcastReceiver encounters an error while processing + * this intent it should set the result code appropriately.
+ * + *Note:
+ * The broadcast receiver that filters for this intent must declare
+ * {@link android.Manifest.permission#BROADCAST_SMS} as a required permission in
+ * the {@code
+ *
Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.
+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String SMS_DELIVER_ACTION = + "android.provider.Telephony.SMS_DELIVER"; + + /** + * Broadcast Action: A new text-based SMS message has been received + * by the device. This intent will be delivered to all registered + * receivers as a notification. These apps are not expected to write the + * message or notify the user. The intent will have the following extra + * values: + * + *The extra values can be extracted using + * {@link #getMessagesFromIntent(Intent)}.
+ * + *If a BroadcastReceiver encounters an error while processing + * this intent it should set the result code appropriately.
+ * + *Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.
+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String SMS_RECEIVED_ACTION = + "android.provider.Telephony.SMS_RECEIVED"; + + /** + * Broadcast Action: A new data based SMS message has been received + * by the device. This intent will be delivered to all registered + * receivers as a notification. The intent will have the following extra + * values: + * + *The extra values can be extracted using + * {@link #getMessagesFromIntent(Intent)}.
+ * + *If a BroadcastReceiver encounters an error while processing + * this intent it should set the result code appropriately.
+ * + *Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.
+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String DATA_SMS_RECEIVED_ACTION = + "android.intent.action.DATA_SMS_RECEIVED"; + + /** + * Broadcast Action: A new WAP PUSH message has been received by the + * device. This intent will only be delivered to the default + * sms app. That app is responsible for writing the message and notifying + * the user. The intent will have the following extra values: + * + *If a BroadcastReceiver encounters an error while processing + * this intent it should set the result code appropriately.
+ * + *The contentTypeParameters extra value is map of content parameters keyed by + * their names.
+ * + *If any unassigned well-known parameters are encountered, the key of the map will + * be 'unassigned/0x...', where '...' is the hex value of the unassigned parameter. If + * a parameter has No-Value the value in the map will be null.
+ * + *Requires {@link android.Manifest.permission#RECEIVE_MMS} or + * {@link android.Manifest.permission#RECEIVE_WAP_PUSH} (depending on WAP PUSH type) to + * receive.
+ * + *Note:
+ * The broadcast receiver that filters for this intent must declare
+ * {@link android.Manifest.permission#BROADCAST_WAP_PUSH} as a required permission in
+ * the {@code
+ *
If a BroadcastReceiver encounters an error while processing + * this intent it should set the result code appropriately.
+ * + *The contentTypeParameters extra value is map of content parameters keyed by + * their names.
+ * + *If any unassigned well-known parameters are encountered, the key of the map will + * be 'unassigned/0x...', where '...' is the hex value of the unassigned parameter. If + * a parameter has No-Value the value in the map will be null.
+ * + *Requires {@link android.Manifest.permission#RECEIVE_MMS} or + * {@link android.Manifest.permission#RECEIVE_WAP_PUSH} (depending on WAP PUSH type) to + * receive.
+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String WAP_PUSH_RECEIVED_ACTION = + "android.provider.Telephony.WAP_PUSH_RECEIVED"; + + /** + * Broadcast Action: A new Cell Broadcast message has been received + * by the device. The intent will have the following extra + * values: + * + *The extra values can be extracted using + * {@link #getMessagesFromIntent(Intent)}.
+ * + *If a BroadcastReceiver encounters an error while processing + * this intent it should set the result code appropriately.
+ * + *Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.
+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String SMS_CB_RECEIVED_ACTION = + "android.provider.Telephony.SMS_CB_RECEIVED"; + + /** + * Action: A SMS based carrier provision intent. Used to identify default + * carrier provisioning app on the device. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @TestApi + public static final String SMS_CARRIER_PROVISION_ACTION = + "android.provider.Telephony.SMS_CARRIER_PROVISION"; + + /** + * Broadcast Action: A new Emergency Broadcast message has been received + * by the device. The intent will have the following extra + * values: + * + *The extra values can be extracted using + * {@link #getMessagesFromIntent(Intent)}.
+ * + *If a BroadcastReceiver encounters an error while processing + * this intent it should set the result code appropriately.
+ * + *Requires {@link android.Manifest.permission#RECEIVE_EMERGENCY_BROADCAST} to + * receive.
+ * @removed + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String SMS_EMERGENCY_CB_RECEIVED_ACTION = + "android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED"; + + /** + * Broadcast Action: A new CDMA SMS has been received containing Service Category + * Program Data (updates the list of enabled broadcast channels). The intent will + * have the following extra values: + * + *The extra values can be extracted using + * {@link #getMessagesFromIntent(Intent)}.
+ * + *If a BroadcastReceiver encounters an error while processing + * this intent it should set the result code appropriately.
+ * + *Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.
+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION = + "android.provider.Telephony.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED"; + + /** + * Broadcast Action: The SIM storage for SMS messages is full. If + * space is not freed, messages targeted for the SIM (class 2) may + * not be saved. + * + *Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.
+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String SIM_FULL_ACTION = + "android.provider.Telephony.SIM_FULL"; + + /** + * Broadcast Action: An incoming SMS has been rejected by the + * telephony framework. This intent is sent in lieu of any + * of the RECEIVED_ACTION intents. The intent will have the + * following extra value: + * + *Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.
+ */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String SMS_REJECTED_ACTION = + "android.provider.Telephony.SMS_REJECTED"; + + /** + * Broadcast Action: An incoming MMS has been downloaded. The intent is sent to all + * users, except for secondary users where SMS has been disabled and to managed + * profiles. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String MMS_DOWNLOADED_ACTION = + "android.provider.Telephony.MMS_DOWNLOADED"; + + /** + * Broadcast action: When the default SMS package changes, + * the previous default SMS package and the new default SMS + * package are sent this broadcast to notify them of the change. + * A boolean is specified in {@link #EXTRA_IS_DEFAULT_SMS_APP} to + * indicate whether the package is the new default SMS package. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DEFAULT_SMS_PACKAGE_CHANGED = + "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED"; + + /** + * The IsDefaultSmsApp boolean passed as an + * extra for {@link #ACTION_DEFAULT_SMS_PACKAGE_CHANGED} to indicate whether the + * SMS app is becoming the default SMS app or is no longer the default. + * + * @see #ACTION_DEFAULT_SMS_PACKAGE_CHANGED + */ + public static final String EXTRA_IS_DEFAULT_SMS_APP = + "android.provider.extra.IS_DEFAULT_SMS_APP"; + + /** + * Broadcast action: When a change is made to the SmsProvider or + * MmsProvider by a process other than the default SMS application, + * this intent is broadcast to the default SMS application so it can + * re-sync or update the change. The uri that was used to call the provider + * can be retrieved from the intent with getData(). The actual affected uris + * (which would depend on the selection specified) are not included. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_EXTERNAL_PROVIDER_CHANGE = + "android.provider.action.EXTERNAL_PROVIDER_CHANGE"; + + /** + * Read the PDUs out of an {@link #SMS_RECEIVED_ACTION} or a + * {@link #DATA_SMS_RECEIVED_ACTION} intent. + * + * @param intent the intent to read from + * @return an array of SmsMessages for the PDUs + */ + public static SmsMessage[] getMessagesFromIntent(Intent intent) { + Object[] messages; + try { + messages = (Object[]) intent.getSerializableExtra("pdus"); + } + catch (ClassCastException e) { + Rlog.e(TAG, "getMessagesFromIntent: " + e); + return null; + } + + if (messages == null) { + Rlog.e(TAG, "pdus does not exist in the intent"); + return null; + } + + String format = intent.getStringExtra("format"); + int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + SubscriptionManager.getDefaultSmsSubscriptionId()); + + Rlog.v(TAG, " getMessagesFromIntent sub_id : " + subId); + + int pduCount = messages.length; + SmsMessage[] msgs = new SmsMessage[pduCount]; + + for (int i = 0; i < pduCount; i++) { + byte[] pdu = (byte[]) messages[i]; + msgs[i] = SmsMessage.createFromPdu(pdu, format); + if (msgs[i] != null) msgs[i].setSubId(subId); + } + return msgs; + } + } + } + + /** + * Base columns for tables that contain MMSs. + */ + public interface BaseMmsColumns extends BaseColumns { + + /** Message box: all messages. */ + public static final int MESSAGE_BOX_ALL = 0; + /** Message box: inbox. */ + public static final int MESSAGE_BOX_INBOX = 1; + /** Message box: sent messages. */ + public static final int MESSAGE_BOX_SENT = 2; + /** Message box: drafts. */ + public static final int MESSAGE_BOX_DRAFTS = 3; + /** Message box: outbox. */ + public static final int MESSAGE_BOX_OUTBOX = 4; + /** Message box: failed. */ + public static final int MESSAGE_BOX_FAILED = 5; + + /** + * The thread ID of the message. + *Type: INTEGER (long)
+ */ + public static final String THREAD_ID = "thread_id"; + + /** + * The date the message was received. + *Type: INTEGER (long)
+ */ + public static final String DATE = "date"; + + /** + * The date the message was sent. + *Type: INTEGER (long)
+ */ + public static final String DATE_SENT = "date_sent"; + + /** + * The box which the message belongs to, e.g. {@link #MESSAGE_BOX_INBOX}. + *Type: INTEGER
+ */ + public static final String MESSAGE_BOX = "msg_box"; + + /** + * Has the message been read? + *Type: INTEGER (boolean)
+ */ + public static final String READ = "read"; + + /** + * Has the message been seen by the user? The "seen" flag determines + * whether we need to show a new message notification. + *Type: INTEGER (boolean)
+ */ + public static final String SEEN = "seen"; + + /** + * Does the message have only a text part (can also have a subject) with + * no picture, slideshow, sound, etc. parts? + *Type: INTEGER (boolean)
+ */ + public static final String TEXT_ONLY = "text_only"; + + /** + * The {@code Message-ID} of the message. + *Type: TEXT
+ */ + public static final String MESSAGE_ID = "m_id"; + + /** + * The subject of the message, if present. + *Type: TEXT
+ */ + public static final String SUBJECT = "sub"; + + /** + * The character set of the subject, if present. + *Type: INTEGER
+ */ + public static final String SUBJECT_CHARSET = "sub_cs"; + + /** + * The {@code Content-Type} of the message. + *Type: TEXT
+ */ + public static final String CONTENT_TYPE = "ct_t"; + + /** + * The {@code Content-Location} of the message. + *Type: TEXT
+ */ + public static final String CONTENT_LOCATION = "ct_l"; + + /** + * The expiry time of the message. + *Type: INTEGER (long)
+ */ + public static final String EXPIRY = "exp"; + + /** + * The class of the message. + *Type: TEXT
+ */ + public static final String MESSAGE_CLASS = "m_cls"; + + /** + * The type of the message defined by MMS spec. + *Type: INTEGER
+ */ + public static final String MESSAGE_TYPE = "m_type"; + + /** + * The version of the specification that this message conforms to. + *Type: INTEGER
+ */ + public static final String MMS_VERSION = "v"; + + /** + * The size of the message. + *Type: INTEGER
+ */ + public static final String MESSAGE_SIZE = "m_size"; + + /** + * The priority of the message. + *Type: INTEGER
+ */ + public static final String PRIORITY = "pri"; + + /** + * The {@code read-report} of the message. + *Type: INTEGER (boolean)
+ */ + public static final String READ_REPORT = "rr"; + + /** + * Is read report allowed? + *Type: INTEGER (boolean)
+ */ + public static final String REPORT_ALLOWED = "rpt_a"; + + /** + * The {@code response-status} of the message. + *Type: INTEGER
+ */ + public static final String RESPONSE_STATUS = "resp_st"; + + /** + * The {@code status} of the message. + *Type: INTEGER
+ */ + public static final String STATUS = "st"; + + /** + * The {@code transaction-id} of the message. + *Type: TEXT
+ */ + public static final String TRANSACTION_ID = "tr_id"; + + /** + * The {@code retrieve-status} of the message. + *Type: INTEGER
+ */ + public static final String RETRIEVE_STATUS = "retr_st"; + + /** + * The {@code retrieve-text} of the message. + *Type: TEXT
+ */ + public static final String RETRIEVE_TEXT = "retr_txt"; + + /** + * The character set of the retrieve-text. + *Type: INTEGER
+ */ + public static final String RETRIEVE_TEXT_CHARSET = "retr_txt_cs"; + + /** + * The {@code read-status} of the message. + *Type: INTEGER
+ */ + public static final String READ_STATUS = "read_status"; + + /** + * The {@code content-class} of the message. + *Type: INTEGER
+ */ + public static final String CONTENT_CLASS = "ct_cls"; + + /** + * The {@code delivery-report} of the message. + *Type: INTEGER
+ */ + public static final String DELIVERY_REPORT = "d_rpt"; + + /** + * The {@code delivery-time-token} of the message. + *Type: INTEGER
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String DELIVERY_TIME_TOKEN = "d_tm_tok"; + + /** + * The {@code delivery-time} of the message. + *Type: INTEGER
+ */ + public static final String DELIVERY_TIME = "d_tm"; + + /** + * The {@code response-text} of the message. + *Type: TEXT
+ */ + public static final String RESPONSE_TEXT = "resp_txt"; + + /** + * The {@code sender-visibility} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String SENDER_VISIBILITY = "s_vis"; + + /** + * The {@code reply-charging} of the message. + *Type: INTEGER
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String REPLY_CHARGING = "r_chg"; + + /** + * The {@code reply-charging-deadline-token} of the message. + *Type: INTEGER
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String REPLY_CHARGING_DEADLINE_TOKEN = "r_chg_dl_tok"; + + /** + * The {@code reply-charging-deadline} of the message. + *Type: INTEGER
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String REPLY_CHARGING_DEADLINE = "r_chg_dl"; + + /** + * The {@code reply-charging-id} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String REPLY_CHARGING_ID = "r_chg_id"; + + /** + * The {@code reply-charging-size} of the message. + *Type: INTEGER
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String REPLY_CHARGING_SIZE = "r_chg_sz"; + + /** + * The {@code previously-sent-by} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String PREVIOUSLY_SENT_BY = "p_s_by"; + + /** + * The {@code previously-sent-date} of the message. + *Type: INTEGER
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String PREVIOUSLY_SENT_DATE = "p_s_d"; + + /** + * The {@code store} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String STORE = "store"; + + /** + * The {@code mm-state} of the message. + *Type: INTEGER
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String MM_STATE = "mm_st"; + + /** + * The {@code mm-flags-token} of the message. + *Type: INTEGER
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String MM_FLAGS_TOKEN = "mm_flg_tok"; + + /** + * The {@code mm-flags} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String MM_FLAGS = "mm_flg"; + + /** + * The {@code store-status} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String STORE_STATUS = "store_st"; + + /** + * The {@code store-status-text} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String STORE_STATUS_TEXT = "store_st_txt"; + + /** + * The {@code stored} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String STORED = "stored"; + + /** + * The {@code totals} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String TOTALS = "totals"; + + /** + * The {@code mbox-totals} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String MBOX_TOTALS = "mb_t"; + + /** + * The {@code mbox-totals-token} of the message. + *Type: INTEGER
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String MBOX_TOTALS_TOKEN = "mb_t_tok"; + + /** + * The {@code quotas} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String QUOTAS = "qt"; + + /** + * The {@code mbox-quotas} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String MBOX_QUOTAS = "mb_qt"; + + /** + * The {@code mbox-quotas-token} of the message. + *Type: INTEGER
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String MBOX_QUOTAS_TOKEN = "mb_qt_tok"; + + /** + * The {@code message-count} of the message. + *Type: INTEGER
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String MESSAGE_COUNT = "m_cnt"; + + /** + * The {@code start} of the message. + *Type: INTEGER
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String START = "start"; + + /** + * The {@code distribution-indicator} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String DISTRIBUTION_INDICATOR = "d_ind"; + + /** + * The {@code element-descriptor} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String ELEMENT_DESCRIPTOR = "e_des"; + + /** + * The {@code limit} of the message. + *Type: INTEGER
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String LIMIT = "limit"; + + /** + * The {@code recommended-retrieval-mode} of the message. + *Type: INTEGER
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String RECOMMENDED_RETRIEVAL_MODE = "r_r_mod"; + + /** + * The {@code recommended-retrieval-mode-text} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String RECOMMENDED_RETRIEVAL_MODE_TEXT = "r_r_mod_txt"; + + /** + * The {@code status-text} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String STATUS_TEXT = "st_txt"; + + /** + * The {@code applic-id} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String APPLIC_ID = "apl_id"; + + /** + * The {@code reply-applic-id} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String REPLY_APPLIC_ID = "r_apl_id"; + + /** + * The {@code aux-applic-id} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String AUX_APPLIC_ID = "aux_apl_id"; + + /** + * The {@code drm-content} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String DRM_CONTENT = "drm_c"; + + /** + * The {@code adaptation-allowed} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String ADAPTATION_ALLOWED = "adp_a"; + + /** + * The {@code replace-id} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String REPLACE_ID = "repl_id"; + + /** + * The {@code cancel-id} of the message. + *Type: TEXT
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String CANCEL_ID = "cl_id"; + + /** + * The {@code cancel-status} of the message. + *Type: INTEGER
+ * @deprecated this column is no longer supported. + * @hide + */ + @Deprecated + public static final String CANCEL_STATUS = "cl_st"; + + /** + * Is the message locked? + *Type: INTEGER (boolean)
+ */ + public static final String LOCKED = "locked"; + + /** + * The subscription to which the message belongs to. Its value will be + * < 0 if the sub id cannot be determined. + *Type: INTEGER (long)
+ */ + public static final String SUBSCRIPTION_ID = "sub_id"; + + /** + * The identity of the sender of a sent message. It is + * usually the package name of the app which sends the message. + *Note: + * This column is read-only. It is set by the provider and can not be changed by apps. + *
Type: TEXT
+ */ + public static final String CREATOR = "creator"; + } + + /** + * Columns for the "canonical_addresses" table used by MMS and SMS. + */ + public interface CanonicalAddressesColumns extends BaseColumns { + /** + * An address used in MMS or SMS. Email addresses are + * converted to lower case and are compared by string + * equality. Other addresses are compared using + * PHONE_NUMBERS_EQUAL. + *Type: TEXT
+ */ + public static final String ADDRESS = "address"; + } + + /** + * Columns for the "threads" table used by MMS and SMS. + */ + public interface ThreadsColumns extends BaseColumns { + + /** + * The date at which the thread was created. + *Type: INTEGER (long)
+ */ + public static final String DATE = "date"; + + /** + * A string encoding of the recipient IDs of the recipients of + * the message, in numerical order and separated by spaces. + *Type: TEXT
+ */ + public static final String RECIPIENT_IDS = "recipient_ids"; + + /** + * The message count of the thread. + *Type: INTEGER
+ */ + public static final String MESSAGE_COUNT = "message_count"; + + /** + * Indicates whether all messages of the thread have been read. + *Type: INTEGER
+ */ + public static final String READ = "read"; + + /** + * The snippet of the latest message in the thread. + *Type: TEXT
+ */ + public static final String SNIPPET = "snippet"; + + /** + * The charset of the snippet. + *Type: INTEGER
+ */ + public static final String SNIPPET_CHARSET = "snippet_cs"; + + /** + * Type of the thread, either {@link Threads#COMMON_THREAD} or + * {@link Threads#BROADCAST_THREAD}. + *Type: INTEGER
+ */ + public static final String TYPE = "type"; + + /** + * Indicates whether there is a transmission error in the thread. + *Type: INTEGER
+ */ + public static final String ERROR = "error"; + + /** + * Indicates whether this thread contains any attachments. + *Type: INTEGER
+ */ + public static final String HAS_ATTACHMENT = "has_attachment"; + + /** + * If the thread is archived + *Type: INTEGER (boolean)
+ */ + public static final String ARCHIVED = "archived"; + } + + /** + * Helper functions for the "threads" table used by MMS and SMS. + */ + public static final class Threads implements ThreadsColumns { + + private static final String[] ID_PROJECTION = { BaseColumns._ID }; + + /** + * Private {@code content://} style URL for this table. Used by + * {@link #getOrCreateThreadId(android.content.Context, java.util.Set)}. + */ + private static final Uri THREAD_ID_CONTENT_URI = Uri.parse( + "content://mms-sms/threadID"); + + /** + * The {@code content://} style URL for this table, by conversation. + */ + public static final Uri CONTENT_URI = Uri.withAppendedPath( + MmsSms.CONTENT_URI, "conversations"); + + /** + * The {@code content://} style URL for this table, for obsolete threads. + */ + public static final Uri OBSOLETE_THREADS_URI = Uri.withAppendedPath( + CONTENT_URI, "obsolete"); + + /** Thread type: common thread. */ + public static final int COMMON_THREAD = 0; + + /** Thread type: broadcast thread. */ + public static final int BROADCAST_THREAD = 1; + + /** + * Not instantiable. + * @hide + */ + private Threads() { + } + + /** + * This is a single-recipient version of {@code getOrCreateThreadId}. + * It's convenient for use with SMS messages. + * @param context the context object to use. + * @param recipient the recipient to send to. + */ + public static long getOrCreateThreadId(Context context, String recipient) { + SetFind the thread ID of the same set of recipients (in any order, + * without any additions). If one is found, return it. Otherwise, + * return a unique thread ID.
+ */ + public static long getOrCreateThreadId( + Context context, SetType: INTEGER (long)
+ */ + public static final String MSG_ID = "msg_id"; + + /** + * The ID of contact entry in Phone Book. + *Type: INTEGER (long)
+ */ + public static final String CONTACT_ID = "contact_id"; + + /** + * The address text. + *Type: TEXT
+ */ + public static final String ADDRESS = "address"; + + /** + * Type of address: must be one of {@code PduHeaders.BCC}, + * {@code PduHeaders.CC}, {@code PduHeaders.FROM}, {@code PduHeaders.TO}. + *Type: INTEGER
+ */ + public static final String TYPE = "type"; + + /** + * Character set of this entry (MMS charset value). + *Type: INTEGER
+ */ + public static final String CHARSET = "charset"; + } + + /** + * Contains message parts. + */ + public static final class Part implements BaseColumns { + + /** + * Not instantiable. + * @hide + */ + private Part() { + } + + /** + * The identifier of the message which this part belongs to. + *Type: INTEGER
+ */ + public static final String MSG_ID = "mid"; + + /** + * The order of the part. + *Type: INTEGER
+ */ + public static final String SEQ = "seq"; + + /** + * The content type of the part. + *Type: TEXT
+ */ + public static final String CONTENT_TYPE = "ct"; + + /** + * The name of the part. + *Type: TEXT
+ */ + public static final String NAME = "name"; + + /** + * The charset of the part. + *Type: TEXT
+ */ + public static final String CHARSET = "chset"; + + /** + * The file name of the part. + *Type: TEXT
+ */ + public static final String FILENAME = "fn"; + + /** + * The content disposition of the part. + *Type: TEXT
+ */ + public static final String CONTENT_DISPOSITION = "cd"; + + /** + * The content ID of the part. + *Type: INTEGER
+ */ + public static final String CONTENT_ID = "cid"; + + /** + * The content location of the part. + *Type: INTEGER
+ */ + public static final String CONTENT_LOCATION = "cl"; + + /** + * The start of content-type of the message. + *Type: INTEGER
+ */ + public static final String CT_START = "ctt_s"; + + /** + * The type of content-type of the message. + *Type: TEXT
+ */ + public static final String CT_TYPE = "ctt_t"; + + /** + * The location (on filesystem) of the binary data of the part. + *Type: INTEGER
+ */ + public static final String _DATA = "_data"; + + /** + * The message text. + *Type: TEXT
+ */ + public static final String TEXT = "text"; + } + + /** + * Message send rate table. + */ + public static final class Rate { + + /** + * Not instantiable. + * @hide + */ + private Rate() { + } + + /** + * The {@code content://} style URL for this table. + */ + public static final Uri CONTENT_URI = Uri.withAppendedPath( + Mms.CONTENT_URI, "rate"); + + /** + * When a message was successfully sent. + *Type: INTEGER (long)
+ */ + public static final String SENT_TIME = "sent_time"; + } + + /** + * Intents class. + */ + public static final class Intents { + + /** + * Not instantiable. + * @hide + */ + private Intents() { + } + + /** + * Indicates that the contents of specified URIs were changed. + * The application which is showing or caching these contents + * should be updated. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String CONTENT_CHANGED_ACTION + = "android.intent.action.CONTENT_CHANGED"; + + /** + * An extra field which stores the URI of deleted contents. + */ + public static final String DELETED_CONTENTS = "deleted_contents"; + } + } + + /** + * Contains all MMS and SMS messages. + */ + public static final class MmsSms implements BaseColumns { + + /** + * Not instantiable. + * @hide + */ + private MmsSms() { + } + + /** + * The column to distinguish SMS and MMS messages in query results. + */ + public static final String TYPE_DISCRIMINATOR_COLUMN = + "transport_type"; + + /** + * The {@code content://} style URL for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://mms-sms/"); + + /** + * The {@code content://} style URL for this table, by conversation. + */ + public static final Uri CONTENT_CONVERSATIONS_URI = Uri.parse( + "content://mms-sms/conversations"); + + /** + * The {@code content://} style URL for this table, by phone number. + */ + public static final Uri CONTENT_FILTER_BYPHONE_URI = Uri.parse( + "content://mms-sms/messages/byphone"); + + /** + * The {@code content://} style URL for undelivered messages in this table. + */ + public static final Uri CONTENT_UNDELIVERED_URI = Uri.parse( + "content://mms-sms/undelivered"); + + /** + * The {@code content://} style URL for draft messages in this table. + */ + public static final Uri CONTENT_DRAFT_URI = Uri.parse( + "content://mms-sms/draft"); + + /** + * The {@code content://} style URL for locked messages in this table. + */ + public static final Uri CONTENT_LOCKED_URI = Uri.parse( + "content://mms-sms/locked"); + + /** + * Pass in a query parameter called "pattern" which is the text to search for. + * The sort order is fixed to be: {@code thread_id ASC, date DESC}. + */ + public static final Uri SEARCH_URI = Uri.parse( + "content://mms-sms/search"); + + // Constants for message protocol types. + + /** SMS protocol type. */ + public static final int SMS_PROTO = 0; + + /** MMS protocol type. */ + public static final int MMS_PROTO = 1; + + // Constants for error types of pending messages. + + /** Error type: no error. */ + public static final int NO_ERROR = 0; + + /** Error type: generic transient error. */ + public static final int ERR_TYPE_GENERIC = 1; + + /** Error type: SMS protocol transient error. */ + public static final int ERR_TYPE_SMS_PROTO_TRANSIENT = 2; + + /** Error type: MMS protocol transient error. */ + public static final int ERR_TYPE_MMS_PROTO_TRANSIENT = 3; + + /** Error type: transport failure. */ + public static final int ERR_TYPE_TRANSPORT_FAILURE = 4; + + /** Error type: permanent error (along with all higher error values). */ + public static final int ERR_TYPE_GENERIC_PERMANENT = 10; + + /** Error type: SMS protocol permanent error. */ + public static final int ERR_TYPE_SMS_PROTO_PERMANENT = 11; + + /** Error type: MMS protocol permanent error. */ + public static final int ERR_TYPE_MMS_PROTO_PERMANENT = 12; + + /** + * Contains pending messages info. + */ + public static final class PendingMessages implements BaseColumns { + + /** + * Not instantiable. + * @hide + */ + private PendingMessages() { + } + + public static final Uri CONTENT_URI = Uri.withAppendedPath( + MmsSms.CONTENT_URI, "pending"); + + /** + * The type of transport protocol (MMS or SMS). + *Type: INTEGER
+ */ + public static final String PROTO_TYPE = "proto_type"; + + /** + * The ID of the message to be sent or downloaded. + *Type: INTEGER (long)
+ */ + public static final String MSG_ID = "msg_id"; + + /** + * The type of the message to be sent or downloaded. + * This field is only valid for MM. For SM, its value is always set to 0. + *Type: INTEGER
+ */ + public static final String MSG_TYPE = "msg_type"; + + /** + * The type of the error code. + *Type: INTEGER
+ */ + public static final String ERROR_TYPE = "err_type"; + + /** + * The error code of sending/retrieving process. + *Type: INTEGER
+ */ + public static final String ERROR_CODE = "err_code"; + + /** + * How many times we tried to send or download the message. + *Type: INTEGER
+ */ + public static final String RETRY_INDEX = "retry_index"; + + /** + * The time to do next retry. + *Type: INTEGER (long)
+ */ + public static final String DUE_TIME = "due_time"; + + /** + * The time we last tried to send or download the message. + *Type: INTEGER (long)
+ */ + public static final String LAST_TRY = "last_try"; + + /** + * The subscription to which the message belongs to. Its value will be + * < 0 if the sub id cannot be determined. + *Type: INTEGER (long)
+ */ + public static final String SUBSCRIPTION_ID = "pending_sub_id"; + } + + /** + * Words table used by provider for full-text searches. + * @hide + */ + public static final class WordsTable { + + /** + * Not instantiable. + * @hide + */ + private WordsTable() {} + + /** + * Primary key. + *Type: INTEGER (long)
+ */ + public static final String ID = "_id"; + + /** + * Source row ID. + *Type: INTEGER (long)
+ */ + public static final String SOURCE_ROW_ID = "source_id"; + + /** + * Table ID (either 1 or 2). + *Type: INTEGER
+ */ + public static final String TABLE_ID = "table_to_use"; + + /** + * The words to index. + *Type: TEXT
+ */ + public static final String INDEXED_TEXT = "index_text"; + } + } + + /** + * Carriers class contains information about APNs, including MMSC information. + */ + public static final class Carriers implements BaseColumns { + + /** + * Not instantiable. + * @hide + */ + private Carriers() {} + + /** + * The {@code content://} style URL for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://telephony/carriers"); + + /** + * The default sort order for this table. + */ + public static final String DEFAULT_SORT_ORDER = "name ASC"; + + /** + * Entry name. + *Type: TEXT
+ */ + public static final String NAME = "name"; + + /** + * APN name. + *Type: TEXT
+ */ + public static final String APN = "apn"; + + /** + * Proxy address. + *Type: TEXT
+ */ + public static final String PROXY = "proxy"; + + /** + * Proxy port. + *Type: TEXT
+ */ + public static final String PORT = "port"; + + /** + * MMS proxy address. + *Type: TEXT
+ */ + public static final String MMSPROXY = "mmsproxy"; + + /** + * MMS proxy port. + *Type: TEXT
+ */ + public static final String MMSPORT = "mmsport"; + + /** + * Server address. + *Type: TEXT
+ */ + public static final String SERVER = "server"; + + /** + * APN username. + *Type: TEXT
+ */ + public static final String USER = "user"; + + /** + * APN password. + *Type: TEXT
+ */ + public static final String PASSWORD = "password"; + + /** + * MMSC URL. + *Type: TEXT
+ */ + public static final String MMSC = "mmsc"; + + /** + * Mobile Country Code (MCC). + *Type: TEXT
+ */ + public static final String MCC = "mcc"; + + /** + * Mobile Network Code (MNC). + *Type: TEXT
+ */ + public static final String MNC = "mnc"; + + /** + * Numeric operator ID (as String). Usually {@code MCC + MNC}. + *Type: TEXT
+ */ + public static final String NUMERIC = "numeric"; + + /** + * Authentication type. + *Type: INTEGER
+ */ + public static final String AUTH_TYPE = "authtype"; + + /** + * Comma-delimited list of APN types. + *Type: TEXT
+ */ + public static final String TYPE = "type"; + + /** + * The protocol to use to connect to this APN. + * + * One of the {@code PDP_type} values in TS 27.007 section 10.1.1. + * For example: {@code IP}, {@code IPV6}, {@code IPV4V6}, or {@code PPP}. + *Type: TEXT
+ */ + public static final String PROTOCOL = "protocol"; + + /** + * The protocol to use to connect to this APN when roaming. + * The syntax is the same as protocol. + *Type: TEXT
+ */ + public static final String ROAMING_PROTOCOL = "roaming_protocol"; + + /** + * Is this the current APN? + *Type: INTEGER (boolean)
+ */ + public static final String CURRENT = "current"; + + /** + * Is this APN enabled? + *Type: INTEGER (boolean)
+ */ + public static final String CARRIER_ENABLED = "carrier_enabled"; + + /** + * Radio Access Technology info. + * To check what values are allowed, refer to {@link android.telephony.ServiceState}. + * This should be spread to other technologies, + * but is currently only used for LTE (14) and eHRPD (13). + *Type: INTEGER
+ */ + public static final String BEARER = "bearer"; + + /** + * Radio Access Technology bitmask. + * To check what values can be contained, refer to {@link android.telephony.ServiceState}. + * 0 indicates all techs otherwise first bit refers to RAT/bearer 1, second bit refers to + * RAT/bearer 2 and so on. + * Bitmask for a radio tech R is (1 << (R - 1)) + *Type: INTEGER
+ * @hide + */ + public static final String BEARER_BITMASK = "bearer_bitmask"; + + /** + * MVNO type: + * {@code SPN (Service Provider Name), IMSI, GID (Group Identifier Level 1)}. + *Type: TEXT
+ */ + public static final String MVNO_TYPE = "mvno_type"; + + /** + * MVNO data. + * Use the following examples. + *Type: TEXT
+ */ + public static final String MVNO_MATCH_DATA = "mvno_match_data"; + + /** + * The subscription to which the APN belongs to + *Type: INTEGER (long)
+ */ + public static final String SUBSCRIPTION_ID = "sub_id"; + + /** + * The profile_id to which the APN saved in modem + *Type: INTEGER
+ *@hide + */ + public static final String PROFILE_ID = "profile_id"; + + /** + * Is the apn setting to be set in modem + *Type: INTEGER (boolean)
+ *@hide + */ + public static final String MODEM_COGNITIVE = "modem_cognitive"; + + /** + * The max connections of this apn + *Type: INTEGER
+ *@hide + */ + public static final String MAX_CONNS = "max_conns"; + + /** + * The wait time for retry of the apn + *Type: INTEGER
+ *@hide + */ + public static final String WAIT_TIME = "wait_time"; + + /** + * The time to limit max connection for the apn + *Type: INTEGER
+ *@hide + */ + public static final String MAX_CONNS_TIME = "max_conns_time"; + + /** + * The MTU size of the mobile interface to which the APN connected + *Type: INTEGER
+ * @hide + */ + public static final String MTU = "mtu"; + + /** + * Is this APN added/edited/deleted by a user or carrier? + *Type: INTEGER
+ * @hide + */ + public static final String EDITED = "edited"; + + /** + * Is this APN visible to the user? + *Type: INTEGER (boolean)
+ * @hide + */ + public static final String USER_VISIBLE = "user_visible"; + + /** + * Following are possible values for the EDITED field + * @hide + */ + public static final int UNEDITED = 0; + /** + * @hide + */ + public static final int USER_EDITED = 1; + /** + * @hide + */ + public static final int USER_DELETED = 2; + /** + * DELETED_BUT_PRESENT is an intermediate value used to indicate that an entry deleted + * by the user is still present in the new APN database and therefore must remain tagged + * as user deleted rather than completely removed from the database + * @hide + */ + public static final int USER_DELETED_BUT_PRESENT_IN_XML = 3; + /** + * @hide + */ + public static final int CARRIER_EDITED = 4; + /** + * CARRIER_DELETED values are currently not used as there is no usecase. If they are used, + * delete() will have to change accordingly. Currently it is hardcoded to USER_DELETED. + * @hide + */ + public static final int CARRIER_DELETED = 5; + /** + * @hide + */ + public static final int CARRIER_DELETED_BUT_PRESENT_IN_XML = 6; + } + + /** + * Contains received SMS cell broadcast messages. + * @hide + */ + public static final class CellBroadcasts implements BaseColumns { + + /** + * Not instantiable. + * @hide + */ + private CellBroadcasts() {} + + /** + * The {@code content://} URI for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://cellbroadcasts"); + + /** + * Message geographical scope. + *Type: INTEGER
+ */ + public static final String GEOGRAPHICAL_SCOPE = "geo_scope"; + + /** + * Message serial number. + *Type: INTEGER
+ */ + public static final String SERIAL_NUMBER = "serial_number"; + + /** + * PLMN of broadcast sender. {@code SERIAL_NUMBER + PLMN + LAC + CID} uniquely identifies + * a broadcast for duplicate detection purposes. + *Type: TEXT
+ */ + public static final String PLMN = "plmn"; + + /** + * Location Area (GSM) or Service Area (UMTS) of broadcast sender. Unused for CDMA. + * Only included if Geographical Scope of message is not PLMN wide (01). + *Type: INTEGER
+ */ + public static final String LAC = "lac"; + + /** + * Cell ID of message sender (GSM/UMTS). Unused for CDMA. Only included when the + * Geographical Scope of message is cell wide (00 or 11). + *Type: INTEGER
+ */ + public static final String CID = "cid"; + + /** + * Message code. OBSOLETE: merged into SERIAL_NUMBER. + *Type: INTEGER
+ */ + public static final String V1_MESSAGE_CODE = "message_code"; + + /** + * Message identifier. OBSOLETE: renamed to SERVICE_CATEGORY. + *Type: INTEGER
+ */ + public static final String V1_MESSAGE_IDENTIFIER = "message_id"; + + /** + * Service category (GSM/UMTS: message identifier; CDMA: service category). + *Type: INTEGER
+ */ + public static final String SERVICE_CATEGORY = "service_category"; + + /** + * Message language code. + *Type: TEXT
+ */ + public static final String LANGUAGE_CODE = "language"; + + /** + * Message body. + *Type: TEXT
+ */ + public static final String MESSAGE_BODY = "body"; + + /** + * Message delivery time. + *Type: INTEGER (long)
+ */ + public static final String DELIVERY_TIME = "date"; + + /** + * Has the message been viewed? + *Type: INTEGER (boolean)
+ */ + public static final String MESSAGE_READ = "read"; + + /** + * Message format (3GPP or 3GPP2). + *Type: INTEGER
+ */ + public static final String MESSAGE_FORMAT = "format"; + + /** + * Message priority (including emergency). + *Type: INTEGER
+ */ + public static final String MESSAGE_PRIORITY = "priority"; + + /** + * ETWS warning type (ETWS alerts only). + *Type: INTEGER
+ */ + public static final String ETWS_WARNING_TYPE = "etws_warning_type"; + + /** + * CMAS message class (CMAS alerts only). + *Type: INTEGER
+ */ + public static final String CMAS_MESSAGE_CLASS = "cmas_message_class"; + + /** + * CMAS category (CMAS alerts only). + *Type: INTEGER
+ */ + public static final String CMAS_CATEGORY = "cmas_category"; + + /** + * CMAS response type (CMAS alerts only). + *Type: INTEGER
+ */ + public static final String CMAS_RESPONSE_TYPE = "cmas_response_type"; + + /** + * CMAS severity (CMAS alerts only). + *Type: INTEGER
+ */ + public static final String CMAS_SEVERITY = "cmas_severity"; + + /** + * CMAS urgency (CMAS alerts only). + *Type: INTEGER
+ */ + public static final String CMAS_URGENCY = "cmas_urgency"; + + /** + * CMAS certainty (CMAS alerts only). + *Type: INTEGER
+ */ + public static final String CMAS_CERTAINTY = "cmas_certainty"; + + /** The default sort order for this table. */ + public static final String DEFAULT_SORT_ORDER = DELIVERY_TIME + " DESC"; + + /** + * Query columns for instantiating {@link android.telephony.CellBroadcastMessage} objects. + */ + public static final String[] QUERY_COLUMNS = { + _ID, + GEOGRAPHICAL_SCOPE, + PLMN, + LAC, + CID, + SERIAL_NUMBER, + SERVICE_CATEGORY, + LANGUAGE_CODE, + MESSAGE_BODY, + DELIVERY_TIME, + MESSAGE_READ, + MESSAGE_FORMAT, + MESSAGE_PRIORITY, + ETWS_WARNING_TYPE, + CMAS_MESSAGE_CLASS, + CMAS_CATEGORY, + CMAS_RESPONSE_TYPE, + CMAS_SEVERITY, + CMAS_URGENCY, + CMAS_CERTAINTY + }; + } +} diff --git a/telephony/java/com/android/internal/telephony/IccUtils.java b/telephony/java/com/android/internal/telephony/IccUtils.java new file mode 100644 index 0000000000000..67de87f2bf856 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/IccUtils.java @@ -0,0 +1,570 @@ +/* + * 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.uicc; + +import android.content.res.Resources; +import android.content.res.Resources.NotFoundException; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.telephony.Rlog; + +import com.android.internal.telephony.GsmAlphabet; + +import java.io.UnsupportedEncodingException; + +/** + * Various methods, useful for dealing with SIM data. + */ +public class IccUtils { + static final String LOG_TAG="IccUtils"; + + /** + * Many fields in GSM SIM's are stored as nibble-swizzled BCD + * + * Assumes left-justified field that may be padded right with 0xf + * values. + * + * Stops on invalid BCD value, returning string so far + */ + public static String + bcdToString(byte[] data, int offset, int length) { + StringBuilder ret = new StringBuilder(length*2); + + for (int i = offset ; i < offset + length ; i++) { + int v; + + v = data[i] & 0xf; + if (v > 9) break; + ret.append((char)('0' + v)); + + v = (data[i] >> 4) & 0xf; + // Some PLMNs have 'f' as high nibble, ignore it + if (v == 0xf) continue; + if (v > 9) break; + ret.append((char)('0' + v)); + } + + return ret.toString(); + } + + /** + * PLMN (MCC/MNC) is encoded as per 24.008 10.5.1.3 + * Returns a concatenated string of MCC+MNC, stripping + * a trailing character for a 2-digit MNC + */ + public static String bcdPlmnToString(byte[] data, int offset) { + if (offset + 3 > data.length) { + return null; + } + byte[] trans = new byte[3]; + trans[0] = (byte) ((data[0 + offset] << 4) | ((data[0 + offset] >> 4) & 0xF)); + trans[1] = (byte) ((data[1 + offset] << 4) | (data[2 + offset] & 0xF)); + trans[2] = (byte) ((data[2 + offset] & 0xF0) | ((data[1 + offset] >> 4) & 0xF)); + String ret = bytesToHexString(trans); + + // For a 2-digit MNC we trim the trailing 'f' + if (ret.endsWith("f")) { + ret = ret.substring(0, ret.length() - 1); + } + return ret; + } + + /** + * Some fields (like ICC ID) in GSM SIMs are stored as nibble-swizzled BCH + */ + public static String + bchToString(byte[] data, int offset, int length) { + StringBuilder ret = new StringBuilder(length*2); + + for (int i = offset ; i < offset + length ; i++) { + int v; + + v = data[i] & 0xf; + ret.append("0123456789abcdef".charAt(v)); + + v = (data[i] >> 4) & 0xf; + ret.append("0123456789abcdef".charAt(v)); + } + + return ret.toString(); + } + + /** + * Decode cdma byte into String. + */ + public static String + cdmaBcdToString(byte[] data, int offset, int length) { + StringBuilder ret = new StringBuilder(length); + + int count = 0; + for (int i = offset; count < length; i++) { + int v; + v = data[i] & 0xf; + if (v > 9) v = 0; + ret.append((char)('0' + v)); + + if (++count == length) break; + + v = (data[i] >> 4) & 0xf; + if (v > 9) v = 0; + ret.append((char)('0' + v)); + ++count; + } + return ret.toString(); + } + + /** + * Decodes a GSM-style BCD byte, returning an int ranging from 0-99. + * + * In GSM land, the least significant BCD digit is stored in the most + * significant nibble. + * + * Out-of-range digits are treated as 0 for the sake of the time stamp, + * because of this: + * + * TS 23.040 section 9.2.3.11 + * "if the MS receives a non-integer value in the SCTS, it shall + * assume the digit is set to 0 but shall store the entire field + * exactly as received" + */ + public static int + gsmBcdByteToInt(byte b) { + int ret = 0; + + // treat out-of-range BCD values as 0 + if ((b & 0xf0) <= 0x90) { + ret = (b >> 4) & 0xf; + } + + if ((b & 0x0f) <= 0x09) { + ret += (b & 0xf) * 10; + } + + return ret; + } + + /** + * Decodes a CDMA style BCD byte like {@link #gsmBcdByteToInt}, but + * opposite nibble format. The least significant BCD digit + * is in the least significant nibble and the most significant + * is in the most significant nibble. + */ + public static int + cdmaBcdByteToInt(byte b) { + int ret = 0; + + // treat out-of-range BCD values as 0 + if ((b & 0xf0) <= 0x90) { + ret = ((b >> 4) & 0xf) * 10; + } + + if ((b & 0x0f) <= 0x09) { + ret += (b & 0xf); + } + + return ret; + } + + /** + * Decodes a string field that's formatted like the EF[ADN] alpha + * identifier + * + * From TS 51.011 10.5.1: + * Coding: + * this alpha tagging shall use either + * - the SMS default 7 bit coded alphabet as defined in + * TS 23.038 [12] with bit 8 set to 0. The alpha identifier + * shall be left justified. Unused bytes shall be set to 'FF'; or + * - one of the UCS2 coded options as defined in annex B. + * + * Annex B from TS 11.11 V8.13.0: + * 1) If the first octet in the alpha string is '80', then the + * remaining octets are 16 bit UCS2 characters ... + * 2) if the first octet in the alpha string is '81', then the + * second octet contains a value indicating the number of + * characters in the string, and the third octet contains an + * 8 bit number which defines bits 15 to 8 of a 16 bit + * base pointer, where bit 16 is set to zero and bits 7 to 1 + * are also set to zero. These sixteen bits constitute a + * base pointer to a "half page" in the UCS2 code space, to be + * used with some or all of the remaining octets in the string. + * The fourth and subsequent octets contain codings as follows: + * If bit 8 of the octet is set to zero, the remaining 7 bits + * of the octet contain a GSM Default Alphabet character, + * whereas if bit 8 of the octet is set to one, then the + * remaining seven bits are an offset value added to the + * 16 bit base pointer defined earlier... + * 3) If the first octet of the alpha string is set to '82', then + * the second octet contains a value indicating the number of + * characters in the string, and the third and fourth octets + * contain a 16 bit number which defines the complete 16 bit + * base pointer to a "half page" in the UCS2 code space... + */ + public static String + adnStringFieldToString(byte[] data, int offset, int length) { + if (length == 0) { + return ""; + } + if (length >= 1) { + if (data[offset] == (byte) 0x80) { + int ucslen = (length - 1) / 2; + String ret = null; + + try { + ret = new String(data, offset + 1, ucslen * 2, "utf-16be"); + } catch (UnsupportedEncodingException ex) { + Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", + ex); + } + + if (ret != null) { + // trim off trailing FFFF characters + + ucslen = ret.length(); + while (ucslen > 0 && ret.charAt(ucslen - 1) == '\uFFFF') + ucslen--; + + return ret.substring(0, ucslen); + } + } + } + + boolean isucs2 = false; + char base = '\0'; + int len = 0; + + if (length >= 3 && data[offset] == (byte) 0x81) { + len = data[offset + 1] & 0xFF; + if (len > length - 3) + len = length - 3; + + base = (char) ((data[offset + 2] & 0xFF) << 7); + offset += 3; + isucs2 = true; + } else if (length >= 4 && data[offset] == (byte) 0x82) { + len = data[offset + 1] & 0xFF; + if (len > length - 4) + len = length - 4; + + base = (char) (((data[offset + 2] & 0xFF) << 8) | + (data[offset + 3] & 0xFF)); + offset += 4; + isucs2 = true; + } + + if (isucs2) { + StringBuilder ret = new StringBuilder(); + + while (len > 0) { + // UCS2 subset case + + if (data[offset] < 0) { + ret.append((char) (base + (data[offset] & 0x7F))); + offset++; + len--; + } + + // GSM character set case + + int count = 0; + while (count < len && data[offset + count] >= 0) + count++; + + ret.append(GsmAlphabet.gsm8BitUnpackedToString(data, + offset, count)); + + offset += count; + len -= count; + } + + return ret.toString(); + } + + Resources resource = Resources.getSystem(); + String defaultCharset = ""; + try { + defaultCharset = + resource.getString(com.android.internal.R.string.gsm_alphabet_default_charset); + } catch (NotFoundException e) { + // Ignore Exception and defaultCharset is set to a empty string. + } + return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length, defaultCharset.trim()); + } + + static int + hexCharToInt(char c) { + if (c >= '0' && c <= '9') return (c - '0'); + if (c >= 'A' && c <= 'F') return (c - 'A' + 10); + if (c >= 'a' && c <= 'f') return (c - 'a' + 10); + + throw new RuntimeException ("invalid hex char '" + c + "'"); + } + + /** + * Converts a hex String to a byte array. + * + * @param s A string of hexadecimal characters, must be an even number of + * chars long + * + * @return byte array representation + * + * @throws RuntimeException on invalid format + */ + public static byte[] + hexStringToBytes(String s) { + byte[] ret; + + if (s == null) return null; + + int sz = s.length(); + + ret = new byte[sz/2]; + + for (int i=0 ; i+ * Caller must pass in the correct user context if calling from a singleton service. + * @param context context from the calling app + * @param updateIfNeeded update the default app if there is no valid default app configured. + * @return component name of the app and class to direct SEND_TO (smsto) intent to + */ + public static ComponentName getDefaultSendToApplication(Context context, + boolean updateIfNeeded) { + int userId = getIncomingUserId(context); + final long token = Binder.clearCallingIdentity(); + try { + ComponentName component = null; + SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded, + userId); + if (smsApplicationData != null) { + component = new ComponentName(smsApplicationData.mPackageName, + smsApplicationData.mSendToClass); + } + return component; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + /** + * Gets the default application that handles external changes to the SmsProvider and + * MmsProvider. + * @param context context from the calling app + * @param updateIfNeeded update the default app if there is no valid default app configured. + * @return component name of the app and class to deliver change intents to + */ + public static ComponentName getDefaultExternalTelephonyProviderChangedApplication( + Context context, boolean updateIfNeeded) { + int userId = getIncomingUserId(context); + final long token = Binder.clearCallingIdentity(); + try { + ComponentName component = null; + SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded, + userId); + if (smsApplicationData != null + && smsApplicationData.mProviderChangedReceiverClass != null) { + component = new ComponentName(smsApplicationData.mPackageName, + smsApplicationData.mProviderChangedReceiverClass); + } + return component; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + /** + * Gets the default application that handles sim full event. + * @param context context from the calling app + * @param updateIfNeeded update the default app if there is no valid default app configured. + * @return component name of the app and class to deliver change intents to + */ + public static ComponentName getDefaultSimFullApplication( + Context context, boolean updateIfNeeded) { + int userId = getIncomingUserId(context); + final long token = Binder.clearCallingIdentity(); + try { + ComponentName component = null; + SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded, + userId); + if (smsApplicationData != null + && smsApplicationData.mSimFullReceiverClass != null) { + component = new ComponentName(smsApplicationData.mPackageName, + smsApplicationData.mSimFullReceiverClass); + } + return component; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + /** + * Returns whether need to write the SMS message to SMS database for this package. + *
+ * Caller must pass in the correct user context if calling from a singleton service.
+ */
+ public static boolean shouldWriteMessageForPackage(String packageName, Context context) {
+ if (SmsManager.getDefault().getAutoPersisting()) {
+ return true;
+ }
+ return !isDefaultSmsApplication(context, packageName);
+ }
+
+ /**
+ * Check if a package is default sms app (or equivalent, like bluetooth)
+ *
+ * @param context context from the calling app
+ * @param packageName the name of the package to be checked
+ * @return true if the package is default sms app or bluetooth
+ */
+ public static boolean isDefaultSmsApplication(Context context, String packageName) {
+ if (packageName == null) {
+ return false;
+ }
+ final String defaultSmsPackage = getDefaultSmsApplicationPackageName(context);
+ if ((defaultSmsPackage != null && defaultSmsPackage.equals(packageName))
+ || BLUETOOTH_PACKAGE_NAME.equals(packageName)) {
+ return true;
+ }
+ return false;
+ }
+
+ private static String getDefaultSmsApplicationPackageName(Context context) {
+ final ComponentName component = getDefaultSmsApplication(context, false);
+ if (component != null) {
+ return component.getPackageName();
+ }
+ return null;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsCbCmasInfo.java b/telephony/java/com/android/internal/telephony/SmsCbCmasInfo.java
new file mode 100644
index 0000000000000..c912924424b1c
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsCbCmasInfo.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2012 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 android.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Contains CMAS warning notification Type 1 elements for a {@link SmsCbMessage}.
+ * Supported values for each element are defined in TIA-1149-0-1 (CMAS over CDMA) and
+ * 3GPP TS 23.041 (for GSM/UMTS).
+ *
+ * {@hide}
+ */
+public class SmsCbCmasInfo implements Parcelable {
+
+ // CMAS message class (in GSM/UMTS message identifier or CDMA service category).
+
+ /** Presidential-level alert (Korean Public Alert System Class 0 message). */
+ public static final int CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT = 0x00;
+
+ /** Extreme threat to life and property (Korean Public Alert System Class 1 message). */
+ public static final int CMAS_CLASS_EXTREME_THREAT = 0x01;
+
+ /** Severe threat to life and property (Korean Public Alert System Class 1 message). */
+ public static final int CMAS_CLASS_SEVERE_THREAT = 0x02;
+
+ /** Child abduction emergency (AMBER Alert). */
+ public static final int CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY = 0x03;
+
+ /** CMAS test message. */
+ public static final int CMAS_CLASS_REQUIRED_MONTHLY_TEST = 0x04;
+
+ /** CMAS exercise. */
+ public static final int CMAS_CLASS_CMAS_EXERCISE = 0x05;
+
+ /** CMAS category for operator defined use. */
+ public static final int CMAS_CLASS_OPERATOR_DEFINED_USE = 0x06;
+
+ /** CMAS category for warning types that are reserved for future extension. */
+ public static final int CMAS_CLASS_UNKNOWN = -1;
+
+ // CMAS alert category (in CDMA type 1 elements record).
+
+ /** CMAS alert category: Geophysical including landslide. */
+ public static final int CMAS_CATEGORY_GEO = 0x00;
+
+ /** CMAS alert category: Meteorological including flood. */
+ public static final int CMAS_CATEGORY_MET = 0x01;
+
+ /** CMAS alert category: General emergency and public safety. */
+ public static final int CMAS_CATEGORY_SAFETY = 0x02;
+
+ /** CMAS alert category: Law enforcement, military, homeland/local/private security. */
+ public static final int CMAS_CATEGORY_SECURITY = 0x03;
+
+ /** CMAS alert category: Rescue and recovery. */
+ public static final int CMAS_CATEGORY_RESCUE = 0x04;
+
+ /** CMAS alert category: Fire suppression and rescue. */
+ public static final int CMAS_CATEGORY_FIRE = 0x05;
+
+ /** CMAS alert category: Medical and public health. */
+ public static final int CMAS_CATEGORY_HEALTH = 0x06;
+
+ /** CMAS alert category: Pollution and other environmental. */
+ public static final int CMAS_CATEGORY_ENV = 0x07;
+
+ /** CMAS alert category: Public and private transportation. */
+ public static final int CMAS_CATEGORY_TRANSPORT = 0x08;
+
+ /** CMAS alert category: Utility, telecom, other non-transport infrastructure. */
+ public static final int CMAS_CATEGORY_INFRA = 0x09;
+
+ /** CMAS alert category: Chem, bio, radiological, nuclear, high explosive threat or attack. */
+ public static final int CMAS_CATEGORY_CBRNE = 0x0a;
+
+ /** CMAS alert category: Other events. */
+ public static final int CMAS_CATEGORY_OTHER = 0x0b;
+
+ /**
+ * CMAS alert category is unknown. The category is only available for CDMA broadcasts
+ * containing a type 1 elements record, so GSM and UMTS broadcasts always return unknown.
+ */
+ public static final int CMAS_CATEGORY_UNKNOWN = -1;
+
+ // CMAS response type (in CDMA type 1 elements record).
+
+ /** CMAS response type: Take shelter in place. */
+ public static final int CMAS_RESPONSE_TYPE_SHELTER = 0x00;
+
+ /** CMAS response type: Evacuate (Relocate). */
+ public static final int CMAS_RESPONSE_TYPE_EVACUATE = 0x01;
+
+ /** CMAS response type: Make preparations. */
+ public static final int CMAS_RESPONSE_TYPE_PREPARE = 0x02;
+
+ /** CMAS response type: Execute a pre-planned activity. */
+ public static final int CMAS_RESPONSE_TYPE_EXECUTE = 0x03;
+
+ /** CMAS response type: Attend to information sources. */
+ public static final int CMAS_RESPONSE_TYPE_MONITOR = 0x04;
+
+ /** CMAS response type: Avoid hazard. */
+ public static final int CMAS_RESPONSE_TYPE_AVOID = 0x05;
+
+ /** CMAS response type: Evaluate the information in this message (not for public warnings). */
+ public static final int CMAS_RESPONSE_TYPE_ASSESS = 0x06;
+
+ /** CMAS response type: No action recommended. */
+ public static final int CMAS_RESPONSE_TYPE_NONE = 0x07;
+
+ /**
+ * CMAS response type is unknown. The response type is only available for CDMA broadcasts
+ * containing a type 1 elements record, so GSM and UMTS broadcasts always return unknown.
+ */
+ public static final int CMAS_RESPONSE_TYPE_UNKNOWN = -1;
+
+ // 4-bit CMAS severity (in GSM/UMTS message identifier or CDMA type 1 elements record).
+
+ /** CMAS severity type: Extraordinary threat to life or property. */
+ public static final int CMAS_SEVERITY_EXTREME = 0x0;
+
+ /** CMAS severity type: Significant threat to life or property. */
+ public static final int CMAS_SEVERITY_SEVERE = 0x1;
+
+ /**
+ * CMAS alert severity is unknown. The severity is available for CDMA warning alerts
+ * containing a type 1 elements record and for all GSM and UMTS alerts except for the
+ * Presidential-level alert class (Korean Public Alert System Class 0).
+ */
+ public static final int CMAS_SEVERITY_UNKNOWN = -1;
+
+ // CMAS urgency (in GSM/UMTS message identifier or CDMA type 1 elements record).
+
+ /** CMAS urgency type: Responsive action should be taken immediately. */
+ public static final int CMAS_URGENCY_IMMEDIATE = 0x0;
+
+ /** CMAS urgency type: Responsive action should be taken within the next hour. */
+ public static final int CMAS_URGENCY_EXPECTED = 0x1;
+
+ /**
+ * CMAS alert urgency is unknown. The urgency is available for CDMA warning alerts
+ * containing a type 1 elements record and for all GSM and UMTS alerts except for the
+ * Presidential-level alert class (Korean Public Alert System Class 0).
+ */
+ public static final int CMAS_URGENCY_UNKNOWN = -1;
+
+ // CMAS certainty (in GSM/UMTS message identifier or CDMA type 1 elements record).
+
+ /** CMAS certainty type: Determined to have occurred or to be ongoing. */
+ public static final int CMAS_CERTAINTY_OBSERVED = 0x0;
+
+ /** CMAS certainty type: Likely (probability > ~50%). */
+ public static final int CMAS_CERTAINTY_LIKELY = 0x1;
+
+ /**
+ * CMAS alert certainty is unknown. The certainty is available for CDMA warning alerts
+ * containing a type 1 elements record and for all GSM and UMTS alerts except for the
+ * Presidential-level alert class (Korean Public Alert System Class 0).
+ */
+ public static final int CMAS_CERTAINTY_UNKNOWN = -1;
+
+ /** CMAS message class. */
+ private final int mMessageClass;
+
+ /** CMAS category. */
+ private final int mCategory;
+
+ /** CMAS response type. */
+ private final int mResponseType;
+
+ /** CMAS severity. */
+ private final int mSeverity;
+
+ /** CMAS urgency. */
+ private final int mUrgency;
+
+ /** CMAS certainty. */
+ private final int mCertainty;
+
+ /** Create a new SmsCbCmasInfo object with the specified values. */
+ public SmsCbCmasInfo(int messageClass, int category, int responseType, int severity,
+ int urgency, int certainty) {
+ mMessageClass = messageClass;
+ mCategory = category;
+ mResponseType = responseType;
+ mSeverity = severity;
+ mUrgency = urgency;
+ mCertainty = certainty;
+ }
+
+ /** Create a new SmsCbCmasInfo object from a Parcel. */
+ SmsCbCmasInfo(Parcel in) {
+ mMessageClass = in.readInt();
+ mCategory = in.readInt();
+ mResponseType = in.readInt();
+ mSeverity = in.readInt();
+ mUrgency = in.readInt();
+ mCertainty = in.readInt();
+ }
+
+ /**
+ * Flatten this object into a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written (ignored).
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mMessageClass);
+ dest.writeInt(mCategory);
+ dest.writeInt(mResponseType);
+ dest.writeInt(mSeverity);
+ dest.writeInt(mUrgency);
+ dest.writeInt(mCertainty);
+ }
+
+ /**
+ * Returns the CMAS message class, e.g. {@link #CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT}.
+ * @return one of the {@code CMAS_CLASS} values
+ */
+ public int getMessageClass() {
+ return mMessageClass;
+ }
+
+ /**
+ * Returns the CMAS category, e.g. {@link #CMAS_CATEGORY_GEO}.
+ * @return one of the {@code CMAS_CATEGORY} values
+ */
+ public int getCategory() {
+ return mCategory;
+ }
+
+ /**
+ * Returns the CMAS response type, e.g. {@link #CMAS_RESPONSE_TYPE_SHELTER}.
+ * @return one of the {@code CMAS_RESPONSE_TYPE} values
+ */
+ public int getResponseType() {
+ return mResponseType;
+ }
+
+ /**
+ * Returns the CMAS severity, e.g. {@link #CMAS_SEVERITY_EXTREME}.
+ * @return one of the {@code CMAS_SEVERITY} values
+ */
+ public int getSeverity() {
+ return mSeverity;
+ }
+
+ /**
+ * Returns the CMAS urgency, e.g. {@link #CMAS_URGENCY_IMMEDIATE}.
+ * @return one of the {@code CMAS_URGENCY} values
+ */
+ public int getUrgency() {
+ return mUrgency;
+ }
+
+ /**
+ * Returns the CMAS certainty, e.g. {@link #CMAS_CERTAINTY_OBSERVED}.
+ * @return one of the {@code CMAS_CERTAINTY} values
+ */
+ public int getCertainty() {
+ return mCertainty;
+ }
+
+ @Override
+ public String toString() {
+ return "SmsCbCmasInfo{messageClass=" + mMessageClass + ", category=" + mCategory
+ + ", responseType=" + mResponseType + ", severity=" + mSeverity
+ + ", urgency=" + mUrgency + ", certainty=" + mCertainty + '}';
+ }
+
+ /**
+ * Describe the kinds of special objects contained in the marshalled representation.
+ * @return a bitmask indicating this Parcelable contains no special objects
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Creator for unparcelling objects. */
+ public static final Parcelable.Creator There are also four different CB message formats: GSM, ETWS Primary Notification (GSM only),
+ * UMTS, and CDMA. Some fields are only applicable for some message formats. Other fields were
+ * unified under a common name, avoiding some names, such as "Message Identifier", that refer to
+ * two completely different concepts in 3GPP and CDMA.
+ *
+ * The GSM/UMTS Message Identifier field is available via {@link #getServiceCategory}, the name
+ * of the equivalent field in CDMA. In both cases the service category is a 16-bit value, but 3GPP
+ * and 3GPP2 have completely different meanings for the respective values. For ETWS and CMAS, the
+ * application should
+ *
+ * The CDMA Message Identifier field is available via {@link #getSerialNumber}, which is used
+ * to detect the receipt of a duplicate message to be discarded. In CDMA, the message ID is
+ * unique to the current PLMN. In GSM/UMTS, there is a 16-bit serial number containing a 2-bit
+ * Geographical Scope field which indicates whether the 10-bit message code and 4-bit update number
+ * are considered unique to the PLMN, to the current cell, or to the current Location Area (or
+ * Service Area in UMTS). The relevant values are concatenated into a single String which will be
+ * unique if the messages are not duplicates.
+ *
+ * The SMS dispatcher does not detect duplicate messages. However, it does concatenate the
+ * pages of a GSM multi-page cell broadcast into a single SmsCbMessage object.
+ *
+ * Interested applications with {@code RECEIVE_SMS_PERMISSION} can register to receive
+ * {@code SMS_CB_RECEIVED_ACTION} broadcast intents for incoming non-emergency broadcasts.
+ * Only system applications such as the CellBroadcastReceiver may receive notifications for
+ * emergency broadcasts (ETWS and CMAS). This is intended to prevent any potential for delays or
+ * interference with the immediate display of the alert message and playing of the alert sound and
+ * vibration pattern, which could be caused by poorly written or malicious non-system code.
+ *
+ * @hide
+ */
+public class SmsCbMessage implements Parcelable {
+
+ protected static final String LOG_TAG = "SMSCB";
+
+ /** Cell wide geographical scope with immediate display (GSM/UMTS only). */
+ public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0;
+
+ /** PLMN wide geographical scope (GSM/UMTS and all CDMA broadcasts). */
+ public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1;
+
+ /** Location / service area wide geographical scope (GSM/UMTS only). */
+ public static final int GEOGRAPHICAL_SCOPE_LA_WIDE = 2;
+
+ /** Cell wide geographical scope (GSM/UMTS only). */
+ public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE = 3;
+
+ /** GSM or UMTS format cell broadcast. */
+ public static final int MESSAGE_FORMAT_3GPP = 1;
+
+ /** CDMA format cell broadcast. */
+ public static final int MESSAGE_FORMAT_3GPP2 = 2;
+
+ /** Normal message priority. */
+ public static final int MESSAGE_PRIORITY_NORMAL = 0;
+
+ /** Interactive message priority. */
+ public static final int MESSAGE_PRIORITY_INTERACTIVE = 1;
+
+ /** Urgent message priority. */
+ public static final int MESSAGE_PRIORITY_URGENT = 2;
+
+ /** Emergency message priority. */
+ public static final int MESSAGE_PRIORITY_EMERGENCY = 3;
+
+ /** Format of this message (for interpretation of service category values). */
+ private final int mMessageFormat;
+
+ /** Geographical scope of broadcast. */
+ private final int mGeographicalScope;
+
+ /**
+ * Serial number of broadcast (message identifier for CDMA, geographical scope + message code +
+ * update number for GSM/UMTS). The serial number plus the location code uniquely identify
+ * a cell broadcast for duplicate detection.
+ */
+ private final int mSerialNumber;
+
+ /**
+ * Location identifier for this message. It consists of the current operator MCC/MNC as a
+ * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the
+ * message is not binary 01, the Location Area is included for comparison. If the GS is
+ * 00 or 11, the Cell ID is also included. LAC and Cell ID are -1 if not specified.
+ */
+ private final SmsCbLocation mLocation;
+
+ /**
+ * 16-bit CDMA service category or GSM/UMTS message identifier. For ETWS and CMAS warnings,
+ * the information provided by the category is also available via {@link #getEtwsWarningInfo()}
+ * or {@link #getCmasWarningInfo()}.
+ */
+ private final int mServiceCategory;
+
+ /** Message language, as a two-character string, e.g. "en". */
+ private final String mLanguage;
+
+ /** Message body, as a String. */
+ private final String mBody;
+
+ /** Message priority (including emergency priority). */
+ private final int mPriority;
+
+ /** ETWS warning notification information (ETWS warnings only). */
+ private final SmsCbEtwsInfo mEtwsWarningInfo;
+
+ /** CMAS warning notification information (CMAS warnings only). */
+ private final SmsCbCmasInfo mCmasWarningInfo;
+
+ /**
+ * Create a new SmsCbMessage with the specified data.
+ */
+ public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber,
+ SmsCbLocation location, int serviceCategory, String language, String body,
+ int priority, SmsCbEtwsInfo etwsWarningInfo, SmsCbCmasInfo cmasWarningInfo) {
+ mMessageFormat = messageFormat;
+ mGeographicalScope = geographicalScope;
+ mSerialNumber = serialNumber;
+ mLocation = location;
+ mServiceCategory = serviceCategory;
+ mLanguage = language;
+ mBody = body;
+ mPriority = priority;
+ mEtwsWarningInfo = etwsWarningInfo;
+ mCmasWarningInfo = cmasWarningInfo;
+ }
+
+ /** Create a new SmsCbMessage object from a Parcel. */
+ public SmsCbMessage(Parcel in) {
+ mMessageFormat = in.readInt();
+ mGeographicalScope = in.readInt();
+ mSerialNumber = in.readInt();
+ mLocation = new SmsCbLocation(in);
+ mServiceCategory = in.readInt();
+ mLanguage = in.readString();
+ mBody = in.readString();
+ mPriority = in.readInt();
+ int type = in.readInt();
+ switch (type) {
+ case 'E':
+ // unparcel ETWS warning information
+ mEtwsWarningInfo = new SmsCbEtwsInfo(in);
+ mCmasWarningInfo = null;
+ break;
+
+ case 'C':
+ // unparcel CMAS warning information
+ mEtwsWarningInfo = null;
+ mCmasWarningInfo = new SmsCbCmasInfo(in);
+ break;
+
+ default:
+ mEtwsWarningInfo = null;
+ mCmasWarningInfo = null;
+ }
+ }
+
+ /**
+ * Flatten this object into a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written (ignored).
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mMessageFormat);
+ dest.writeInt(mGeographicalScope);
+ dest.writeInt(mSerialNumber);
+ mLocation.writeToParcel(dest, flags);
+ dest.writeInt(mServiceCategory);
+ dest.writeString(mLanguage);
+ dest.writeString(mBody);
+ dest.writeInt(mPriority);
+ if (mEtwsWarningInfo != null) {
+ // parcel ETWS warning information
+ dest.writeInt('E');
+ mEtwsWarningInfo.writeToParcel(dest, flags);
+ } else if (mCmasWarningInfo != null) {
+ // parcel CMAS warning information
+ dest.writeInt('C');
+ mCmasWarningInfo.writeToParcel(dest, flags);
+ } else {
+ // no ETWS or CMAS warning information
+ dest.writeInt('0');
+ }
+ }
+
+ public static final Parcelable.Creator
+ *
+ *
+ * TP-Reply-Path bit is set in
+ * this message.
+ */
+ public abstract boolean isReplyPathPresent();
+
+ /**
+ * Returns the status of the message on the ICC (read, unread, sent, unsent).
+ *
+ * @return the status of the message on the ICC. These are:
+ * SmsManager.STATUS_ON_ICC_FREE
+ * SmsManager.STATUS_ON_ICC_READ
+ * SmsManager.STATUS_ON_ICC_UNREAD
+ * SmsManager.STATUS_ON_ICC_SEND
+ * SmsManager.STATUS_ON_ICC_UNSENT
+ */
+ public int getStatusOnIcc() {
+ return mStatusOnIcc;
+ }
+
+ /**
+ * Returns the record index of the message on the ICC (1-based index).
+ * @return the record index of the message on the ICC, or -1 if this
+ * SmsMessage was not created from a ICC SMS EF record.
+ */
+ public int getIndexOnIcc() {
+ return mIndexOnIcc;
+ }
+
+ protected void parseMessageBody() {
+ // originatingAddress could be null if this message is from a status
+ // report.
+ if (mOriginatingAddress != null && mOriginatingAddress.couldBeEmailGateway()) {
+ extractEmailAddressFromMessageBody();
+ }
+ }
+
+ /**
+ * Try to parse this message as an email gateway message
+ * There are two ways specified in TS 23.040 Section 3.8 :
+ * - SMS message "may have its TP-PID set for Internet electronic mail - MT
+ * SMS format: [SubmitPdu containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ * @hide
+ */
+ public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message,
+ boolean statusReportRequested, SmsHeader smsHeader) {
+
+ /**
+ * TODO(cleanup): Do we really want silent failure like this?
+ * Would it not be much more reasonable to make sure we don't
+ * call this function if we really want nothing done?
+ */
+ if (message == null || destAddr == null) {
+ return null;
+ }
+
+ UserData uData = new UserData();
+ uData.payloadStr = message;
+ uData.userDataHeader = smsHeader;
+ return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a data message to a destination address and port.
+ *
+ * @param scAddr Service Centre address. null == use default
+ * @param destAddr the address of the destination for the message
+ * @param destPort the port to deliver the message to at the
+ * destination
+ * @param data the data for the message
+ * @return a SubmitPdu containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, int destPort,
+ byte[] data, boolean statusReportRequested) {
+
+ /**
+ * TODO(cleanup): this is not a general-purpose SMS creation
+ * method, but rather something specialized to messages
+ * containing OCTET encoded (meaning non-human-readable) user
+ * data. The name should reflect that, and not just overload.
+ */
+
+ SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
+ portAddrs.destPort = destPort;
+ portAddrs.origPort = 0;
+ portAddrs.areEightBits = false;
+
+ SmsHeader smsHeader = new SmsHeader();
+ smsHeader.portAddrs = portAddrs;
+
+ UserData uData = new UserData();
+ uData.userDataHeader = smsHeader;
+ uData.msgEncoding = UserData.ENCODING_OCTET;
+ uData.msgEncodingSet = true;
+ uData.payload = data;
+
+ return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a data message to a destination address & port
+ *
+ * @param destAddr the address of the destination for the message
+ * @param userData the data for the message
+ * @param statusReportRequested Indicates whether a report is requested for this message.
+ * @return a SubmitPdu containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String destAddr, UserData userData,
+ boolean statusReportRequested) {
+ return privateGetSubmitPdu(destAddr, statusReportRequested, userData);
+ }
+
+ /**
+ * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+ */
+ @Override
+ public int getProtocolIdentifier() {
+ Rlog.w(LOG_TAG, "getProtocolIdentifier: is not supported in CDMA mode.");
+ // (3GPP TS 23.040): "no interworking, but SME to SME protocol":
+ return 0;
+ }
+
+ /**
+ * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+ */
+ @Override
+ public boolean isReplace() {
+ Rlog.w(LOG_TAG, "isReplace: is not supported in CDMA mode.");
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+ */
+ @Override
+ public boolean isCphsMwiMessage() {
+ Rlog.w(LOG_TAG, "isCphsMwiMessage: is not supported in CDMA mode.");
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isMWIClearMessage() {
+ return ((mBearerData != null) && (mBearerData.numberOfMessages == 0));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isMWISetMessage() {
+ return ((mBearerData != null) && (mBearerData.numberOfMessages > 0));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isMwiDontStore() {
+ return ((mBearerData != null) &&
+ (mBearerData.numberOfMessages > 0) &&
+ (mBearerData.userData == null));
+ }
+
+ /**
+ * Returns the status for a previously submitted message.
+ * For not interfering with status codes from GSM, this status code is
+ * shifted to the bits 31-16.
+ */
+ @Override
+ public int getStatus() {
+ return (status << 16);
+ }
+
+ /** Return true iff the bearer data message type is DELIVERY_ACK. */
+ @Override
+ public boolean isStatusReportMessage() {
+ return (mBearerData.messageType == BearerData.MESSAGE_TYPE_DELIVERY_ACK);
+ }
+
+ /**
+ * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+ */
+ @Override
+ public boolean isReplyPathPresent() {
+ Rlog.w(LOG_TAG, "isReplyPathPresent: is not supported in CDMA mode.");
+ return false;
+ }
+
+ /**
+ * Calculate the number of septets needed to encode the message.
+ *
+ * @param messageBody the message to encode
+ * @param use7bitOnly ignore (but still count) illegal characters if true
+ * @param isEntireMsg indicates if this is entire msg or a segment in multipart msg
+ * @return TextEncodingDetails
+ */
+ public static TextEncodingDetails calculateLength(CharSequence messageBody,
+ boolean use7bitOnly, boolean isEntireMsg) {
+ CharSequence newMsgBody = null;
+ Resources r = Resources.getSystem();
+ if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
+ newMsgBody = Sms7BitEncodingTranslator.translate(messageBody);
+ }
+ if (TextUtils.isEmpty(newMsgBody)) {
+ newMsgBody = messageBody;
+ }
+ return BearerData.calcTextEncodingDetails(newMsgBody, use7bitOnly, isEntireMsg);
+ }
+
+ /**
+ * Returns the teleservice type of the message.
+ * @return the teleservice:
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_NOT_SET},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WMT},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WEMT},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_VMN},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WAP}
+ */
+ public int getTeleService() {
+ return mEnvelope.teleService;
+ }
+
+ /**
+ * Returns the message type of the message.
+ * @return the message type:
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_POINT_TO_POINT},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_BROADCAST},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_ACKNOWLEDGE},
+ */
+ public int getMessageType() {
+ // NOTE: mEnvelope.messageType is not set correctly for cell broadcasts with some RILs.
+ // Use the service category parameter to detect CMAS and other cell broadcast messages.
+ if (mEnvelope.serviceCategory != 0) {
+ return SmsEnvelope.MESSAGE_TYPE_BROADCAST;
+ } else {
+ return SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT;
+ }
+ }
+
+ /**
+ * Decodes pdu to an empty SMS object.
+ * In the CDMA case the pdu is just an internal byte stream representation
+ * of the SMS Java-object.
+ * @see #createPdu()
+ */
+ private void parsePdu(byte[] pdu) {
+ ByteArrayInputStream bais = new ByteArrayInputStream(pdu);
+ DataInputStream dis = new DataInputStream(bais);
+ int length;
+ int bearerDataLength;
+ SmsEnvelope env = new SmsEnvelope();
+ CdmaSmsAddress addr = new CdmaSmsAddress();
+
+ try {
+ env.messageType = dis.readInt();
+ env.teleService = dis.readInt();
+ env.serviceCategory = dis.readInt();
+
+ addr.digitMode = dis.readByte();
+ addr.numberMode = dis.readByte();
+ addr.ton = dis.readByte();
+ addr.numberPlan = dis.readByte();
+
+ length = dis.readUnsignedByte();
+ addr.numberOfDigits = length;
+
+ // sanity check on the length
+ if (length > pdu.length) {
+ throw new RuntimeException(
+ "createFromPdu: Invalid pdu, addr.numberOfDigits " + length
+ + " > pdu len " + pdu.length);
+ }
+ addr.origBytes = new byte[length];
+ dis.read(addr.origBytes, 0, length); // digits
+
+ env.bearerReply = dis.readInt();
+ // CauseCode values:
+ env.replySeqNo = dis.readByte();
+ env.errorClass = dis.readByte();
+ env.causeCode = dis.readByte();
+
+ //encoded BearerData:
+ bearerDataLength = dis.readInt();
+ // sanity check on the length
+ if (bearerDataLength > pdu.length) {
+ throw new RuntimeException(
+ "createFromPdu: Invalid pdu, bearerDataLength " + bearerDataLength
+ + " > pdu len " + pdu.length);
+ }
+ env.bearerData = new byte[bearerDataLength];
+ dis.read(env.bearerData, 0, bearerDataLength);
+ dis.close();
+ } catch (IOException ex) {
+ throw new RuntimeException(
+ "createFromPdu: conversion from byte array to object failed: " + ex, ex);
+ } catch (Exception ex) {
+ Rlog.e(LOG_TAG, "createFromPdu: conversion from byte array to object failed: " + ex);
+ }
+
+ // link the filled objects to this SMS
+ mOriginatingAddress = addr;
+ env.origAddress = addr;
+ mEnvelope = env;
+ mPdu = pdu;
+
+ parseSms();
+ }
+
+ /**
+ * Decodes 3GPP2 sms stored in CSIM/RUIM cards As per 3GPP2 C.S0015-0
+ */
+ private void parsePduFromEfRecord(byte[] pdu) {
+ ByteArrayInputStream bais = new ByteArrayInputStream(pdu);
+ DataInputStream dis = new DataInputStream(bais);
+ SmsEnvelope env = new SmsEnvelope();
+ CdmaSmsAddress addr = new CdmaSmsAddress();
+ CdmaSmsSubaddress subAddr = new CdmaSmsSubaddress();
+
+ try {
+ env.messageType = dis.readByte();
+
+ while (dis.available() > 0) {
+ int parameterId = dis.readByte();
+ int parameterLen = dis.readUnsignedByte();
+ byte[] parameterData = new byte[parameterLen];
+
+ switch (parameterId) {
+ case TELESERVICE_IDENTIFIER:
+ /*
+ * 16 bit parameter that identifies which upper layer
+ * service access point is sending or should receive
+ * this message
+ */
+ env.teleService = dis.readUnsignedShort();
+ Rlog.i(LOG_TAG, "teleservice = " + env.teleService);
+ break;
+ case SERVICE_CATEGORY:
+ /*
+ * 16 bit parameter that identifies type of service as
+ * in 3GPP2 C.S0015-0 Table 3.4.3.2-1
+ */
+ env.serviceCategory = dis.readUnsignedShort();
+ break;
+ case ORIGINATING_ADDRESS:
+ case DESTINATION_ADDRESS:
+ dis.read(parameterData, 0, parameterLen);
+ BitwiseInputStream addrBis = new BitwiseInputStream(parameterData);
+ addr.digitMode = addrBis.read(1);
+ addr.numberMode = addrBis.read(1);
+ int numberType = 0;
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ numberType = addrBis.read(3);
+ addr.ton = numberType;
+
+ if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK)
+ addr.numberPlan = addrBis.read(4);
+ }
+
+ addr.numberOfDigits = addrBis.read(8);
+
+ byte[] data = new byte[addr.numberOfDigits];
+ byte b = 0x00;
+
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF) {
+ /* As per 3GPP2 C.S0005-0 Table 2.7.1.3.2.4-4 */
+ for (int index = 0; index < addr.numberOfDigits; index++) {
+ b = (byte) (0xF & addrBis.read(4));
+ // convert the value if it is 4-bit DTMF to 8
+ // bit
+ data[index] = convertDtmfToAscii(b);
+ }
+ } else if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK) {
+ for (int index = 0; index < addr.numberOfDigits; index++) {
+ b = (byte) (0xFF & addrBis.read(8));
+ data[index] = b;
+ }
+
+ } else if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_DATA_NETWORK) {
+ if (numberType == 2)
+ Rlog.e(LOG_TAG, "TODO: Originating Addr is email id");
+ else
+ Rlog.e(LOG_TAG,
+ "TODO: Originating Addr is data network address");
+ } else {
+ Rlog.e(LOG_TAG, "Originating Addr is of incorrect type");
+ }
+ } else {
+ Rlog.e(LOG_TAG, "Incorrect Digit mode");
+ }
+ addr.origBytes = data;
+ Rlog.i(LOG_TAG, "Originating Addr=" + addr.toString());
+ break;
+ case ORIGINATING_SUB_ADDRESS:
+ case DESTINATION_SUB_ADDRESS:
+ dis.read(parameterData, 0, parameterLen);
+ BitwiseInputStream subAddrBis = new BitwiseInputStream(parameterData);
+ subAddr.type = subAddrBis.read(3);
+ subAddr.odd = subAddrBis.readByteArray(1)[0];
+ int subAddrLen = subAddrBis.read(8);
+ byte[] subdata = new byte[subAddrLen];
+ for (int index = 0; index < subAddrLen; index++) {
+ b = (byte) (0xFF & subAddrBis.read(4));
+ // convert the value if it is 4-bit DTMF to 8 bit
+ subdata[index] = convertDtmfToAscii(b);
+ }
+ subAddr.origBytes = subdata;
+ break;
+ case BEARER_REPLY_OPTION:
+ dis.read(parameterData, 0, parameterLen);
+ BitwiseInputStream replyOptBis = new BitwiseInputStream(parameterData);
+ env.bearerReply = replyOptBis.read(6);
+ break;
+ case CAUSE_CODES:
+ dis.read(parameterData, 0, parameterLen);
+ BitwiseInputStream ccBis = new BitwiseInputStream(parameterData);
+ env.replySeqNo = ccBis.readByteArray(6)[0];
+ env.errorClass = ccBis.readByteArray(2)[0];
+ if (env.errorClass != 0x00)
+ env.causeCode = ccBis.readByteArray(8)[0];
+ break;
+ case BEARER_DATA:
+ dis.read(parameterData, 0, parameterLen);
+ env.bearerData = parameterData;
+ break;
+ default:
+ throw new Exception("unsupported parameterId (" + parameterId + ")");
+ }
+ }
+ bais.close();
+ dis.close();
+ } catch (Exception ex) {
+ Rlog.e(LOG_TAG, "parsePduFromEfRecord: conversion from pdu to SmsMessage failed" + ex);
+ }
+
+ // link the filled objects to this SMS
+ mOriginatingAddress = addr;
+ env.origAddress = addr;
+ env.origSubaddress = subAddr;
+ mEnvelope = env;
+ mPdu = pdu;
+
+ parseSms();
+ }
+
+ /**
+ * Parses a SMS message from its BearerData stream. (mobile-terminated only)
+ */
+ public void parseSms() {
+ // Message Waiting Info Record defined in 3GPP2 C.S-0005, 3.7.5.6
+ // It contains only an 8-bit number with the number of messages waiting
+ if (mEnvelope.teleService == SmsEnvelope.TELESERVICE_MWI) {
+ mBearerData = new BearerData();
+ if (mEnvelope.bearerData != null) {
+ mBearerData.numberOfMessages = 0x000000FF & mEnvelope.bearerData[0];
+ }
+ if (VDBG) {
+ Rlog.d(LOG_TAG, "parseSms: get MWI " +
+ Integer.toString(mBearerData.numberOfMessages));
+ }
+ return;
+ }
+ mBearerData = BearerData.decode(mEnvelope.bearerData);
+ if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
+ Rlog.d(LOG_TAG, "MT raw BearerData = '" +
+ HexDump.toHexString(mEnvelope.bearerData) + "'");
+ Rlog.d(LOG_TAG, "MT (decoded) BearerData = " + mBearerData);
+ }
+ mMessageRef = mBearerData.messageId;
+ if (mBearerData.userData != null) {
+ mUserData = mBearerData.userData.payload;
+ mUserDataHeader = mBearerData.userData.userDataHeader;
+ mMessageBody = mBearerData.userData.payloadStr;
+ }
+
+ if (mOriginatingAddress != null) {
+ mOriginatingAddress.address = new String(mOriginatingAddress.origBytes);
+ if (mOriginatingAddress.ton == CdmaSmsAddress.TON_INTERNATIONAL_OR_IP) {
+ if (mOriginatingAddress.address.charAt(0) != '+') {
+ mOriginatingAddress.address = "+" + mOriginatingAddress.address;
+ }
+ }
+ if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: "
+ + mOriginatingAddress.address);
+ }
+
+ if (mBearerData.msgCenterTimeStamp != null) {
+ mScTimeMillis = mBearerData.msgCenterTimeStamp.toMillis(true);
+ }
+
+ if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis);
+
+ // Message Type (See 3GPP2 C.S0015-B, v2, 4.5.1)
+ if (mBearerData.messageType == BearerData.MESSAGE_TYPE_DELIVERY_ACK) {
+ // The BearerData MsgStatus subparameter should only be
+ // included for DELIVERY_ACK messages. If it occurred for
+ // other messages, it would be unclear what the status
+ // being reported refers to. The MsgStatus subparameter
+ // is primarily useful to indicate error conditions -- a
+ // message without this subparameter is assumed to
+ // indicate successful delivery (status == 0).
+ if (! mBearerData.messageStatusSet) {
+ Rlog.d(LOG_TAG, "DELIVERY_ACK message without msgStatus (" +
+ (mUserData == null ? "also missing" : "does have") +
+ " userData).");
+ status = 0;
+ } else {
+ status = mBearerData.errorClass << 8;
+ status |= mBearerData.messageStatus;
+ }
+ } else if (mBearerData.messageType != BearerData.MESSAGE_TYPE_DELIVER) {
+ throw new RuntimeException("Unsupported message type: " + mBearerData.messageType);
+ }
+
+ if (mMessageBody != null) {
+ if (VDBG) Rlog.v(LOG_TAG, "SMS message body: '" + mMessageBody + "'");
+ parseMessageBody();
+ } else if ((mUserData != null) && VDBG) {
+ Rlog.v(LOG_TAG, "SMS payload: '" + IccUtils.bytesToHexString(mUserData) + "'");
+ }
+ }
+
+ /**
+ * Parses a broadcast SMS, possibly containing a CMAS alert.
+ */
+ public SmsCbMessage parseBroadcastSms() {
+ BearerData bData = BearerData.decode(mEnvelope.bearerData, mEnvelope.serviceCategory);
+ if (bData == null) {
+ Rlog.w(LOG_TAG, "BearerData.decode() returned null");
+ return null;
+ }
+
+ if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
+ Rlog.d(LOG_TAG, "MT raw BearerData = " + HexDump.toHexString(mEnvelope.bearerData));
+ }
+
+ String plmn = TelephonyManager.getDefault().getNetworkOperator();
+ SmsCbLocation location = new SmsCbLocation(plmn);
+
+ return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP2,
+ SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE, bData.messageId, location,
+ mEnvelope.serviceCategory, bData.getLanguage(), bData.userData.payloadStr,
+ bData.priority, null, bData.cmasWarningInfo);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public SmsConstants.MessageClass getMessageClass() {
+ if (BearerData.DISPLAY_MODE_IMMEDIATE == mBearerData.displayMode ) {
+ return SmsConstants.MessageClass.CLASS_0;
+ } else {
+ return SmsConstants.MessageClass.UNKNOWN;
+ }
+ }
+
+ /**
+ * Calculate the next message id, starting at 1 and iteratively
+ * incrementing within the range 1..65535 remembering the state
+ * via a persistent system property. (See C.S0015-B, v2.0,
+ * 4.3.1.5) Since this routine is expected to be accessed via via
+ * binder-call, and hence should be thread-safe, it has been
+ * synchronized.
+ */
+ public synchronized static int getNextMessageId() {
+ // Testing and dialog with partners has indicated that
+ // msgId==0 is (sometimes?) treated specially by lower levels.
+ // Specifically, the ID is not preserved for delivery ACKs.
+ // Hence, avoid 0 -- constraining the range to 1..65535.
+ int msgId = SystemProperties.getInt(TelephonyProperties.PROPERTY_CDMA_MSG_ID, 1);
+ String nextMsgId = Integer.toString((msgId % 0xFFFF) + 1);
+ try{
+ SystemProperties.set(TelephonyProperties.PROPERTY_CDMA_MSG_ID, nextMsgId);
+ if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
+ Rlog.d(LOG_TAG, "next " + TelephonyProperties.PROPERTY_CDMA_MSG_ID + " = " + nextMsgId);
+ Rlog.d(LOG_TAG, "readback gets " +
+ SystemProperties.get(TelephonyProperties.PROPERTY_CDMA_MSG_ID));
+ }
+ } catch(RuntimeException ex) {
+ Rlog.e(LOG_TAG, "set nextMessage ID failed: " + ex);
+ }
+ return msgId;
+ }
+
+ /**
+ * Creates BearerData and Envelope from parameters for a Submit SMS.
+ * @return byte stream for SubmitPdu.
+ */
+ private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested,
+ UserData userData) {
+
+ /**
+ * TODO(cleanup): give this function a more meaningful name.
+ */
+
+ /**
+ * TODO(cleanup): Make returning null from the getSubmitPdu
+ * variations meaningful -- clean up the error feedback
+ * mechanism, and avoid null pointer exceptions.
+ */
+
+ /**
+ * North America Plus Code :
+ * Convert + code to 011 and dial out for international SMS
+ */
+ CdmaSmsAddress destAddr = CdmaSmsAddress.parse(
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCodeForSms(destAddrStr));
+ if (destAddr == null) return null;
+
+ BearerData bearerData = new BearerData();
+ bearerData.messageType = BearerData.MESSAGE_TYPE_SUBMIT;
+
+ bearerData.messageId = getNextMessageId();
+
+ bearerData.deliveryAckReq = statusReportRequested;
+ bearerData.userAckReq = false;
+ bearerData.readAckReq = false;
+ bearerData.reportReq = false;
+
+ bearerData.userData = userData;
+
+ byte[] encodedBearerData = BearerData.encode(bearerData);
+ if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
+ Rlog.d(LOG_TAG, "MO (encoded) BearerData = " + bearerData);
+ Rlog.d(LOG_TAG, "MO raw BearerData = '" + HexDump.toHexString(encodedBearerData) + "'");
+ }
+ if (encodedBearerData == null) return null;
+
+ int teleservice = bearerData.hasUserDataHeader ?
+ SmsEnvelope.TELESERVICE_WEMT : SmsEnvelope.TELESERVICE_WMT;
+
+ SmsEnvelope envelope = new SmsEnvelope();
+ envelope.messageType = SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT;
+ envelope.teleService = teleservice;
+ envelope.destAddress = destAddr;
+ envelope.bearerReply = RETURN_ACK;
+ envelope.bearerData = encodedBearerData;
+
+ /**
+ * TODO(cleanup): envelope looks to be a pointless class, get
+ * rid of it. Also -- most of the envelope fields set here
+ * are ignored, why?
+ */
+
+ try {
+ /**
+ * TODO(cleanup): reference a spec and get rid of the ugly comments
+ */
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
+ DataOutputStream dos = new DataOutputStream(baos);
+ dos.writeInt(envelope.teleService);
+ dos.writeInt(0); //servicePresent
+ dos.writeInt(0); //serviceCategory
+ dos.write(destAddr.digitMode);
+ dos.write(destAddr.numberMode);
+ dos.write(destAddr.ton); // number_type
+ dos.write(destAddr.numberPlan);
+ dos.write(destAddr.numberOfDigits);
+ dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits
+ // Subaddress is not supported.
+ dos.write(0); //subaddressType
+ dos.write(0); //subaddr_odd
+ dos.write(0); //subaddr_nbr_of_digits
+ dos.write(encodedBearerData.length);
+ dos.write(encodedBearerData, 0, encodedBearerData.length);
+ dos.close();
+
+ SubmitPdu pdu = new SubmitPdu();
+ pdu.encodedMessage = baos.toByteArray();
+ pdu.encodedScAddress = null;
+ return pdu;
+ } catch(IOException ex) {
+ Rlog.e(LOG_TAG, "creating SubmitPdu failed: " + ex);
+ }
+ return null;
+ }
+
+ /**
+ * Creates byte array (pseudo pdu) from SMS object.
+ * Note: Do not call this method more than once per object!
+ */
+ private void createPdu() {
+ SmsEnvelope env = mEnvelope;
+ CdmaSmsAddress addr = env.origAddress;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
+ DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(baos));
+
+ try {
+ dos.writeInt(env.messageType);
+ dos.writeInt(env.teleService);
+ dos.writeInt(env.serviceCategory);
+
+ dos.writeByte(addr.digitMode);
+ dos.writeByte(addr.numberMode);
+ dos.writeByte(addr.ton);
+ dos.writeByte(addr.numberPlan);
+ dos.writeByte(addr.numberOfDigits);
+ dos.write(addr.origBytes, 0, addr.origBytes.length); // digits
+
+ dos.writeInt(env.bearerReply);
+ // CauseCode values:
+ dos.writeByte(env.replySeqNo);
+ dos.writeByte(env.errorClass);
+ dos.writeByte(env.causeCode);
+ //encoded BearerData:
+ dos.writeInt(env.bearerData.length);
+ dos.write(env.bearerData, 0, env.bearerData.length);
+ dos.close();
+
+ /**
+ * TODO(cleanup) -- The mPdu field is managed in
+ * a fragile manner, and it would be much nicer if
+ * accessing the serialized representation used a less
+ * fragile mechanism. Maybe the getPdu method could
+ * generate a representation if there was not yet one?
+ */
+
+ mPdu = baos.toByteArray();
+ } catch (IOException ex) {
+ Rlog.e(LOG_TAG, "createPdu: conversion from object to byte array failed: " + ex);
+ }
+ }
+
+ /**
+ * Converts a 4-Bit DTMF encoded symbol from the calling address number to ASCII character
+ */
+ private byte convertDtmfToAscii(byte dtmfDigit) {
+ byte asciiDigit;
+
+ switch (dtmfDigit) {
+ case 0: asciiDigit = 68; break; // 'D'
+ case 1: asciiDigit = 49; break; // '1'
+ case 2: asciiDigit = 50; break; // '2'
+ case 3: asciiDigit = 51; break; // '3'
+ case 4: asciiDigit = 52; break; // '4'
+ case 5: asciiDigit = 53; break; // '5'
+ case 6: asciiDigit = 54; break; // '6'
+ case 7: asciiDigit = 55; break; // '7'
+ case 8: asciiDigit = 56; break; // '8'
+ case 9: asciiDigit = 57; break; // '9'
+ case 10: asciiDigit = 48; break; // '0'
+ case 11: asciiDigit = 42; break; // '*'
+ case 12: asciiDigit = 35; break; // '#'
+ case 13: asciiDigit = 65; break; // 'A'
+ case 14: asciiDigit = 66; break; // 'B'
+ case 15: asciiDigit = 67; break; // 'C'
+ default:
+ asciiDigit = 32; // Invalid DTMF code
+ break;
+ }
+
+ return asciiDigit;
+ }
+
+ /** This function shall be called to get the number of voicemails.
+ * @hide
+ */
+ public int getNumOfVoicemails() {
+ return mBearerData.numberOfMessages;
+ }
+
+ /**
+ * Returns a byte array that can be use to uniquely identify a received SMS message.
+ * C.S0015-B 4.3.1.6 Unique Message Identification.
+ *
+ * @return byte array uniquely identifying the message.
+ * @hide
+ */
+ public byte[] getIncomingSmsFingerprint() {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+ output.write(mEnvelope.serviceCategory);
+ output.write(mEnvelope.teleService);
+ output.write(mEnvelope.origAddress.origBytes, 0, mEnvelope.origAddress.origBytes.length);
+ output.write(mEnvelope.bearerData, 0, mEnvelope.bearerData.length);
+ output.write(mEnvelope.origSubaddress.origBytes, 0,
+ mEnvelope.origSubaddress.origBytes.length);
+
+ return output.toByteArray();
+ }
+
+ /**
+ * Returns the list of service category program data, if present.
+ * @return a list of CdmaSmsCbProgramData objects, or null if not present
+ * @hide
+ */
+ public ArrayListSubmitPdu containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ * @hide
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message,
+ boolean statusReportRequested, byte[] header) {
+ return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, header,
+ ENCODING_UNKNOWN, 0, 0);
+ }
+
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message using the
+ * specified encoding.
+ *
+ * @param scAddress Service Centre address. Null means use default.
+ * @param encoding Encoding defined by constants in
+ * com.android.internal.telephony.SmsConstants.ENCODING_*
+ * @param languageTable
+ * @param languageShiftTable
+ * @return a SubmitPdu containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ * @hide
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message,
+ boolean statusReportRequested, byte[] header, int encoding,
+ int languageTable, int languageShiftTable) {
+
+ // Perform null parameter checks.
+ if (message == null || destinationAddress == null) {
+ return null;
+ }
+
+ if (encoding == ENCODING_UNKNOWN) {
+ // Find the best encoding to use
+ TextEncodingDetails ted = calculateLength(message, false);
+ encoding = ted.codeUnitSize;
+ languageTable = ted.languageTable;
+ languageShiftTable = ted.languageShiftTable;
+
+ if (encoding == ENCODING_7BIT &&
+ (languageTable != 0 || languageShiftTable != 0)) {
+ if (header != null) {
+ SmsHeader smsHeader = SmsHeader.fromByteArray(header);
+ if (smsHeader.languageTable != languageTable
+ || smsHeader.languageShiftTable != languageShiftTable) {
+ Rlog.w(LOG_TAG, "Updating language table in SMS header: "
+ + smsHeader.languageTable + " -> " + languageTable + ", "
+ + smsHeader.languageShiftTable + " -> " + languageShiftTable);
+ smsHeader.languageTable = languageTable;
+ smsHeader.languageShiftTable = languageShiftTable;
+ header = SmsHeader.toByteArray(smsHeader);
+ }
+ } else {
+ SmsHeader smsHeader = new SmsHeader();
+ smsHeader.languageTable = languageTable;
+ smsHeader.languageShiftTable = languageShiftTable;
+ header = SmsHeader.toByteArray(smsHeader);
+ }
+ }
+ }
+
+ SubmitPdu ret = new SubmitPdu();
+ // MTI = SMS-SUBMIT, UDHI = header != null
+ byte mtiByte = (byte)(0x01 | (header != null ? 0x40 : 0x00));
+ ByteArrayOutputStream bo = getSubmitPduHead(
+ scAddress, destinationAddress, mtiByte,
+ statusReportRequested, ret);
+
+ // User Data (and length)
+ byte[] userData;
+ try {
+ if (encoding == ENCODING_7BIT) {
+ userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header,
+ languageTable, languageShiftTable);
+ } else { //assume UCS-2
+ try {
+ userData = encodeUCS2(message, header);
+ } catch(UnsupportedEncodingException uex) {
+ Rlog.e(LOG_TAG,
+ "Implausible UnsupportedEncodingException ",
+ uex);
+ return null;
+ }
+ }
+ } catch (EncodeException ex) {
+ // Encoding to the 7-bit alphabet failed. Let's see if we can
+ // send it as a UCS-2 encoded message
+ try {
+ userData = encodeUCS2(message, header);
+ encoding = ENCODING_16BIT;
+ } catch(UnsupportedEncodingException uex) {
+ Rlog.e(LOG_TAG,
+ "Implausible UnsupportedEncodingException ",
+ uex);
+ return null;
+ }
+ }
+
+ if (encoding == ENCODING_7BIT) {
+ if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) {
+ // Message too long
+ Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " septets)");
+ return null;
+ }
+ // TP-Data-Coding-Scheme
+ // Default encoding, uncompressed
+ // To test writing messages to the SIM card, change this value 0x00
+ // to 0x12, which means "bits 1 and 0 contain message class, and the
+ // class is 2". Note that this takes effect for the sender. In other
+ // words, messages sent by the phone with this change will end up on
+ // the receiver's SIM card. You can then send messages to yourself
+ // (on a phone with this change) and they'll end up on the SIM card.
+ bo.write(0x00);
+ } else { // assume UCS-2
+ if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) {
+ // Message too long
+ Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " bytes)");
+ return null;
+ }
+ // TP-Data-Coding-Scheme
+ // UCS-2 encoding, uncompressed
+ bo.write(0x08);
+ }
+
+ // (no TP-Validity-Period)
+ bo.write(userData, 0, userData.length);
+ ret.encodedMessage = bo.toByteArray();
+ return ret;
+ }
+
+ /**
+ * Packs header and UCS-2 encoded message. Includes TP-UDL & TP-UDHL if necessary
+ *
+ * @return encoded message as UCS2
+ * @throws UnsupportedEncodingException
+ */
+ private static byte[] encodeUCS2(String message, byte[] header)
+ throws UnsupportedEncodingException {
+ byte[] userData, textPart;
+ textPart = message.getBytes("utf-16be");
+
+ if (header != null) {
+ // Need 1 byte for UDHL
+ userData = new byte[header.length + textPart.length + 1];
+
+ userData[0] = (byte)header.length;
+ System.arraycopy(header, 0, userData, 1, header.length);
+ System.arraycopy(textPart, 0, userData, header.length + 1, textPart.length);
+ }
+ else {
+ userData = textPart;
+ }
+ byte[] ret = new byte[userData.length+1];
+ ret[0] = (byte) (userData.length & 0xff );
+ System.arraycopy(userData, 0, ret, 1, userData.length);
+ return ret;
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message
+ *
+ * @param scAddress Service Centre address. Null means use default.
+ * @return a SubmitPdu containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message,
+ boolean statusReportRequested) {
+
+ return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, null);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a data message to a destination address & port
+ *
+ * @param scAddress Service Centre address. null == use default
+ * @param destinationAddress the address of the destination for the message
+ * @param destinationPort the port to deliver the message to at the
+ * destination
+ * @param data the data for the message
+ * @return a SubmitPdu containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, int destinationPort, byte[] data,
+ boolean statusReportRequested) {
+
+ SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
+ portAddrs.destPort = destinationPort;
+ portAddrs.origPort = 0;
+ portAddrs.areEightBits = false;
+
+ SmsHeader smsHeader = new SmsHeader();
+ smsHeader.portAddrs = portAddrs;
+
+ byte[] smsHeaderData = SmsHeader.toByteArray(smsHeader);
+
+ if ((data.length + smsHeaderData.length + 1) > MAX_USER_DATA_BYTES) {
+ Rlog.e(LOG_TAG, "SMS data message may only contain "
+ + (MAX_USER_DATA_BYTES - smsHeaderData.length - 1) + " bytes");
+ return null;
+ }
+
+ SubmitPdu ret = new SubmitPdu();
+ ByteArrayOutputStream bo = getSubmitPduHead(
+ scAddress, destinationAddress, (byte) 0x41, // MTI = SMS-SUBMIT,
+ // TP-UDHI = true
+ statusReportRequested, ret);
+
+ // TP-Data-Coding-Scheme
+ // No class, 8 bit data
+ bo.write(0x04);
+
+ // (no TP-Validity-Period)
+
+ // Total size
+ bo.write(data.length + smsHeaderData.length + 1);
+
+ // User data header
+ bo.write(smsHeaderData.length);
+ bo.write(smsHeaderData, 0, smsHeaderData.length);
+
+ // User data
+ bo.write(data, 0, data.length);
+
+ ret.encodedMessage = bo.toByteArray();
+ return ret;
+ }
+
+ /**
+ * Create the beginning of a SUBMIT PDU. This is the part of the
+ * SUBMIT PDU that is common to the two versions of {@link #getSubmitPdu},
+ * one of which takes a byte array and the other of which takes a
+ * String.
+ *
+ * @param scAddress Service Centre address. null == use default
+ * @param destinationAddress the address of the destination for the message
+ * @param mtiByte
+ * @param ret SubmitPdu containing the encoded SC
+ * address, if applicable, and the encoded message
+ */
+ private static ByteArrayOutputStream getSubmitPduHead(
+ String scAddress, String destinationAddress, byte mtiByte,
+ boolean statusReportRequested, SubmitPdu ret) {
+ ByteArrayOutputStream bo = new ByteArrayOutputStream(
+ MAX_USER_DATA_BYTES + 40);
+
+ // SMSC address with length octet, or 0
+ if (scAddress == null) {
+ ret.encodedScAddress = null;
+ } else {
+ ret.encodedScAddress = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(
+ scAddress);
+ }
+
+ // TP-Message-Type-Indicator (and friends)
+ if (statusReportRequested) {
+ // Set TP-Status-Report-Request bit.
+ mtiByte |= 0x20;
+ if (VDBG) Rlog.d(LOG_TAG, "SMS status report requested");
+ }
+ bo.write(mtiByte);
+
+ // space for TP-Message-Reference
+ bo.write(0);
+
+ byte[] daBytes;
+
+ daBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(destinationAddress);
+
+ // destination address length in BCD digits, ignoring TON byte and pad
+ // TODO Should be better.
+ bo.write((daBytes.length - 1) * 2
+ - ((daBytes[daBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0));
+
+ // destination address
+ bo.write(daBytes, 0, daBytes.length);
+
+ // TP-Protocol-Identifier
+ bo.write(0);
+ return bo;
+ }
+
+ private static class PduParser {
+ byte mPdu[];
+ int mCur;
+ SmsHeader mUserDataHeader;
+ byte[] mUserData;
+ int mUserDataSeptetPadding;
+
+ PduParser(byte[] pdu) {
+ mPdu = pdu;
+ mCur = 0;
+ mUserDataSeptetPadding = 0;
+ }
+
+ /**
+ * Parse and return the SC address prepended to SMS messages coming via
+ * the TS 27.005 / AT interface. Returns null on invalid address
+ */
+ String getSCAddress() {
+ int len;
+ String ret;
+
+ // length of SC Address
+ len = getByte();
+
+ if (len == 0) {
+ // no SC address
+ ret = null;
+ } else {
+ // SC address
+ try {
+ ret = PhoneNumberUtils
+ .calledPartyBCDToString(mPdu, mCur, len);
+ } catch (RuntimeException tr) {
+ Rlog.d(LOG_TAG, "invalid SC address: ", tr);
+ ret = null;
+ }
+ }
+
+ mCur += len;
+
+ return ret;
+ }
+
+ /**
+ * returns non-sign-extended byte value
+ */
+ int getByte() {
+ return mPdu[mCur++] & 0xff;
+ }
+
+ /**
+ * Any address except the SC address (eg, originating address) See TS
+ * 23.040 9.1.2.5
+ */
+ GsmSmsAddress getAddress() {
+ GsmSmsAddress ret;
+
+ // "The Address-Length field is an integer representation of
+ // the number field, i.e. excludes any semi-octet containing only
+ // fill bits."
+ // The TOA field is not included as part of this
+ int addressLength = mPdu[mCur] & 0xff;
+ int lengthBytes = 2 + (addressLength + 1) / 2;
+
+ try {
+ ret = new GsmSmsAddress(mPdu, mCur, lengthBytes);
+ } catch (ParseException e) {
+ ret = null;
+ //This is caught by createFromPdu(byte[] pdu)
+ throw new RuntimeException(e.getMessage());
+ }
+
+ mCur += lengthBytes;
+
+ return ret;
+ }
+
+ /**
+ * Parses an SC timestamp and returns a currentTimeMillis()-style
+ * timestamp
+ */
+
+ long getSCTimestampMillis() {
+ // TP-Service-Centre-Time-Stamp
+ int year = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+ int month = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+ int day = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+ int hour = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+ int minute = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+ int second = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+
+ // For the timezone, the most significant bit of the
+ // least significant nibble is the sign byte
+ // (meaning the max range of this field is 79 quarter-hours,
+ // which is more than enough)
+
+ byte tzByte = mPdu[mCur++];
+
+ // Mask out sign bit.
+ int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08)));
+
+ timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset;
+
+ Time time = new Time(Time.TIMEZONE_UTC);
+
+ // It's 2006. Should I really support years < 2000?
+ time.year = year >= 90 ? year + 1900 : year + 2000;
+ time.month = month - 1;
+ time.monthDay = day;
+ time.hour = hour;
+ time.minute = minute;
+ time.second = second;
+
+ // Timezone offset is in quarter hours.
+ return time.toMillis(true) - (timezoneOffset * 15 * 60 * 1000);
+ }
+
+ /**
+ * Pulls the user data out of the PDU, and separates the payload from
+ * the header if there is one.
+ *
+ * @param hasUserDataHeader true if there is a user data header
+ * @param dataInSeptets true if the data payload is in septets instead
+ * of octets
+ * @return the number of septets or octets in the user data payload
+ */
+ int constructUserData(boolean hasUserDataHeader, boolean dataInSeptets) {
+ int offset = mCur;
+ int userDataLength = mPdu[offset++] & 0xff;
+ int headerSeptets = 0;
+ int userDataHeaderLength = 0;
+
+ if (hasUserDataHeader) {
+ userDataHeaderLength = mPdu[offset++] & 0xff;
+
+ byte[] udh = new byte[userDataHeaderLength];
+ System.arraycopy(mPdu, offset, udh, 0, userDataHeaderLength);
+ mUserDataHeader = SmsHeader.fromByteArray(udh);
+ offset += userDataHeaderLength;
+
+ int headerBits = (userDataHeaderLength + 1) * 8;
+ headerSeptets = headerBits / 7;
+ headerSeptets += (headerBits % 7) > 0 ? 1 : 0;
+ mUserDataSeptetPadding = (headerSeptets * 7) - headerBits;
+ }
+
+ int bufferLen;
+ if (dataInSeptets) {
+ /*
+ * Here we just create the user data length to be the remainder of
+ * the pdu minus the user data header, since userDataLength means
+ * the number of uncompressed septets.
+ */
+ bufferLen = mPdu.length - offset;
+ } else {
+ /*
+ * userDataLength is the count of octets, so just subtract the
+ * user data header.
+ */
+ bufferLen = userDataLength - (hasUserDataHeader ? (userDataHeaderLength + 1) : 0);
+ if (bufferLen < 0) {
+ bufferLen = 0;
+ }
+ }
+
+ mUserData = new byte[bufferLen];
+ System.arraycopy(mPdu, offset, mUserData, 0, mUserData.length);
+ mCur = offset;
+
+ if (dataInSeptets) {
+ // Return the number of septets
+ int count = userDataLength - headerSeptets;
+ // If count < 0, return 0 (means UDL was probably incorrect)
+ return count < 0 ? 0 : count;
+ } else {
+ // Return the number of octets
+ return mUserData.length;
+ }
+ }
+
+ /**
+ * Returns the user data payload, not including the headers
+ *
+ * @return the user data payload, not including the headers
+ */
+ byte[] getUserData() {
+ return mUserData;
+ }
+
+ /**
+ * Returns an object representing the user data headers
+ *
+ * {@hide}
+ */
+ SmsHeader getUserDataHeader() {
+ return mUserDataHeader;
+ }
+
+ /**
+ * Interprets the user data payload as packed GSM 7bit characters, and
+ * decodes them into a String.
+ *
+ * @param septetCount the number of septets in the user data payload
+ * @return a String with the decoded characters
+ */
+ String getUserDataGSM7Bit(int septetCount, int languageTable,
+ int languageShiftTable) {
+ String ret;
+
+ ret = GsmAlphabet.gsm7BitPackedToString(mPdu, mCur, septetCount,
+ mUserDataSeptetPadding, languageTable, languageShiftTable);
+
+ mCur += (septetCount * 7) / 8;
+
+ return ret;
+ }
+
+ /**
+ * Interprets the user data payload as pack GSM 8-bit (a GSM alphabet string that's
+ * stored in 8-bit unpacked format) characters, and decodes them into a String.
+ *
+ * @param byteCount the number of byest in the user data payload
+ * @return a String with the decoded characters
+ */
+ String getUserDataGSM8bit(int byteCount) {
+ String ret;
+
+ ret = GsmAlphabet.gsm8BitUnpackedToString(mPdu, mCur, byteCount);
+
+ mCur += byteCount;
+
+ return ret;
+ }
+
+ /**
+ * Interprets the user data payload as UCS2 characters, and
+ * decodes them into a String.
+ *
+ * @param byteCount the number of bytes in the user data payload
+ * @return a String with the decoded characters
+ */
+ String getUserDataUCS2(int byteCount) {
+ String ret;
+
+ try {
+ ret = new String(mPdu, mCur, byteCount, "utf-16");
+ } catch (UnsupportedEncodingException ex) {
+ ret = "";
+ Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
+ }
+
+ mCur += byteCount;
+ return ret;
+ }
+
+ /**
+ * Interprets the user data payload as KSC-5601 characters, and
+ * decodes them into a String.
+ *
+ * @param byteCount the number of bytes in the user data payload
+ * @return a String with the decoded characters
+ */
+ String getUserDataKSC5601(int byteCount) {
+ String ret;
+
+ try {
+ ret = new String(mPdu, mCur, byteCount, "KSC5601");
+ } catch (UnsupportedEncodingException ex) {
+ ret = "";
+ Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
+ }
+
+ mCur += byteCount;
+ return ret;
+ }
+
+ boolean moreDataPresent() {
+ return (mPdu.length > mCur);
+ }
+ }
+
+ /**
+ * Calculates the number of SMS's required to encode the message body and
+ * the number of characters remaining until the next message.
+ *
+ * @param msgBody the message to encode
+ * @param use7bitOnly ignore (but still count) illegal characters if true
+ * @return TextEncodingDetails
+ */
+ public static TextEncodingDetails calculateLength(CharSequence msgBody,
+ boolean use7bitOnly) {
+ CharSequence newMsgBody = null;
+ Resources r = Resources.getSystem();
+ if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
+ newMsgBody = Sms7BitEncodingTranslator.translate(msgBody);
+ }
+ if (TextUtils.isEmpty(newMsgBody)) {
+ newMsgBody = msgBody;
+ }
+ TextEncodingDetails ted = GsmAlphabet.countGsmSeptets(newMsgBody, use7bitOnly);
+ if (ted == null) {
+ return SmsMessageBase.calcUnicodeEncodingDetails(newMsgBody);
+ }
+ return ted;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getProtocolIdentifier() {
+ return mProtocolIdentifier;
+ }
+
+ /**
+ * Returns the TP-Data-Coding-Scheme byte, for acknowledgement of SMS-PP download messages.
+ * @return the TP-DCS field of the SMS header
+ */
+ int getDataCodingScheme() {
+ return mDataCodingScheme;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isReplace() {
+ return (mProtocolIdentifier & 0xc0) == 0x40
+ && (mProtocolIdentifier & 0x3f) > 0
+ && (mProtocolIdentifier & 0x3f) < 8;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isCphsMwiMessage() {
+ return ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageClear()
+ || ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isMWIClearMessage() {
+ if (mIsMwi && !mMwiSense) {
+ return true;
+ }
+
+ return mOriginatingAddress != null
+ && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageClear();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isMWISetMessage() {
+ if (mIsMwi && mMwiSense) {
+ return true;
+ }
+
+ return mOriginatingAddress != null
+ && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isMwiDontStore() {
+ if (mIsMwi && mMwiDontStore) {
+ return true;
+ }
+
+ if (isCphsMwiMessage()) {
+ // See CPHS 4.2 Section B.4.2.1
+ // If the user data is a single space char, do not store
+ // the message. Otherwise, store and display as usual
+ if (" ".equals(getMessageBody())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getStatus() {
+ return mStatus;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isStatusReportMessage() {
+ return mIsStatusReportMessage;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isReplyPathPresent() {
+ return mReplyPathPresent;
+ }
+
+ /**
+ * TS 27.005 3.1, <pdu> definition "In the case of SMS: 3GPP TS 24.011 [6]
+ * SC address followed by 3GPP TS 23.040 [3] TPDU in hexadecimal format:
+ * ME/TA converts each octet of TP data unit into two IRA character long
+ * hex number (e.g. octet with integer value 42 is presented to TE as two
+ * characters 2A (IRA 50 and 65))" ...in the case of cell broadcast,
+ * something else...
+ */
+ private void parsePdu(byte[] pdu) {
+ mPdu = pdu;
+ // Rlog.d(LOG_TAG, "raw sms message:");
+ // Rlog.d(LOG_TAG, s);
+
+ PduParser p = new PduParser(pdu);
+
+ mScAddress = p.getSCAddress();
+
+ if (mScAddress != null) {
+ if (VDBG) Rlog.d(LOG_TAG, "SMS SC address: " + mScAddress);
+ }
+
+ // TODO(mkf) support reply path, user data header indicator
+
+ // TP-Message-Type-Indicator
+ // 9.2.3
+ int firstByte = p.getByte();
+
+ mMti = firstByte & 0x3;
+ switch (mMti) {
+ // TP-Message-Type-Indicator
+ // 9.2.3
+ case 0:
+ case 3: //GSM 03.40 9.2.3.1: MTI == 3 is Reserved.
+ //This should be processed in the same way as MTI == 0 (Deliver)
+ parseSmsDeliver(p, firstByte);
+ break;
+ case 1:
+ parseSmsSubmit(p, firstByte);
+ break;
+ case 2:
+ parseSmsStatusReport(p, firstByte);
+ break;
+ default:
+ // TODO(mkf) the rest of these
+ throw new RuntimeException("Unsupported message type");
+ }
+ }
+
+ /**
+ * Parses a SMS-STATUS-REPORT message.
+ *
+ * @param p A PduParser, cued past the first byte.
+ * @param firstByte The first byte of the PDU, which contains MTI, etc.
+ */
+ private void parseSmsStatusReport(PduParser p, int firstByte) {
+ mIsStatusReportMessage = true;
+
+ // TP-Message-Reference
+ mMessageRef = p.getByte();
+ // TP-Recipient-Address
+ mRecipientAddress = p.getAddress();
+ // TP-Service-Centre-Time-Stamp
+ mScTimeMillis = p.getSCTimestampMillis();
+ p.getSCTimestampMillis();
+ // TP-Status
+ mStatus = p.getByte();
+
+ // The following are optional fields that may or may not be present.
+ if (p.moreDataPresent()) {
+ // TP-Parameter-Indicator
+ int extraParams = p.getByte();
+ int moreExtraParams = extraParams;
+ while ((moreExtraParams & 0x80) != 0) {
+ // We only know how to parse a few extra parameters, all
+ // indicated in the first TP-PI octet, so skip over any
+ // additional TP-PI octets.
+ moreExtraParams = p.getByte();
+ }
+ // As per 3GPP 23.040 section 9.2.3.27 TP-Parameter-Indicator,
+ // only process the byte if the reserved bits (bits3 to 6) are zero.
+ if ((extraParams & 0x78) == 0) {
+ // TP-Protocol-Identifier
+ if ((extraParams & 0x01) != 0) {
+ mProtocolIdentifier = p.getByte();
+ }
+ // TP-Data-Coding-Scheme
+ if ((extraParams & 0x02) != 0) {
+ mDataCodingScheme = p.getByte();
+ }
+ // TP-User-Data-Length (implies existence of TP-User-Data)
+ if ((extraParams & 0x04) != 0) {
+ boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
+ parseUserData(p, hasUserDataHeader);
+ }
+ }
+ }
+ }
+
+ private void parseSmsDeliver(PduParser p, int firstByte) {
+ mReplyPathPresent = (firstByte & 0x80) == 0x80;
+
+ mOriginatingAddress = p.getAddress();
+
+ if (mOriginatingAddress != null) {
+ if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: "
+ + mOriginatingAddress.address);
+ }
+
+ // TP-Protocol-Identifier (TP-PID)
+ // TS 23.040 9.2.3.9
+ mProtocolIdentifier = p.getByte();
+
+ // TP-Data-Coding-Scheme
+ // see TS 23.038
+ mDataCodingScheme = p.getByte();
+
+ if (VDBG) {
+ Rlog.v(LOG_TAG, "SMS TP-PID:" + mProtocolIdentifier
+ + " data coding scheme: " + mDataCodingScheme);
+ }
+
+ mScTimeMillis = p.getSCTimestampMillis();
+
+ if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis);
+
+ boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
+
+ parseUserData(p, hasUserDataHeader);
+ }
+
+ /**
+ * Parses a SMS-SUBMIT message.
+ *
+ * @param p A PduParser, cued past the first byte.
+ * @param firstByte The first byte of the PDU, which contains MTI, etc.
+ */
+ private void parseSmsSubmit(PduParser p, int firstByte) {
+ mReplyPathPresent = (firstByte & 0x80) == 0x80;
+
+ // TP-MR (TP-Message Reference)
+ mMessageRef = p.getByte();
+
+ mRecipientAddress = p.getAddress();
+
+ if (mRecipientAddress != null) {
+ if (VDBG) Rlog.v(LOG_TAG, "SMS recipient address: " + mRecipientAddress.address);
+ }
+
+ // TP-Protocol-Identifier (TP-PID)
+ // TS 23.040 9.2.3.9
+ mProtocolIdentifier = p.getByte();
+
+ // TP-Data-Coding-Scheme
+ // see TS 23.038
+ mDataCodingScheme = p.getByte();
+
+ if (VDBG) {
+ Rlog.v(LOG_TAG, "SMS TP-PID:" + mProtocolIdentifier
+ + " data coding scheme: " + mDataCodingScheme);
+ }
+
+ // TP-Validity-Period-Format
+ int validityPeriodLength = 0;
+ int validityPeriodFormat = ((firstByte>>3) & 0x3);
+ if (0x0 == validityPeriodFormat) /* 00, TP-VP field not present*/
+ {
+ validityPeriodLength = 0;
+ }
+ else if (0x2 == validityPeriodFormat) /* 10, TP-VP: relative format*/
+ {
+ validityPeriodLength = 1;
+ }
+ else /* other case, 11 or 01, TP-VP: absolute or enhanced format*/
+ {
+ validityPeriodLength = 7;
+ }
+
+ // TP-Validity-Period is not used on phone, so just ignore it for now.
+ while (validityPeriodLength-- > 0)
+ {
+ p.getByte();
+ }
+
+ boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
+
+ parseUserData(p, hasUserDataHeader);
+ }
+
+ /**
+ * Parses the User Data of an SMS.
+ *
+ * @param p The current PduParser.
+ * @param hasUserDataHeader Indicates whether a header is present in the
+ * User Data.
+ */
+ private void parseUserData(PduParser p, boolean hasUserDataHeader) {
+ boolean hasMessageClass = false;
+ boolean userDataCompressed = false;
+
+ int encodingType = ENCODING_UNKNOWN;
+
+ // Look up the data encoding scheme
+ if ((mDataCodingScheme & 0x80) == 0) {
+ userDataCompressed = (0 != (mDataCodingScheme & 0x20));
+ hasMessageClass = (0 != (mDataCodingScheme & 0x10));
+
+ if (userDataCompressed) {
+ Rlog.w(LOG_TAG, "4 - Unsupported SMS data coding scheme "
+ + "(compression) " + (mDataCodingScheme & 0xff));
+ } else {
+ switch ((mDataCodingScheme >> 2) & 0x3) {
+ case 0: // GSM 7 bit default alphabet
+ encodingType = ENCODING_7BIT;
+ break;
+
+ case 2: // UCS 2 (16bit)
+ encodingType = ENCODING_16BIT;
+ break;
+
+ case 1: // 8 bit data
+ //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string
+ //that's stored in 8-bit unpacked format) characters.
+ Resources r = Resources.getSystem();
+ if (r.getBoolean(com.android.internal.
+ R.bool.config_sms_decode_gsm_8bit_data)) {
+ encodingType = ENCODING_8BIT;
+ break;
+ }
+
+ case 3: // reserved
+ Rlog.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
+ + (mDataCodingScheme & 0xff));
+ encodingType = ENCODING_8BIT;
+ break;
+ }
+ }
+ } else if ((mDataCodingScheme & 0xf0) == 0xf0) {
+ hasMessageClass = true;
+ userDataCompressed = false;
+
+ if (0 == (mDataCodingScheme & 0x04)) {
+ // GSM 7 bit default alphabet
+ encodingType = ENCODING_7BIT;
+ } else {
+ // 8 bit data
+ encodingType = ENCODING_8BIT;
+ }
+ } else if ((mDataCodingScheme & 0xF0) == 0xC0
+ || (mDataCodingScheme & 0xF0) == 0xD0
+ || (mDataCodingScheme & 0xF0) == 0xE0) {
+ // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
+
+ // 0xC0 == 7 bit, don't store
+ // 0xD0 == 7 bit, store
+ // 0xE0 == UCS-2, store
+
+ if ((mDataCodingScheme & 0xF0) == 0xE0) {
+ encodingType = ENCODING_16BIT;
+ } else {
+ encodingType = ENCODING_7BIT;
+ }
+
+ userDataCompressed = false;
+ boolean active = ((mDataCodingScheme & 0x08) == 0x08);
+ // bit 0x04 reserved
+
+ // VM - If TP-UDH is present, these values will be overwritten
+ if ((mDataCodingScheme & 0x03) == 0x00) {
+ mIsMwi = true; /* Indicates vmail */
+ mMwiSense = active;/* Indicates vmail notification set/clear */
+ mMwiDontStore = ((mDataCodingScheme & 0xF0) == 0xC0);
+
+ /* Set voice mail count based on notification bit */
+ if (active == true) {
+ mVoiceMailCount = -1; // unknown number of messages waiting
+ } else {
+ mVoiceMailCount = 0; // no unread messages
+ }
+
+ Rlog.w(LOG_TAG, "MWI in DCS for Vmail. DCS = "
+ + (mDataCodingScheme & 0xff) + " Dont store = "
+ + mMwiDontStore + " vmail count = " + mVoiceMailCount);
+
+ } else {
+ mIsMwi = false;
+ Rlog.w(LOG_TAG, "MWI in DCS for fax/email/other: "
+ + (mDataCodingScheme & 0xff));
+ }
+ } else if ((mDataCodingScheme & 0xC0) == 0x80) {
+ // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
+ // 0x80..0xBF == Reserved coding groups
+ if (mDataCodingScheme == 0x84) {
+ // This value used for KSC5601 by carriers in Korea.
+ encodingType = ENCODING_KSC5601;
+ } else {
+ Rlog.w(LOG_TAG, "5 - Unsupported SMS data coding scheme "
+ + (mDataCodingScheme & 0xff));
+ }
+ } else {
+ Rlog.w(LOG_TAG, "3 - Unsupported SMS data coding scheme "
+ + (mDataCodingScheme & 0xff));
+ }
+
+ // set both the user data and the user data header.
+ int count = p.constructUserData(hasUserDataHeader,
+ encodingType == ENCODING_7BIT);
+ this.mUserData = p.getUserData();
+ this.mUserDataHeader = p.getUserDataHeader();
+
+ /*
+ * Look for voice mail indication in TP_UDH TS23.040 9.2.3.24
+ * ieid = 1 (0x1) (SPECIAL_SMS_MSG_IND)
+ * ieidl =2 octets
+ * ieda msg_ind_type = 0x00 (voice mail; discard sms )or
+ * = 0x80 (voice mail; store sms)
+ * msg_count = 0x00 ..0xFF
+ */
+ if (hasUserDataHeader && (mUserDataHeader.specialSmsMsgList.size() != 0)) {
+ for (SmsHeader.SpecialSmsMsg msg : mUserDataHeader.specialSmsMsgList) {
+ int msgInd = msg.msgIndType & 0xff;
+ /*
+ * TS 23.040 V6.8.1 Sec 9.2.3.24.2
+ * bits 1 0 : basic message indication type
+ * bits 4 3 2 : extended message indication type
+ * bits 6 5 : Profile id bit 7 storage type
+ */
+ if ((msgInd == 0) || (msgInd == 0x80)) {
+ mIsMwi = true;
+ if (msgInd == 0x80) {
+ /* Store message because TP_UDH indicates so*/
+ mMwiDontStore = false;
+ } else if (mMwiDontStore == false) {
+ /* Storage bit is not set by TP_UDH
+ * Check for conflict
+ * between message storage bit in TP_UDH
+ * & DCS. The message shall be stored if either of
+ * the one indicates so.
+ * TS 23.040 V6.8.1 Sec 9.2.3.24.2
+ */
+ if (!((((mDataCodingScheme & 0xF0) == 0xD0)
+ || ((mDataCodingScheme & 0xF0) == 0xE0))
+ && ((mDataCodingScheme & 0x03) == 0x00))) {
+ /* Even DCS did not have voice mail with Storage bit
+ * 3GPP TS 23.038 V7.0.0 section 4
+ * So clear this flag*/
+ mMwiDontStore = true;
+ }
+ }
+
+ mVoiceMailCount = msg.msgCount & 0xff;
+
+ /*
+ * In the event of a conflict between message count setting
+ * and DCS then the Message Count in the TP-UDH shall
+ * override the indication in the TP-DCS. Set voice mail
+ * notification based on count in TP-UDH
+ */
+ if (mVoiceMailCount > 0)
+ mMwiSense = true;
+ else
+ mMwiSense = false;
+
+ Rlog.w(LOG_TAG, "MWI in TP-UDH for Vmail. Msg Ind = " + msgInd
+ + " Dont store = " + mMwiDontStore + " Vmail count = "
+ + mVoiceMailCount);
+
+ /*
+ * There can be only one IE for each type of message
+ * indication in TP_UDH. In the event they are duplicated
+ * last occurence will be used. Hence the for loop
+ */
+ } else {
+ Rlog.w(LOG_TAG, "TP_UDH fax/email/"
+ + "extended msg/multisubscriber profile. Msg Ind = " + msgInd);
+ }
+ } // end of for
+ } // end of if UDH
+
+ switch (encodingType) {
+ case ENCODING_UNKNOWN:
+ mMessageBody = null;
+ break;
+
+ case ENCODING_8BIT:
+ //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string
+ //that's stored in 8-bit unpacked format) characters.
+ Resources r = Resources.getSystem();
+ if (r.getBoolean(com.android.internal.
+ R.bool.config_sms_decode_gsm_8bit_data)) {
+ mMessageBody = p.getUserDataGSM8bit(count);
+ } else {
+ mMessageBody = null;
+ }
+ break;
+
+ case ENCODING_7BIT:
+ mMessageBody = p.getUserDataGSM7Bit(count,
+ hasUserDataHeader ? mUserDataHeader.languageTable : 0,
+ hasUserDataHeader ? mUserDataHeader.languageShiftTable : 0);
+ break;
+
+ case ENCODING_16BIT:
+ mMessageBody = p.getUserDataUCS2(count);
+ break;
+
+ case ENCODING_KSC5601:
+ mMessageBody = p.getUserDataKSC5601(count);
+ break;
+ }
+
+ if (VDBG) Rlog.v(LOG_TAG, "SMS message body (raw): '" + mMessageBody + "'");
+
+ if (mMessageBody != null) {
+ parseMessageBody();
+ }
+
+ if (!hasMessageClass) {
+ messageClass = MessageClass.UNKNOWN;
+ } else {
+ switch (mDataCodingScheme & 0x3) {
+ case 0:
+ messageClass = MessageClass.CLASS_0;
+ break;
+ case 1:
+ messageClass = MessageClass.CLASS_1;
+ break;
+ case 2:
+ messageClass = MessageClass.CLASS_2;
+ break;
+ case 3:
+ messageClass = MessageClass.CLASS_3;
+ break;
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MessageClass getMessageClass() {
+ return messageClass;
+ }
+
+ /**
+ * Returns true if this is a (U)SIM data download type SM.
+ * See 3GPP TS 31.111 section 9.1 and TS 23.040 section 9.2.3.9.
+ *
+ * @return true if this is a USIM data download message; false otherwise
+ */
+ boolean isUsimDataDownload() {
+ return messageClass == MessageClass.CLASS_2 &&
+ (mProtocolIdentifier == 0x7f || mProtocolIdentifier == 0x7c);
+ }
+
+ public int getNumOfVoicemails() {
+ /*
+ * Order of priority if multiple indications are present is 1.UDH,
+ * 2.DCS, 3.CPHS.
+ * Voice mail count if voice mail present indication is
+ * received
+ * 1. UDH (or both UDH & DCS): mVoiceMailCount = 0 to 0xff. Ref[TS 23. 040]
+ * 2. DCS only: count is unknown mVoiceMailCount= -1
+ * 3. CPHS only: count is unknown mVoiceMailCount = 0xff. Ref[GSM-BTR-1-4700]
+ * Voice mail clear, mVoiceMailCount = 0.
+ */
+ if ((!mIsMwi) && isCphsMwiMessage()) {
+ if (mOriginatingAddress != null
+ && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet()) {
+ mVoiceMailCount = 0xff;
+ } else {
+ mVoiceMailCount = 0;
+ }
+ Rlog.v(LOG_TAG, "CPHS voice mail message");
+ }
+ return mVoiceMailCount;
+ }
+}