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

Commit cd964150 authored by Steve Kondik's avatar Steve Kondik Committed by Gerrit Code Review
Browse files

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

parents 4eb2ebe1 2d042a67
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -557,6 +557,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. The intent will have the following extra
+269 −0
Original line number Diff line number Diff line
@@ -28,6 +28,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.PackageInfo;
import android.content.pm.PackageManager;
@@ -49,10 +50,13 @@ 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.EventLog;
import android.util.Log;
import android.telephony.Rlog;
import android.view.LayoutInflater;
import android.view.View;
@@ -69,8 +73,11 @@ import com.android.internal.telephony.GsmAlphabet.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.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.HashMap;
import java.util.Random;
@@ -154,6 +161,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. */
@@ -221,6 +230,10 @@ public abstract class SMSDispatcher extends Handler {
        mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
                Settings.Global.SMS_SHORT_CODE_RULE), false, mSettingsObserver);

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

        createWakelock();

        mSmsCapable = mContext.getResources().getBoolean(
@@ -277,6 +290,7 @@ public abstract class SMSDispatcher extends Handler {
    @Override
    protected void finalize() {
        Rlog.d(TAG, "SMSDispatcher finalized");
        mMockSmsReceiver.unregisterReceiver();
    }


@@ -1519,4 +1533,259 @@ public abstract class SMSDispatcher extends Handler {
            dispatch(intent, RECEIVE_SMS_PERMISSION, AppOpsManager.OP_RECEIVE_SMS);
        }
    }

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

            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
                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) {
                    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,
                            AppOpsManager.OP_RECEIVE_SMS);
                }

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