From 75b976449ffcc4747341601e2288ee4e4101cfe1 Mon Sep 17 00:00:00 2001 From: Eric House Date: Sat, 23 Nov 2013 19:02:57 -0800 Subject: [PATCH] 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? --- xwords4/android/XWords4/AndroidManifest.xml | 8 + .../org/eehouse/android/xw4/SMSReceiver.java | 33 +-- .../org/eehouse/android/xw4/SMSService.java | 200 ++++++++++++++++-- .../src/org/eehouse/android/xw4/Utils.java | 13 +- 4 files changed, 219 insertions(+), 35 deletions(-) diff --git a/xwords4/android/XWords4/AndroidManifest.xml b/xwords4/android/XWords4/AndroidManifest.xml index 429b022a3..963a7c7f8 100644 --- a/xwords4/android/XWords4/AndroidManifest.xml +++ b/xwords4/android/XWords4/AndroidManifest.xml @@ -206,6 +206,14 @@ + + + + + + + + diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSReceiver.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSReceiver.java index 32161091d..009ec6246 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSReceiver.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSReceiver.java @@ -33,28 +33,37 @@ public class SMSReceiver extends BroadcastReceiver { @Override public void onReceive( Context context, Intent intent ) { + String action = intent.getAction(); Bundle bundle = intent.getExtras(); + DbgUtils.logf( "onReceive: action=%s", action ); if ( null != bundle ) { + boolean isData = + action.equals("android.intent.action.DATA_SMS_RECEIVED"); boolean isMine = false; Object[] pdus = (Object[])bundle.get( "pdus" ); SmsMessage[] smses = new SmsMessage[pdus.length]; for ( int ii = 0; ii < pdus.length; ++ii ) { SmsMessage sms = SmsMessage.createFromPdu((byte[])pdus[ii]); - String body = sms.getMessageBody(); - String postDetectable = SMSService.fromPublicFmt( body ); - isMine = null != postDetectable; - if ( isMine ) { - String phone = sms.getOriginatingAddress(); - DbgUtils.logf( "SMSReceiver: \"%s\" from %s", - body, phone ); - SMSService.handleFrom( context, postDetectable, phone ); + String phone = sms.getOriginatingAddress(); + if ( isData ) { + byte[] body = sms.getUserData(); + SMSService.handleFrom( context, body, phone ); + } else { + String body = sms.getMessageBody(); + String postDetectable = SMSService.fromPublicFmt( body ); + isMine = null != postDetectable; + if ( isMine ) { + DbgUtils.logf( "SMSReceiver: \"%s\" from %s", + body, phone ); + SMSService.handleFrom( context, postDetectable, phone ); + } } - } - if ( isMine ) { - DbgUtils.logf( "SMSReceiver: CONSUMING message" ); - abortBroadcast(); + if ( isMine ) { + DbgUtils.logf( "SMSReceiver: CONSUMING message" ); + abortBroadcast(); + } } } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java index 773291d39..55be5587b 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java @@ -55,12 +55,14 @@ public class SMSService extends XWService { 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 boolean s_asData = true; private static final String MSG_SENT = "MSG_SENT"; private static final String MSG_DELIVERED = "MSG_DELIVERED"; private static final int SMS_PROTO_VERSION = 0; 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 INVITE = 2; 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 ADDED_MISSING = 7; private static final int STOP_SELF = 8; + private static final int HANDLEDATA = 9; private static final String CMD_STR = "CMD"; private static final String BUFFER = "BUFFER"; @@ -118,6 +121,15 @@ public class SMSService extends XWService { 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, int gameID, String gameName, int lang, String dict, @@ -256,6 +268,7 @@ public class SMSService extends XWService { } break; case HANDLE: + case HANDLEDATA: ++m_nReceived; ConnStatusHandler. updateStatusIn( this, null, @@ -263,9 +276,14 @@ public class SMSService extends XWService { if ( s_showToasts ) { DbgUtils.showf( this, "got %dth msg", m_nReceived ); } - String buffer = intent.getStringExtra( BUFFER ); String phone = intent.getStringExtra( PHONE ); - receiveBuffer( buffer, phone ); + if ( HANDLE == cmd ) { + String buffer = intent.getStringExtra( BUFFER ); + receiveBuffer( buffer, phone ); + } else { + byte[] buffer = intent.getByteArrayExtra( BUFFER ); + receiveBuffer( buffer, phone ); + } break; case INVITE: case ADDED_MISSING: @@ -387,9 +405,16 @@ public class SMSService extends XWService { das.write( bytes, 0, bytes.length ); das.flush(); - String as64 = XwJNI.base64Encode( bas.toByteArray() ); - String[] msgs = breakAndEncode( as64 ); - return sendBuffers( msgs, phone ); + byte[] data = bas.toByteArray(); + boolean result; + 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 ) @@ -417,6 +442,33 @@ public class SMSService extends XWService { 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 ) { 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 ) { 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 perPhone = + s_partialMsgs.get( senderPhone ); + if ( null == perPhone ) { + perPhone = new HashMap (); + 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, int count, String msg ) { @@ -503,18 +596,41 @@ public class SMSService extends XWService { } MsgStore store = perPhone.get( id ); if ( null == store ) { - store = new MsgStore( id, count ); + store = new MsgStore( id, count, true ); perPhone.put( id, store ); } if ( store.add( index, msg ).isComplete() ) { - disAssemble( senderPhone, store.message() ); + disAssemble( senderPhone, store.messageText() ); 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 ) { byte[] data = XwJNI.base64Decode( fullMsg ); @@ -592,6 +708,33 @@ public class SMSService extends XWService { 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, int gameID, String gameName, int lang, String dict, @@ -728,41 +871,68 @@ public class SMSService extends XWService { } private class MsgStore { - String[] m_msgs; + String[] m_msgsText; + byte[][] m_msgsData; int m_msgID; int m_haveCount; int m_fullLength; - public MsgStore( int id, int count ) + public MsgStore( int id, int count, boolean usingStrings ) { m_msgID = id; - m_msgs = new String[count]; + if ( usingStrings ) { + m_msgsText = new String[count]; + } else { + m_msgsData = new byte[count][]; + } m_fullLength = 0; } public MsgStore add( int index, String msg ) { - if ( null == m_msgs[index] ) { + if ( null == m_msgsText[index] ) { ++m_haveCount; 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; } 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; } - public String message() + public String messageText() { StringBuffer sb = new StringBuffer(m_fullLength); - for ( String msg : m_msgs ) { + for ( String msg : m_msgsText ) { sb.append( msg ); } 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; + } } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java index 1832493f0..3816f18f1 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/Utils.java @@ -100,14 +100,11 @@ public class Utils { { if ( null == s_deviceSupportSMS ) { boolean doesSMS = false; - // TEMPORARY: disable SMS on KITKAT - if ( 19 > Integer.valueOf( android.os.Build.VERSION.SDK ) ) { - TelephonyManager tm = (TelephonyManager) - context.getSystemService(Context.TELEPHONY_SERVICE); - if ( null != tm ) { - int type = tm.getPhoneType(); - doesSMS = TelephonyManager.PHONE_TYPE_NONE != type; - } + TelephonyManager tm = (TelephonyManager) + context.getSystemService(Context.TELEPHONY_SERVICE); + if ( null != tm ) { + int type = tm.getPhoneType(); + doesSMS = TelephonyManager.PHONE_TYPE_NONE != type; } s_deviceSupportSMS = new Boolean( doesSMS ); }