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

Commit ad87897f authored by Danny Baumann's avatar Danny Baumann Committed by Gerrit Code Review
Browse files

Merge "Telephony: Mock SMS (2/4)" into cm-11.0

parents 4443722c de393c76
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -923,6 +923,28 @@ public final class Telephony {
            public static final String DATA_SMS_RECEIVED_ACTION =
                    "android.intent.action.DATA_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 WAP PUSH message has been received by the
             * device. This intent will only be delivered to the default
+303 −0
Original line number Diff line number Diff line
@@ -20,16 +20,34 @@ package com.android.internal.telephony;

import static android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;

import android.Manifest;
import android.app.Activity;
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
import android.provider.Settings;
import android.provider.Telephony.Sms.Intents;
import android.telephony.PhoneNumberUtils;
import android.telephony.Rlog;
import android.telephony.TelephonyManager;
import android.telephony.SmsMessage.SubmitPdu;
import android.text.TextUtils;
import android.util.Log;

import com.android.internal.R;
import com.android.internal.telephony.cdma.CdmaSMSDispatcher;
@@ -47,6 +65,8 @@ public class ImsSMSDispatcher extends SMSDispatcher {
    protected GsmInboundSmsHandler mGsmInboundSmsHandler;
    protected CdmaInboundSmsHandler mCdmaInboundSmsHandler;

    private MockSmsDispatcher mMockSmsDispatcher;


    /** true if IMS is registered and sms is supported, false otherwise.*/
    private boolean mIms = false;
@@ -83,6 +103,10 @@ public class ImsSMSDispatcher extends SMSDispatcher {
        Thread broadcastThread = new Thread(new SmsBroadcastUndelivered(phone.getContext(),
                mGsmInboundSmsHandler, mCdmaInboundSmsHandler));
        broadcastThread.start();

        // Register the mock SMS receiver to simulate the reception of SMS
        mMockSmsDispatcher = new MockSmsDispatcher();
        mMockSmsDispatcher.registerReceiver();
    }


@@ -104,6 +128,7 @@ public class ImsSMSDispatcher extends SMSDispatcher {
        mCdmaDispatcher.dispose();
        mGsmInboundSmsHandler.dispose();
        mCdmaInboundSmsHandler.dispose();
        mMockSmsDispatcher.unregisterReceiver();
    }

    /**
@@ -412,4 +437,282 @@ public class ImsSMSDispatcher extends SMSDispatcher {
        }
        return true;
    }


    /**
     * 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 MockSmsDispatcher extends BroadcastReceiver {
        private static final String TAG = "MockSmsReceiver";

        private static final String MOCK_ADDRESS = "+01123456789";

        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);
            String action = intent.getAction();
            if (!Intents.MOCK_SMS_RECEIVED_ACTION.equals(action)) {
                return;
            }

            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.
                    Log.w(TAG, "Mock SMS is not allowed. Enable Mock SMS on " +
                            "Settings/Delevelopment.");
                    return;
                }

                // Check that the device supports the telephony subsystem
                PackageManager packageManager = mContext.getPackageManager();
                if (!packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
                    // Telephony is not supported
                    Log.w(TAG, "Mock SMS is not allowed because telephony is not supported.");
                    return;
                }

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

                } 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 = MOCK_ADDRESS;
                    }
                    if (TextUtils.isEmpty(senderAddress)) {
                        senderAddress = MOCK_ADDRESS;
                    }
                    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));

                    // Fragment the text in message according to SMS length
                    List<String> fragmentMsgs = android.telephony.SmsMessage.fragmentText(msg);
                    for (String fragmentMsg : fragmentMsgs) {
                        msgs.add(getPdus(scAddress, senderAddress, fragmentMsg));
                    }
                }

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

                // Send messages
                for (byte[][] pdus : msgs) {
                    dispatch(pdus, android.telephony.SmsMessage.FORMAT_3GPP);
                }

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

        private void dispatch(byte[][] pdus, String format) {
            Intent intent = new Intent(Intents.SMS_DELIVER_ACTION);

            // Direct the intent to only the default SMS app. If we can't find a default SMS app
            // then sent it to all broadcast receivers.
            ComponentName componentName = SmsApplication.getDefaultSmsApplication(mContext, true);
            if (componentName != null) {
                // Deliver SMS message only to this receiver
                intent.setComponent(componentName);
                Log.w(TAG, "Delivering SMS to: " + componentName.getPackageName() +
                        " " + componentName.getClassName());
            }

            intent.putExtra("pdus", pdus);
            intent.putExtra("format", format);
            intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
            mContext.sendOrderedBroadcast(intent, Manifest.permission.RECEIVE_SMS,
                    AppOpsManager.OP_RECEIVE_SMS, null, null, Activity.RESULT_OK, null, null);
        }

        /**
         * 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 msg The mock message body
         * @return byte[] The array of bytes of the PDU
         */
        private byte[][] getPdus(String scAddress, String senderAddress, String msg) {

            // Get a SubmitPdu (use a phone number to get a valid pdu)
            SubmitPdu submitPdu =
                    android.telephony.SmsMessage.getSubmitPdu(
                                                        scAddress,
                                                        MOCK_ADDRESS,
                                                        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 = null;
            // Check if the senderAddress is a vanish number
            if (!PhoneNumberUtils.isWellFormedSmsAddress(senderAddress)) {
                try {
                    byte[] sender7BitPacked = GsmAlphabet.stringToGsm7BitPacked(senderAddress);
                    encSender = new byte[2 + sender7BitPacked.length - 1];
                    encSender[0] = (byte)((sender7BitPacked.length - 1) * 2);
                    encSender[1] = (byte)0xD0; // Alphabetic sender
                    System.arraycopy(sender7BitPacked, 1, encSender, 2, sender7BitPacked.length - 1);
                } catch (EncodeException e) {
                    Log.e(TAG, "Failed to decode sender address. Using default.", e);
                    encSender = new byte[dataPos - 4];
                    System.arraycopy(
                            submitPdu.encodedMessage, 2,
                            encSender, 0, dataPos - 4);
                }
            } else {
                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);

            // Return the PDUs
            return new byte[][]{pdu};
        }

        /**
         * 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;
        }
    }
}