Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6f027d2b authored by Jorge Ruesga's avatar Jorge Ruesga Committed by Gerrit Code Review
Browse files

Framework Base: Allow Mock SMS (1/2)

This change brings the possibility of simulate the reception of SMS on real devices. Fow now,
the only way of simulate the reception of SMS is using the android emulator, but this hasn't have
a CM10 image.

This change adds a MockSmsReceiver inside SMSDispatcher that have a BroadcastReceiver listening
for Intents.MOCK_SMS_RECEIVED_ACTION action. This class accept the SMS simulation if and only if
Settings.Secure.ALLOW_MOCK_SMS is enabled. The application that sends the mock SMS
requires the "android.permission.SEND_MOCK_SMS" permission.

This receiver should be used in the next way:

Intent in = new Intent(Intents.MOCK_SMS_RECEIVED_ACTION);
in.putExtra("scAddr", "+01123456789");
in.putExtra("senderAddr", "+01123456789");
in.putExtra("msg", "This is a mock SMS message.");
sendBroadcast(in);

or

String pdu = "07914151551512f2040B916105551511f100006060605130308A04D4F29C0E";
byte[][] pdus = new byte[1][];
pdus[0] = HexDump.hexStringToByteArray(pdu);
Intent in = new Intent(Intents.MOCK_SMS_RECEIVED_ACTION);
intent.putExtra("pdus", pdus);
sendBroadcast(in);

Patch 2: * Protect the mock SMS BroadcastReceiver with the permission "android.permission.SEND_MOCK_SMS".
           This permission is required for send mock SMSs, in addition to ALLOW_MOCK_SMS system setting.
         * Add result to ordered broadcast requests.
             Activity.RESULT_OK -> Ok. At least one message was sent
             Activity.RESULT_CANCELED -> Error. No messages were sent
Patch 3: English typos
Patch 4: Add Marco Brohet's suggestion

Change-Id: I5322cf748449a863ea8e8deda977108b9b58a413
parent 4a8249cf
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import android.util.AndroidException;
import android.util.Log;
import android.view.WindowOrientationListener;

import com.android.internal.telephony.SMSDispatcher;
import com.android.internal.widget.ILockSettings;

import java.net.URISyntaxException;
@@ -3442,6 +3443,12 @@ public final class Settings {
         */
        public static final String ALLOW_MOCK_LOCATION = "mock_location";

        /**
         * Setting to allow the use of {@link SMSDispatcher#MockSmsReceiver} to simulate
         * the reception of SMS for testing purposes during application development.
         */
        public static final String ALLOW_MOCK_SMS = "mock_sms";

        /**
         * A 64-bit number (as a hex string) that is randomly
         * generated on the device's first boot and should remain
@@ -5226,6 +5233,7 @@ public final class Settings {
        public static final String[] SETTINGS_TO_BACKUP = {
            ADB_ENABLED,
            ALLOW_MOCK_LOCATION,
            ALLOW_MOCK_SMS,
            PARENTAL_CONTROL_ENABLED,
            PARENTAL_CONTROL_REDIRECT_URL,
            USB_MASS_STORAGE_ENABLED,
+22 −0
Original line number Diff line number Diff line
@@ -541,6 +541,28 @@ public final class Telephony {
            public static final String SMS_RECEIVED_ACTION =
                    "android.provider.Telephony.SMS_RECEIVED";

            /**
             * Broadcast Action: A new text based mock SMS message has been received
             * by the device. For development purpose only. The intent will have the
             * following extra values:</p>
             *
             * <ul>
             *   <li><em>pdus</em> - An Object[] od byte[]s containing the PDUs
             *   that make up the message.</li>
             * </ul></p>
             * or</p>
             * <ul>
             *   <li><em>scAddress</em> - The mock SC address. xe: +01123456789.</li>
             *   <li><em>senderAddr</em> - The mock sender address. xe: +01123456789.</li>
             *   <li><em>msg</em> - The mock message. Multiple SMS are sent if the
             *   length of the message exceed the SMS maximum length.</li>
             * </ul>
             * @hide
             */
            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
            public static final String MOCK_SMS_RECEIVED_ACTION =
                    "android.provider.Telephony.MOCK_SMS_RECEIVED";

            /**
             * Broadcast Action: A new data based SMS message has been received
             * by the device. The intent will have the following extra
+7 −0
Original line number Diff line number Diff line
@@ -156,6 +156,13 @@
        android:label="@string/permlab_sendSms"
        android:description="@string/permdesc_sendSms" />

    <!-- Allows an application to send mock SMS messages. -->
    <permission android:name="android.permission.SEND_MOCK_SMS"
        android:permissionGroup="android.permission-group.MESSAGES"
        android:protectionLevel="dangerous"
        android:label="@string/permlab_sendMockSms"
        android:description="@string/permdesc_sendMockSms" />

    <!-- Allows an application to send SMS messages via the Messaging app with no user
         input or confirmation.
         @hide -->
+7 −0
Original line number Diff line number Diff line
@@ -549,6 +549,13 @@
     This may result in unexpected charges. Malicious apps may cost you money by
     sending messages without your confirmation.</string>

    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
    <string name="permlab_sendMockSms">Send mock SMS messages</string>
    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
    <string name="permdesc_sendMockSms">Allows the app to send mock SMS messages.
     This allows an app send SMS to trusted applications. Malicious apps could send messages
     continuously, blocking the device notification system and disrupting the user.</string>

     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
    <string name="permlab_sendSmsNoConfirmation">send SMS messages with no confirmation</string>
    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+259 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -38,15 +39,18 @@ import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.os.SystemProperties;
import android.provider.Settings;
import android.provider.Telephony;
import android.provider.Telephony.Sms.Intents;
import android.telephony.PhoneNumberUtils;
import android.telephony.ServiceState;
import android.telephony.SmsCbMessage;
import android.telephony.SmsMessage;
import android.telephony.SmsMessage.SubmitPdu;
import android.telephony.TelephonyManager;
import android.text.Html;
import android.text.Spanned;
import android.text.TextUtils;
import android.util.Log;
import android.view.WindowManager;

@@ -55,9 +59,12 @@ import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
import com.android.internal.util.HexDump;

import java.io.ByteArrayOutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Random;

import static android.telephony.SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE;
@@ -125,6 +132,8 @@ public abstract class SMSDispatcher extends Handler {

    protected final WapPushOverSms mWapPush;

    private final MockSmsReceiver mMockSmsReceiver;

    protected static final Uri mRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw");

    /** Maximum number of times to retry sending a failed SMS. */
@@ -188,6 +197,10 @@ public abstract class SMSDispatcher extends Handler {
        mUsageMonitor = usageMonitor;
        mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);

        // Register the mock SMS receiver to simulate the reception of SMS
        mMockSmsReceiver = new MockSmsReceiver();
        mMockSmsReceiver.registerReceiver();

        createWakelock();

        mSmsCapable = mContext.getResources().getBoolean(
@@ -222,6 +235,7 @@ public abstract class SMSDispatcher extends Handler {

    @Override
    protected void finalize() {
        mMockSmsReceiver.unregisterReceiver();
        Log.d(TAG, "SMSDispatcher finalized");
    }

@@ -1166,4 +1180,249 @@ public abstract class SMSDispatcher extends Handler {
            dispatch(intent, RECEIVE_SMS_PERMISSION);
        }
    }

    /**
     * A private class that allow simulating the receive of SMS.<br/>
     * <br/>
     * A developer must use {@link Context#sendBroadcast(Intent)}, using the action
     * {@link Intents#MOCK_SMS_RECEIVED_ACTION}. The application requires
     * {@linkplain "android.permission.SEND_MOCK_SMS"} permission.<br/>
     * <br/>
     * This receiver should be used in the next way:<br/>
     * <pre>
     * Intent in = new Intent(Intents.MOCK_SMS_RECEIVED_ACTION);
     * in.putExtra("scAddr", "+01123456789");
     * in.putExtra("senderAddr", "+01123456789");
     * in.putExtra("msg", "This is a mock SMS message.");
     * sendBroadcast(in);
     * </pre><br/>
     * or<br/>
     * <pre>
     * String pdu = "07914151551512f2040B916105551511f100006060605130308A04D4F29C0E";
     * byte[][] pdus = new byte[1][];
     * pdus[0] = HexDump.hexStringToByteArray(pdu);
     * Intent in = new Intent(Intents.MOCK_SMS_RECEIVED_ACTION);
     * intent.putExtra("pdus", pdus);
     * sendBroadcast(in);
     * </pre><br/>
     */
    private final class MockSmsReceiver extends BroadcastReceiver {
        private static final String TAG = "MockSmsReceiver";

        private static final String SEND_MOCK_SMS_PERMISSION =
                                        "android.permission.SEND_MOCK_SMS";

        /**
         * Method that register the MockSmsReceiver class as a BroadcastReceiver
         */
        public final void registerReceiver() {
            try {
                Handler handler = new Handler();
                IntentFilter filter = new IntentFilter();
                filter.addAction(Intents.MOCK_SMS_RECEIVED_ACTION);
                mContext.registerReceiver(this, filter, SEND_MOCK_SMS_PERMISSION, handler);
                Log.d(TAG, "Registered MockSmsReceiver");
            } catch (Exception ex) {
                Log.e(TAG, "Failed to register MockSmsReceiver", ex);
            }
        }

        /**
         * Method that unregister the MockSmsReceiver class as a BroadcastReceiver
         */
        public final void unregisterReceiver() {
            try {
                mContext.unregisterReceiver(this);
            } catch (Exception ex) {
                Log.e(TAG, "Failed to unregister MockSmsReceiver", ex);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public final void onReceive(Context context, Intent intent) {
            Log.d(TAG, "New mock SMS reception request. Intent: " + intent);

            try {
                // Check that developer option is enabled, and mock
                // messages are allowed
                boolean allowMockSMS = Settings.Secure.getInt(mContext.getContentResolver(),
                        Settings.Secure.ALLOW_MOCK_SMS, 0) == 1;
                if (!allowMockSMS) {
                    // Mock SMS is not allowed. This
                    Log.w(TAG,
                            "Mock SMS is not allowed. Enable Mock SMS on Settings/Delevelopment.");
                    return;
                }

                // Extract PDUs
                byte[][] pdus = null;
                Object[] messages = (Object[]) intent.getSerializableExtra("pdus");
                if (messages != null && messages.length > 0) {
                    // Use the PDUs from the intent
                    pdus = new byte[messages.length][];
                    for (int i = 0; i < messages.length; i++) {
                        pdus[i] = (byte[]) messages[i];
                    }

                } else {
                    // Build the PDUs from SMS data
                    String scAddress = intent.getStringExtra("scAddr");
                    String senderAddress = intent.getStringExtra("senderAddr");
                    String msg = intent.getStringExtra("msg");

                    // Check that values are valid. Otherwise fill will default values
                    if (TextUtils.isEmpty(scAddress)) {
                        scAddress = "+01123456789";
                    }
                    if (TextUtils.isEmpty(senderAddress)) {
                        senderAddress = "+01123456789";
                    }
                    if (TextUtils.isEmpty(msg)) {
                        msg = "This is a mock SMS message.";
                    }
                    Log.d(TAG,
                            String.format(
                                    "Mock SMS. scAddress: %s, senderAddress: %s, msg: %s",
                                    scAddress, senderAddress, msg));

                    // Create the PDUs
                    pdus = getPdus(scAddress, senderAddress, msg);
                }

                // How messages are going to send?
                Log.d(TAG,
                        String.format(
                                "Mock SMS. Number of msg: %d",
                                pdus.length));

                // Send messages
                Intent mockSmsIntent = new Intent(Intents.SMS_RECEIVED_ACTION);
                mockSmsIntent.putExtra("pdus", pdus );
                mockSmsIntent.putExtra("format", android.telephony.SmsMessage.FORMAT_3GPP );
                dispatch(mockSmsIntent, SMSDispatcher.RECEIVE_SMS_PERMISSION);

                // Set result (messages were sent)
                setResultCode(android.app.Activity.RESULT_OK);

            } catch (Exception ex) {
                Log.e(TAG, "Failed to dispatch SMS", ex);

                // Set result (messages were not sent)
                setResultCode(android.app.Activity.RESULT_CANCELED);
            }
        }

        /**
         * Method that convert the basic SMS string data to a PDUs messages
         *
         * @param scAddress The mock the SC address
         * @param senderAddress The mock the sender address
         * @param message The mock message body
         * @return byte[] The array of bytes of the PDU
         */
        private byte[][] getPdus(String scAddress, String senderAddress, String message) {

            // Fragment the text in message according to SMS length
            List<String> msgs = android.telephony.SmsMessage.fragmentText(message);

            // Create the array of PDUs to return
            byte[][] pdus = new byte[msgs.size()][];

            // Retrieve a PDU for every fragmented message
            for (int i=0; i<msgs.size(); i++) {
                String msg = msgs.get(i);

                // Get a SubmitPdu
                SubmitPdu submitPdu =
                        android.telephony.SmsMessage.getSubmitPdu(
                                                            scAddress,
                                                            senderAddress,
                                                            msg,
                                                            false);

                // Translate the submit data to a received PDU
                int dataLen = android.telephony.SmsMessage.calculateLength(msg, true)[1];

                // Locate protocol + data encoding scheme
                byte[] pds = {(byte)0, (byte)0, (byte)dataLen};
                int dataPos = new String(submitPdu.encodedMessage).indexOf(new String(pds), 4) + 2;

                // Set arrays dimension
                byte[] encSc = submitPdu.encodedScAddress;
                byte[] encMsg = new byte[submitPdu.encodedMessage.length - dataPos];
                System.arraycopy(
                        submitPdu.encodedMessage, dataPos,
                        encMsg, 0, submitPdu.encodedMessage.length - dataPos);
                byte[] encSender = new byte[dataPos - 4];
                System.arraycopy(
                        submitPdu.encodedMessage, 2,
                        encSender, 0, dataPos - 4);
                byte[] encTs = bcdTimestamp();
                byte[] pdu = new byte[
                                      encSc.length +
                                      1 +       /** SMS-DELIVER **/
                                      encSender.length +
                                      2 +       /** Protocol + Data Encoding Scheme **/
                                      encTs.length +
                                      encMsg.length];

                // Copy the SC address
                int c = 0;
                System.arraycopy(encSc, 0, pdu, c, encSc.length);
                c+=encSc.length;
                // SMS-DELIVER
                pdu[c] = 0x04;
                c++;
                // Sender
                System.arraycopy(encSender, 0, pdu, c, encSender.length);
                c+=encSender.length;
                // Protocol + Data encoding scheme
                pdu[c] = 0x00;
                c++;
                pdu[c] = 0x00;
                c++;
                // Timestamp
                System.arraycopy(encTs, 0, pdu, c, encTs.length);
                c+=encTs.length;
                // Message
                System.arraycopy(encMsg, 0, pdu, c, encMsg.length);

                // Store the PDU
                pdus[i] = pdu;
            }

            // Return the PDUs
            return pdus;
        }

        /**
         * Method that return the current timestamp in a BCD format
         *
         * @return byte[] The BCD timestamp
         */
        private byte[] bcdTimestamp() {
            Calendar c = Calendar.getInstance();
            SimpleDateFormat sdf = new SimpleDateFormat("yy"); //$NON-NLS-1$
            SimpleDateFormat sdf2 = new SimpleDateFormat("Z"); //$NON-NLS-1$
            byte year = (byte)Integer.parseInt(
                            String.valueOf(Integer.parseInt(sdf.format(c.getTime()))), 16);
            byte month = (byte)Integer.parseInt(String.valueOf(c.get(Calendar.MONTH) + 1), 16);
            byte day = (byte)Integer.parseInt(String.valueOf(c.get(Calendar.DAY_OF_MONTH)), 16);
            byte hour = (byte)Integer.parseInt(String.valueOf(c.get(Calendar.HOUR)), 16);
            byte minute = (byte)Integer.parseInt(String.valueOf(c.get(Calendar.MINUTE)), 16);
            byte second = (byte)Integer.parseInt(String.valueOf(c.get(Calendar.SECOND)), 16);
            String tz = sdf2.format(c.getTime()).substring(1);
            int timezone = Integer.parseInt(tz) / 100;
            if (timezone < 0) {
                timezone += 0x80;
            }
            byte[] data = {year, month, day, hour, minute, second, 0};
            byte[] ts = IccUtils.hexStringToBytes(IccUtils.bcdToString(data, 0, data.length));
            ts[6] = (byte)Integer.parseInt(String.valueOf(timezone), 16);
            return ts;
        }
    }
}