Merge branch 'android_branch' into android_localize

Conflicts:
	xwords4/android/XWords4/src/org/eehouse/android/xw4/PrefsActivity.java
	xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java
This commit is contained in:
Eric House 2014-04-26 22:04:57 -07:00
commit 716109e979
7 changed files with 931 additions and 689 deletions

View file

@ -208,6 +208,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"/>

File diff suppressed because it is too large Load diff

View file

@ -41,6 +41,7 @@
<string name="key_dict_host">key_dict_host3</string> <string name="key_dict_host">key_dict_host3</string>
<string name="key_logging_on">key_logging_on</string> <string name="key_logging_on">key_logging_on</string>
<string name="key_show_sms">key_show_sms</string> <string name="key_show_sms">key_show_sms</string>
<string name="key_send_data_sms">key_send_data_sms</string>
<string name="key_init_hintsallowed">key_init_hintsallowed</string> <string name="key_init_hintsallowed">key_init_hintsallowed</string>
<string name="key_init_nethintsallowed">key_init_nethintsallowed</string> <string name="key_init_nethintsallowed">key_init_nethintsallowed</string>
<string name="key_init_autojuggle">key_init_autojuggle</string> <string name="key_init_autojuggle">key_init_autojuggle</string>
@ -146,6 +147,8 @@
<string name="debug_features">Enable debug features</string> <string name="debug_features">Enable debug features</string>
<string name="debug_features_summary">Menuitems etc. (release builds <string name="debug_features_summary">Menuitems etc. (release builds
only)</string> only)</string>
<string name="title_send_data_sms">Send SMS as data</string>
<string name="summary_send_data_sms">(fails on CDMA phones)</string>
<string name="board_menu_game_netstats">Network stats</string> <string name="board_menu_game_netstats">Network stats</string>
<string name="netstats_title">Game network stats</string> <string name="netstats_title">Game network stats</string>

View file

@ -344,6 +344,11 @@
android:title="Show SMS sends, receives" android:title="Show SMS sends, receives"
android:defaultValue="false" android:defaultValue="false"
/> />
<CheckBoxPreference android:key="@string/key_send_data_sms"
android:title="@string/title_send_data_sms"
android:summary="@string/summary_send_data_sms"
android:defaultValue="true"
/>
<org.eehouse.android.xw4.XWEditTextPreference <org.eehouse.android.xw4.XWEditTextPreference
android:key="@string/key_relay_host" android:key="@string/key_relay_host"
android:title="@string/relay_host" android:title="@string/relay_host"

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

@ -29,6 +29,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@ -62,6 +63,7 @@ public class SMSService extends XWService {
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;
@ -70,6 +72,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";
@ -77,6 +80,7 @@ public class SMSService extends XWService {
private static final String PHONE = "PHONE"; private static final String PHONE = "PHONE";
private static Boolean s_showToasts = null; private static Boolean s_showToasts = null;
private static Boolean s_asData = null;
// All messages are base64-encoded byte arrays. The first byte is // All messages are base64-encoded byte arrays. The first byte is
// always one of these. What follows depends. // always one of these. What follows depends.
@ -84,6 +88,7 @@ public class SMSService extends XWService {
private BroadcastReceiver m_sentReceiver; private BroadcastReceiver m_sentReceiver;
private BroadcastReceiver m_receiveReceiver; private BroadcastReceiver m_receiveReceiver;
private OnSharedPreferenceChangeListener m_prefsListener;
private int m_nReceived = 0; private int m_nReceived = 0;
private static int s_nSent = 0; private static int s_nSent = 0;
@ -119,6 +124,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,
@ -201,10 +215,8 @@ public class SMSService extends XWService {
private static Intent getIntentTo( Context context, int cmd ) private static Intent getIntentTo( Context context, int cmd )
{ {
if ( null == s_showToasts ) { if ( null == s_showToasts ) {
SharedPreferences sp s_showToasts =
= PreferenceManager.getDefaultSharedPreferences( context ); XWPrefs.getPrefsBoolean( context, R.string.key_show_sms, false );
String key = LocUtils.getString( context, R.string.key_show_sms );
s_showToasts = sp.getBoolean( key, false );
} }
Intent intent = new Intent( context, SMSService.class ); Intent intent = new Intent( context, SMSService.class );
@ -233,6 +245,13 @@ public class SMSService extends XWService {
unregisterReceiver( m_receiveReceiver ); unregisterReceiver( m_receiveReceiver );
m_receiveReceiver = null; m_receiveReceiver = null;
} }
if ( null != m_prefsListener ) {
SharedPreferences sp
= PreferenceManager.getDefaultSharedPreferences( this );
sp.unregisterOnSharedPreferenceChangeListener( m_prefsListener );
m_prefsListener = null;
}
super.onDestroy(); super.onDestroy();
} }
@ -257,6 +276,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,
@ -264,9 +284,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:
@ -388,13 +413,24 @@ 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 ( null == s_asData ) {
boolean asData =
XWPrefs.getPrefsBoolean( this, R.string.key_send_data_sms,
true );
s_asData = new Boolean( asData );
}
if ( s_asData ) {
byte[][] msgs = breakAndEncode( data );
result = sendBuffers( msgs, phone, data );
} else {
result = sendAsText( data, phone );
}
return result;
} }
private String[] breakAndEncode( String msg ) private String[] breakAndEncode( String msg ) throws java.io.IOException
throws java.io.IOException
{ {
// TODO: as optimization, truncate header when only one packet // TODO: as optimization, truncate header when only one packet
// required // required
@ -418,6 +454,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 =
@ -473,6 +536,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( ":" );
@ -488,6 +564,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 )
{ {
@ -504,18 +608,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 );
@ -593,6 +720,39 @@ public class SMSService extends XWService {
return success; return success;
} }
private boolean sendBuffers( byte[][] fragments, String phone, byte[] data )
{
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 ( NullPointerException npe ) {
DbgUtils.showf( this, "Switching to regular SMS" );
s_asData = new Boolean( false );
XWPrefs.setPrefsBoolean( this, R.string.key_send_data_sms,
false );
success = sendAsText( data, phone );
} 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,
@ -691,14 +851,39 @@ public class SMSService extends XWService {
@Override @Override
public void onReceive(Context arg0, Intent arg1) public void onReceive(Context arg0, Intent arg1)
{ {
if ( Activity.RESULT_OK == getResultCode() ) { DbgUtils.logf( "SMS delivery result: %s",
DbgUtils.logf( "SUCCESS!!!" ); Activity.RESULT_OK == getResultCode()
} else { ? "SUCCESS" : "FAILURE" );
DbgUtils.logf( "FAILURE!!!" );
}
} }
}; };
registerReceiver( m_receiveReceiver, new IntentFilter(MSG_DELIVERED) ); registerReceiver( m_receiveReceiver, new IntentFilter(MSG_DELIVERED) );
m_prefsListener = new OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged( SharedPreferences sp,
String key ) {
if ( key.equals( getString( R.string.key_show_sms ) ) ) {
s_showToasts = null;
} else if ( key.equals( getString( R.string
.key_send_data_sms ))) {
s_asData = null;
}
}
};
SharedPreferences sp
= PreferenceManager.getDefaultSharedPreferences( this );
sp.registerOnSharedPreferenceChangeListener( m_prefsListener );
}
private boolean sendAsText( byte[] data, String phone )
{
boolean success = false;
try {
String[] msgs = breakAndEncode( XwJNI.base64Encode( data ) );
success = sendBuffers( msgs, phone );
} catch ( java.io.IOException ioe ) {
DbgUtils.loge( ioe );
}
return success;
} }
private class SMSMsgSink extends MultiMsgSink { private class SMSMsgSink extends MultiMsgSink {
@ -729,41 +914,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

@ -103,8 +103,10 @@ public class Utils {
{ {
if ( null == s_deviceSupportSMS ) { if ( null == s_deviceSupportSMS ) {
boolean doesSMS = false; boolean doesSMS = false;
// TEMPORARY: disable SMS on KITKAT // TEMPORARY: disable SMS on KITKAT UNLESS use-text turned on
if ( 19 > Integer.valueOf( android.os.Build.VERSION.SDK ) ) { if ( 19 > Integer.valueOf( android.os.Build.VERSION.SDK )
|| XWPrefs.getPrefsBoolean( context, R.string.key_send_data_sms,
false ) ) {
TelephonyManager tm = (TelephonyManager) TelephonyManager tm = (TelephonyManager)
context.getSystemService(Context.TELEPHONY_SERVICE); context.getSystemService(Context.TELEPHONY_SERVICE);
if ( null != tm ) { if ( null != tm ) {