turn SMS back on, and modify to send/receive "data" SMS rather than

text.  This works between two t-mobile devices without filling the
kitkat one's message box. TBD: does it work on CDMA?
This commit is contained in:
Eric House 2013-11-23 19:02:57 -08:00
parent 97fbbf8897
commit 75b976449f
4 changed files with 219 additions and 35 deletions

View file

@ -206,6 +206,14 @@
<intent-filter android:priority="999" > <intent-filter android:priority="999" >
<action android:name="android.provider.Telephony.SMS_RECEIVED" /> <action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.DATA_SMS_RECEIVED" />
<data android:scheme="sms" />
<data android:port="3344" />
<data android:host="*" />
</intent-filter>
</receiver> </receiver>
<service android:name="SMSService"/> <service android:name="SMSService"/>

View file

@ -33,19 +33,27 @@ public class SMSReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive( Context context, Intent intent ) public void onReceive( Context context, Intent intent )
{ {
String action = intent.getAction();
Bundle bundle = intent.getExtras(); Bundle bundle = intent.getExtras();
DbgUtils.logf( "onReceive: action=%s", action );
if ( null != bundle ) { if ( null != bundle ) {
boolean isData =
action.equals("android.intent.action.DATA_SMS_RECEIVED");
boolean isMine = false; boolean isMine = false;
Object[] pdus = (Object[])bundle.get( "pdus" ); Object[] pdus = (Object[])bundle.get( "pdus" );
SmsMessage[] smses = new SmsMessage[pdus.length]; SmsMessage[] smses = new SmsMessage[pdus.length];
for ( int ii = 0; ii < pdus.length; ++ii ) { for ( int ii = 0; ii < pdus.length; ++ii ) {
SmsMessage sms = SmsMessage.createFromPdu((byte[])pdus[ii]); SmsMessage sms = SmsMessage.createFromPdu((byte[])pdus[ii]);
String phone = sms.getOriginatingAddress();
if ( isData ) {
byte[] body = sms.getUserData();
SMSService.handleFrom( context, body, phone );
} else {
String body = sms.getMessageBody(); String body = sms.getMessageBody();
String postDetectable = SMSService.fromPublicFmt( body ); String postDetectable = SMSService.fromPublicFmt( body );
isMine = null != postDetectable; isMine = null != postDetectable;
if ( isMine ) { if ( isMine ) {
String phone = sms.getOriginatingAddress();
DbgUtils.logf( "SMSReceiver: \"%s\" from %s", DbgUtils.logf( "SMSReceiver: \"%s\" from %s",
body, phone ); body, phone );
SMSService.handleFrom( context, postDetectable, phone ); SMSService.handleFrom( context, postDetectable, phone );
@ -59,3 +67,4 @@ public class SMSReceiver extends BroadcastReceiver {
} }
} }
} }
}

View file

@ -55,12 +55,14 @@ public class SMSService extends XWService {
private static final String INSTALL_URL = "http://eehouse.org/_/a.py/a "; private static final String INSTALL_URL = "http://eehouse.org/_/a.py/a ";
private static final int MAX_SMS_LEN = 140; // ??? differs by network private static final int MAX_SMS_LEN = 140; // ??? differs by network
private static final boolean s_asData = true;
private static final String MSG_SENT = "MSG_SENT"; private static final String MSG_SENT = "MSG_SENT";
private static final String MSG_DELIVERED = "MSG_DELIVERED"; private static final String MSG_DELIVERED = "MSG_DELIVERED";
private static final int SMS_PROTO_VERSION = 0; private static final int SMS_PROTO_VERSION = 0;
private static final int MAX_LEN_TEXT = 100; private static final int MAX_LEN_TEXT = 100;
private static final int MAX_LEN_BINARY = 100;
private static final int HANDLE = 1; private static final int HANDLE = 1;
private static final int INVITE = 2; private static final int INVITE = 2;
private static final int SEND = 3; private static final int SEND = 3;
@ -69,6 +71,7 @@ public class SMSService extends XWService {
private static final int CHECK_MSGDB = 6; private static final int CHECK_MSGDB = 6;
private static final int ADDED_MISSING = 7; private static final int ADDED_MISSING = 7;
private static final int STOP_SELF = 8; private static final int STOP_SELF = 8;
private static final int HANDLEDATA = 9;
private static final String CMD_STR = "CMD"; private static final String CMD_STR = "CMD";
private static final String BUFFER = "BUFFER"; private static final String BUFFER = "BUFFER";
@ -118,6 +121,15 @@ public class SMSService extends XWService {
context.startService( intent ); context.startService( intent );
} }
public static void handleFrom( Context context, byte[] buffer,
String phone )
{
Intent intent = getIntentTo( context, HANDLEDATA );
intent.putExtra( BUFFER, buffer );
intent.putExtra( PHONE, phone );
context.startService( intent );
}
public static void inviteRemote( Context context, String phone, public static void inviteRemote( Context context, String phone,
int gameID, String gameName, int gameID, String gameName,
int lang, String dict, int lang, String dict,
@ -256,6 +268,7 @@ public class SMSService extends XWService {
} }
break; break;
case HANDLE: case HANDLE:
case HANDLEDATA:
++m_nReceived; ++m_nReceived;
ConnStatusHandler. ConnStatusHandler.
updateStatusIn( this, null, updateStatusIn( this, null,
@ -263,9 +276,14 @@ public class SMSService extends XWService {
if ( s_showToasts ) { if ( s_showToasts ) {
DbgUtils.showf( this, "got %dth msg", m_nReceived ); DbgUtils.showf( this, "got %dth msg", m_nReceived );
} }
String buffer = intent.getStringExtra( BUFFER );
String phone = intent.getStringExtra( PHONE ); String phone = intent.getStringExtra( PHONE );
if ( HANDLE == cmd ) {
String buffer = intent.getStringExtra( BUFFER );
receiveBuffer( buffer, phone ); receiveBuffer( buffer, phone );
} else {
byte[] buffer = intent.getByteArrayExtra( BUFFER );
receiveBuffer( buffer, phone );
}
break; break;
case INVITE: case INVITE:
case ADDED_MISSING: case ADDED_MISSING:
@ -387,9 +405,16 @@ public class SMSService extends XWService {
das.write( bytes, 0, bytes.length ); das.write( bytes, 0, bytes.length );
das.flush(); das.flush();
String as64 = XwJNI.base64Encode( bas.toByteArray() ); byte[] data = bas.toByteArray();
String[] msgs = breakAndEncode( as64 ); boolean result;
return sendBuffers( msgs, phone ); if ( s_asData ) {
byte[][] msgs = breakAndEncode( data );
result = sendBuffers( msgs, phone );
} else {
String[] msgs = breakAndEncode( XwJNI.base64Encode( data ) );
result = sendBuffers( msgs, phone );
}
return result;
} }
private String[] breakAndEncode( String msg ) private String[] breakAndEncode( String msg )
@ -417,6 +442,33 @@ public class SMSService extends XWService {
return result; return result;
} }
private byte[][] breakAndEncode( byte msg[] ) throws java.io.IOException
{
int count = (msg.length + (MAX_LEN_BINARY-1)) / MAX_LEN_BINARY;
byte[][] result = new byte[count][];
int msgID = ++s_nSent % 0x000000FF;
int start = 0;
int end = 0;
for ( int ii = 0; ii < count; ++ii ) {
int len = msg.length - end;
if ( len > MAX_LEN_BINARY ) {
len = MAX_LEN_BINARY;
}
end += len;
byte[] part = new byte[4 + len];
part[0] = (byte)0; // proto
part[1] = (byte)msgID;
part[2] = (byte)ii;
part[3] = (byte)count;
System.arraycopy( msg, start, part, 4, len );
result[ii] = part;
start = end;
}
return result;
}
private void receive( SMS_CMD cmd, byte[] data, String phone ) private void receive( SMS_CMD cmd, byte[] data, String phone )
{ {
DataInputStream dis = DataInputStream dis =
@ -472,6 +524,19 @@ public class SMSService extends XWService {
} }
} }
private void receiveBuffer( byte[] buffer, String senderPhone )
{
byte proto = buffer[0];
int id = buffer[1];
int index = buffer[2];
int count = buffer[3];
byte[] rest = new byte[buffer.length - 4];
System.arraycopy( buffer, 4, rest, 0, rest.length );
tryAssemble( senderPhone, id, index, count, rest );
sendResult( MultiEvent.SMS_RECEIVE_OK );
}
private void receiveBuffer( String as64, String senderPhone ) private void receiveBuffer( String as64, String senderPhone )
{ {
String[] parts = as64.split( ":" ); String[] parts = as64.split( ":" );
@ -487,6 +552,34 @@ public class SMSService extends XWService {
} }
} }
private void tryAssemble( String senderPhone, int id, int index,
int count, byte[] msg )
{
if ( index == 0 && count == 1 ) {
disAssemble( senderPhone, msg );
} else {
// required? Should always be in main thread.
synchronized( s_partialMsgs ) {
HashMap<Integer, MsgStore> perPhone =
s_partialMsgs.get( senderPhone );
if ( null == perPhone ) {
perPhone = new HashMap <Integer, MsgStore>();
s_partialMsgs.put( senderPhone, perPhone );
}
MsgStore store = perPhone.get( id );
if ( null == store ) {
store = new MsgStore( id, count, false );
perPhone.put( id, store );
}
if ( store.add( index, msg ).isComplete() ) {
disAssemble( senderPhone, store.messageData() );
perPhone.remove( id );
}
}
}
}
private void tryAssemble( String senderPhone, int id, int index, private void tryAssemble( String senderPhone, int id, int index,
int count, String msg ) int count, String msg )
{ {
@ -503,18 +596,41 @@ public class SMSService extends XWService {
} }
MsgStore store = perPhone.get( id ); MsgStore store = perPhone.get( id );
if ( null == store ) { if ( null == store ) {
store = new MsgStore( id, count ); store = new MsgStore( id, count, true );
perPhone.put( id, store ); perPhone.put( id, store );
} }
if ( store.add( index, msg ).isComplete() ) { if ( store.add( index, msg ).isComplete() ) {
disAssemble( senderPhone, store.message() ); disAssemble( senderPhone, store.messageText() );
perPhone.remove( id ); perPhone.remove( id );
} }
} }
} }
} }
private void disAssemble( String senderPhone, byte[] fullMsg )
{
DataInputStream dis =
new DataInputStream( new ByteArrayInputStream(fullMsg) );
try {
byte proto = dis.readByte();
if ( SMS_PROTO_VERSION != proto ) {
DbgUtils.logf( "SMSService.disAssemble: bad proto %d; dropping",
proto );
} else {
SMS_CMD cmd = SMS_CMD.values()[dis.readByte()];
byte[] rest = new byte[dis.available()];
dis.read( rest );
receive( cmd, rest, senderPhone );
}
} catch ( java.io.IOException ioe ) {
DbgUtils.loge( ioe );
} catch ( ArrayIndexOutOfBoundsException oob ) {
// enum this older code doesn't know about; drop it
DbgUtils.logf( "disAssemble: dropping message with too-new enum" );
}
}
private void disAssemble( String senderPhone, String fullMsg ) private void disAssemble( String senderPhone, String fullMsg )
{ {
byte[] data = XwJNI.base64Decode( fullMsg ); byte[] data = XwJNI.base64Decode( fullMsg );
@ -592,6 +708,33 @@ public class SMSService extends XWService {
return success; return success;
} }
private boolean sendBuffers( byte[][] fragments, String phone )
{
boolean success = false;
try {
SmsManager mgr = SmsManager.getDefault();
PendingIntent sent = makeStatusIntent( MSG_SENT );
PendingIntent delivery = makeStatusIntent( MSG_DELIVERED );
for ( byte[] fragment : fragments ) {
mgr.sendDataMessage( phone, null, (short)3344, fragment,
sent, delivery );
}
if ( s_showToasts ) {
DbgUtils.showf( this, "sent %dth msg", s_nSent );
}
success = true;
} catch ( IllegalArgumentException iae ) {
DbgUtils.logf( "sendBuffers(%s): %s", phone, iae.toString() );
} catch ( Exception ee ) {
DbgUtils.loge( ee );
}
ConnStatusHandler.updateStatusOut( this, null,
CommsConnType.COMMS_CONN_SMS,
success );
return success;
}
private static void fillInviteIntent( Intent intent, String phone, private static void fillInviteIntent( Intent intent, String phone,
int gameID, String gameName, int gameID, String gameName,
int lang, String dict, int lang, String dict,
@ -728,41 +871,68 @@ public class SMSService extends XWService {
} }
private class MsgStore { private class MsgStore {
String[] m_msgs; String[] m_msgsText;
byte[][] m_msgsData;
int m_msgID; int m_msgID;
int m_haveCount; int m_haveCount;
int m_fullLength; int m_fullLength;
public MsgStore( int id, int count ) public MsgStore( int id, int count, boolean usingStrings )
{ {
m_msgID = id; m_msgID = id;
m_msgs = new String[count]; if ( usingStrings ) {
m_msgsText = new String[count];
} else {
m_msgsData = new byte[count][];
}
m_fullLength = 0; m_fullLength = 0;
} }
public MsgStore add( int index, String msg ) public MsgStore add( int index, String msg )
{ {
if ( null == m_msgs[index] ) { if ( null == m_msgsText[index] ) {
++m_haveCount; ++m_haveCount;
m_fullLength += msg.length(); m_fullLength += msg.length();
} }
m_msgs[index] = msg; m_msgsText[index] = msg;
return this;
}
public MsgStore add( int index, byte[] msg )
{
if ( null == m_msgsData[index] ) {
++m_haveCount;
m_fullLength += msg.length;
}
m_msgsData[index] = msg;
return this; return this;
} }
public boolean isComplete() public boolean isComplete()
{ {
boolean complete = m_msgs.length == m_haveCount; int count = null != m_msgsText ? m_msgsText.length : m_msgsData.length;
boolean complete = count == m_haveCount;
return complete; return complete;
} }
public String message() public String messageText()
{ {
StringBuffer sb = new StringBuffer(m_fullLength); StringBuffer sb = new StringBuffer(m_fullLength);
for ( String msg : m_msgs ) { for ( String msg : m_msgsText ) {
sb.append( msg ); sb.append( msg );
} }
return sb.toString(); return sb.toString();
} }
public byte[] messageData()
{
byte[] result = new byte[m_fullLength];
int indx = 0;
for ( byte[] msg : m_msgsData ) {
System.arraycopy( msg, 0, result, indx, msg.length );
indx += msg.length;
}
return result;
}
} }
} }

View file

@ -100,15 +100,12 @@ public class Utils {
{ {
if ( null == s_deviceSupportSMS ) { if ( null == s_deviceSupportSMS ) {
boolean doesSMS = false; boolean doesSMS = false;
// TEMPORARY: disable SMS on KITKAT
if ( 19 > Integer.valueOf( android.os.Build.VERSION.SDK ) ) {
TelephonyManager tm = (TelephonyManager) TelephonyManager tm = (TelephonyManager)
context.getSystemService(Context.TELEPHONY_SERVICE); context.getSystemService(Context.TELEPHONY_SERVICE);
if ( null != tm ) { if ( null != tm ) {
int type = tm.getPhoneType(); int type = tm.getPhoneType();
doesSMS = TelephonyManager.PHONE_TYPE_NONE != type; doesSMS = TelephonyManager.PHONE_TYPE_NONE != type;
} }
}
s_deviceSupportSMS = new Boolean( doesSMS ); s_deviceSupportSMS = new Boolean( doesSMS );
} }
return s_deviceSupportSMS; return s_deviceSupportSMS;